Repository: thlorenz/rid-examples Branch: master Commit: aa617bb459fa Files: 631 Total size: 1.9 MB Directory structure: gitextract_k9gs7ue9/ ├── .github/ │ └── FUNDING.yml ├── .gitignore ├── README.md ├── dart/ │ └── todo/ │ ├── .gitignore │ ├── Cargo.toml │ ├── README.md │ ├── lib/ │ │ └── main.dart │ ├── pubspec.yaml │ ├── rid_build.rs │ ├── sh/ │ │ ├── build │ │ └── run │ └── src/ │ └── app.rs └── flutter/ ├── reddit_ticker/ │ ├── .gitignore │ ├── Cargo.toml │ ├── README.md │ ├── analysis_options.yaml │ ├── android/ │ │ ├── .gitignore │ │ ├── app/ │ │ │ ├── build.gradle │ │ │ └── src/ │ │ │ ├── debug/ │ │ │ │ └── AndroidManifest.xml │ │ │ ├── main/ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ ├── kotlin/ │ │ │ │ │ └── com/ │ │ │ │ │ └── example/ │ │ │ │ │ └── reddit_ticker/ │ │ │ │ │ └── MainActivity.kt │ │ │ │ └── res/ │ │ │ │ ├── drawable/ │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable-v21/ │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── values/ │ │ │ │ │ └── styles.xml │ │ │ │ └── values-night/ │ │ │ │ └── styles.xml │ │ │ └── profile/ │ │ │ └── AndroidManifest.xml │ │ ├── build.gradle │ │ ├── gradle/ │ │ │ └── wrapper/ │ │ │ └── gradle-wrapper.properties │ │ ├── gradle.properties │ │ ├── reddit_ticker_android.iml │ │ └── settings.gradle │ ├── deps/ │ │ └── charts/ │ │ ├── .gitignore │ │ ├── .travis.yml │ │ ├── AUTHORS │ │ ├── CONTRIBUTING.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── charts_common/ │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── charts_common.gwsq │ │ │ ├── lib/ │ │ │ │ ├── common.dart │ │ │ │ └── src/ │ │ │ │ ├── chart/ │ │ │ │ │ ├── bar/ │ │ │ │ │ │ ├── bar_chart.dart │ │ │ │ │ │ ├── bar_error_decorator.dart │ │ │ │ │ │ ├── bar_label_decorator.dart │ │ │ │ │ │ ├── bar_lane_renderer.dart │ │ │ │ │ │ ├── bar_lane_renderer_config.dart │ │ │ │ │ │ ├── bar_renderer.dart │ │ │ │ │ │ ├── bar_renderer_config.dart │ │ │ │ │ │ ├── bar_renderer_decorator.dart │ │ │ │ │ │ ├── bar_target_line_renderer.dart │ │ │ │ │ │ ├── bar_target_line_renderer_config.dart │ │ │ │ │ │ ├── base_bar_renderer.dart │ │ │ │ │ │ ├── base_bar_renderer_config.dart │ │ │ │ │ │ └── base_bar_renderer_element.dart │ │ │ │ │ ├── cartesian/ │ │ │ │ │ │ ├── axis/ │ │ │ │ │ │ │ ├── axis.dart │ │ │ │ │ │ │ ├── axis_tick.dart │ │ │ │ │ │ │ ├── collision_report.dart │ │ │ │ │ │ │ ├── draw_strategy/ │ │ │ │ │ │ │ │ ├── base_tick_draw_strategy.dart │ │ │ │ │ │ │ │ ├── gridline_draw_strategy.dart │ │ │ │ │ │ │ │ ├── none_draw_strategy.dart │ │ │ │ │ │ │ │ ├── range_tick_draw_strategy.dart │ │ │ │ │ │ │ │ ├── small_tick_draw_strategy.dart │ │ │ │ │ │ │ │ └── tick_draw_strategy.dart │ │ │ │ │ │ │ ├── end_points_tick_provider.dart │ │ │ │ │ │ │ ├── linear/ │ │ │ │ │ │ │ │ ├── bucketing_numeric_axis.dart │ │ │ │ │ │ │ │ ├── bucketing_numeric_tick_provider.dart │ │ │ │ │ │ │ │ ├── linear_scale.dart │ │ │ │ │ │ │ │ ├── linear_scale_domain_info.dart │ │ │ │ │ │ │ │ ├── linear_scale_function.dart │ │ │ │ │ │ │ │ └── linear_scale_viewport.dart │ │ │ │ │ │ │ ├── numeric_extents.dart │ │ │ │ │ │ │ ├── numeric_scale.dart │ │ │ │ │ │ │ ├── numeric_tick_provider.dart │ │ │ │ │ │ │ ├── ordinal_extents.dart │ │ │ │ │ │ │ ├── ordinal_scale.dart │ │ │ │ │ │ │ ├── ordinal_scale_domain_info.dart │ │ │ │ │ │ │ ├── ordinal_tick_provider.dart │ │ │ │ │ │ │ ├── range_axis_tick.dart │ │ │ │ │ │ │ ├── range_tick.dart │ │ │ │ │ │ │ ├── range_tick_provider.dart │ │ │ │ │ │ │ ├── scale.dart │ │ │ │ │ │ │ ├── simple_ordinal_scale.dart │ │ │ │ │ │ │ ├── spec/ │ │ │ │ │ │ │ │ ├── axis_spec.dart │ │ │ │ │ │ │ │ ├── bucketing_axis_spec.dart │ │ │ │ │ │ │ │ ├── date_time_axis_spec.dart │ │ │ │ │ │ │ │ ├── end_points_time_axis_spec.dart │ │ │ │ │ │ │ │ ├── numeric_axis_spec.dart │ │ │ │ │ │ │ │ ├── ordinal_axis_spec.dart │ │ │ │ │ │ │ │ ├── percent_axis_spec.dart │ │ │ │ │ │ │ │ ├── range_tick_spec.dart │ │ │ │ │ │ │ │ └── tick_spec.dart │ │ │ │ │ │ │ ├── static_tick_provider.dart │ │ │ │ │ │ │ ├── tick.dart │ │ │ │ │ │ │ ├── tick_formatter.dart │ │ │ │ │ │ │ ├── tick_provider.dart │ │ │ │ │ │ │ └── time/ │ │ │ │ │ │ │ ├── auto_adjusting_date_time_tick_provider.dart │ │ │ │ │ │ │ ├── base_time_stepper.dart │ │ │ │ │ │ │ ├── date_time_axis.dart │ │ │ │ │ │ │ ├── date_time_extents.dart │ │ │ │ │ │ │ ├── date_time_scale.dart │ │ │ │ │ │ │ ├── date_time_tick_formatter.dart │ │ │ │ │ │ │ ├── day_time_stepper.dart │ │ │ │ │ │ │ ├── hour_tick_formatter.dart │ │ │ │ │ │ │ ├── hour_time_stepper.dart │ │ │ │ │ │ │ ├── minute_time_stepper.dart │ │ │ │ │ │ │ ├── month_time_stepper.dart │ │ │ │ │ │ │ ├── simple_time_tick_formatter.dart │ │ │ │ │ │ │ ├── time_range_tick_provider.dart │ │ │ │ │ │ │ ├── time_range_tick_provider_impl.dart │ │ │ │ │ │ │ ├── time_stepper.dart │ │ │ │ │ │ │ ├── time_tick_formatter.dart │ │ │ │ │ │ │ ├── time_tick_formatter_impl.dart │ │ │ │ │ │ │ └── year_time_stepper.dart │ │ │ │ │ │ ├── cartesian_chart.dart │ │ │ │ │ │ └── cartesian_renderer.dart │ │ │ │ │ ├── common/ │ │ │ │ │ │ ├── base_chart.dart │ │ │ │ │ │ ├── behavior/ │ │ │ │ │ │ │ ├── a11y/ │ │ │ │ │ │ │ │ ├── a11y_explore_behavior.dart │ │ │ │ │ │ │ │ ├── a11y_node.dart │ │ │ │ │ │ │ │ ├── domain_a11y_explore_behavior.dart │ │ │ │ │ │ │ │ └── keyboard_domain_navigator.dart │ │ │ │ │ │ │ ├── calculation/ │ │ │ │ │ │ │ │ └── percent_injector.dart │ │ │ │ │ │ │ ├── chart_behavior.dart │ │ │ │ │ │ │ ├── chart_title/ │ │ │ │ │ │ │ │ └── chart_title.dart │ │ │ │ │ │ │ ├── domain_highlighter.dart │ │ │ │ │ │ │ ├── domain_outliner.dart │ │ │ │ │ │ │ ├── initial_selection.dart │ │ │ │ │ │ │ ├── legend/ │ │ │ │ │ │ │ │ ├── datum_legend.dart │ │ │ │ │ │ │ │ ├── legend.dart │ │ │ │ │ │ │ │ ├── legend_entry.dart │ │ │ │ │ │ │ │ ├── legend_entry_generator.dart │ │ │ │ │ │ │ │ ├── per_datum_legend_entry_generator.dart │ │ │ │ │ │ │ │ ├── per_series_legend_entry_generator.dart │ │ │ │ │ │ │ │ └── series_legend.dart │ │ │ │ │ │ │ ├── line_point_highlighter.dart │ │ │ │ │ │ │ ├── range_annotation.dart │ │ │ │ │ │ │ ├── selection/ │ │ │ │ │ │ │ │ ├── lock_selection.dart │ │ │ │ │ │ │ │ ├── select_nearest.dart │ │ │ │ │ │ │ │ └── selection_trigger.dart │ │ │ │ │ │ │ ├── slider/ │ │ │ │ │ │ │ │ └── slider.dart │ │ │ │ │ │ │ ├── sliding_viewport.dart │ │ │ │ │ │ │ ├── sunburst_ring_expander.dart │ │ │ │ │ │ │ └── zoom/ │ │ │ │ │ │ │ ├── initial_hint_behavior.dart │ │ │ │ │ │ │ ├── pan_and_zoom_behavior.dart │ │ │ │ │ │ │ ├── pan_behavior.dart │ │ │ │ │ │ │ └── panning_tick_provider.dart │ │ │ │ │ │ ├── canvas_shapes.dart │ │ │ │ │ │ ├── chart_canvas.dart │ │ │ │ │ │ ├── chart_context.dart │ │ │ │ │ │ ├── datum_details.dart │ │ │ │ │ │ ├── processed_series.dart │ │ │ │ │ │ ├── selection_model/ │ │ │ │ │ │ │ └── selection_model.dart │ │ │ │ │ │ ├── series_datum.dart │ │ │ │ │ │ ├── series_renderer.dart │ │ │ │ │ │ ├── series_renderer_config.dart │ │ │ │ │ │ └── unitconverter/ │ │ │ │ │ │ ├── identity_converter.dart │ │ │ │ │ │ └── unit_converter.dart │ │ │ │ │ ├── layout/ │ │ │ │ │ │ ├── layout_config.dart │ │ │ │ │ │ ├── layout_manager.dart │ │ │ │ │ │ ├── layout_manager_impl.dart │ │ │ │ │ │ ├── layout_margin_strategy.dart │ │ │ │ │ │ └── layout_view.dart │ │ │ │ │ ├── line/ │ │ │ │ │ │ ├── line_chart.dart │ │ │ │ │ │ ├── line_renderer.dart │ │ │ │ │ │ └── line_renderer_config.dart │ │ │ │ │ ├── pie/ │ │ │ │ │ │ ├── arc_label_decorator.dart │ │ │ │ │ │ ├── arc_renderer.dart │ │ │ │ │ │ ├── arc_renderer_config.dart │ │ │ │ │ │ ├── arc_renderer_decorator.dart │ │ │ │ │ │ ├── arc_renderer_element.dart │ │ │ │ │ │ ├── base_arc_renderer.dart │ │ │ │ │ │ ├── base_arc_renderer_config.dart │ │ │ │ │ │ └── pie_chart.dart │ │ │ │ │ ├── scatter_plot/ │ │ │ │ │ │ ├── comparison_points_decorator.dart │ │ │ │ │ │ ├── point_renderer.dart │ │ │ │ │ │ ├── point_renderer_config.dart │ │ │ │ │ │ ├── point_renderer_decorator.dart │ │ │ │ │ │ ├── scatter_plot_chart.dart │ │ │ │ │ │ ├── symbol_annotation_renderer.dart │ │ │ │ │ │ └── symbol_annotation_renderer_config.dart │ │ │ │ │ ├── sunburst/ │ │ │ │ │ │ ├── sunburst_arc_label_decorator.dart │ │ │ │ │ │ ├── sunburst_arc_renderer.dart │ │ │ │ │ │ ├── sunburst_arc_renderer_config.dart │ │ │ │ │ │ └── sunburst_chart.dart │ │ │ │ │ ├── time_series/ │ │ │ │ │ │ └── time_series_chart.dart │ │ │ │ │ └── treemap/ │ │ │ │ │ ├── base_treemap_renderer.dart │ │ │ │ │ ├── dice_treemap_renderer.dart │ │ │ │ │ ├── slice_dice_treemap_renderer.dart │ │ │ │ │ ├── slice_treemap_renderer.dart │ │ │ │ │ ├── squarified_treemap_renderer.dart │ │ │ │ │ ├── treemap_chart.dart │ │ │ │ │ ├── treemap_label_decorator.dart │ │ │ │ │ ├── treemap_renderer_config.dart │ │ │ │ │ ├── treemap_renderer_decorator.dart │ │ │ │ │ └── treemap_renderer_element.dart │ │ │ │ ├── common/ │ │ │ │ │ ├── color.dart │ │ │ │ │ ├── date_time_factory.dart │ │ │ │ │ ├── gesture_listener.dart │ │ │ │ │ ├── graphics_factory.dart │ │ │ │ │ ├── line_style.dart │ │ │ │ │ ├── material_palette.dart │ │ │ │ │ ├── math.dart │ │ │ │ │ ├── paint_style.dart │ │ │ │ │ ├── palette.dart │ │ │ │ │ ├── performance.dart │ │ │ │ │ ├── proxy_gesture_listener.dart │ │ │ │ │ ├── rate_limit_utils.dart │ │ │ │ │ ├── rtl_spec.dart │ │ │ │ │ ├── style/ │ │ │ │ │ │ ├── material_style.dart │ │ │ │ │ │ ├── style.dart │ │ │ │ │ │ └── style_factory.dart │ │ │ │ │ ├── symbol_renderer.dart │ │ │ │ │ ├── text_element.dart │ │ │ │ │ ├── text_measurement.dart │ │ │ │ │ ├── text_style.dart │ │ │ │ │ ├── text_utils.dart │ │ │ │ │ └── typed_registry.dart │ │ │ │ └── data/ │ │ │ │ ├── series.dart │ │ │ │ └── tree.dart │ │ │ └── pubspec.yaml │ │ ├── charts_flutter/ │ │ │ ├── CHANGELOG.md │ │ │ ├── ExternalTapQueues.METADATA │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── charts_flutter.gwsq │ │ │ ├── lib/ │ │ │ │ ├── flutter.dart │ │ │ │ └── src/ │ │ │ │ ├── bar_chart.dart │ │ │ │ ├── base_chart.dart │ │ │ │ ├── base_chart_state.dart │ │ │ │ ├── behaviors/ │ │ │ │ │ ├── a11y/ │ │ │ │ │ │ └── domain_a11y_explore_behavior.dart │ │ │ │ │ ├── calculation/ │ │ │ │ │ │ └── percent_injector.dart │ │ │ │ │ ├── chart_behavior.dart │ │ │ │ │ ├── chart_title/ │ │ │ │ │ │ └── chart_title.dart │ │ │ │ │ ├── domain_highlighter.dart │ │ │ │ │ ├── initial_selection.dart │ │ │ │ │ ├── legend/ │ │ │ │ │ │ ├── datum_legend.dart │ │ │ │ │ │ ├── legend.dart │ │ │ │ │ │ ├── legend_content_builder.dart │ │ │ │ │ │ ├── legend_entry_layout.dart │ │ │ │ │ │ ├── legend_layout.dart │ │ │ │ │ │ └── series_legend.dart │ │ │ │ │ ├── line_point_highlighter.dart │ │ │ │ │ ├── range_annotation.dart │ │ │ │ │ ├── select_nearest.dart │ │ │ │ │ ├── slider/ │ │ │ │ │ │ └── slider.dart │ │ │ │ │ ├── sliding_viewport.dart │ │ │ │ │ └── zoom/ │ │ │ │ │ ├── initial_hint_behavior.dart │ │ │ │ │ ├── pan_and_zoom_behavior.dart │ │ │ │ │ └── pan_behavior.dart │ │ │ │ ├── canvas/ │ │ │ │ │ ├── circle_sector_painter.dart │ │ │ │ │ ├── line_painter.dart │ │ │ │ │ ├── pie_painter.dart │ │ │ │ │ ├── point_painter.dart │ │ │ │ │ └── polygon_painter.dart │ │ │ │ ├── cartesian_chart.dart │ │ │ │ ├── chart_canvas.dart │ │ │ │ ├── chart_container.dart │ │ │ │ ├── chart_gesture_detector.dart │ │ │ │ ├── chart_state.dart │ │ │ │ ├── combo_chart/ │ │ │ │ │ └── combo_chart.dart │ │ │ │ ├── graphics_factory.dart │ │ │ │ ├── line_chart.dart │ │ │ │ ├── line_style.dart │ │ │ │ ├── pie_chart.dart │ │ │ │ ├── scatter_plot_chart.dart │ │ │ │ ├── selection_model_config.dart │ │ │ │ ├── symbol_renderer.dart │ │ │ │ ├── text_element.dart │ │ │ │ ├── text_style.dart │ │ │ │ ├── time_series_chart.dart │ │ │ │ ├── user_managed_state.dart │ │ │ │ ├── util/ │ │ │ │ │ └── color.dart │ │ │ │ ├── util.dart │ │ │ │ └── widget_layout_delegate.dart │ │ │ ├── minimum_os.bzl │ │ │ └── pubspec.yaml │ │ └── docs/ │ │ ├── Gemfile │ │ ├── flutter/ │ │ │ └── gallery.md │ │ └── index.md │ ├── ios/ │ │ ├── .gitignore │ │ ├── Flutter/ │ │ │ ├── AppFrameworkInfo.plist │ │ │ ├── Debug.xcconfig │ │ │ └── Release.xcconfig │ │ ├── Podfile │ │ ├── Runner/ │ │ │ ├── AppDelegate.swift │ │ │ ├── Assets.xcassets/ │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ └── Contents.json │ │ │ │ └── LaunchImage.imageset/ │ │ │ │ ├── Contents.json │ │ │ │ └── README.md │ │ │ ├── Base.lproj/ │ │ │ │ ├── LaunchScreen.storyboard │ │ │ │ └── Main.storyboard │ │ │ ├── Info.plist │ │ │ └── Runner-Bridging-Header.h │ │ └── Runner.xcworkspace/ │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata/ │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings │ ├── lib/ │ │ ├── cubit/ │ │ │ ├── add_post_cubit.dart │ │ │ ├── add_post_state.dart │ │ │ ├── post_cubit.dart │ │ │ ├── post_state.dart │ │ │ ├── posts_cubit.dart │ │ │ └── posts_state.dart │ │ ├── main.dart │ │ ├── rid/ │ │ │ └── messaging.dart │ │ └── views/ │ │ ├── add_post.dart │ │ ├── post.dart │ │ └── posts.dart │ ├── macos/ │ │ ├── .gitignore │ │ ├── Flutter/ │ │ │ ├── Flutter-Debug.xcconfig │ │ │ ├── Flutter-Release.xcconfig │ │ │ └── GeneratedPluginRegistrant.swift │ │ ├── Podfile │ │ ├── Runner/ │ │ │ ├── AppDelegate.swift │ │ │ ├── Assets.xcassets/ │ │ │ │ └── AppIcon.appiconset/ │ │ │ │ └── Contents.json │ │ │ ├── Base.lproj/ │ │ │ │ └── MainMenu.xib │ │ │ ├── Configs/ │ │ │ │ ├── AppInfo.xcconfig │ │ │ │ ├── Debug.xcconfig │ │ │ │ ├── Release.xcconfig │ │ │ │ └── Warnings.xcconfig │ │ │ ├── DebugProfile.entitlements │ │ │ ├── Info.plist │ │ │ ├── MainFlutterWindow.swift │ │ │ └── Release.entitlements │ │ └── Runner.xcworkspace/ │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata/ │ │ └── IDEWorkspaceChecks.plist │ ├── plugin/ │ │ ├── .gitignore │ │ ├── LICENSE │ │ ├── analysis_options.yaml │ │ ├── android/ │ │ │ ├── .gitignore │ │ │ ├── build.gradle │ │ │ ├── gradle/ │ │ │ │ └── wrapper/ │ │ │ │ └── gradle-wrapper.properties │ │ │ ├── gradle.properties │ │ │ ├── settings.gradle │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── AndroidManifest.xml │ │ │ └── kotlin/ │ │ │ └── com/ │ │ │ └── example/ │ │ │ └── plugin/ │ │ │ └── Plugin.kt │ │ ├── ios/ │ │ │ ├── .gitignore │ │ │ ├── Assets/ │ │ │ │ └── .gitkeep │ │ │ ├── Classes/ │ │ │ │ ├── Plugin.h │ │ │ │ ├── Plugin.m │ │ │ │ └── SwiftPlugin.swift │ │ │ └── plugin.podspec │ │ ├── lib/ │ │ │ └── plugin.dart │ │ ├── macos/ │ │ │ ├── Classes/ │ │ │ │ └── Plugin.swift │ │ │ └── plugin.podspec │ │ └── pubspec.yaml │ ├── pubspec.yaml │ ├── reddit_ticker.iml │ ├── rid_build.rs │ ├── sh/ │ │ ├── android │ │ ├── bindgen │ │ ├── bindgen-dart │ │ ├── clean │ │ ├── ios │ │ ├── linux │ │ └── macos │ ├── sql/ │ │ ├── all-posts.sql │ │ └── all-scores.sql │ ├── src/ │ │ ├── db.rs │ │ ├── lib.rs │ │ └── reddit/ │ │ ├── mod.rs │ │ ├── reddit.rs │ │ ├── reddit_api_response.rs │ │ └── reddit_page_response.rs │ └── test/ │ ├── logging.dart │ └── wip.dart ├── todo/ │ ├── .gitignore │ ├── Cargo.toml │ ├── README.md │ ├── android/ │ │ ├── .gitignore │ │ ├── app/ │ │ │ ├── build.gradle │ │ │ └── src/ │ │ │ ├── debug/ │ │ │ │ └── AndroidManifest.xml │ │ │ ├── main/ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ ├── kotlin/ │ │ │ │ │ └── com/ │ │ │ │ │ └── example/ │ │ │ │ │ └── todo/ │ │ │ │ │ └── MainActivity.kt │ │ │ │ └── res/ │ │ │ │ ├── drawable/ │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable-v21/ │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── values/ │ │ │ │ │ └── styles.xml │ │ │ │ └── values-night/ │ │ │ │ └── styles.xml │ │ │ └── profile/ │ │ │ └── AndroidManifest.xml │ │ ├── build.gradle │ │ ├── gradle/ │ │ │ └── wrapper/ │ │ │ └── gradle-wrapper.properties │ │ ├── gradle.properties │ │ ├── settings.gradle │ │ └── todo_android.iml │ ├── ios/ │ │ ├── .gitignore │ │ ├── Flutter/ │ │ │ ├── AppFrameworkInfo.plist │ │ │ ├── Debug.xcconfig │ │ │ └── Release.xcconfig │ │ ├── Podfile │ │ ├── Runner/ │ │ │ ├── AppDelegate.swift │ │ │ ├── Assets.xcassets/ │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ └── Contents.json │ │ │ │ └── LaunchImage.imageset/ │ │ │ │ ├── Contents.json │ │ │ │ └── README.md │ │ │ ├── Base.lproj/ │ │ │ │ ├── LaunchScreen.storyboard │ │ │ │ └── Main.storyboard │ │ │ ├── Info.plist │ │ │ └── Runner-Bridging-Header.h │ │ └── Runner.xcworkspace/ │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata/ │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings │ ├── lib/ │ │ ├── main.dart │ │ └── views/ │ │ ├── expiry.dart │ │ ├── menu.dart │ │ ├── todo.dart │ │ └── todos.dart │ ├── linux/ │ │ ├── .gitignore │ │ ├── CMakeLists.txt │ │ ├── flutter/ │ │ │ ├── CMakeLists.txt │ │ │ ├── generated_plugin_registrant.cc │ │ │ ├── generated_plugin_registrant.h │ │ │ └── generated_plugins.cmake │ │ ├── main.cc │ │ ├── my_application.cc │ │ └── my_application.h │ ├── macos/ │ │ ├── .gitignore │ │ ├── Flutter/ │ │ │ ├── Flutter-Debug.xcconfig │ │ │ ├── Flutter-Release.xcconfig │ │ │ └── GeneratedPluginRegistrant.swift │ │ ├── Podfile │ │ ├── Runner/ │ │ │ ├── AppDelegate.swift │ │ │ ├── Assets.xcassets/ │ │ │ │ └── AppIcon.appiconset/ │ │ │ │ └── Contents.json │ │ │ ├── Base.lproj/ │ │ │ │ └── MainMenu.xib │ │ │ ├── Configs/ │ │ │ │ ├── AppInfo.xcconfig │ │ │ │ ├── Debug.xcconfig │ │ │ │ ├── Release.xcconfig │ │ │ │ └── Warnings.xcconfig │ │ │ ├── DebugProfile.entitlements │ │ │ ├── Info.plist │ │ │ ├── MainFlutterWindow.swift │ │ │ └── Release.entitlements │ │ └── Runner.xcworkspace/ │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata/ │ │ └── IDEWorkspaceChecks.plist │ ├── plugin/ │ │ ├── .gitignore │ │ ├── LICENSE │ │ ├── android/ │ │ │ ├── .gitignore │ │ │ ├── build.gradle │ │ │ ├── gradle/ │ │ │ │ └── wrapper/ │ │ │ │ └── gradle-wrapper.properties │ │ │ ├── gradle.properties │ │ │ ├── settings.gradle │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── AndroidManifest.xml │ │ │ └── kotlin/ │ │ │ └── com/ │ │ │ └── example/ │ │ │ └── plugin/ │ │ │ └── Plugin.kt │ │ ├── ios/ │ │ │ ├── .gitignore │ │ │ ├── Assets/ │ │ │ │ └── .gitkeep │ │ │ ├── Classes/ │ │ │ │ ├── Plugin.h │ │ │ │ ├── Plugin.m │ │ │ │ └── SwiftPlugin.swift │ │ │ └── plugin.podspec │ │ ├── macos/ │ │ │ ├── Classes/ │ │ │ │ └── Plugin.swift │ │ │ └── plugin.podspec │ │ ├── plugin.iml │ │ └── pubspec.yaml │ ├── pubspec.yaml │ ├── rid_build.rs │ ├── sh/ │ │ ├── android │ │ ├── bindgen │ │ ├── clean │ │ ├── ios │ │ ├── linux │ │ └── macos │ ├── src/ │ │ └── lib.rs │ ├── test/ │ │ └── widget_test.dart │ └── todo.iml ├── todo_cubit/ │ ├── .gitignore │ ├── Cargo.toml │ ├── README.md │ ├── analysis_options.yaml │ ├── android/ │ │ ├── .gitignore │ │ ├── app/ │ │ │ ├── build.gradle │ │ │ └── src/ │ │ │ ├── debug/ │ │ │ │ └── AndroidManifest.xml │ │ │ ├── main/ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ ├── kotlin/ │ │ │ │ │ └── com/ │ │ │ │ │ └── example/ │ │ │ │ │ └── todo_cubit/ │ │ │ │ │ └── MainActivity.kt │ │ │ │ └── res/ │ │ │ │ ├── drawable/ │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable-v21/ │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── values/ │ │ │ │ │ └── styles.xml │ │ │ │ └── values-night/ │ │ │ │ └── styles.xml │ │ │ └── profile/ │ │ │ └── AndroidManifest.xml │ │ ├── build.gradle │ │ ├── gradle/ │ │ │ └── wrapper/ │ │ │ └── gradle-wrapper.properties │ │ ├── gradle.properties │ │ ├── settings.gradle │ │ └── todo_cubit_android.iml │ ├── ios/ │ │ ├── .gitignore │ │ ├── Flutter/ │ │ │ ├── AppFrameworkInfo.plist │ │ │ ├── Debug.xcconfig │ │ │ └── Release.xcconfig │ │ ├── Podfile │ │ ├── Runner/ │ │ │ ├── AppDelegate.swift │ │ │ ├── Assets.xcassets/ │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ └── Contents.json │ │ │ │ └── LaunchImage.imageset/ │ │ │ │ ├── Contents.json │ │ │ │ └── README.md │ │ │ ├── Base.lproj/ │ │ │ │ ├── LaunchScreen.storyboard │ │ │ │ └── Main.storyboard │ │ │ ├── Info.plist │ │ │ └── Runner-Bridging-Header.h │ │ ├── Runner.xcodeproj/ │ │ │ ├── project.pbxproj │ │ │ ├── project.xcworkspace/ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ └── xcshareddata/ │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ └── WorkspaceSettings.xcsettings │ │ │ └── xcshareddata/ │ │ │ └── xcschemes/ │ │ │ └── Runner.xcscheme │ │ └── Runner.xcworkspace/ │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata/ │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings │ ├── lib/ │ │ ├── blocs/ │ │ │ └── cubit/ │ │ │ ├── filter_cubit.dart │ │ │ ├── settings_cubit.dart │ │ │ ├── todo_cubit.dart │ │ │ ├── todo_state.dart │ │ │ ├── todos_cubit.dart │ │ │ └── todos_state.dart │ │ ├── main.dart │ │ └── views/ │ │ ├── expiry.dart │ │ ├── menu.dart │ │ ├── todo.dart │ │ └── todos.dart │ ├── linux/ │ │ ├── .gitignore │ │ ├── CMakeLists.txt │ │ ├── flutter/ │ │ │ ├── CMakeLists.txt │ │ │ ├── generated_plugin_registrant.cc │ │ │ ├── generated_plugin_registrant.h │ │ │ └── generated_plugins.cmake │ │ ├── main.cc │ │ ├── my_application.cc │ │ └── my_application.h │ ├── macos/ │ │ ├── .gitignore │ │ ├── Flutter/ │ │ │ ├── Flutter-Debug.xcconfig │ │ │ ├── Flutter-Release.xcconfig │ │ │ └── GeneratedPluginRegistrant.swift │ │ ├── Podfile │ │ ├── Runner/ │ │ │ ├── AppDelegate.swift │ │ │ ├── Assets.xcassets/ │ │ │ │ └── AppIcon.appiconset/ │ │ │ │ └── Contents.json │ │ │ ├── Base.lproj/ │ │ │ │ └── MainMenu.xib │ │ │ ├── Configs/ │ │ │ │ ├── AppInfo.xcconfig │ │ │ │ ├── Debug.xcconfig │ │ │ │ ├── Release.xcconfig │ │ │ │ └── Warnings.xcconfig │ │ │ ├── DebugProfile.entitlements │ │ │ ├── Info.plist │ │ │ ├── MainFlutterWindow.swift │ │ │ └── Release.entitlements │ │ ├── Runner.xcodeproj/ │ │ │ ├── project.pbxproj │ │ │ ├── project.xcworkspace/ │ │ │ │ └── xcshareddata/ │ │ │ │ └── IDEWorkspaceChecks.plist │ │ │ └── xcshareddata/ │ │ │ └── xcschemes/ │ │ │ └── Runner.xcscheme │ │ └── Runner.xcworkspace/ │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata/ │ │ └── IDEWorkspaceChecks.plist │ ├── plugin/ │ │ ├── .gitignore │ │ ├── LICENSE │ │ ├── analysis_options.yaml │ │ ├── android/ │ │ │ ├── .gitignore │ │ │ ├── build.gradle │ │ │ ├── gradle/ │ │ │ │ └── wrapper/ │ │ │ │ └── gradle-wrapper.properties │ │ │ ├── gradle.properties │ │ │ ├── settings.gradle │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── AndroidManifest.xml │ │ │ └── kotlin/ │ │ │ └── com/ │ │ │ └── example/ │ │ │ └── plugin/ │ │ │ └── Plugin.kt │ │ ├── ios/ │ │ │ ├── .gitignore │ │ │ ├── Assets/ │ │ │ │ └── .gitkeep │ │ │ ├── Classes/ │ │ │ │ ├── Plugin.h │ │ │ │ ├── Plugin.m │ │ │ │ └── SwiftPlugin.swift │ │ │ └── plugin.podspec │ │ ├── macos/ │ │ │ ├── Classes/ │ │ │ │ └── Plugin.swift │ │ │ └── plugin.podspec │ │ ├── plugin.iml │ │ └── pubspec.yaml │ ├── pubspec.yaml │ ├── rid_build.rs │ ├── sh/ │ │ ├── android │ │ ├── android-emulator │ │ ├── bindgen │ │ ├── clean │ │ ├── ios │ │ ├── linux │ │ └── macos │ ├── src/ │ │ └── lib.rs │ ├── test/ │ │ └── widget_test.dart │ ├── todo_cubit.iml │ ├── web/ │ │ ├── index.html │ │ └── manifest.json │ └── windows/ │ ├── .gitignore │ ├── CMakeLists.txt │ ├── flutter/ │ │ ├── CMakeLists.txt │ │ ├── generated_plugin_registrant.cc │ │ ├── generated_plugin_registrant.h │ │ └── generated_plugins.cmake │ └── runner/ │ ├── CMakeLists.txt │ ├── Runner.rc │ ├── flutter_window.cpp │ ├── flutter_window.h │ ├── main.cpp │ ├── resource.h │ ├── run_loop.cpp │ ├── run_loop.h │ ├── runner.exe.manifest │ ├── utils.cpp │ ├── utils.h │ ├── win32_window.cpp │ └── win32_window.h └── todo_cubit_wasm/ ├── .gitignore ├── Cargo.toml ├── README.md ├── analysis_options.yaml ├── lib/ │ ├── blocs/ │ │ └── cubit/ │ │ ├── filter_cubit.dart │ │ ├── settings_cubit.dart │ │ ├── todo_cubit.dart │ │ ├── todo_state.dart │ │ ├── todos_cubit.dart │ │ └── todos_state.dart │ ├── main.dart │ └── views/ │ ├── expiry.dart │ ├── menu.dart │ ├── todo.dart │ └── todos.dart ├── plugin/ │ ├── .gitignore │ ├── LICENSE │ ├── analysis_options.yaml │ ├── lib/ │ │ └── wasm/ │ │ ├── reply_channel.dart │ │ └── utils.dart │ ├── plugin.iml │ └── pubspec.yaml ├── pubspec.yaml ├── rid_build.rs ├── sh/ │ ├── build-web │ ├── clean │ ├── debug-web │ ├── wasm │ ├── wasm-release │ └── wasmgen ├── src/ │ ├── alloc.rs │ ├── lib.rs │ └── replies.rs ├── todo_cubit.iml ├── twitch.md └── web/ ├── index.html └── manifest.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/FUNDING.yml ================================================ github: [thlorenz] ================================================ FILE: .gitignore ================================================ .DS_Store .idea/ .metadata ## Flutter ### # Flutter/Dart/Pub related **/doc/api/ .dart_tool/ .flutter-plugins .flutter-plugins-dependencies .fvm/ .packages .pub-cache/ .pub/ build/ coverage/ lib/generated_plugin_registrant.dart # For library packages, don’t commit the pubspec.lock file. # Regenerating the pubspec.lock file lets you test your package against the latest compatible versions of its dependencies. # See https://dart.dev/guides/libraries/private-files#pubspeclock #pubspec.lock # Android related **/android/**/gradle-wrapper.jar **/android/.gradle **/android/captures/ **/android/gradlew **/android/gradlew.bat **/android/key.properties **/android/local.properties **/android/**/GeneratedPluginRegistrant.java # Linux related **/linux/**/*.so **/linux/**/*.a # iOS/XCode related **/ios/**/*.mode1v3 **/ios/**/*.mode2v3 **/ios/**/*.moved-aside **/ios/**/*.pbxuser **/ios/**/*.perspectivev3 **/ios/**/*sync/ **/ios/**/.sconsign.dblite **/ios/**/.tags* **/ios/**/.vagrant/ **/ios/**/DerivedData/ **/ios/**/Icon? **/ios/**/Pods/ **/ios/**/.symlinks/ **/ios/**/profile **/ios/**/xcuserdata **/ios/.generated/ **/ios/Flutter/.last_build_id **/ios/Flutter/App.framework **/ios/Flutter/Flutter.framework **/ios/Flutter/Flutter.podspec **/ios/Flutter/Generated.xcconfig **/ios/Flutter/app.flx **/ios/Flutter/app.zip **/ios/Flutter/flutter_assets/ **/ios/Flutter/flutter_export_environment.sh **/ios/ServiceDefinitions.json **/ios/Runner/GeneratedPluginRegistrant.* # Exceptions to above rules. !**/ios/**/default.mode1v3 !**/ios/**/default.mode2v3 !**/ios/**/default.pbxuser !**/ios/**/default.perspectivev3 !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages ### Rust ### **/target/** **/Cargo.lock ================================================ FILE: README.md ================================================ # rid-examples Examples showing how to use [**Rid**](https://thlorenz.com/rid-site/) in order to build Dart/Flutter apps integrated with Rust. ## What is Rid? _Rid_ stands for _Rust integrates Dart_ and is a tool I am working on that allows to call Rust functions from Dart and Flutter applications by simply annotating them. Its main goal is to **make it super easy to implement your UI in Flutter and the logic in Rust**. This in turn allows you to benefit from the respective strength of each platform. Learn more by following the [Getting Started](https://thlorenz.com/rid-site/docs/getting-started/introduction/) guide. ## How does Rid work? _Rid_ consumes the annotations added to your Rust code to generate all the [FFI](https://doc.rust-lang.org/nomicon/ffi.html) boilerplate to interact with them from Dart /Flutter. Additionally it generates extension methods on entities, such as _models_ in order to expose an API on the Dart/Flutter end that is super fun to work with. Learn more _rids_ [application architecture](https://thlorenz.com/rid-site/docs/getting-started/architecture/). ## Examples ### Flutter - [Todo App](./flutter/todo) ### Dart Only - [Command Line Todo App](./dart/todo) ## Is Rid open sourced? _Rid_ is _[Sponsorware](https://github.com/sponsorware/docs)_ and thus not open sourced yet. Please [learn more here](https://thlorenz.com/rid-site/docs/contributing/sponsor/) about how you can [sponsor rid via a monthly contribution](https://github.com/sponsors/thlorenz) and when _rid_ will be fully open sourced. ## LICENSE MIT ================================================ FILE: dart/todo/.gitignore ================================================ **/generated/ ================================================ FILE: dart/todo/Cargo.toml ================================================ [package] name = "rid_dart_todo" version = "0.1.0" authors = ["Thorsten Lorenz "] edition = "2018" [lib] name = "rid_dart_todo" crate-type = ["cdylib"] path = "src/app.rs" doctest = false test = false [[bin]] name = "rid_build" path = "rid_build.rs" # Note that until rid is open sourced and published to crates.io the examples # assume it to reside in a relative folder [dependencies] rid = { path = "../../../rid" } rid_build = { path = "../../../rid/rid-build" } [build-dependencies] rid_build = { path = "../../../rid/rid-build" } ================================================ FILE: dart/todo/README.md ================================================ # Rid Todo Example An example todo app with user interaction implemented in Dart and app logic in Rust. ## Getting Started _Please see [Caveats](#Caveats) first_. ```sh ./sh/build ./sh/run ``` Repeat the first step every time you modify Rust code. ## Rid Model What the Model annotations communicate to _rid_: ```rust #[rid::model] // this is a model and all its fields should be accessible from Dart #[rid::structs(Todo)] // the referenced Todo type is a struct #[rid::enums(Filter)] // the referenced Filter type is an enum #[derive(Debug)] // expose a `model.debug(pretty?)` function to Dart pub struct Model { last_added_id: u32, todos: Vec, filter: Filter, } ``` Possible Model use in Dart code: ```dart final filter = model.filter; for (final todo in model.todos.iter()) { // do something } ``` ## Rid Message How to setup sending messages to the _Model_ via _rid_: ```rust #[rid::message(Model)] // this is a message that will update the model #[rid::enums(Filter)] // the referenced Filter type is an enum pub enum Msg { AddTodo(String), // sent via Dart: model.msgAddTodo("Learn Rid"); ToggleTodo(u32), // sent via Dart: model.msgToggleTodo(todoId); CompleteAll, // sent via Dart: model.msgCompleteAll(); SetFilter(Filter), // sent via Dart: model.msgSetFilter(RidFilter.index); } impl Model { fn update(&mut self, msg: Msg) { // handle the message here } } ``` ## Exporting Methods ```rust #[rid::export] // rid will scan this impl block for exports impl Model { #[rid::export(initModel)] // exports this method to be called via Dart: rid_ffi.initModel(); fn new() -> Self { Self { last_added_id: 0, todos: vec![], filter: Filter::All, } } } ``` ## Caveats At this point _Rid_ hasn't been published, therefore the build step cannot be performed and this example only serves to demonstrate what is possible once it _is_ published and open sourced. For more information please see [_Is Rid Open Sourced?_](../../README.md#is-rid-open-sourced) ================================================ FILE: dart/todo/lib/main.dart ================================================ import 'generated/rid_api.dart'; import 'dart:io'; printStatus(Store store) { final todos = store.todos; final total = todos.length; final filter = store.filter; print("Total Todos: $total"); print("Filter: ${filter.display()}"); print("\nMatching Todos:"); // NOTE: using raw API here to access `display`. // To do that properly we lock the store while we're iterating. store.raw.runLocked((rawStore) { final matchingTodos = rawStore.filtered_todos(); for (final todo in matchingTodos.iter()) { print(" ${todo.display()}"); } matchingTodos.dispose(); }); } Future handleCommand(Store store, String line) async { String cmd; String payload; if (line.length > 2) { cmd = line.substring(0, 3); payload = line.substring(3).trim(); } else { cmd = line.substring(0, 2); payload = ""; } switch (cmd) { case "add": await store.msgAddTodo(payload); break; case "del": await store.msgRemoveTodo(int.parse(payload)); break; case "cmp": await store.msgCompleteTodo(int.parse(payload)); break; case "tog": await store.msgToggleTodo(int.parse(payload)); break; case "rst": store.msgRestartTodo(int.parse(payload)); break; case "fil": final filter = payload == "cmp" ? Filter.Completed : payload == "pen" ? Filter.Pending : Filter.All; await store.msgSetFilter(filter); break; case "ca": await store.msgCompleteAll(); break; case "dc": await store.msgRemoveCompleted(); break; case "ra": await store.msgRestartAll(); break; default: print("\nUnknown command '$cmd'\n"); return false; } return true; } printCommands() { print("\nPlease select one of the below:\n"); print(" add -- to add a todo"); print(" del -- to delete a todo by id"); print(" cmp -- to complete a todo by id"); print(" rst -- to restart a todo by id"); print(" tog -- to toggle a todo by id"); print(" fil all|cmp|pen -- to set filter to"); print(" ca -- to completed all todos"); print(" dc -- to delete completed todos"); print(" ra -- to restart all todos"); print(" q -- to quit"); } void main(List args) async { final store = Store.instance; { await store.msgAddTodo("Complete this Todo via: cmp 1"); await store.msgAddTodo("Delete this Todo via: del 2"); await store.msgAddTodo("Toggle this Todo via: tog 3"); await store.msgAddTodo("Restart the first Todo via: rst 1"); String? input; bool ok = true; while (true) { if (ok) { print("\x1B[2J\x1B[0;0H"); } printStatus(store); printCommands(); stdout.write("\n> "); input = stdin.readLineSync(); if (input == "q") { break; } if (input != null && input.length > 1) { ok = await handleCommand(store, input.trim()); } } } store.dispose(); } ================================================ FILE: dart/todo/pubspec.yaml ================================================ name: rid_dart_todo version: 0.0.0 environment: sdk: '>=2.13.0 <=3.0.0' dependencies: ffi: ^1.0.0 ffigen: 4.0.0-dev.2 ================================================ FILE: dart/todo/rid_build.rs ================================================ use rid_build::{build, BuildConfig, BuildTarget, Project}; use std::env; fn main() { let crate_dir = env::var("CARGO_MANIFEST_DIR") .expect("Missing CARGO_MANIFEST_DIR, please run this via 'cargo run'"); let crate_name = &env::var("CARGO_PKG_NAME") .expect("Missing CARGO_PKG_NAME, please run this via 'cargo run'"); let lib_name = &format!("lib{}", &crate_name); let build_config = BuildConfig { target: BuildTarget::Debug, project: Project::Dart, lib_name, crate_name, project_root: &crate_dir, workspace_root: Some(&crate_dir), }; let build_result = build(&build_config).expect("Build failed"); eprintln!("{}", build_result); } ================================================ FILE: dart/todo/sh/build ================================================ #!/usr/bin/env bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" (cd $DIR/.. && cargo run rid_build) ================================================ FILE: dart/todo/sh/run ================================================ #!/usr/bin/env bash dart \ --enable-asserts \ --packages=.packages \ package:rid_dart_todo/main.dart ================================================ FILE: dart/todo/src/app.rs ================================================ #![allow(dead_code)] use rid::RidStore; use std::fmt::Display; // ----------------- // Store // ----------------- #[rid::store] #[rid::structs(Todo)] #[rid::enums(Filter)] #[derive(Debug)] pub struct Store { last_added_id: u32, todos: Vec, filter: Filter, } impl RidStore for Store { fn create() -> Self { Self { last_added_id: 0, todos: vec![], filter: Filter::All, } } fn update(&mut self, req_id: u64, msg: Msg) { use Msg::*; match msg { AddTodo(title) => { self.last_added_id += 1; let todo = Todo { id: self.last_added_id, title, completed: false, }; self.todos.push(todo); rid::post(Reply::AddedTodo(req_id, self.last_added_id.to_string())); } RemoveTodo(id) => { let mut enumerated = self.todos.iter().enumerate(); let idx = match enumerated.find(|(_, todo)| todo.id == id) { Some((idx, _)) => idx, None => return eprintln!("Could not find Todo with id '{}'", id), }; self.todos.remove(idx); rid::post(Reply::RemovedTodo(req_id, self.last_added_id.to_string())); } RemoveCompleted => { self.todos.retain(|todo| !todo.completed); rid::post(Reply::RemovedCompleted(req_id)); } CompleteTodo(id) => { self.update_todo(id, |todo| todo.completed = true); rid::post(Reply::CompletedTodo(req_id, id.to_string())); } RestartTodo(id) => { self.update_todo(id, |todo| todo.completed = false); rid::post(Reply::RestartedTodo(req_id, id.to_string())); } ToggleTodo(id) => { self.update_todo(id, |todo| todo.completed = !todo.completed); rid::post(Reply::ToggledTodo(req_id, id.to_string())); } CompleteAll => { self.todos.iter_mut().for_each(|x| x.completed = true); rid::post(Reply::CompletedAll(req_id)); } RestartAll => { self.todos.iter_mut().for_each(|x| x.completed = false); rid::post(Reply::RestartedAll(req_id)); } SetFilter(filter) => { self.filter = filter; rid::post(Reply::SetFilter(req_id)); } }; } } #[rid::export] impl Store { fn update_todo(&mut self, id: u32, update: F) { match self.todos.iter_mut().find(|x| x.id == id) { Some(todo) => update(todo), None => eprintln!("Could not find Todo with id '{}'", id), }; } #[rid::export] #[rid::structs(Todo)] fn filtered_todos(&self) -> Vec<&Todo> { let mut vec: Vec<&Todo> = match self.filter { Filter::Completed => self.todos.iter().filter(|x| x.completed).collect(), Filter::Pending => self.todos.iter().filter(|x| !x.completed).collect(), Filter::All => self.todos.iter().collect(), }; vec.sort(); vec } } // ----------------- // Todo Model // ----------------- #[rid::model] #[derive(Debug, PartialEq, Eq, PartialOrd, rid::Display)] pub struct Todo { id: u32, title: String, completed: bool, } impl Ord for Todo { fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.id.cmp(&other.id) } } impl Display for Todo { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let status = if self.completed { "✓" } else { " " }; write!(f, "[{}] ({}) '{}'", status, self.id, self.title) } } // ----------------- // Filter // ----------------- #[rid::model] #[derive(Clone, Debug, rid::Display)] pub enum Filter { Completed, Pending, All, } impl Display for Filter { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Filter::Completed => write!(f, "Completed"), Filter::Pending => write!(f, "Pending"), Filter::All => write!(f, "All"), } } } // ----------------- // Msg // ----------------- #[rid::message(Reply)] #[rid::enums(Filter)] #[derive(Debug)] pub enum Msg { AddTodo(String), RemoveTodo(u32), RemoveCompleted, CompleteTodo(u32), RestartTodo(u32), ToggleTodo(u32), CompleteAll, RestartAll, SetFilter(Filter), } // ----------------- // Reply // ----------------- #[rid::reply] pub enum Reply { AddedTodo(u64, String), RemovedTodo(u64, String), RemovedCompleted(u64), CompletedTodo(u64, String), RestartedTodo(u64, String), ToggledTodo(u64, String), CompletedAll(u64), RestartedAll(u64), SetFilter(u64), } ================================================ FILE: flutter/reddit_ticker/.gitignore ================================================ .DS_Store .idea/ .metadata ## Flutter ### # Flutter/Dart/Pub related **/doc/api/ .dart_tool/ .flutter-plugins .flutter-plugins-dependencies .fvm/ .packages .pub-cache/ .pub/ build/ coverage/ lib/generated_plugin_registrant.dart # For library packages, don’t commit the pubspec.lock file. # Regenerating the pubspec.lock file lets you test your package against the latest compatible versions of its dependencies. # See https://dart.dev/guides/libraries/private-files#pubspeclock #pubspec.lock # Android related **/android/**/gradle-wrapper.jar **/android/.gradle **/android/captures/ **/android/gradlew **/android/gradlew.bat **/android/key.properties **/android/local.properties **/android/**/GeneratedPluginRegistrant.java # iOS/XCode related **/ios/**/*.mode1v3 **/ios/**/*.mode2v3 **/ios/**/*.moved-aside **/ios/**/*.pbxuser **/ios/**/*.perspectivev3 **/ios/**/*sync/ **/ios/**/.sconsign.dblite **/ios/**/.tags* **/ios/**/.vagrant/ **/ios/**/DerivedData/ **/ios/**/Icon? **/ios/**/Pods/ **/ios/**/.symlinks/ **/ios/**/profile **/ios/**/xcuserdata **/ios/.generated/ **/ios/Flutter/.last_build_id **/ios/Flutter/App.framework **/ios/Flutter/Flutter.framework **/ios/Flutter/Flutter.podspec **/ios/Flutter/Generated.xcconfig **/ios/Flutter/app.flx **/ios/Flutter/app.zip **/ios/Flutter/flutter_assets/ **/ios/Flutter/flutter_export_environment.sh **/ios/ServiceDefinitions.json **/ios/Runner/GeneratedPluginRegistrant.* # Exceptions to above rules. !**/ios/**/default.mode1v3 !**/ios/**/default.mode2v3 !**/ios/**/default.pbxuser !**/ios/**/default.perspectivev3 !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages ### Rust ### **/target/** **/Cargo.lock ### Rid ### **/generated/** **/Classes/bindings.h **/macos/*.a **/ios/*.a **/android/src/main/jniLibs/* ================================================ FILE: flutter/reddit_ticker/Cargo.toml ================================================ [package] name = "reddit_ticker" version = "0.1.0" authors = ["Thorsten Lorenz "] edition = "2018" [lib] crate-type = ["cdylib", "staticlib" ] [[bin]] name = "rid_build" path = "rid_build.rs" [dependencies] cbindgen = "0.20.0" rid_build = { path = "../../../rid/rid-build" } rid = { path = "../../../rid" } serde = { version = "1.0.123", features = [ "derive" ] } serde_json = "1.0.64" ureq = { version = "2.0.2", features = [ "json" ] } anyhow = "1.0.38" rusqlite = { version = "0.24.2", features = [ "bundled" ] } ================================================ FILE: flutter/reddit_ticker/README.md ================================================ # reddit_ticker Rust integrated Dart Flutter Project ## Getting Started Use the below scripts to get the app ready to run with Flutter. ### 1. Generate Glue Code ```sh ./sh/bindgen ``` ### 2. Build For Desired Target/Device Run any of the below three to build the binary for the specific device and have it placed into the devices specific plugin folder. ```sh ./sh/macos ``` ### 3. Run with Flutter Run on the device. ```sh flutter run -d macos ``` ### 4. Develop Run step `1` whenever a function exposed to Flutter changes. Run step `2` whenever any of your Rust code changes. **Note** that to apply changes from Rust you need to restart the app to reload the compiled binary. A hot restart/reload does not achieve this. ## Folder Structure ``` ├── android ├── ios ├── macos ├── lib ├── plugin │ ├── android │ ├── ios │ ├── macos │ └── lib └── src ``` ### `./plugin` Provides connection from Flutter to Rust. Rust binaries are placed into the respective plugin folders `./ios, ./macos, ./android` when they are built. Generated Dart glue code is placed inside `./plugin/lib/generated` while `./plugin/lib/plugin.dart` just exposes the API to the app. ### `./src` Contains the starter Rust code inside `./src/lib.rs`. Keep developing the Rust part of your app here. ### `./lib` Contains the starter Flutter app inside `./lib/main.dart`. ### `./sh` Provides scripts to run build and code generation tasks. In the future a tool will provide the functionality currently provided by these scripts. - `bindgen` generates the `binding.h` header file for the extern Rust functions found inside `./src`. These are then placed inside the `./plugin` device folders were needed as well as `./plugin/lib/generated/binding.h` where they are used to generate Dart glue code - as part of this script `ffigen` generates Dart glue code inside `./plugin/lib/generated/ffigen_binding.dart` using `./plugin/lib/generated/binding.h` as input - `./android` builds the Rust binary to run on Android devices/emulators and places it inside `./plugin/lib/android` - `./ios` builds the Rust binary to run on IOS devices/emulators and places it inside `./plugin/lib/ios` - `./macos` builds the Rust binary to run on MacOs directly and places it inside `./plugin/lib/macos`, this is the same format as running `cargo build` on your Mac - `clean` cleans both the Flutter plugin and application, run this to reset Flutter when things aren't working ================================================ FILE: flutter/reddit_ticker/analysis_options.yaml ================================================ # This file configures the analyzer, which statically analyzes Dart code to # check for errors, warnings, and lints. # # The issues identified by the analyzer are surfaced in the UI of Dart-enabled # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be # invoked from the command line by running `flutter analyze`. # The following line activates a set of recommended lints for Flutter apps, # packages, and plugins designed to encourage good coding practices. include: package:flutter_lints/flutter.yaml linter: # The lint rules applied to this project can be customized in the # section below to disable rules from the `package:flutter_lints/flutter.yaml` # included above or to enable additional rules. A list of all available lints # and their documentation is published at # https://dart-lang.github.io/linter/lints/index.html. # # Instead of disabling a lint rule for the entire project in the # section below, it can also be suppressed for a single line of code # or a specific dart file by using the `// ignore: name_of_lint` and # `// ignore_for_file: name_of_lint` syntax on the line or in the file # producing the lint. rules: # avoid_print: false # Uncomment to disable the `avoid_print` rule # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options ================================================ FILE: flutter/reddit_ticker/android/.gitignore ================================================ 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 **/*.keystore **/*.jks ================================================ FILE: flutter/reddit_ticker/android/app/build.gradle ================================================ 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 compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } kotlinOptions { jvmTarget = '1.8' } 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.reddit_ticker" 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" } ================================================ FILE: flutter/reddit_ticker/android/app/src/debug/AndroidManifest.xml ================================================ ================================================ FILE: flutter/reddit_ticker/android/app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: flutter/reddit_ticker/android/app/src/main/kotlin/com/example/reddit_ticker/MainActivity.kt ================================================ package com.example.reddit_ticker import io.flutter.embedding.android.FlutterActivity class MainActivity: FlutterActivity() { } ================================================ FILE: flutter/reddit_ticker/android/app/src/main/res/drawable/launch_background.xml ================================================ ================================================ FILE: flutter/reddit_ticker/android/app/src/main/res/drawable-v21/launch_background.xml ================================================ ================================================ FILE: flutter/reddit_ticker/android/app/src/main/res/values/styles.xml ================================================ ================================================ FILE: flutter/reddit_ticker/android/app/src/main/res/values-night/styles.xml ================================================ ================================================ FILE: flutter/reddit_ticker/android/app/src/profile/AndroidManifest.xml ================================================ ================================================ FILE: flutter/reddit_ticker/android/build.gradle ================================================ buildscript { ext.kotlin_version = '1.3.50' repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:4.1.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } allprojects { repositories { google() mavenCentral() } } rootProject.buildDir = '../build' subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" project.evaluationDependsOn(':app') } task clean(type: Delete) { delete rootProject.buildDir } ================================================ FILE: flutter/reddit_ticker/android/gradle/wrapper/gradle-wrapper.properties ================================================ #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 ================================================ FILE: flutter/reddit_ticker/android/gradle.properties ================================================ org.gradle.jvmargs=-Xmx1536M android.useAndroidX=true android.enableJetifier=true ================================================ FILE: flutter/reddit_ticker/android/reddit_ticker_android.iml ================================================ ================================================ FILE: flutter/reddit_ticker/android/settings.gradle ================================================ 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" ================================================ FILE: flutter/reddit_ticker/deps/charts/.gitignore ================================================ .dart_tool/ .packages pubspec.lock .bundle _site build/ **/ios/.generated/ **/ios/Flutter/Generated.xcconfig **/ios/Runner/GeneratedPluginRegistrant.* **/doc/ **/example/ **/test/ ================================================ FILE: flutter/reddit_ticker/deps/charts/.travis.yml ================================================ matrix: include: # Job 1) Run tests - os: linux env: - SHARD=Tests sudo: false addons: apt: # Flutter depends on /usr/lib/x86_64-linux-gnu/libstdc++.so.6 version GLIBCXX_3.4.18 sources: - ubuntu-toolchain-r-test # if we don't specify this, the libstdc++6 we get is the wrong version packages: - libstdc++6 - fonts-droid-fallback before_script: - git clone https://github.com/flutter/flutter.git - export PATH=`pwd`/flutter/bin:`pwd`/flutter/bin/cache/dart-sdk/bin:$PATH - flutter doctor script: - cd charts_common # TODO Enable travis to run charts common test after fixing timezone test bug. #- pub get #- pub run test - cd ../charts_flutter - flutter test cache: directories: - $HOME/.pub-cache ================================================ FILE: flutter/reddit_ticker/deps/charts/AUTHORS ================================================ # Below is a list of people and organizations that have contributed # to the Charts project. Names should be added to the list like so: # # Name/Organization Google Inc. ================================================ FILE: flutter/reddit_ticker/deps/charts/CONTRIBUTING.md ================================================ This project is developed internally at Google and published for external consumption, external contributions unfortunately cannot be taken at this time. ================================================ FILE: flutter/reddit_ticker/deps/charts/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: flutter/reddit_ticker/deps/charts/README.md ================================================ Charts is a general charting library, currently enabled for the [Flutter mobile UI framework](https://flutter.io). See the [online gallery](https://google.github.io/charts/flutter/gallery.html) for supported chart types and examples of how to custom components of the chart. *Note*: This is not an official Google product. [![Travis CI Build Status](https://travis-ci.org/google/charts.svg?branch=master)](https://travis-ci.org/google/charts) ## charts_common [![charts_common pub package](https://img.shields.io/pub/v/charts_common.svg)](https://pub.dartlang.org/packages/charts_common) A common library for charting packages. ## charts_flutter [![charts_flutter pub package](https://img.shields.io/pub/v/charts_flutter.svg)](https://pub.dartlang.org/packages/charts_flutter) A charting package for [Flutter](https://flutter.io), supporting both Android and iOS. All charts packages are licensed under the Apache 2 license, see the [LICENSE](LICENSE) and [AUTHORS](AUTHORS) files for details. ## Development This project is developed internally at Google and published for external consumption, external contributions unfortunately cannot be taken at this time. ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/CHANGELOG.md ================================================ # 0.11.0 * Null support * Update to latest from internal repo # 0.10.0 * Internal bug fixes * Bump versions of intl due to pull request # 0.9.0 * Internal bug fixes * Bump versions in Gemlock file due to security alerts # 0.8.1 * Update intl version. # 0.8.0 * Bug fixes from open source. # 0.7.0 * Added vertical bar label # 0.6.0 * Bars can now be rendered on line charts. * Negative measure values will now be rendered on bar charts as a separate stack from the positive values. * Added a Datum Legend, which displays one entry per value in the first series on the chart. This is useful for pie and scatter plot charts. * The AxisPosition enum in RTLSpec was refactored to AxisDirection to better reflect its effect on swapping the positions of all start and end components, and not just positioning the measure axes. * Added custom colors for line renderer area skirts and confidence intervals. A new "areaColorFn" has been added to Series, and corresponding data to the datum. We could not use the fillColorFn for these elements, because that color is already applied to the internal section of points on line charts (including highlighter behaviors). # 0.5.0 * SelectionModelConfig's listener parameter has been renamed to "changeListener". This is a breaking change. Please rename any existing uses of the "listener" parameter to "changeListener". This was named in order to add an additional listener "updateListener" that listens to any update requests, regardless if the selection model has changed. * CartesianChart's method getMeasureAxis(String axisId) has been changed to getMeasureAxis({String axisId) so that getting the primary measure axis will not need passing any id that does not match the secondary measure axis id. This affects users implementing custom behaviors using the existing method. # 0.4.0 * Declare compatibility with Dart 2. * BasicNumericTickFormatterSpec now takes in a callback instead of NumberFormat as the default constructor. Use named constructor withNumberFormat instead. This is a breaking change. * BarRendererConfig is no longer default of type String, please change current usage to BarRendererConfig. This is a breaking change. * BarTargetLineRendererConfig is no longer default of type String, please change current usage to BarTargetLineRendererConfig. This is a breaking change. # 0.3.0 * Simplified API by removing the requirement for specifying the datum type when creating a chart. For example, previously to construct a bar chart the syntax was 'new BarChart()'. The syntax is now cleaned up to be 'new BarChart()'. Please refer to the [online gallery](https://google.github.io/charts/flutter/gallery.html) for the correct syntax. * Added scatter plot charts * Added tap to hide for legends * Added support for rendering area skirts to line charts * Added support for configurable fill colors to bar charts # 0.2.0 * Update color palette. Please use MaterialPalette instead of QuantumPalette. * Dart2 fixes # 0.1.0 Initial release. ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/README.md ================================================ # Common Charting library [![pub package](https://img.shields.io/pub/v/charts_common.svg)](https://pub.dartlang.org/packages/charts_common) Common componnets for charting libraries. ## Development This project is developed internally at Google and published for external consumption, external contributions unfortunately cannot be taken at this time. ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/charts_common.gwsq ================================================ send_cls_to('dart-charts-team+reviews'); send_cls_to('dart-charts-team'); define Main { reassign_to_list(from_owners_file('third_party/dart/charts_common/OWNERS')); } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/common.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. export 'src/chart/bar/bar_chart.dart' show BarChart; export 'src/chart/bar/bar_error_decorator.dart' show BarErrorDecorator; export 'src/chart/bar/bar_label_decorator.dart' show BarLabelAnchor, BarLabelDecorator, BarLabelPlacement, BarLabelPosition; export 'src/chart/bar/bar_lane_renderer_config.dart' show BarLaneRendererConfig; export 'src/chart/bar/bar_renderer.dart' show BarRenderer, BarRendererElement, ImmutableBarRendererElement; export 'src/chart/bar/bar_renderer_config.dart' show BarRendererConfig, CornerStrategy, ConstCornerStrategy, NoCornerStrategy; export 'src/chart/bar/bar_renderer_decorator.dart' show BarRendererDecorator; export 'src/chart/bar/bar_target_line_renderer.dart' show BarTargetLineRenderer; export 'src/chart/bar/bar_target_line_renderer_config.dart' show BarTargetLineRendererConfig; export 'src/chart/bar/base_bar_renderer.dart' show barGroupIndexKey, barGroupCountKey, barGroupWeightKey; export 'src/chart/bar/base_bar_renderer_config.dart' show BarGroupingType, BaseBarRendererConfig; export 'src/chart/cartesian/axis/axis.dart' show domainAxisKey, measureAxisIdKey, measureAxisKey, Axis, ImmutableAxis, AxisOrientation, NumericAxis, OrdinalAxis, OrdinalViewport; export 'src/chart/cartesian/axis/draw_strategy/base_tick_draw_strategy.dart' show BaseRenderSpec, BaseTickDrawStrategy; export 'src/chart/cartesian/axis/draw_strategy/gridline_draw_strategy.dart' show GridlineRendererSpec; export 'src/chart/cartesian/axis/draw_strategy/none_draw_strategy.dart' show NoneRenderSpec; export 'src/chart/cartesian/axis/draw_strategy/range_tick_draw_strategy.dart' show RangeTickRendererSpec; export 'src/chart/cartesian/axis/draw_strategy/small_tick_draw_strategy.dart' show SmallTickRendererSpec; export 'src/chart/cartesian/axis/draw_strategy/tick_draw_strategy.dart' show TickDrawStrategy; export 'src/chart/cartesian/axis/numeric_extents.dart' show NumericExtents; export 'src/chart/cartesian/axis/spec/axis_spec.dart' show AxisSpec, LineStyleSpec, RenderSpec, TextStyleSpec, TickLabelAnchor, TickLabelJustification, TickFormatterSpec, TickProviderSpec; export 'src/chart/cartesian/axis/spec/bucketing_axis_spec.dart' show BucketingAxisSpec, BucketingNumericTickProviderSpec; export 'src/chart/cartesian/axis/spec/date_time_axis_spec.dart' show DateTimeAxisSpec, DayTickProviderSpec, AutoDateTimeTickFormatterSpec, AutoDateTimeTickProviderSpec, DateTimeEndPointsTickProviderSpec, DateTimeTickFormatterSpec, DateTimeTickProviderSpec, BasicDateTimeTickFormatterSpec, TimeFormatterSpec, StaticDateTimeTickProviderSpec; export 'src/chart/cartesian/axis/spec/end_points_time_axis_spec.dart' show EndPointsTimeAxisSpec; export 'src/chart/cartesian/axis/spec/numeric_axis_spec.dart' show NumericAxisSpec, NumericEndPointsTickProviderSpec, NumericTickProviderSpec, NumericTickFormatterSpec, BasicNumericTickFormatterSpec, BasicNumericTickProviderSpec, StaticNumericTickProviderSpec; export 'src/chart/cartesian/axis/spec/ordinal_axis_spec.dart' show BasicOrdinalTickProviderSpec, BasicOrdinalTickFormatterSpec, FixedPixelOrdinalScaleSpec, FixedPixelSpaceOrdinalScaleSpec, OrdinalAxisSpec, OrdinalTickFormatterSpec, OrdinalTickProviderSpec, OrdinalScaleSpec, RangeOrdinalTickProviderSpec, SimpleOrdinalScaleSpec, StaticOrdinalTickProviderSpec; export 'src/chart/cartesian/axis/spec/percent_axis_spec.dart' show PercentAxisSpec; export 'src/chart/cartesian/axis/spec/range_tick_spec.dart' show RangeTickSpec; export 'src/chart/cartesian/axis/spec/tick_spec.dart' show TickSpec; export 'src/chart/cartesian/axis/tick.dart' show Tick; export 'src/chart/cartesian/axis/tick_formatter.dart' show SimpleTickFormatterBase, TickFormatter; export 'src/chart/cartesian/axis/time/auto_adjusting_date_time_tick_provider.dart' show AutoAdjustingDateTimeTickProvider; export 'src/chart/cartesian/axis/time/base_time_stepper.dart' show BaseTimeStepper; export 'src/chart/cartesian/axis/time/date_time_extents.dart' show DateTimeExtents; export 'src/chart/cartesian/axis/time/date_time_tick_formatter.dart' show DateTimeTickFormatter; export 'src/chart/cartesian/axis/time/time_range_tick_provider_impl.dart' show TimeRangeTickProviderImpl; export 'src/chart/cartesian/cartesian_chart.dart' show CartesianChart, NumericCartesianChart, OrdinalCartesianChart; export 'src/chart/cartesian/cartesian_renderer.dart' show BaseCartesianRenderer; export 'src/chart/common/base_chart.dart' show BaseChart, LifecycleListener; export 'src/chart/common/behavior/a11y/a11y_explore_behavior.dart' show ExploreModeTrigger; export 'src/chart/common/behavior/a11y/a11y_node.dart' show A11yNode; export 'src/chart/common/behavior/a11y/domain_a11y_explore_behavior.dart' show DomainA11yExploreBehavior, VocalizationCallback; export 'src/chart/common/behavior/a11y/keyboard_domain_navigator.dart' show KeyboardDomainNavigator; export 'src/chart/common/behavior/calculation/percent_injector.dart' show PercentInjector, PercentInjectorTotalType; export 'src/chart/common/behavior/chart_behavior.dart' show BehaviorPosition, ChartBehavior, InsideJustification, OutsideJustification; export 'src/chart/common/behavior/chart_title/chart_title.dart' show ChartTitle, ChartTitleDirection; export 'src/chart/common/behavior/domain_highlighter.dart' show DomainHighlighter; export 'src/chart/common/behavior/domain_outliner.dart' show DomainOutliner; export 'src/chart/common/behavior/initial_selection.dart' show InitialSelection; export 'src/chart/common/behavior/legend/datum_legend.dart' show DatumLegend; export 'src/chart/common/behavior/legend/legend.dart' show Legend, LegendCellPadding, LegendState, LegendTapHandling; export 'src/chart/common/behavior/legend/legend_entry.dart' show LegendEntry, LegendCategory, LegendEntryBase; export 'src/chart/common/behavior/legend/legend_entry_generator.dart' show LegendEntryGenerator, LegendDefaultMeasure; export 'src/chart/common/behavior/legend/series_legend.dart' show SeriesLegend; export 'src/chart/common/behavior/line_point_highlighter.dart' show LinePointHighlighter, LinePointHighlighterFollowLineType; export 'src/chart/common/behavior/range_annotation.dart' show AnnotationLabelAnchor, AnnotationLabelDirection, AnnotationLabelPosition, AnnotationSegment, LineAnnotationSegment, RangeAnnotation, RangeAnnotationAxisType, RangeAnnotationSegment; export 'src/chart/common/behavior/selection/lock_selection.dart' show LockSelection; export 'src/chart/common/behavior/selection/select_nearest.dart' show SelectNearest, SelectionMode; export 'src/chart/common/behavior/selection/selection_trigger.dart' show SelectionTrigger; export 'src/chart/common/behavior/slider/slider.dart' show Slider, SliderHandlePosition, SliderListenerCallback, SliderListenerDragState, SliderStyle; export 'src/chart/common/behavior/sliding_viewport.dart' show SlidingViewport; export 'src/chart/common/behavior/sunburst_ring_expander.dart' show SunburstRingExpander; export 'src/chart/common/behavior/zoom/initial_hint_behavior.dart' show InitialHintBehavior; export 'src/chart/common/behavior/zoom/pan_and_zoom_behavior.dart' show PanAndZoomBehavior; export 'src/chart/common/behavior/zoom/pan_behavior.dart' show PanBehavior, PanningCompletedCallback; export 'src/chart/common/behavior/zoom/panning_tick_provider.dart' show PanningTickProviderMode; export 'src/chart/common/canvas_shapes.dart' show CanvasBarStack, CanvasPie, CanvasPieSlice, CanvasRect; export 'src/chart/common/chart_canvas.dart' show ChartCanvas, FillPatternType, BlendMode; export 'src/chart/common/chart_context.dart' show ChartContext; export 'src/chart/common/datum_details.dart' show DatumDetails, DomainFormatter, MeasureFormatter; export 'src/chart/common/processed_series.dart' show ImmutableSeries, MutableSeries; export 'src/chart/common/selection_model/selection_model.dart' show MutableSelectionModel, SelectionModel, SelectionModelType, SelectionModelListener; export 'src/chart/common/series_datum.dart' show SeriesDatum, SeriesDatumConfig; export 'src/chart/common/series_renderer.dart' show rendererIdKey, rendererKey, SeriesRenderer; export 'src/chart/common/series_renderer_config.dart' show RendererAttributeKey, SeriesRendererConfig; export 'src/chart/layout/layout_config.dart' show LayoutConfig, MarginSpec; export 'src/chart/layout/layout_view.dart' show LayoutPosition, LayoutView, LayoutViewConfig, LayoutViewPaintOrder, LayoutViewPositionOrder, ViewMargin, ViewMeasuredSizes; export 'src/chart/line/line_chart.dart' show LineChart; export 'src/chart/line/line_renderer.dart' show LineRenderer; export 'src/chart/line/line_renderer_config.dart' show LineRendererConfig; export 'src/chart/pie/arc_label_decorator.dart' show ArcLabelDecorator, ArcLabelLeaderLineStyleSpec, ArcLabelPosition; export 'src/chart/pie/arc_renderer.dart' show ArcRenderer; export 'src/chart/pie/arc_renderer_config.dart' show ArcRendererConfig; export 'src/chart/pie/pie_chart.dart' show PieChart; export 'src/chart/scatter_plot/comparison_points_decorator.dart' show ComparisonPointsDecorator; export 'src/chart/scatter_plot/point_renderer.dart' show boundsLineRadiusPxKey, boundsLineRadiusPxFnKey, pointSymbolRendererFnKey, pointSymbolRendererIdKey, PointRenderer, PointRendererElement; export 'src/chart/scatter_plot/point_renderer_config.dart' show PointRendererConfig; export 'src/chart/scatter_plot/point_renderer_decorator.dart' show PointRendererDecorator; export 'src/chart/scatter_plot/scatter_plot_chart.dart' show ScatterPlotChart; export 'src/chart/scatter_plot/symbol_annotation_renderer.dart' show SymbolAnnotationRenderer; export 'src/chart/sunburst/sunburst_chart.dart' show SunburstChart; export 'src/chart/sunburst/sunburst_arc_renderer.dart' show SunburstArcRenderer; export 'src/chart/sunburst/sunburst_arc_renderer_config.dart' show SunburstArcRendererConfig, SunburstColorStrategy; export 'src/chart/sunburst/sunburst_arc_label_decorator.dart' show SunburstArcLabelDecorator; export 'src/chart/scatter_plot/symbol_annotation_renderer_config.dart' show SymbolAnnotationRendererConfig; export 'src/chart/time_series/time_series_chart.dart' show TimeSeriesChart; export 'src/chart/treemap/squarified_treemap_renderer.dart' show SquarifiedTreeMapRenderer; export 'src/chart/treemap/treemap_chart.dart' show TreeMapChart; export 'src/chart/treemap/treemap_label_decorator.dart' show TreeMapLabelDecorator; export 'src/chart/treemap/treemap_renderer_config.dart' show TreeMapRendererConfig, TreeMapTileType; export 'src/common/color.dart' show Color; export 'src/common/date_time_factory.dart' show DateTimeFactory, LocalDateTimeFactory, UTCDateTimeFactory; export 'src/common/gesture_listener.dart' show GestureListener; export 'src/common/graphics_factory.dart' show GraphicsFactory; export 'src/common/line_style.dart' show LineStyle; export 'src/common/material_palette.dart' show MaterialPalette; export 'src/common/math.dart' show NullablePoint; export 'src/common/performance.dart' show Performance; export 'src/common/proxy_gesture_listener.dart' show ProxyGestureListener; export 'src/common/rtl_spec.dart' show AxisDirection, RTLSpec; export 'src/common/style/material_style.dart' show MaterialStyle; export 'src/common/style/style_factory.dart' show StyleFactory; export 'src/common/symbol_renderer.dart' show CircleSymbolRenderer, CylinderSymbolRenderer, LineSymbolRenderer, PointSymbolRenderer, RectSymbolRenderer, RectangleRangeSymbolRenderer, RoundedRectSymbolRenderer, SymbolRenderer, TriangleSymbolRenderer; export 'src/common/text_element.dart' show TextElement, TextDirection, MaxWidthStrategy; export 'src/common/text_measurement.dart' show TextMeasurement; export 'src/common/text_style.dart' show TextStyle; export 'src/data/series.dart' show AttributeKey, Series, TypedAccessorFn; export 'src/data/tree.dart' show Tree, TreeNode; // // DO NOT ADD ANYTHING BELOW THIS. IT WILL BREAK OPENSOURCE. // ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/bar/bar_chart.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:collection' show LinkedHashMap; import '../bar/bar_renderer.dart' show BarRenderer; import '../cartesian/axis/axis.dart' show NumericAxis; import '../cartesian/cartesian_chart.dart' show OrdinalCartesianChart; import '../common/series_renderer.dart' show SeriesRenderer; import '../layout/layout_config.dart' show LayoutConfig; class BarChart extends OrdinalCartesianChart { BarChart( {bool? vertical, LayoutConfig? layoutConfig, NumericAxis? primaryMeasureAxis, NumericAxis? secondaryMeasureAxis, LinkedHashMap? disjointMeasureAxes}) : super( vertical: vertical, layoutConfig: layoutConfig, primaryMeasureAxis: primaryMeasureAxis, secondaryMeasureAxis: secondaryMeasureAxis, disjointMeasureAxes: disjointMeasureAxes); @override SeriesRenderer makeDefaultRenderer() { return BarRenderer()..rendererId = SeriesRenderer.defaultRendererId; } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/bar/bar_error_decorator.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math'; import '../../common/color.dart' show Color; import '../../common/graphics_factory.dart' show GraphicsFactory; import '../common/chart_canvas.dart' show ChartCanvas; import 'bar_renderer.dart' show ImmutableBarRendererElement; import 'bar_renderer_decorator.dart' show BarRendererDecorator; import '../cartesian/axis/axis.dart' show ImmutableAxis, measureAxisKey; /// Decorates bars with error whiskers. /// /// Used to represent confidence intervals for bar charts. class BarErrorDecorator extends BarRendererDecorator { static const Color _defaultStrokeColor = Color.black; static const double _defaultStrokeWidthPx = 1; static const double _defaultEndpointLengthPx = 16; static const Color _defaultOutlineColor = Color.white; static const double _defaultOutlineWidthPx = 0; final double strokeWidthPx; final double endpointLengthPx; final double outlineWidthPx; final Color strokeColor; final Color outlineColor; BarErrorDecorator( {this.strokeColor = _defaultStrokeColor, this.strokeWidthPx = _defaultStrokeWidthPx, this.endpointLengthPx = _defaultEndpointLengthPx, this.outlineWidthPx = _defaultOutlineWidthPx, this.outlineColor = _defaultOutlineColor}); @override void decorate( Iterable> barElements, ChartCanvas canvas, GraphicsFactory graphicsFactory, { required Rectangle drawBounds, required double animationPercent, required bool renderingVertically, bool rtl = false, }) { // Only decorate the bars when animation is at 100%. if (animationPercent != 1.0) { return; } for (var element in barElements) { final bounds = element.bounds!; final datumIndex = element.index; final series = element.series!; final measureLowerBoundFn = series.measureLowerBoundFn; final measureUpperBoundFn = series.measureUpperBoundFn; if (measureLowerBoundFn != null && measureUpperBoundFn != null) { final measureOffsetFn = series.measureOffsetFn!; final measureAxis = series.getAttr(measureAxisKey) as ImmutableAxis; if (renderingVertically) { final startY = measureAxis.getLocation( (measureLowerBoundFn(datumIndex) ?? 0) + measureOffsetFn(datumIndex)!)!; final endY = measureAxis.getLocation( (measureUpperBoundFn(datumIndex) ?? 0) + measureOffsetFn(datumIndex)!)!; if (startY != endY) { final barWidth = bounds.right - bounds.left; final x = (bounds.left + bounds.right) / 2; final rectWidth = min(strokeWidthPx + 2 * outlineWidthPx, barWidth.toDouble()); final strokeWidth = rectWidth - 2 * outlineWidthPx; final rectEndpointLength = min(endpointLengthPx + 2 * outlineWidthPx, barWidth.toDouble()); final endpointLength = rectEndpointLength - 2 * outlineWidthPx; if (outlineWidthPx > 0) { // Draw rectangle rendering the outline for the vertical line. canvas.drawRect( Rectangle.fromPoints(Point(x - rectWidth / 2, startY), Point(x + rectWidth / 2, endY)), fill: outlineColor, strokeWidthPx: outlineWidthPx); // Draw rectangle rendering the outline for the horizontal // endpoint representing the lower bound. canvas.drawRect( Rectangle(x - rectEndpointLength / 2, startY - rectWidth / 2, rectEndpointLength, rectWidth), fill: outlineColor, strokeWidthPx: outlineWidthPx); // Draw rectangle rendering the outline for the horizontal // endpoint representing the upper bound. canvas.drawRect( Rectangle(x - rectEndpointLength / 2, endY - rectWidth / 2, rectEndpointLength, rectWidth), fill: outlineColor, strokeWidthPx: outlineWidthPx); } // Draw vertical whisker line. canvas.drawLine( points: [Point(x, startY), Point(x, endY)], stroke: strokeColor, strokeWidthPx: strokeWidth); // Draw horizontal whisker line for the lower bound. canvas.drawLine(points: [ Point(x - endpointLength / 2, startY), Point(x + endpointLength / 2, startY) ], stroke: strokeColor, strokeWidthPx: strokeWidth); // Draw horizontal whisker line for the upper bound. canvas.drawLine(points: [ Point(x - endpointLength / 2, endY), Point(x + endpointLength / 2, endY) ], stroke: strokeColor, strokeWidthPx: strokeWidth); } } else { final startX = measureAxis.getLocation( (measureLowerBoundFn(datumIndex) ?? 0) + measureOffsetFn(datumIndex)!)!; final endX = measureAxis.getLocation( (measureUpperBoundFn(datumIndex) ?? 0) + measureOffsetFn(datumIndex)!)!; if (startX != endX) { final barWidth = bounds.bottom - bounds.top; final y = (bounds.top + bounds.bottom) / 2; final rectWidth = min(strokeWidthPx + 2 * outlineWidthPx, barWidth.toDouble()); final strokeWidth = rectWidth - 2 * outlineWidthPx; final rectEndpointLength = min(endpointLengthPx + 2 * outlineWidthPx, barWidth.toDouble()); final endpointLength = rectEndpointLength - 2 * outlineWidthPx; if (outlineWidthPx > 0) { // Draw rectangle rendering the outline for the horizontal line. canvas.drawRect( Rectangle.fromPoints(Point(startX, y - rectWidth / 2), Point(endX, y + rectWidth / 2)), fill: outlineColor, strokeWidthPx: outlineWidthPx); // Draw rectangle rendering the outline for the vertical // endpoint representing the lower bound. canvas.drawRect( Rectangle(startX - rectWidth / 2, y - rectEndpointLength / 2, rectWidth, rectEndpointLength), fill: outlineColor, strokeWidthPx: outlineWidthPx); // Draw rectangle rendering the outline for the vertical // endpoint representing the upper bound. canvas.drawRect( Rectangle(endX - rectWidth / 2, y - rectEndpointLength / 2, rectWidth, rectEndpointLength), fill: outlineColor, strokeWidthPx: outlineWidthPx); } // Draw horizontal whisker line. canvas.drawLine( points: [Point(startX, y), Point(endX, y)], stroke: strokeColor, strokeWidthPx: strokeWidth); // Draw vertical whisker line for the lower bound. canvas.drawLine(points: [ Point(startX, y - endpointLength / 2), Point(startX, y + endpointLength / 2) ], stroke: strokeColor, strokeWidthPx: strokeWidth); // Draw vertical whisker line for the upper bound. canvas.drawLine(points: [ Point(endX, y - endpointLength / 2), Point(endX, y + endpointLength / 2) ], stroke: strokeColor, strokeWidthPx: strokeWidth); } } } } } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/bar/bar_label_decorator.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math' show Rectangle; import '../../common/color.dart' show Color; import '../../common/graphics_factory.dart' show GraphicsFactory; import '../../common/text_element.dart' show TextDirection, TextElement; import '../../common/text_style.dart' show TextStyle; import '../../data/series.dart' show AccessorFn; import '../cartesian/axis/spec/axis_spec.dart' show TextStyleSpec; import '../common/chart_canvas.dart' show ChartCanvas; import 'bar_renderer.dart' show ImmutableBarRendererElement; import 'bar_renderer_decorator.dart' show BarRendererDecorator; class BarLabelDecorator extends BarRendererDecorator { // Default configuration static const _defaultLabelPosition = BarLabelPosition.auto; static const _defaultLabelPadding = 5; static const _defaultLabelPlacement = BarLabelPlacement.followMeasureAxis; static const _defaultHorizontalLabelAnchor = BarLabelAnchor.start; static const _defaultVerticalLabelAnchor = BarLabelAnchor.end; static final _defaultInsideLabelStyle = TextStyleSpec(fontSize: 12, color: Color.white); static final _defaultOutsideLabelStyle = TextStyleSpec(fontSize: 12, color: Color.black); static final _labelSplitPattern = '\n'; static final _defaultMultiLineLabelPadding = 2; /// Configures [TextStyleSpec] for labels placed inside the bars. final TextStyleSpec insideLabelStyleSpec; /// Configures [TextStyleSpec] for labels placed outside the bars. final TextStyleSpec outsideLabelStyleSpec; /// Configures where to place the label relative to the bars. final BarLabelPosition labelPosition; /// Configures where to place the label relative to the axis. final BarLabelPlacement labelPlacement; /// For labels drawn inside the bar, configures label anchor position. final BarLabelAnchor? labelAnchor; /// Space before and after the label text. final int labelPadding; BarLabelDecorator( {TextStyleSpec? insideLabelStyleSpec, TextStyleSpec? outsideLabelStyleSpec, this.labelAnchor, this.labelPosition = _defaultLabelPosition, this.labelPlacement = _defaultLabelPlacement, this.labelPadding = _defaultLabelPadding}) : insideLabelStyleSpec = insideLabelStyleSpec ?? _defaultInsideLabelStyle, outsideLabelStyleSpec = outsideLabelStyleSpec ?? _defaultOutsideLabelStyle; @override void decorate(Iterable> barElements, ChartCanvas canvas, GraphicsFactory graphicsFactory, {required Rectangle drawBounds, required double animationPercent, required bool renderingVertically, bool rtl = false}) { // Only decorate the bars when animation is at 100%. if (animationPercent != 1.0) { return; } if (renderingVertically) { _decorateVerticalBars( barElements, canvas, graphicsFactory, drawBounds, rtl); } else { _decorateHorizontalBars( barElements, canvas, graphicsFactory, drawBounds, rtl); } } void _decorateVerticalBars( Iterable> barElements, ChartCanvas canvas, GraphicsFactory graphicsFactory, Rectangle drawBounds, bool rtl) { // Create [TextStyle] from [TextStyleSpec] to be used by all the elements. // The [GraphicsFactory] is needed so it can't be created earlier. final insideLabelStyle = _getTextStyle(graphicsFactory, insideLabelStyleSpec); final outsideLabelStyle = _getTextStyle(graphicsFactory, outsideLabelStyleSpec); for (var element in barElements) { final labelFn = element.series!.labelAccessorFn; final measureFn = element.series!.measureFn; final datumIndex = element.index; final label = labelFn?.call(datumIndex); final measure = measureFn(datumIndex) ?? 0.0; // If there are custom styles, use that instead of the default or the // style defined for the entire decorator. final datumInsideLabelStyle = _getDatumStyle( element.series!.insideLabelStyleAccessorFn, datumIndex, graphicsFactory, defaultStyle: insideLabelStyle); final datumOutsideLabelStyle = _getDatumStyle( element.series!.outsideLabelStyleAccessorFn, datumIndex, graphicsFactory, defaultStyle: outsideLabelStyle); // Skip calculation and drawing for this element if no label. if (label == null || label.isEmpty) { continue; } var labelElements = label .split(_labelSplitPattern) .map((labelPart) => graphicsFactory.createTextElement(labelPart)); final bounds = element.bounds!; // Get space available inside and outside the bar. final totalPadding = labelPadding * 2; final insideBarHeight = bounds.height - totalPadding; var calculatedLabelPosition = labelPosition; if (calculatedLabelPosition == BarLabelPosition.auto) { // For auto, first try to fit the text inside the bar. labelElements = labelElements.map( (labelElement) => labelElement..textStyle = datumInsideLabelStyle); final labelMaxWidth = labelElements .map( (labelElement) => labelElement.measurement.horizontalSliceWidth) .fold(0, (max, current) => max > current ? max : current); // Total label height depends on the label element's text style. final totalLabelHeight = _getTotalLabelHeight(labelElements); // A label fits if the length and width of the text fits. calculatedLabelPosition = totalLabelHeight < insideBarHeight && labelMaxWidth < bounds.width ? BarLabelPosition.inside : BarLabelPosition.outside; } // Set the max width, text style, and text direction. labelElements = labelElements.map((labelElement) => labelElement ..textStyle = calculatedLabelPosition == BarLabelPosition.inside ? datumInsideLabelStyle : datumOutsideLabelStyle ..maxWidth = bounds.width ..textDirection = rtl ? TextDirection.rtl : TextDirection.ltr); // Total label height depends on the label element's text style. final totalLabelHeight = _getTotalLabelHeight(labelElements); var labelsDrawn = 0; for (var labelElement in labelElements) { // Calculate the start position of label based on [labelAnchor]. final int labelY; final labelHeight = labelElement.measurement.verticalSliceWidth.round(); final offsetHeight = (labelHeight + _defaultMultiLineLabelPadding) * labelsDrawn; if (calculatedLabelPosition == BarLabelPosition.inside) { final anchor = _resolveLabelAnchor( measure, labelAnchor ?? _defaultVerticalLabelAnchor); switch (anchor) { case BarLabelAnchor.end: labelY = bounds.top + labelPadding + offsetHeight; break; case BarLabelAnchor.middle: labelY = (bounds.bottom - bounds.height / 2 - totalLabelHeight / 2 + offsetHeight) .round(); break; case BarLabelAnchor.start: labelY = bounds.bottom - labelPadding - totalLabelHeight + offsetHeight; break; } } else { // calculatedLabelPosition == LabelPosition.outside if (measure < 0 && labelPlacement == BarLabelPlacement.opposeAxisBaseline) { labelY = bounds.bottom + labelPadding + offsetHeight; } else { labelY = bounds.top - labelPadding - totalLabelHeight + offsetHeight; } } // Center the label inside the bar. final labelX = (bounds.left + bounds.width / 2 - labelElement.measurement.horizontalSliceWidth / 2) .round(); canvas.drawText(labelElement, labelX, labelY); labelsDrawn += 1; } } } void _decorateHorizontalBars( Iterable> barElements, ChartCanvas canvas, GraphicsFactory graphicsFactory, Rectangle drawBounds, bool rtl) { // Create [TextStyle] from [TextStyleSpec] to be used by all the elements. // The [GraphicsFactory] is needed so it can't be created earlier. final insideLabelStyle = _getTextStyle(graphicsFactory, insideLabelStyleSpec); final outsideLabelStyle = _getTextStyle(graphicsFactory, outsideLabelStyleSpec); for (var element in barElements) { final labelFn = element.series!.labelAccessorFn; final measureFn = element.series!.measureFn; final datumIndex = element.index; final label = labelFn?.call(datumIndex); final measure = measureFn(datumIndex) ?? 0.0; // If there are custom styles, use that instead of the default or the // style defined for the entire decorator. final datumInsideLabelStyle = _getDatumStyle( element.series!.insideLabelStyleAccessorFn, datumIndex, graphicsFactory, defaultStyle: insideLabelStyle); final datumOutsideLabelStyle = _getDatumStyle( element.series!.outsideLabelStyleAccessorFn, datumIndex, graphicsFactory, defaultStyle: outsideLabelStyle); // Skip calculation and drawing for this element if no label. if (label == null || label.isEmpty) { continue; } final bounds = element.bounds!; // Get space available inside and outside the bar. final totalPadding = labelPadding * 2; final insideBarWidth = bounds.width - totalPadding; final outsideBarWidth = drawBounds.width - bounds.width - totalPadding; final labelElement = graphicsFactory.createTextElement(label); var calculatedLabelPosition = labelPosition; if (calculatedLabelPosition == BarLabelPosition.auto) { // For auto, first try to fit the text inside the bar. labelElement.textStyle = datumInsideLabelStyle; // A label fits if the space inside the bar is >= outside bar or if the // length of the text fits and the space. This is because if the bar has // more space than the outside, it makes more sense to place the label // inside the bar, even if the entire label does not fit. calculatedLabelPosition = (insideBarWidth >= outsideBarWidth || labelElement.measurement.horizontalSliceWidth < insideBarWidth) ? BarLabelPosition.inside : BarLabelPosition.outside; } // Set the max width and text style. if (calculatedLabelPosition == BarLabelPosition.inside) { labelElement.textStyle = datumInsideLabelStyle; labelElement.maxWidth = insideBarWidth; } else { // calculatedLabelPosition == LabelPosition.outside labelElement.textStyle = datumOutsideLabelStyle; labelElement.maxWidth = outsideBarWidth; } // Only calculate and draw label if there's actually space for the label. if (labelElement.maxWidth! < 0 || (labelElement.maxWidthStrategy == null && labelElement.measurement.horizontalSliceWidth > labelElement.maxWidth!)) { return; } // Calculate the start position of label based on [labelAnchor]. final int labelX; if (calculatedLabelPosition == BarLabelPosition.inside) { final anchor = _resolveLabelAnchor( measure, labelAnchor ?? _defaultHorizontalLabelAnchor); switch (anchor) { case BarLabelAnchor.middle: labelX = (bounds.left + bounds.width / 2 - labelElement.measurement.horizontalSliceWidth / 2) .round(); labelElement.textDirection = rtl ? TextDirection.rtl : TextDirection.ltr; break; case BarLabelAnchor.end: case BarLabelAnchor.start: final alignLeft = rtl ? (anchor == BarLabelAnchor.end) : (anchor == BarLabelAnchor.start); if (alignLeft) { labelX = bounds.left + labelPadding; labelElement.textDirection = TextDirection.ltr; } else { labelX = bounds.right - labelPadding; labelElement.textDirection = TextDirection.rtl; } break; } } else { // calculatedLabelPosition == LabelPosition.outside if (measure < 0 && labelPlacement == BarLabelPlacement.opposeAxisBaseline) { labelX = bounds.left - labelPadding; labelElement.textDirection = TextDirection.rtl; } else { labelX = bounds.right + labelPadding; labelElement.textDirection = TextDirection.ltr; } } // Center the label inside the bar. final labelY = (bounds.top + (bounds.bottom - bounds.top) / 2 - labelElement.measurement.verticalSliceWidth / 2) .round(); canvas.drawText(labelElement, labelX, labelY); } } /// Helper function to get the total height for a group of labels. /// This includes the padding in between the labels. int _getTotalLabelHeight(Iterable labelElements) => (labelElements.first.measurement.verticalSliceWidth * labelElements.length) .round() + _defaultMultiLineLabelPadding * (labelElements.length - 1); // Helper function that converts [TextStyleSpec] to [TextStyle]. TextStyle _getTextStyle( GraphicsFactory graphicsFactory, TextStyleSpec? labelSpec) { return graphicsFactory.createTextPaint() ..color = labelSpec?.color ?? Color.black ..fontFamily = labelSpec?.fontFamily ..fontSize = labelSpec?.fontSize ?? 12 ..lineHeight = labelSpec?.lineHeight; } /// Helper function to get datum specific style TextStyle _getDatumStyle( AccessorFn? labelFn, int? datumIndex, GraphicsFactory graphicsFactory, { required TextStyle defaultStyle, }) { final styleSpec = labelFn?.call(datumIndex); return (styleSpec != null) ? _getTextStyle(graphicsFactory, styleSpec) : defaultStyle; } /// Helper function to get the bar label anchor when [BarLabelPostion] is /// inside. BarLabelAnchor _resolveLabelAnchor(num measure, BarLabelAnchor anchor) { if (labelPlacement == BarLabelPlacement.opposeAxisBaseline) { if (measure >= 0) return anchor; if (anchor == BarLabelAnchor.start) return BarLabelAnchor.end; if (anchor == BarLabelAnchor.end) return BarLabelAnchor.start; return anchor; } return anchor; } } /// Configures where to place the label relative to the bars. enum BarLabelPosition { /// Automatically try to place the label inside the bar first and place it on /// the outside of the space available outside the bar is greater than space /// available inside the bar. auto, /// Always place label on the outside. outside, /// Always place label on the inside. inside, } /// Configures where to place the label relative to the axis. enum BarLabelPlacement { /// Places the label with respect to the increase in measure axis units. The /// bar end is the most positive position along the axis. /// /// This is the default placement. followMeasureAxis, /// Places the label with respect to the zero baseline. The bar end is the /// absolute value aways from the zero baseline. opposeAxisBaseline, } /// Configures where to anchor the label for labels drawn inside the bars. enum BarLabelAnchor { /// Anchor to the measure start. start, /// Anchor to the middle of the measure range. middle, /// Anchor to the measure end. end, } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/bar/bar_lane_renderer.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:collection' show LinkedHashMap; import 'package:collection/collection.dart' show IterableExtension; import '../../data/series.dart' show AttributeKey; import '../cartesian/axis/axis.dart' show ImmutableAxis, domainAxisKey, measureAxisKey; import '../common/chart_canvas.dart' show ChartCanvas; import '../common/processed_series.dart' show ImmutableSeries, MutableSeries; import 'bar_lane_renderer_config.dart' show BarLaneRendererConfig; import 'bar_renderer.dart' show AnimatedBar, BarRenderer, BarRendererElement; import 'base_bar_renderer.dart' show allBarGroupWeightsKey, barGroupCountKey, barGroupIndexKey, barGroupWeightKey, previousBarGroupWeightKey, stackKeyKey; /// Key for storing a list of all domain values that exist in the series data. /// /// In grouped stacked mode, this list will contain a combination of domain /// value and series category. const domainValuesKey = AttributeKey>('BarLaneRenderer.domainValues'); /// Renders series data as a series of bars with lanes. /// /// Every stack of bars will have a swim lane rendered underneath the series /// data, in a gray color by default. The swim lane occupies the same width as /// the bar elements, and will be completely covered up if the bar stack happens /// to take up the entire measure domain range. /// /// If every bar that shares a domain value has a null measure value, then the /// swim lanes may optionally be merged together into one wide lane that covers /// the full domain range band width. class BarLaneRenderer extends BarRenderer { /// Store a map of domain+barGroupIndex+category index to bar lanes in a /// stack. /// /// This map is used to render all the bars in a stack together, to account /// for rendering effects that need to take the full stack into account (e.g. /// corner rounding). /// /// [LinkedHashMap] is used to render the bars on the canvas in the same order /// as the data was given to the chart. For the case where both grouping and /// stacking are disabled, this means that bars for data later in the series /// will be drawn "on top of" bars earlier in the series. // ignore: prefer_collection_literals, https://github.com/dart-lang/linter/issues/1649 final _barLaneStackMap = LinkedHashMap>>(); /// Store a map of flags to track whether all measure values for a given /// domain value are null, for every series on the chart. // ignore: prefer_collection_literals, https://github.com/dart-lang/linter/issues/1649 final _allMeasuresForDomainNullMap = LinkedHashMap(); factory BarLaneRenderer({ BarLaneRendererConfig? config, String? rendererId, }) { rendererId ??= 'bar'; config ??= BarLaneRendererConfig(); return BarLaneRenderer._internal(config: config, rendererId: rendererId); } BarLaneRenderer._internal({ required BarLaneRendererConfig config, required String rendererId, }) : super.internal(config: config, rendererId: rendererId); @override void preprocessSeries(List> seriesList) { super.preprocessSeries(seriesList); _allMeasuresForDomainNullMap.clear(); seriesList.forEach((MutableSeries series) { final domainFn = series.domainFn; final measureFn = series.rawMeasureFn; final domainValues = {}; for (var barIndex = 0; barIndex < series.data.length; barIndex++) { final domain = domainFn(barIndex); final measure = measureFn(barIndex); domainValues.add(domain); // Update the "all measure null" tracking for bars that have the // current domain value. if ((config as BarLaneRendererConfig).mergeEmptyLanes) { final allNull = _allMeasuresForDomainNullMap[domain]; final isNull = measure == null; _allMeasuresForDomainNullMap[domain] = allNull != null ? allNull && isNull : isNull; } } series.setAttr(domainValuesKey, domainValues); }); } @override void update(List> seriesList, bool isAnimatingThisDraw) { super.update(seriesList, isAnimatingThisDraw); // Add gray bars to render under every bar stack. seriesList.forEach((ImmutableSeries series) { var domainValues = series.getAttr(domainValuesKey) as Set; final domainAxis = series.getAttr(domainAxisKey) as ImmutableAxis; final measureAxis = series.getAttr(measureAxisKey) as ImmutableAxis; final seriesStackKey = series.getAttr(stackKeyKey); final barGroupCount = series.getAttr(barGroupCountKey)!; final barGroupIndex = series.getAttr(barGroupIndexKey)!; final previousBarGroupWeight = series.getAttr(previousBarGroupWeightKey); final barGroupWeight = series.getAttr(barGroupWeightKey); final allBarGroupWeights = series.getAttr(allBarGroupWeightsKey); final measureAxisPosition = measureAxis.getLocation(0.0); final maxMeasureValue = _getMaxMeasureValue(measureAxis); // Create a fake series for [BarLabelDecorator] to use when looking up the // index of each datum. final laneSeries = MutableSeries.clone(seriesList[0] as MutableSeries); laneSeries.data = []; // Don't render any labels on the swim lanes. laneSeries.labelAccessorFn = (int? index) => ''; var laneSeriesIndex = 0; domainValues.forEach((D domainValue) { // Skip adding any background bars if they will be covered up by the // domain-spanning null bar. if (_allMeasuresForDomainNullMap[domainValue] == true) { return; } // Add a fake datum to the series for [BarLabelDecorator]. final datum = {'index': laneSeriesIndex}; laneSeries.data.add(datum); // Each bar should be stored in barStackMap in a structure that mirrors // the visual rendering of the bars. Thus, they should be grouped by // domain value, series category (by way of the stack keys that were // generated for each series in the preprocess step), and bar group // index to account for all combinations of grouping and stacking. final barStackMapKey = '${domainValue}__${seriesStackKey}__${barGroupIndex}'; final barKey = barStackMapKey + '0'; final barStackList = _barLaneStackMap.putIfAbsent( barStackMapKey, () => >[]); // If we already have an AnimatingBar for that index, use it. var animatingBar = barStackList.firstWhereOrNull((bar) => bar.key == barKey); // If we don't have any existing bar element, create a new bar and have // it animate in from the domain axis. if (animatingBar == null) { animatingBar = makeAnimatedBar( key: barKey, series: laneSeries, datum: datum, barGroupIndex: barGroupIndex, previousBarGroupWeight: previousBarGroupWeight, barGroupWeight: barGroupWeight, allBarGroupWeights: allBarGroupWeights, color: (config as BarLaneRendererConfig).backgroundBarColor, details: BarRendererElement(), domainValue: domainValue, domainAxis: domainAxis, domainWidth: domainAxis.rangeBand.round(), fillColor: (config as BarLaneRendererConfig).backgroundBarColor, measureValue: maxMeasureValue, measureOffsetValue: 0.0, measureAxisPosition: measureAxisPosition, measureAxis: measureAxis, numBarGroups: barGroupCount, strokeWidthPx: config.strokeWidthPx, measureIsNull: false, measureIsNegative: false); barStackList.add(animatingBar); } else { animatingBar ..datum = datum ..series = laneSeries ..domainValue = domainValue; } // Get the barElement we are going to setup. // Optimization to prevent allocation in non-animating case. final barElement = makeBarRendererElement( barGroupIndex: barGroupIndex, previousBarGroupWeight: previousBarGroupWeight, barGroupWeight: barGroupWeight, allBarGroupWeights: allBarGroupWeights, color: (config as BarLaneRendererConfig).backgroundBarColor, details: BarRendererElement(), domainValue: domainValue, domainAxis: domainAxis, domainWidth: domainAxis.rangeBand.round(), fillColor: (config as BarLaneRendererConfig).backgroundBarColor, measureValue: maxMeasureValue, measureOffsetValue: 0.0, measureAxisPosition: measureAxisPosition, measureAxis: measureAxis, numBarGroups: barGroupCount, strokeWidthPx: config.strokeWidthPx, measureIsNull: false, measureIsNegative: false); animatingBar.setNewTarget(barElement); laneSeriesIndex++; }); }); // Add domain-spanning bars to render when every measure value for every // datum of a given domain is null. if ((config as BarLaneRendererConfig).mergeEmptyLanes) { // Use the axes from the first series. final domainAxis = seriesList[0].getAttr(domainAxisKey) as ImmutableAxis; final measureAxis = seriesList[0].getAttr(measureAxisKey) as ImmutableAxis; final measureAxisPosition = measureAxis.getLocation(0.0); final maxMeasureValue = _getMaxMeasureValue(measureAxis); final barGroupIndex = 0; final previousBarGroupWeight = 0.0; final barGroupWeight = 1.0; final barGroupCount = 1; // Create a fake series for [BarLabelDecorator] to use when looking up the // index of each datum. We don't care about any other series values for // the merged lanes, so just clone the first series. final mergedSeries = MutableSeries.clone(seriesList[0] as MutableSeries); mergedSeries.data = []; // Add a label accessor that returns the empty lane label. mergedSeries.labelAccessorFn = (int? index) => (config as BarLaneRendererConfig).emptyLaneLabel; var mergedSeriesIndex = 0; _allMeasuresForDomainNullMap.forEach((D domainValue, bool allNull) { if (allNull) { // Add a fake datum to the series for [BarLabelDecorator]. final datum = {'index': mergedSeriesIndex}; mergedSeries.data.add(datum); final barStackMapKey = '${domainValue}__allNull__'; final barKey = barStackMapKey + '0'; final barStackList = _barLaneStackMap.putIfAbsent( barStackMapKey, () => >[]); // If we already have an AnimatingBar for that index, use it. var animatingBar = barStackList.firstWhereOrNull((bar) => bar.key == barKey); // If we don't have any existing bar element, create a new bar and have // it animate in from the domain axis. if (animatingBar == null) { animatingBar = makeAnimatedBar( key: barKey, series: mergedSeries, datum: datum, barGroupIndex: barGroupIndex, previousBarGroupWeight: previousBarGroupWeight, barGroupWeight: barGroupWeight, color: (config as BarLaneRendererConfig).backgroundBarColor, details: BarRendererElement(), domainValue: domainValue, domainAxis: domainAxis, domainWidth: domainAxis.rangeBand.round(), fillColor: (config as BarLaneRendererConfig).backgroundBarColor, measureValue: maxMeasureValue, measureOffsetValue: 0.0, measureAxisPosition: measureAxisPosition, measureAxis: measureAxis, numBarGroups: barGroupCount, strokeWidthPx: config.strokeWidthPx, measureIsNull: false, measureIsNegative: false); barStackList.add(animatingBar); } else { animatingBar ..datum = datum ..series = mergedSeries ..domainValue = domainValue; } // Get the barElement we are going to setup. // Optimization to prevent allocation in non-animating case. final barElement = makeBarRendererElement( barGroupIndex: barGroupIndex, previousBarGroupWeight: previousBarGroupWeight, barGroupWeight: barGroupWeight, color: (config as BarLaneRendererConfig).backgroundBarColor, details: BarRendererElement(), domainValue: domainValue, domainAxis: domainAxis, domainWidth: domainAxis.rangeBand.round(), fillColor: (config as BarLaneRendererConfig).backgroundBarColor, measureValue: maxMeasureValue, measureOffsetValue: 0.0, measureAxisPosition: measureAxisPosition, measureAxis: measureAxis, numBarGroups: barGroupCount, strokeWidthPx: config.strokeWidthPx, measureIsNull: false, measureIsNegative: false); animatingBar.setNewTarget(barElement); mergedSeriesIndex++; } }); } } /// Gets the maximum measure value that will fit in the draw area. num _getMaxMeasureValue(ImmutableAxis measureAxis) { final pos = chart.vertical ? chart.drawAreaBounds.top : isRtl ? chart.drawAreaBounds.left : chart.drawAreaBounds.right; return measureAxis.getDomain(pos.toDouble()); } /// Paints the current bar data on the canvas. @override void paint(ChartCanvas canvas, double animationPercent) { _barLaneStackMap.forEach((String stackKey, List> barStack) { // Turn this into a list so that the getCurrentBar isn't called more than // once for each animationPercent if the barElements are iterated more // than once. final barElements = barStack .map((animatingBar) => animatingBar.getCurrentBar(animationPercent)) .toList(); paintBar(canvas, animationPercent, barElements); }); super.paint(canvas, animationPercent); } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/bar/bar_lane_renderer_config.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import '../../common/color.dart' show Color; import '../../common/style/style_factory.dart' show StyleFactory; import '../../common/symbol_renderer.dart'; import '../common/chart_canvas.dart' show FillPatternType; import '../layout/layout_view.dart' show LayoutViewPaintOrder; import 'bar_label_decorator.dart' show BarLabelDecorator; import 'bar_lane_renderer.dart' show BarLaneRenderer; import 'bar_renderer_config.dart' show BarRendererConfig, CornerStrategy; import 'bar_renderer_decorator.dart' show BarRendererDecorator; import 'base_bar_renderer_config.dart' show BarGroupingType; /// Configuration for a bar lane renderer. class BarLaneRendererConfig extends BarRendererConfig { /// The color of background bars. final Color backgroundBarColor; /// Label text to draw on a merged empty lane. /// /// This will only be drawn if all of the measures for a domain are null, and /// [mergeEmptyLanes] is enabled. /// /// The renderer must be configured with a [BarLabelDecorator] for this label /// to be drawn. final String emptyLaneLabel; /// Whether or not all lanes for a given domain value should be merged into /// one wide lane if all measure values for said domain are null. final bool mergeEmptyLanes; BarLaneRendererConfig({ String? customRendererId, CornerStrategy? cornerStrategy, this.emptyLaneLabel = 'No data', FillPatternType? fillPattern, BarGroupingType? groupingType, int layoutPaintOrder = LayoutViewPaintOrder.bar, this.mergeEmptyLanes = false, int minBarLengthPx = 0, int stackedBarPaddingPx = 1, double strokeWidthPx = 0.0, BarRendererDecorator? barRendererDecorator, SymbolRenderer? symbolRenderer, Color? backgroundBarColor, List? weightPattern, }) : backgroundBarColor = backgroundBarColor ?? StyleFactory.style.noDataColor, super( barRendererDecorator: barRendererDecorator, cornerStrategy: cornerStrategy, customRendererId: customRendererId, groupingType: groupingType ?? BarGroupingType.grouped, layoutPaintOrder: layoutPaintOrder, minBarLengthPx: minBarLengthPx, fillPattern: fillPattern, stackedBarPaddingPx: stackedBarPaddingPx, strokeWidthPx: strokeWidthPx, symbolRenderer: symbolRenderer, weightPattern: weightPattern, ); @override BarLaneRenderer build() { return BarLaneRenderer(config: this, rendererId: customRendererId); } @override bool operator ==(Object other) { if (identical(this, other)) { return true; } return other is BarLaneRendererConfig && other.backgroundBarColor == backgroundBarColor && other.emptyLaneLabel == emptyLaneLabel && other.mergeEmptyLanes == mergeEmptyLanes && super == other; } @override int get hashCode { var hash = super.hashCode; hash = hash * 31 + backgroundBarColor.hashCode; hash = hash * 31 + emptyLaneLabel.hashCode; hash = hash * 31 + mergeEmptyLanes.hashCode; return hash; } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/bar/bar_renderer.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math' show max, min, Rectangle; import 'package:meta/meta.dart' show protected; import '../../common/color.dart' show Color; import '../../common/math.dart' show NullablePoint; import '../cartesian/axis/axis.dart' show ImmutableAxis, domainAxisKey, measureAxisKey; import '../common/canvas_shapes.dart' show CanvasBarStack, CanvasRect; import '../common/chart_canvas.dart' show ChartCanvas, FillPatternType; import '../common/datum_details.dart' show DatumDetails; import '../common/processed_series.dart' show ImmutableSeries, MutableSeries; import '../common/series_datum.dart' show SeriesDatum; import 'bar_renderer_config.dart' show BarRendererConfig, CornerStrategy; import 'bar_renderer_decorator.dart' show BarRendererDecorator; import 'base_bar_renderer.dart' show BaseBarRenderer, allBarGroupWeightsKey, barGroupCountKey, barGroupIndexKey, barGroupWeightKey, previousBarGroupWeightKey; import 'base_bar_renderer_element.dart' show BaseAnimatedBar, BaseBarRendererElement; /// Renders series data as a series of bars. class BarRenderer extends BaseBarRenderer, AnimatedBar> { /// If we are grouped, use this spacing between the bars in a group. final _barGroupInnerPadding = 2; /// The padding between bar stacks. /// /// The padding comes out of the bottom of the bar. final int _stackedBarPaddingPx; final BarRendererDecorator? barRendererDecorator; factory BarRenderer({BarRendererConfig? config, String? rendererId}) { rendererId ??= 'bar'; config ??= BarRendererConfig(); return BarRenderer.internal(config: config, rendererId: rendererId); } /// This constructor is protected because it is used by child classes, which /// cannot call the factory in their own constructors. @protected BarRenderer.internal({ required BarRendererConfig config, required String rendererId, }) : barRendererDecorator = config.barRendererDecorator, _stackedBarPaddingPx = config.stackedBarPaddingPx, super( config: config, rendererId: rendererId, layoutPaintOrder: config.layoutPaintOrder ?? 0); @override void configureSeries(List> seriesList) { assignMissingColors(getOrderedSeriesList(seriesList), emptyCategoryUsesSinglePalette: true); } @override DatumDetails addPositionToDetailsForSeriesDatum( DatumDetails details, SeriesDatum seriesDatum) { final series = details.series!; final domainAxis = series.getAttr(domainAxisKey) as ImmutableAxis; final measureAxis = series.getAttr(measureAxisKey) as ImmutableAxis; final barGroupIndex = series.getAttr(barGroupIndexKey)!; final previousBarGroupWeight = series.getAttr(previousBarGroupWeightKey); final barGroupWeight = series.getAttr(barGroupWeightKey); final allBarGroupWeights = series.getAttr(allBarGroupWeightsKey); final numBarGroups = series.getAttr(barGroupCountKey)!; final bounds = _getBarBounds( details.domain, domainAxis, domainAxis.rangeBand.round(), config.maxBarWidthPx, details.measure, details.measureOffset!, measureAxis, barGroupIndex, previousBarGroupWeight, barGroupWeight, allBarGroupWeights, numBarGroups); NullablePoint chartPosition; if (renderingVertically) { chartPosition = NullablePoint( (bounds.left + (bounds.width / 2)).toDouble(), bounds.top.toDouble()); } else { chartPosition = NullablePoint( isRtl ? bounds.left.toDouble() : bounds.right.toDouble(), (bounds.top + (bounds.height / 2)).toDouble()); } return DatumDetails.from(details, chartPosition: chartPosition, bounds: bounds); } @override BarRendererElement getBaseDetails(dynamic datum, int index) { return BarRendererElement(); } CornerStrategy get cornerStrategy { return (config as BarRendererConfig).cornerStrategy; } /// Generates an [AnimatedBar] to represent the previous and current state /// of one bar on the chart. @override AnimatedBar makeAnimatedBar( {required String key, required ImmutableSeries series, List? dashPattern, dynamic datum, Color? color, required BarRendererElement details, D? domainValue, required ImmutableAxis domainAxis, required int domainWidth, num? measureValue, required num measureOffsetValue, required ImmutableAxis measureAxis, double? measureAxisPosition, Color? fillColor, FillPatternType? fillPattern, double? strokeWidthPx, required int barGroupIndex, double? previousBarGroupWeight, double? barGroupWeight, List? allBarGroupWeights, required int numBarGroups, bool? measureIsNull, bool? measureIsNegative}) { return AnimatedBar( key: key, datum: datum, series: series, domainValue: domainValue) ..setNewTarget(makeBarRendererElement( color: color, dashPattern: dashPattern, details: details, domainValue: domainValue, domainAxis: domainAxis, domainWidth: domainWidth, measureValue: measureValue, measureOffsetValue: measureOffsetValue, measureAxisPosition: measureAxisPosition, measureAxis: measureAxis, fillColor: fillColor, fillPattern: fillPattern, strokeWidthPx: strokeWidthPx, barGroupIndex: barGroupIndex, previousBarGroupWeight: previousBarGroupWeight, barGroupWeight: barGroupWeight, allBarGroupWeights: allBarGroupWeights, numBarGroups: numBarGroups, measureIsNull: measureIsNull, measureIsNegative: measureIsNegative)); } /// Generates a [BarRendererElement] to represent the rendering data for one /// bar on the chart. @override BarRendererElement makeBarRendererElement( {Color? color, List? dashPattern, required BarRendererElement details, D? domainValue, required ImmutableAxis domainAxis, required int domainWidth, num? measureValue, required num measureOffsetValue, required ImmutableAxis measureAxis, double? measureAxisPosition, Color? fillColor, FillPatternType? fillPattern, double? strokeWidthPx, required int barGroupIndex, double? previousBarGroupWeight, double? barGroupWeight, List? allBarGroupWeights, required int numBarGroups, bool? measureIsNull, bool? measureIsNegative}) { return BarRendererElement() ..color = color ..dashPattern = dashPattern ..fillColor = fillColor ..fillPattern = fillPattern ..measureAxisPosition = measureAxisPosition ..roundPx = details.roundPx ..strokeWidthPx = strokeWidthPx ..measureIsNull = measureIsNull ..measureIsNegative = measureIsNegative ..bounds = _getBarBounds( domainValue, domainAxis, domainWidth, config.maxBarWidthPx, measureValue, measureOffsetValue, measureAxis, barGroupIndex, previousBarGroupWeight, barGroupWeight, allBarGroupWeights, numBarGroups); } @override void paintBar(ChartCanvas canvas, double animationPercent, Iterable> barElements) { final bars = []; // When adjusting bars for stacked bar padding, do not modify the first bar // if rendering vertically and do not modify the last bar if rendering // horizontally. final unmodifiedBar = renderingVertically ? barElements.first : barElements.last; // Find the max bar width from each segment to calculate corner radius. var maxBarWidth = 0; var measureIsNegative = false; for (var bar in barElements) { var bounds = bar.bounds; measureIsNegative = measureIsNegative || bar.measureIsNegative!; if (bar != unmodifiedBar) { bounds = renderingVertically ? Rectangle( bar.bounds!.left, max( 0, bar.bounds!.top + (measureIsNegative ? _stackedBarPaddingPx : 0)), bar.bounds!.width, max(0, bar.bounds!.height - _stackedBarPaddingPx), ) : Rectangle( max( 0, bar.bounds!.left + (measureIsNegative ? _stackedBarPaddingPx : 0)), bar.bounds!.top, max(0, bar.bounds!.width - _stackedBarPaddingPx), bar.bounds!.height, ); } bars.add(CanvasRect(bounds!, dashPattern: bar.dashPattern, fill: bar.fillColor, pattern: bar.fillPattern, stroke: bar.color, strokeWidthPx: bar.strokeWidthPx)); maxBarWidth = max(maxBarWidth, renderingVertically ? bounds.width : bounds.height); } bool roundTopLeft; bool roundTopRight; bool roundBottomLeft; bool roundBottomRight; if (measureIsNegative) { // Negative bars should be rounded towards the negative axis direction. // In vertical mode, this is the bottom. In horizontal mode, this is the // left side of the chart for LTR, or the right side for RTL. roundTopLeft = !renderingVertically && !isRtl; roundTopRight = !renderingVertically && isRtl; roundBottomLeft = renderingVertically || !isRtl; roundBottomRight = renderingVertically || isRtl; } else { // Positive bars should be rounded towards the positive axis direction. // In vertical mode, this is the top. In horizontal mode, this is the // right side of the chart for LTR, or the left side for RTL. roundTopLeft = renderingVertically || isRtl; roundTopRight = !isRtl; roundBottomLeft = isRtl; roundBottomRight = !(renderingVertically || isRtl); } final barStack = CanvasBarStack( bars, radius: cornerStrategy.getRadius(maxBarWidth), stackedBarPadding: _stackedBarPaddingPx, roundTopLeft: roundTopLeft, roundTopRight: roundTopRight, roundBottomLeft: roundBottomLeft, roundBottomRight: roundBottomRight, ); // If bar stack's range width is: // * Within the component bounds, then draw the bar stack. // * Partially out of component bounds, then clip the stack where it is out // of bounds. // * Fully out of component bounds, do not draw. final componentBounds = this.componentBounds!; final barOutsideBounds = renderingVertically ? barStack.fullStackRect.left < componentBounds.left || barStack.fullStackRect.right > componentBounds.right : barStack.fullStackRect.top < componentBounds.top || barStack.fullStackRect.bottom > componentBounds.bottom; // TODO: When we have initial viewport, add image test for // clipping. if (barOutsideBounds) { final clipBounds = _getBarStackBounds(barStack.fullStackRect); // Do not draw the bar stack if it is completely outside of the component // bounds. if (clipBounds.width <= 0 || clipBounds.height <= 0) { return; } canvas.setClipBounds(clipBounds); } canvas.drawBarStack(barStack, drawAreaBounds: componentBounds); if (barOutsideBounds) { canvas.resetClipBounds(); } // Decorate the bar segments if there is a decorator. barRendererDecorator?.decorate(barElements, canvas, graphicsFactory!, drawBounds: drawBounds!, animationPercent: animationPercent, renderingVertically: renderingVertically, rtl: isRtl); } /// Calculate the clipping region for a rectangle that represents the full bar /// stack. Rectangle _getBarStackBounds(Rectangle barStackRect) { int left; int right; int top; int bottom; final componentBounds = this.componentBounds!; if (renderingVertically) { // Only clip at the start and end so that the bar's width stays within // the viewport, but any bar decorations above the bar can still show. left = max(componentBounds.left, barStackRect.left); right = min(componentBounds.right, barStackRect.right); top = barStackRect.top; bottom = barStackRect.bottom; } else { // Only clip at the top and bottom so that the bar's height stays within // the viewport, but any bar decorations to the right of the bar can still // show. left = barStackRect.left; right = barStackRect.right; top = max(componentBounds.top, barStackRect.top); bottom = min(componentBounds.bottom, barStackRect.bottom); } final width = right - left; final height = bottom - top; return Rectangle(left, top, width, height); } /// Generates a set of bounds that describe a bar. Rectangle _getBarBounds( D? domainValue, ImmutableAxis domainAxis, int domainWidth, int? maxBarWidthPx, num? measureValue, num measureOffsetValue, ImmutableAxis measureAxis, int barGroupIndex, double? previousBarGroupWeight, double? barGroupWeight, List? allBarGroupWeights, int numBarGroups) { // TODO: Investigate why this is negative for a DateTime domain // in RTL mode. domainWidth = domainWidth.abs(); // If no weights were passed in, default to equal weight per bar. if (barGroupWeight == null) { barGroupWeight = 1 / numBarGroups; previousBarGroupWeight = barGroupIndex * barGroupWeight; } // Calculate how wide each bar should be within the group of bars. If we // only have one series, or are stacked, then barWidth should equal // domainWidth. final spacingLoss = _barGroupInnerPadding * (numBarGroups - 1); var desiredWidth = ((domainWidth - spacingLoss) / numBarGroups).round(); if (maxBarWidthPx != null) { desiredWidth = min(desiredWidth, maxBarWidthPx); domainWidth = desiredWidth * numBarGroups + spacingLoss; } // If the series was configured with a weight pattern, treat the "max" bar // width as the average max width. The overall total width will still equal // max times number of bars, but this results in a nicer final picture. var barWidth = desiredWidth; if (allBarGroupWeights != null) { barWidth = (desiredWidth * numBarGroups * allBarGroupWeights[barGroupIndex]) .round(); } // Make sure that bars are at least one pixel wide, so that they will always // be visible on the chart. Ideally we should do something clever with the // size of the chart, and the density and periodicity of the data, but this // at least ensures that dense charts still have visible data. barWidth = max(1, barWidth); // Flip bar group index for calculating location on the domain axis if RTL. final adjustedBarGroupIndex = isRtl ? numBarGroups - barGroupIndex - 1 : barGroupIndex; // Calculate the start and end of the bar, taking into account accumulated // padding for grouped bars. final previousAverageWidth = adjustedBarGroupIndex > 0 ? ((domainWidth - spacingLoss) * (previousBarGroupWeight! / adjustedBarGroupIndex)) .round() : 0; final domainStart = (domainAxis.getLocation(domainValue)! - (domainWidth / 2) + (previousAverageWidth + _barGroupInnerPadding) * adjustedBarGroupIndex) .round(); final domainEnd = domainStart + barWidth; measureValue ??= 0; // Calculate measure locations. Stacked bars should have their // offset calculated previously. int measureStart; int measureEnd; if (measureValue < 0) { measureEnd = measureAxis.getLocation(measureOffsetValue)!.round(); measureStart = measureAxis.getLocation(measureValue + measureOffsetValue)!.round(); } else { measureStart = measureAxis.getLocation(measureOffsetValue)!.round(); measureEnd = measureAxis.getLocation(measureValue + measureOffsetValue)!.round(); } Rectangle bounds; if (renderingVertically) { // Rectangle clamps to zero width/height bounds = Rectangle(domainStart, measureEnd, domainEnd - domainStart, measureStart - measureEnd); } else { // Rectangle clamps to zero width/height bounds = Rectangle(min(measureStart, measureEnd), domainStart, (measureEnd - measureStart).abs(), domainEnd - domainStart); } return bounds; } @override Rectangle? getBoundsForBar(BarRendererElement bar) => bar.bounds; } abstract class ImmutableBarRendererElement { ImmutableSeries? get series; dynamic get datum; int? get index; Rectangle? get bounds; } class BarRendererElement extends BaseBarRendererElement implements ImmutableBarRendererElement { @override ImmutableSeries? series; @override Rectangle? bounds; int? roundPx; @override int? index; dynamic _datum; @override dynamic get datum => _datum; set datum(dynamic datum) { _datum = datum; index = series?.data.indexOf(datum); } BarRendererElement(); BarRendererElement.clone(BarRendererElement other) : super.clone(other) { series = other.series; bounds = other.bounds; roundPx = other.roundPx; index = other.index; _datum = other._datum; } @override void updateAnimationPercent(BaseBarRendererElement previous, BaseBarRendererElement target, double animationPercent) { final localPrevious = previous as BarRendererElement; final localTarget = target as BarRendererElement; final previousBounds = localPrevious.bounds!; final targetBounds = localTarget.bounds!; var top = ((targetBounds.top - previousBounds.top) * animationPercent) + previousBounds.top; var right = ((targetBounds.right - previousBounds.right) * animationPercent) + previousBounds.right; var bottom = ((targetBounds.bottom - previousBounds.bottom) * animationPercent) + previousBounds.bottom; var left = ((targetBounds.left - previousBounds.left) * animationPercent) + previousBounds.left; bounds = Rectangle(left.round(), top.round(), (right - left).round(), (bottom - top).round()); roundPx = localTarget.roundPx; super.updateAnimationPercent(previous, target, animationPercent); } } class AnimatedBar extends BaseAnimatedBar> { AnimatedBar( {required String key, required dynamic datum, required ImmutableSeries series, required D? domainValue}) : super(key: key, datum: datum, series: series, domainValue: domainValue); @override void animateElementToMeasureAxisPosition(BaseBarRendererElement target) { final localTarget = target as BarRendererElement; // TODO: Animate out bars in the middle of a stack. localTarget.bounds = Rectangle( localTarget.bounds!.left + (localTarget.bounds!.width / 2).round(), localTarget.measureAxisPosition!.round(), 0, 0); } @override BarRendererElement getCurrentBar(double animationPercent) { final bar = super.getCurrentBar(animationPercent); // Update with series and datum information to pass to bar decorator. bar.series = series; bar.datum = datum; return bar; } @override BarRendererElement clone(BarRendererElement bar) => BarRendererElement.clone(bar); } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/bar/bar_renderer_config.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import '../../common/symbol_renderer.dart'; import '../common/chart_canvas.dart' show FillPatternType; import '../layout/layout_view.dart' show LayoutViewPaintOrder; import 'bar_renderer.dart' show BarRenderer; import 'bar_renderer_decorator.dart' show BarRendererDecorator; import 'base_bar_renderer_config.dart' show BarGroupingType, BaseBarRendererConfig; /// Configuration for a bar renderer. class BarRendererConfig extends BaseBarRendererConfig { /// Strategy for determining the corner radius of a bar. final CornerStrategy cornerStrategy; /// Decorator for optionally decorating painted bars. final BarRendererDecorator? barRendererDecorator; BarRendererConfig({ String? customRendererId, CornerStrategy? cornerStrategy, FillPatternType? fillPattern, BarGroupingType? groupingType, int layoutPaintOrder = LayoutViewPaintOrder.bar, int minBarLengthPx = 0, int? maxBarWidthPx, int stackedBarPaddingPx = 1, double strokeWidthPx = 0.0, this.barRendererDecorator, SymbolRenderer? symbolRenderer, List? weightPattern, }) : cornerStrategy = cornerStrategy ?? const ConstCornerStrategy(2), super( customRendererId: customRendererId, groupingType: groupingType ?? BarGroupingType.grouped, layoutPaintOrder: layoutPaintOrder, minBarLengthPx: minBarLengthPx, maxBarWidthPx: maxBarWidthPx, fillPattern: fillPattern, stackedBarPaddingPx: stackedBarPaddingPx, strokeWidthPx: strokeWidthPx, symbolRenderer: symbolRenderer, weightPattern: weightPattern, ); @override BarRenderer build() { return BarRenderer(config: this, rendererId: customRendererId); } @override bool operator ==(Object other) { if (identical(this, other)) { return true; } return other is BarRendererConfig && other.cornerStrategy == cornerStrategy && super == other; } @override int get hashCode { var hash = super.hashCode; hash = hash * 31 + cornerStrategy.hashCode; return hash; } } abstract class CornerStrategy { /// Returns the radius of the rounded corners in pixels. int getRadius(int barWidth); } /// Strategy for constant corner radius. class ConstCornerStrategy implements CornerStrategy { final int radius; const ConstCornerStrategy(this.radius); @override int getRadius(_) => radius; @override bool operator ==(Object other) { if (identical(this, other)) { return true; } return other is ConstCornerStrategy && other.radius == radius; } @override int get hashCode => radius.hashCode; } /// Strategy for no corner radius. class NoCornerStrategy extends ConstCornerStrategy { const NoCornerStrategy() : super(0); @override bool operator ==(other) => other is NoCornerStrategy; @override int get hashCode => 31; } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/bar/bar_renderer_decorator.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math' show Rectangle; import '../../common/graphics_factory.dart' show GraphicsFactory; import '../common/chart_canvas.dart' show ChartCanvas; import 'bar_renderer.dart' show ImmutableBarRendererElement; /// Decorates bars after the bars have already been painted. abstract class BarRendererDecorator { const BarRendererDecorator(); void decorate(Iterable> barElements, ChartCanvas canvas, GraphicsFactory graphicsFactory, {required Rectangle drawBounds, required double animationPercent, required bool renderingVertically, bool rtl = false}); } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/bar/bar_target_line_renderer.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math' show Point, Rectangle, max, min; import '../../common/color.dart' show Color; import '../../common/math.dart' show NullablePoint; import '../cartesian/axis/axis.dart' show ImmutableAxis, domainAxisKey, measureAxisKey; import '../common/chart_canvas.dart' show ChartCanvas, FillPatternType; import '../common/datum_details.dart' show DatumDetails; import '../common/processed_series.dart' show ImmutableSeries, MutableSeries; import '../common/series_datum.dart' show SeriesDatum; import '../layout/layout_view.dart' show LayoutViewPaintOrder; import 'bar_target_line_renderer_config.dart' show BarTargetLineRendererConfig; import 'base_bar_renderer.dart' show BaseBarRenderer, allBarGroupWeightsKey, barGroupWeightKey, barGroupCountKey, barGroupIndexKey, previousBarGroupWeightKey; import 'base_bar_renderer_element.dart' show BaseAnimatedBar, BaseBarRendererElement; /// Renders series data as a series of bar target lines. /// /// Usually paired with a BarRenderer to display target metrics alongside actual /// metrics. class BarTargetLineRenderer extends BaseBarRenderer> { /// If we are grouped, use this spacing between the bars in a group. final _barGroupInnerPadding = 2; /// Standard color for all bar target lines. final _color = Color(r: 0, g: 0, b: 0, a: 153); factory BarTargetLineRenderer({ BarTargetLineRendererConfig? config, String? rendererId, }) { config ??= BarTargetLineRendererConfig(); rendererId ??= 'barTargetLine'; return BarTargetLineRenderer._internal( config: config, rendererId: rendererId); } BarTargetLineRenderer._internal({ required BarTargetLineRendererConfig config, required String rendererId, }) : super( config: config, rendererId: rendererId, layoutPaintOrder: config.layoutPaintOrder ?? LayoutViewPaintOrder.barTargetLine); @override void configureSeries(List> seriesList) { seriesList.forEach((MutableSeries series) { series.colorFn ??= (_) => _color; series.fillColorFn ??= (_) => _color; // Fill in missing seriesColor values with the color of the first datum in // the series. Note that [Series.colorFn] should always return a color. if (series.seriesColor == null) { try { series.seriesColor = series.colorFn!(0); } catch (exception) { series.seriesColor = _color; } } }); } @override DatumDetails addPositionToDetailsForSeriesDatum( DatumDetails details, SeriesDatum seriesDatum) { final series = details.series!; final domainAxis = series.getAttr(domainAxisKey) as ImmutableAxis; final measureAxis = series.getAttr(measureAxisKey) as ImmutableAxis; final barGroupIndex = series.getAttr(barGroupIndexKey)!; final previousBarGroupWeight = series.getAttr(previousBarGroupWeightKey); final barGroupWeight = series.getAttr(barGroupWeightKey); final allBarGroupWeights = series.getAttr(allBarGroupWeightsKey); final numBarGroups = series.getAttr(barGroupCountKey)!; final points = _getTargetLinePoints( details.domain, domainAxis, domainAxis.rangeBand.round(), config.maxBarWidthPx, details.measure, details.measureOffset!, measureAxis, barGroupIndex, previousBarGroupWeight, barGroupWeight, allBarGroupWeights, numBarGroups); NullablePoint chartPosition; if (renderingVertically) { chartPosition = NullablePoint( (points[0].x + (points[1].x - points[0].x) / 2).toDouble(), points[0].y.toDouble()); } else { chartPosition = NullablePoint(points[0].x.toDouble(), (points[0].y + (points[1].y - points[0].y) / 2).toDouble()); } return DatumDetails.from(details, chartPosition: chartPosition); } @override _BarTargetLineRendererElement getBaseDetails(dynamic datum, int index) { final localConfig = config as BarTargetLineRendererConfig; return _BarTargetLineRendererElement( roundEndCaps: localConfig.roundEndCaps); } /// Generates an [_AnimatedBarTargetLine] to represent the previous and /// current state of one bar target line on the chart. @override _AnimatedBarTargetLine makeAnimatedBar( {required String key, required ImmutableSeries series, dynamic datum, Color? color, List? dashPattern, required _BarTargetLineRendererElement details, D? domainValue, required ImmutableAxis domainAxis, required int domainWidth, num? measureValue, required num measureOffsetValue, required ImmutableAxis measureAxis, double? measureAxisPosition, Color? fillColor, FillPatternType? fillPattern, required int barGroupIndex, double? previousBarGroupWeight, double? barGroupWeight, List? allBarGroupWeights, required int numBarGroups, double? strokeWidthPx, bool? measureIsNull, bool? measureIsNegative}) { return _AnimatedBarTargetLine( key: key, datum: datum, series: series, domainValue: domainValue) ..setNewTarget(makeBarRendererElement( color: color, details: details, dashPattern: dashPattern, domainValue: domainValue, domainAxis: domainAxis, domainWidth: domainWidth, measureValue: measureValue, measureOffsetValue: measureOffsetValue, measureAxisPosition: measureAxisPosition, measureAxis: measureAxis, fillColor: fillColor, fillPattern: fillPattern, strokeWidthPx: strokeWidthPx, barGroupIndex: barGroupIndex, previousBarGroupWeight: previousBarGroupWeight, barGroupWeight: barGroupWeight, allBarGroupWeights: allBarGroupWeights, numBarGroups: numBarGroups, measureIsNull: measureIsNull, measureIsNegative: measureIsNegative)); } /// Generates a [_BarTargetLineRendererElement] to represent the rendering /// data for one bar target line on the chart. @override _BarTargetLineRendererElement makeBarRendererElement( {Color? color, List? dashPattern, required _BarTargetLineRendererElement details, D? domainValue, required ImmutableAxis domainAxis, required int domainWidth, num? measureValue, required num measureOffsetValue, required ImmutableAxis measureAxis, double? measureAxisPosition, Color? fillColor, FillPatternType? fillPattern, double? strokeWidthPx, required int barGroupIndex, double? previousBarGroupWeight, double? barGroupWeight, List? allBarGroupWeights, required int numBarGroups, bool? measureIsNull, bool? measureIsNegative}) { return _BarTargetLineRendererElement(roundEndCaps: details.roundEndCaps) ..color = color ..dashPattern = dashPattern ..fillColor = fillColor ..fillPattern = fillPattern ..measureAxisPosition = measureAxisPosition ..strokeWidthPx = strokeWidthPx ..measureIsNull = measureIsNull ..measureIsNegative = measureIsNegative ..points = _getTargetLinePoints( domainValue, domainAxis, domainWidth, config.maxBarWidthPx, measureValue, measureOffsetValue, measureAxis, barGroupIndex, previousBarGroupWeight, barGroupWeight, allBarGroupWeights, numBarGroups); } @override void paintBar( ChartCanvas canvas, double animationPercent, Iterable<_BarTargetLineRendererElement> barElements, ) { barElements.forEach((_BarTargetLineRendererElement bar) { // TODO: Combine common line attributes into // GraphicsFactory.lineStyle or similar. canvas.drawLine( clipBounds: drawBounds, points: bar.points, stroke: bar.color, roundEndCaps: bar.roundEndCaps, strokeWidthPx: bar.strokeWidthPx, dashPattern: bar.dashPattern); }); } /// Generates a set of points that describe a bar target line. List> _getTargetLinePoints( D? domainValue, ImmutableAxis domainAxis, int domainWidth, int? maxBarWidthPx, num? measureValue, num measureOffsetValue, ImmutableAxis measureAxis, int barGroupIndex, double? previousBarGroupWeight, double? barGroupWeight, List? allBarGroupWeights, int numBarGroups) { // If no weights were passed in, default to equal weight per bar. if (barGroupWeight == null) { barGroupWeight = 1 / numBarGroups; previousBarGroupWeight = barGroupIndex * barGroupWeight; } final localConfig = config as BarTargetLineRendererConfig; // Calculate how wide each bar target line should be within the group of // bar target lines. If we only have one series, or are stacked, then // barWidth should equal domainWidth. var spacingLoss = _barGroupInnerPadding * (numBarGroups - 1); var desiredWidth = ((domainWidth - spacingLoss) / numBarGroups).round(); if (maxBarWidthPx != null) { desiredWidth = min(desiredWidth, maxBarWidthPx); domainWidth = desiredWidth * numBarGroups + spacingLoss; } // If the series was configured with a weight pattern, treat the "max" bar // width as the average max width. The overall total width will still equal // max times number of bars, but this results in a nicer final picture. var barWidth = desiredWidth; if (allBarGroupWeights != null) { barWidth = (desiredWidth * numBarGroups * allBarGroupWeights[barGroupIndex]) .floor(); } // Get the overdraw boundaries. var overDrawOuterPx = localConfig.overDrawOuterPx; var overDrawPx = localConfig.overDrawPx; var overDrawStartPx = (barGroupIndex == 0) && overDrawOuterPx != null ? overDrawOuterPx : overDrawPx; var overDrawEndPx = (barGroupIndex == numBarGroups - 1) && overDrawOuterPx != null ? overDrawOuterPx : overDrawPx; // Flip bar group index for calculating location on the domain axis if RTL. final adjustedBarGroupIndex = isRtl ? numBarGroups - barGroupIndex - 1 : barGroupIndex; // Calculate the start and end of the bar target line, taking into account // accumulated padding for grouped bars. num previousAverageWidth = adjustedBarGroupIndex > 0 ? ((domainWidth - spacingLoss) * (previousBarGroupWeight! / adjustedBarGroupIndex)) .round() : 0; var domainStart = (domainAxis.getLocation(domainValue)! - (domainWidth / 2) + (previousAverageWidth + _barGroupInnerPadding) * adjustedBarGroupIndex - overDrawStartPx) .round(); var domainEnd = domainStart + barWidth + overDrawStartPx + overDrawEndPx; measureValue = measureValue ?? 0; // Calculate measure locations. Stacked bars should have their // offset calculated previously. var measureStart = measureAxis.getLocation(measureValue + measureOffsetValue)!.round(); List> points; if (renderingVertically) { points = [ Point(domainStart, measureStart), Point(domainEnd, measureStart) ]; } else { points = [ Point(measureStart, domainStart), Point(measureStart, domainEnd) ]; } return points; } @override Rectangle getBoundsForBar(_BarTargetLineRendererElement bar) { final points = bar.points; assert(points.isNotEmpty); var top = points.first.y; var bottom = points.first.y; var left = points.first.x; var right = points.first.x; for (final point in points.skip(1)) { top = min(top, point.y); left = min(left, point.x); bottom = max(bottom, point.y); right = max(right, point.x); } return Rectangle(left, top, right - left, bottom - top); } } class _BarTargetLineRendererElement extends BaseBarRendererElement { late List> points; bool roundEndCaps; _BarTargetLineRendererElement({required this.roundEndCaps}); _BarTargetLineRendererElement.clone(_BarTargetLineRendererElement other) : points = List.of(other.points), roundEndCaps = other.roundEndCaps, super.clone(other); @override void updateAnimationPercent(BaseBarRendererElement previous, BaseBarRendererElement target, double animationPercent) { final localPrevious = previous as _BarTargetLineRendererElement; final localTarget = target as _BarTargetLineRendererElement; final previousPoints = localPrevious.points; final targetPoints = localTarget.points; late Point lastPoint; int pointIndex; for (pointIndex = 0; pointIndex < targetPoints.length; pointIndex++) { var targetPoint = targetPoints[pointIndex]; // If we have more points than the previous line, animate in the new point // by starting its measure position at the last known official point. Point previousPoint; if (previousPoints.length - 1 >= pointIndex) { previousPoint = previousPoints[pointIndex]; lastPoint = previousPoint; } else { previousPoint = Point(targetPoint.x, lastPoint.y); } var x = ((targetPoint.x - previousPoint.x) * animationPercent) + previousPoint.x; var y = ((targetPoint.y - previousPoint.y) * animationPercent) + previousPoint.y; if (points.length - 1 >= pointIndex) { points[pointIndex] = Point(x.round(), y.round()); } else { points.add(Point(x.round(), y.round())); } } // Removing extra points that don't exist anymore. if (pointIndex < points.length) { points.removeRange(pointIndex, points.length); } strokeWidthPx = ((localTarget.strokeWidthPx! - localPrevious.strokeWidthPx!) * animationPercent) + localPrevious.strokeWidthPx!; roundEndCaps = localTarget.roundEndCaps; super.updateAnimationPercent(previous, target, animationPercent); } } class _AnimatedBarTargetLine extends BaseAnimatedBar { _AnimatedBarTargetLine( {required String key, required dynamic datum, required ImmutableSeries series, required D? domainValue}) : super(key: key, datum: datum, series: series, domainValue: domainValue); @override void animateElementToMeasureAxisPosition(BaseBarRendererElement target) { final localTarget = target as _BarTargetLineRendererElement; final newPoints = >[]; for (var index = 0; index < localTarget.points.length; index++) { final targetPoint = localTarget.points[index]; newPoints.add( Point(targetPoint.x, localTarget.measureAxisPosition!.round())); } localTarget.points = newPoints; } @override _BarTargetLineRendererElement clone(_BarTargetLineRendererElement bar) => _BarTargetLineRendererElement.clone(bar); } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/bar/bar_target_line_renderer_config.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import '../../common/symbol_renderer.dart' show SymbolRenderer, LineSymbolRenderer; import '../layout/layout_view.dart' show LayoutViewPaintOrder; import 'bar_target_line_renderer.dart' show BarTargetLineRenderer; import 'base_bar_renderer_config.dart' show BarGroupingType, BaseBarRendererConfig; /// Configuration for a bar target line renderer. class BarTargetLineRendererConfig extends BaseBarRendererConfig { /// The number of pixels that the line will extend beyond the bandwidth at the /// edges of the bar group. /// /// If set, this overrides overDrawPx for the beginning side of the first bar /// target line in the group, and the ending side of the last bar target line. /// overDrawPx will be used for overdrawing the target lines for interior /// sides of the bars. final int? overDrawOuterPx; /// The number of pixels that the line will extend beyond the bandwidth for /// every bar in a group. final int overDrawPx; /// Whether target lines should have round end caps, or square if false. final bool roundEndCaps; BarTargetLineRendererConfig( {String? customRendererId, List? dashPattern, BarGroupingType groupingType = BarGroupingType.grouped, int layoutPaintOrder = LayoutViewPaintOrder.barTargetLine, int minBarLengthPx = 0, this.overDrawOuterPx, this.overDrawPx = 0, this.roundEndCaps = true, double strokeWidthPx = 3.0, SymbolRenderer? symbolRenderer, List? weightPattern}) : super( customRendererId: customRendererId, dashPattern: dashPattern, groupingType: groupingType, layoutPaintOrder: layoutPaintOrder, minBarLengthPx: minBarLengthPx, strokeWidthPx: strokeWidthPx, symbolRenderer: symbolRenderer ?? LineSymbolRenderer(), weightPattern: weightPattern, ); @override BarTargetLineRenderer build() { return BarTargetLineRenderer(config: this, rendererId: customRendererId); } @override bool operator ==(Object other) { if (identical(this, other)) { return true; } return other is BarTargetLineRendererConfig && other.overDrawOuterPx == overDrawOuterPx && other.overDrawPx == overDrawPx && other.roundEndCaps == roundEndCaps && super == other; } @override int get hashCode { var hash = 1; hash = hash * 31 + overDrawOuterPx.hashCode; hash = hash * 31 + overDrawPx.hashCode; hash = hash * 31 + roundEndCaps.hashCode; return hash; } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/bar/base_bar_renderer.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:collection' show LinkedHashMap, HashSet; import 'dart:math' show Point, Rectangle, max; import 'package:collection/collection.dart' show IterableExtension; import 'package:meta/meta.dart' show protected; import '../../common/color.dart' show Color; import '../../common/math.dart' show clamp; import '../../data/series.dart' show AttributeKey; import '../cartesian/axis/axis.dart' show ImmutableAxis, OrdinalAxis, domainAxisKey, measureAxisKey; import '../cartesian/axis/scale.dart' show RangeBandConfig; import '../cartesian/cartesian_renderer.dart' show BaseCartesianRenderer; import '../common/chart_canvas.dart' show ChartCanvas, FillPatternType; import '../common/datum_details.dart' show DatumDetails; import '../common/processed_series.dart' show ImmutableSeries, MutableSeries; import 'base_bar_renderer_config.dart' show BaseBarRendererConfig; import 'base_bar_renderer_element.dart' show BaseAnimatedBar, BaseBarRendererElement; const barGroupIndexKey = AttributeKey('BarRenderer.barGroupIndex'); const barGroupCountKey = AttributeKey('BarRenderer.barGroupCount'); const barGroupWeightKey = AttributeKey('BarRenderer.barGroupWeight'); const previousBarGroupWeightKey = AttributeKey('BarRenderer.previousBarGroupWeight'); const allBarGroupWeightsKey = AttributeKey>('BarRenderer.allBarGroupWeights'); const stackKeyKey = AttributeKey('BarRenderer.stackKey'); const barElementsKey = AttributeKey>('BarRenderer.elements'); /// Base class for bar renderers that implements common stacking and grouping /// logic. /// /// Bar renderers support 4 different modes of rendering multiple series on the /// chart, configured by the grouped and stacked flags. /// * grouped - Render bars for each series that shares a domain value /// side-by-side. /// * stacked - Render bars for each series that shares a domain value in a /// stack, ordered in the same order as the series list. /// * grouped-stacked: Render bars for each series that shares a domain value in /// a group of bar stacks. Each stack will contain all the series that share a /// series category. /// * floating style - When grouped and stacked are both false, all bars that /// share a domain value will be rendered in the same domain space. Each datum /// should be configured with a measure offset to position its bar along the /// measure axis. Bars will freely overlap if their measure values and measure /// offsets overlap. Note that bars for each series will be rendered in order, /// such that bars from the last series will be "on top" of bars from previous /// series. abstract class BaseBarRenderer> extends BaseCartesianRenderer { // `config` can't be a `BaseBarRendererConfig` because `BarLaneRenderer` // passes a `BarLaneRendererConfig`, but `BarLaneRendererConfig` is a // `BarRendererConfig`. final BaseBarRendererConfig config; // Save the chart.vertical value at the start of every draw cycle. If it // changes, delete all of the cached rendering element information so that we // start with a fresh state. var _lastVertical = true; /// Store a map of domain+barGroupIndex+category index to bars in a stack. /// /// This map is used to render all the bars in a stack together, to account /// for rendering effects that need to take the full stack into account (e.g. /// corner rounding). /// /// [LinkedHashMap] is used to render the bars on the canvas in the same order /// as the data was given to the chart. For the case where both grouping and /// stacking are disabled, this means that bars for data later in the series /// will be drawn "on top of" bars earlier in the series. // ignore: prefer_collection_literals, https://github.com/dart-lang/linter/issues/1649 final _barStackMap = LinkedHashMap>(); // Store a list of bar stacks that exist in the series data. // // This list will be used to remove any AnimatingBars that were rendered in // previous draw cycles, but no longer have a corresponding datum in the new // data. final _currentKeys = []; /// Stores a list of stack keys for each group key. // ignore: prefer_collection_literals, https://github.com/dart-lang/linter/issues/1649 final _currentGroupsStackKeys = LinkedHashMap>(); /// Optimization for getNearest to avoid scanning all data if possible. ImmutableAxis? _prevDomainAxis; BaseBarRenderer( {required this.config, required String rendererId, required int layoutPaintOrder}) : super( rendererId: rendererId, layoutPaintOrder: layoutPaintOrder, symbolRenderer: config.symbolRenderer, ); @override void preprocessSeries(List> seriesList) { // If the orientation of the chart changed, delete all data from the last // draw cycle. This allows us to start in a fresh state, so that we do not // get bad animations from the previously drawn data. // // Ideally we should animate the old bars out smoothly in some ways, but // this was the cheapest option. if (_lastVertical != chart.vertical) { _barStackMap.clear(); _currentKeys.clear(); _currentGroupsStackKeys.clear(); } _lastVertical = chart.vertical; var barGroupIndex = 0; // Maps used to store the final measure offset of the previous series, for // each domain value. final posDomainToStackKeyToDetailsMap = >{}; final negDomainToStackKeyToDetailsMap = >{}; final categoryToIndexMap = {}; // Keep track of the largest bar stack size. This should be 1 for grouped // bars, and it should be the size of the tallest stack for stacked or // grouped stacked bars. var maxBarStackSize = 0; final orderedSeriesList = getOrderedSeriesList(seriesList); orderedSeriesList.forEach((MutableSeries series) { var elements = []; var domainFn = series.domainFn; var measureFn = series.measureFn; var measureOffsetFn = series.measureOffsetFn; var fillPatternFn = series.fillPatternFn; var strokeWidthPxFn = series.strokeWidthPxFn; series.dashPatternFn ??= (_) => config.dashPattern; // Identifies which stack the series will go in, by default a single // stack. var stackKey = '__defaultKey__'; // Override the stackKey with seriesCategory if we are GROUPED_STACKED // so we have a way to choose which series go into which stacks. if (config.grouped && config.stacked) { if (series.seriesCategory != null) { stackKey = series.seriesCategory!; } if (categoryToIndexMap.containsKey(stackKey)) { barGroupIndex = categoryToIndexMap[stackKey]!; } else { barGroupIndex = categoryToIndexMap.length; categoryToIndexMap[stackKey] = barGroupIndex; } } var needsMeasureOffset = false; for (var barIndex = 0; barIndex < series.data.length; barIndex++) { dynamic datum = series.data[barIndex]; final details = getBaseDetails(datum, barIndex); details.barStackIndex = 0; details.measureOffset = measureOffsetFn!(barIndex); if (fillPatternFn != null) { details.fillPattern = fillPatternFn(barIndex); } else { details.fillPattern = config.fillPattern; } if (strokeWidthPxFn != null) { details.strokeWidthPx = strokeWidthPxFn(barIndex)?.toDouble(); } else { details.strokeWidthPx = config.strokeWidthPx; } // When stacking is enabled, adjust the measure offset for each domain // value in each series by adding up the measures and offsets of lower // series. if (config.stacked) { needsMeasureOffset = true; var domain = domainFn(barIndex); var measure = measureFn(barIndex); // We will render positive bars in one stack, and negative bars in a // separate stack. Keep track of the measure offsets for these stacks // independently. var domainToCategoryToDetailsMap = measure == null || measure >= 0 ? posDomainToStackKeyToDetailsMap : negDomainToStackKeyToDetailsMap; var categoryToDetailsMap = domainToCategoryToDetailsMap.putIfAbsent( domain, () => {}); var prevDetail = categoryToDetailsMap[stackKey]; if (prevDetail != null) { details.barStackIndex = prevDetail.barStackIndex! + 1; } details.cumulativeTotal = measure ?? 0; // Get the previous series' measure offset. var measureOffset = measureOffsetFn(barIndex)!; if (prevDetail != null) { measureOffset += prevDetail.measureOffsetPlusMeasure!; details.cumulativeTotal = details.cumulativeTotal! + prevDetail.cumulativeTotal!; } // And overwrite the details measure offset. details.measureOffset = measureOffset; var measureValue = measure ?? 0; details.measureOffsetPlusMeasure = measureOffset + measureValue; categoryToDetailsMap[stackKey] = details; } maxBarStackSize = max(maxBarStackSize, details.barStackIndex! + 1); elements.add(details); } if (needsMeasureOffset) { // Override the measure offset function to return the measure offset we // calculated for each datum. This already includes any measure offset // that was configured in the series data. series.measureOffsetFn = (index) => elements[index!].measureOffset!; } series.setAttr(barGroupIndexKey, barGroupIndex); series.setAttr(stackKeyKey, stackKey); series.setAttr(barElementsKey, elements); if (config.grouped) { barGroupIndex++; } }); // Compute number of bar groups. This must be done after we have processed // all of the series once, so that we know how many categories we have. var numBarGroups = 0; if (config.grouped && config.stacked) { // For grouped stacked bars, categoryToIndexMap effectively one list per // group of stacked bars. numBarGroups = categoryToIndexMap.length; } else if (config.stacked) { numBarGroups = 1; } else { numBarGroups = seriesList.length; } // Compute bar group weights. final barWeights = _calculateBarWeights(numBarGroups); seriesList.forEach((MutableSeries series) { series.setAttr(barGroupCountKey, numBarGroups); if (barWeights.isNotEmpty) { final barGroupIndex = series.getAttr(barGroupIndexKey)!; final barWeight = barWeights[barGroupIndex]; // In RTL mode, we need to grab the weights for the bars that follow // this datum in the series (instead of precede it). The first datum is // physically positioned on the canvas to the right of all the rest of // the bar group data that follows it. final previousBarWeights = isRtl ? barWeights.getRange(barGroupIndex + 1, numBarGroups) : barWeights.getRange(0, barGroupIndex); final previousBarWeight = previousBarWeights.isNotEmpty ? previousBarWeights.reduce((a, b) => a + b) : 0.0; series.setAttr(barGroupWeightKey, barWeight); series.setAttr(previousBarGroupWeightKey, previousBarWeight); series.setAttr(allBarGroupWeightsKey, barWeights); } }); } /// Calculates bar weights for a list of series from [config.weightPattern]. /// /// If [config.weightPattern] is not set, then this will assign a weight /// proportional to the number of bar groups for every series. List _calculateBarWeights(int numBarGroups) { // Set up bar weights for each series as a ratio of the total weight. final weights = []; if (config.weightPattern != null) { if (numBarGroups > config.weightPattern!.length) { throw ArgumentError('Number of series exceeds length of weight ' 'pattern ${config.weightPattern}'); } var totalBarWeight = 0; for (var i = 0; i < numBarGroups; i++) { totalBarWeight += config.weightPattern![i]; } for (var i = 0; i < numBarGroups; i++) { weights.add(config.weightPattern![i] / totalBarWeight); } } else { for (var i = 0; i < numBarGroups; i++) { weights.add(1 / numBarGroups); } } return weights; } /// Construct a base details element for a given datum. /// /// This is intended to be overridden by child classes that need to add /// customized rendering properties. @protected R getBaseDetails(dynamic datum, int index); @override void configureDomainAxes(List> seriesList) { super.configureDomainAxes(seriesList); // Configure the domain axis to use a range band configuration. if (seriesList.isNotEmpty) { // Given that charts can only have one domain axis, just grab it from the // first series. final domainAxis = seriesList.first.getAttr(domainAxisKey)!; // rangeBandConfig is set when current config is not valid to render // bars (this is necessary with combo charts that have NumericAxis) if (!domainAxis.hasValidBarChartRangeBandConfig) { domainAxis.setRangeBandConfig(RangeBandConfig.styleAssignedPercent()); } } } @override void update(List> seriesList, bool isAnimatingThisDraw) { _currentKeys.clear(); _currentGroupsStackKeys.clear(); final orderedSeriesList = getOrderedSeriesList(seriesList); orderedSeriesList.forEach((final ImmutableSeries series) { final domainAxis = series.getAttr(domainAxisKey) as ImmutableAxis; final domainFn = series.domainFn; final measureAxis = series.getAttr(measureAxisKey) as ImmutableAxis; final measureFn = series.measureFn; final colorFn = series.colorFn; final dashPatternFn = series.dashPatternFn; final fillColorFn = series.fillColorFn; final seriesStackKey = series.getAttr(stackKeyKey); final barGroupCount = series.getAttr(barGroupCountKey); final barGroupIndex = series.getAttr(barGroupIndexKey); final previousBarGroupWeight = series.getAttr(previousBarGroupWeightKey); final barGroupWeight = series.getAttr(barGroupWeightKey); final allBarGroupWeights = series.getAttr(allBarGroupWeightsKey); final measureAxisPosition = measureAxis.getLocation(0.0); var elementsList = series.getAttr(barElementsKey); // Save off domainAxis for getNearest. _prevDomainAxis = domainAxis; for (var barIndex = 0; barIndex < series.data.length; barIndex++) { final Object? datum = series.data[barIndex]; final details = elementsList![barIndex]; final domainValue = domainFn(barIndex); final measureValue = measureFn(barIndex); final measureIsNull = measureValue == null; final measureIsNegative = !measureIsNull && measureValue! < 0; // Each bar should be stored in barStackMap in a structure that mirrors // the visual rendering of the bars. Thus, they should be grouped by // domain value, series category (by way of the stack keys that were // generated for each series in the preprocess step), and bar group // index to account for all combinations of grouping and stacking. var barStackMapKey = '$domainValue' '__' '$seriesStackKey' '__' '${measureIsNegative ? 'pos' : 'neg'}' '__' '$barGroupIndex'; var barKey = '${barStackMapKey}${details.barStackIndex}'; var barStackList = _barStackMap.putIfAbsent(barStackMapKey, () => []); // If we already have an AnimatingBarfor that index, use it. var animatingBar = barStackList.firstWhereOrNull((B bar) => bar.key == barKey); // If we don't have any existing bar element, create a new bar and have // it animate in from the domain axis. // TODO: Animate bars in the middle of a stack from their // nearest neighbors, instead of the measure axis. if (animatingBar == null) { // If the measure is null and there was no existing animating bar, it // means we don't need to draw this bar at all. if (!measureIsNull) { animatingBar = makeAnimatedBar( key: barKey, series: series, datum: datum, barGroupIndex: barGroupIndex!, previousBarGroupWeight: previousBarGroupWeight, barGroupWeight: barGroupWeight, allBarGroupWeights: allBarGroupWeights, color: colorFn!(barIndex), dashPattern: dashPatternFn!(barIndex), details: details as R, domainValue: domainFn(barIndex), domainAxis: domainAxis, domainWidth: domainAxis.rangeBand.round(), fillColor: fillColorFn!(barIndex), fillPattern: details.fillPattern, measureValue: 0.0, measureOffsetValue: 0.0, measureAxisPosition: measureAxisPosition, measureAxis: measureAxis, numBarGroups: barGroupCount!, strokeWidthPx: details.strokeWidthPx, measureIsNull: measureIsNull, measureIsNegative: measureIsNegative); barStackList.add(animatingBar); } } else { animatingBar ..datum = datum ..series = series ..domainValue = domainValue; } if (animatingBar == null) { continue; } // Update the set of bars that still exist in the series data. _currentKeys.add(barKey); // Store off stack keys for each bar group to help getNearest identify // groups of stacks. _currentGroupsStackKeys .putIfAbsent(domainValue, () => {}) .add(barStackMapKey); // Get the barElement we are going to setup. // Optimization to prevent allocation in non-animating case. BaseBarRendererElement barElement = makeBarRendererElement( barGroupIndex: barGroupIndex!, previousBarGroupWeight: previousBarGroupWeight, barGroupWeight: barGroupWeight, allBarGroupWeights: allBarGroupWeights, color: colorFn!(barIndex), dashPattern: dashPatternFn!(barIndex), details: details as R, domainValue: domainFn(barIndex), domainAxis: domainAxis, domainWidth: domainAxis.rangeBand.round(), fillColor: fillColorFn!(barIndex), fillPattern: details.fillPattern, measureValue: measureValue, measureOffsetValue: details.measureOffset!, measureAxisPosition: measureAxisPosition, measureAxis: measureAxis, numBarGroups: barGroupCount!, strokeWidthPx: details.strokeWidthPx, measureIsNull: measureIsNull, measureIsNegative: measureIsNegative); animatingBar.setNewTarget(barElement as R); } }); // Animate out bars that don't exist anymore. _barStackMap.forEach((String key, List barStackList) { for (var barIndex = 0; barIndex < barStackList.length; barIndex++) { final bar = barStackList[barIndex]; if (_currentKeys.contains(bar.key) != true) { bar.animateOut(); } } }); } /// Generates a [BaseAnimatedBar] to represent the previous and current state /// of one bar on the chart. @protected B makeAnimatedBar( {required String key, required ImmutableSeries series, dynamic datum, required int barGroupIndex, double? previousBarGroupWeight, double? barGroupWeight, List? allBarGroupWeights, Color? color, List? dashPattern, required R details, D? domainValue, required ImmutableAxis domainAxis, required int domainWidth, num? measureValue, required num measureOffsetValue, required ImmutableAxis measureAxis, double? measureAxisPosition, required int numBarGroups, Color? fillColor, FillPatternType? fillPattern, double? strokeWidthPx, bool? measureIsNull, bool? measureIsNegative}); /// Generates a [BaseBarRendererElement] to represent the rendering data for /// one bar on the chart. @protected R makeBarRendererElement( {required int barGroupIndex, double? previousBarGroupWeight, double? barGroupWeight, List? allBarGroupWeights, Color? color, List? dashPattern, required R details, D? domainValue, required ImmutableAxis domainAxis, required int domainWidth, num? measureValue, required num measureOffsetValue, required ImmutableAxis measureAxis, double? measureAxisPosition, required int numBarGroups, Color? fillColor, FillPatternType? fillPattern, double? strokeWidthPx, bool? measureIsNull, bool? measureIsNegative}); /// Paints the current bar data on the canvas. @override void paint(ChartCanvas canvas, double animationPercent) { // Clean up the bars that no longer exist. if (animationPercent == 1.0) { final keysToRemove = HashSet(); _barStackMap.forEach((String key, List barStackList) { barStackList.retainWhere( (B bar) => !bar.animatingOut && !bar.targetBar!.measureIsNull!); if (barStackList.isEmpty) { keysToRemove.add(key); } }); // When cleaning up the animation, also clean up the keys used to lookup // if a bar is selected. for (final key in keysToRemove) { _barStackMap.remove(key); _currentKeys.remove(key); } _currentGroupsStackKeys.forEach((domain, keys) { keys.removeWhere(keysToRemove.contains); }); } _barStackMap.forEach((String stackKey, List barStack) { // Turn this into a list so that the getCurrentBar isn't called more than // once for each animationPercent if the barElements are iterated more // than once. final barElements = barStack .map((B animatingBar) => animatingBar.getCurrentBar(animationPercent)) .toList(); if (barElements.isNotEmpty) { paintBar(canvas, animationPercent, barElements); } }); } /// Paints a stack of bar elements on the canvas. @protected void paintBar( ChartCanvas canvas, double animationPercent, Iterable barElements); @override List> getNearestDatumDetailPerSeries( Point chartPoint, bool byDomain, Rectangle? boundsOverride, { bool selectOverlappingPoints = false, bool selectExactEventLocation = false, }) { var nearest = >[]; // Was it even in the component bounds? if (!isPointWithinBounds(chartPoint, boundsOverride)) { return nearest; } if (_prevDomainAxis is OrdinalAxis) { final domainValue = _prevDomainAxis! .getDomain(renderingVertically ? chartPoint.x : chartPoint.y); // If we have a domainValue for the event point, then find all segments // that match it. if (domainValue != null) { if (renderingVertically) { nearest = _getVerticalDetailsForDomainValue(domainValue, chartPoint); } else { nearest = _getHorizontalDetailsForDomainValue(domainValue, chartPoint); } } } else { if (renderingVertically) { nearest = _getVerticalDetailsForDomainValue(null, chartPoint); } else { nearest = _getHorizontalDetailsForDomainValue(null, chartPoint); } // Find the closest domain and only keep values that match the domain. var minRelativeDistance = double.maxFinite; var minDomainDistance = double.maxFinite; var minMeasureDistance = double.maxFinite; D? nearestDomain; // TODO: Optimize this with a binary search based on chartX. for (final detail in nearest) { if (byDomain) { if (detail.domainDistance! < minDomainDistance || (detail.domainDistance! == minDomainDistance && detail.measureDistance! < minMeasureDistance)) { minDomainDistance = detail.domainDistance!; minMeasureDistance = detail.measureDistance!; nearestDomain = detail.domain; } } else { if (detail.relativeDistance! < minRelativeDistance) { minRelativeDistance = detail.relativeDistance!; nearestDomain = detail.domain; } } } nearest.retainWhere((d) => d.domain == nearestDomain); } // Note: the details are already sorted by domain & measure distance in // base chart. return nearest; } @protected Rectangle? getBoundsForBar(R bar); @protected List> _getSegmentsForDomainValue(D? domainValue, {bool Function(BaseAnimatedBar bar)? where}) { final matchingSegments = >[]; // [domainValue] is null only when the bar renderer is being used with in // a non ordinal axis (ex. date time axis). // // In the case of null [domainValue] return all values to be compared, since // we can't use the optimized comparison for [OrdinalAxis]. final stackKeys = (domainValue != null) ? _currentGroupsStackKeys[domainValue] : _currentGroupsStackKeys.values .reduce((allKeys, keys) => allKeys..addAll(keys)); stackKeys?.forEach((String stackKey) { if (where != null) { matchingSegments.addAll(_barStackMap[stackKey]!.where(where)); } else { matchingSegments.addAll(_barStackMap[stackKey]!); } }); return matchingSegments; } // In the case of null [domainValue] return all values to be compared, since // we can't use the optimized comparison for [OrdinalAxis]. List> _getVerticalDetailsForDomainValue( D? domainValue, Point chartPoint) { return List>.from(_getSegmentsForDomainValue(domainValue, where: (BaseAnimatedBar bar) => !bar.series.overlaySeries) .map>((BaseAnimatedBar bar) { final barBounds = getBoundsForBar(bar.currentBar!)!; final segmentDomainDistance = _getDistance(chartPoint.x.round(), barBounds.left, barBounds.right); final segmentMeasureDistance = _getDistance(chartPoint.y.round(), barBounds.top, barBounds.bottom); final nearestPoint = Point( clamp(chartPoint.x, barBounds.left, barBounds.right).toDouble(), clamp(chartPoint.y, barBounds.top, barBounds.bottom).toDouble()); final relativeDistance = chartPoint.distanceTo(nearestPoint); return DatumDetails( series: bar.series, datum: bar.datum, domain: bar.domainValue, domainDistance: segmentDomainDistance, measureDistance: segmentMeasureDistance, relativeDistance: relativeDistance, ); })); } List> _getHorizontalDetailsForDomainValue( D? domainValue, Point chartPoint) { return List>.from(_getSegmentsForDomainValue(domainValue, where: (BaseAnimatedBar bar) => !bar.series.overlaySeries) .map>((BaseAnimatedBar bar) { final barBounds = getBoundsForBar(bar.currentBar!)!; final segmentDomainDistance = _getDistance(chartPoint.y.round(), barBounds.top, barBounds.bottom); final segmentMeasureDistance = _getDistance(chartPoint.x.round(), barBounds.left, barBounds.right); return DatumDetails( series: bar.series, datum: bar.datum, domain: bar.domainValue, domainDistance: segmentDomainDistance, measureDistance: segmentMeasureDistance, ); })); } double _getDistance(int point, int min, int max) { if (max >= point && min <= point) { return 0.0; } return (point > max ? (point - max) : (min - point)).toDouble(); } /// Gets the iterator for the series based grouped/stacked and orientation. /// /// For vertical stacked bars: /// * If grouped, return the iterator that keeps the category order but /// reverse the order of the series so the first series is on the top of the /// stack. /// * Otherwise, return iterator of the reversed list /// /// All other types, use the in order iterator. @protected Iterable getOrderedSeriesList>( List seriesList) { return (renderingVertically && config.stacked) ? config.grouped ? _ReversedSeriesIterable(seriesList) : seriesList.reversed : seriesList; } bool get isRtl => chart.context.isRtl; } /// Iterable wrapping the seriesList that returns the ReversedSeriesItertor. class _ReversedSeriesIterable> extends Iterable { final List seriesList; _ReversedSeriesIterable(this.seriesList); @override Iterator get iterator => _ReversedSeriesIterator(seriesList); } /// Iterator that keeps reverse series order but keeps category order. /// /// This is needed because for grouped stacked bars, the category stays in the /// order it was passed in for the grouping, but the series is flipped so that /// the first series of that category is on the top of the stack. class _ReversedSeriesIterator> extends Iterator { final List _list; final _visitIndex = []; int? _current; _ReversedSeriesIterator(List list) : _list = list { // In the order of the list, save the category and the indices of the series // with the same category. final categoryAndSeriesIndexMap = >{}; for (var i = 0; i < list.length; i++) { categoryAndSeriesIndexMap .putIfAbsent(list[i].seriesCategory, () => []) .add(i); } // Creates a visit that is categories in order, but the series is reversed. categoryAndSeriesIndexMap .forEach((_, indices) => _visitIndex.addAll(indices.reversed)); } @override bool moveNext() { _current = (_current == null) ? 0 : _current! + 1; return _current! < _list.length; } @override S get current => _list[_visitIndex[_current!]]; } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/bar/base_bar_renderer_config.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'package:collection/collection.dart' show ListEquality; import '../../common/symbol_renderer.dart' show SymbolRenderer, RoundedRectSymbolRenderer; import '../common/chart_canvas.dart' show FillPatternType; import '../common/series_renderer_config.dart' show RendererAttributes, SeriesRendererConfig; import '../layout/layout_view.dart' show LayoutViewConfig; /// Shared configuration for bar chart renderers. /// /// Bar renderers support 4 different modes of rendering multiple series on the /// chart, configured by the grouped and stacked flags. /// * grouped - Render bars for each series that shares a domain value /// side-by-side. /// * stacked - Render bars for each series that shares a domain value in a /// stack, ordered in the same order as the series list. /// * grouped-stacked: Render bars for each series that shares a domain value in /// a group of bar stacks. Each stack will contain all the series that share a /// series category. /// * floating style - When grouped and stacked are both false, all bars that /// share a domain value will be rendered in the same domain space. Each datum /// should be configured with a measure offset to position its bar along the /// measure axis. Bars will freely overlap if their measure values and measure /// offsets overlap. Note that bars for each series will be rendered in order, /// such that bars from the last series will be "on top" of bars from previous /// series. abstract class BaseBarRendererConfig extends LayoutViewConfig implements SeriesRendererConfig { @override final String? customRendererId; @override final SymbolRenderer symbolRenderer; /// Dash pattern for the stroke line around the edges of the bar. final List? dashPattern; /// Defines the way multiple series of bars are rendered per domain. final BarGroupingType groupingType; /// The order to paint this renderer on the canvas. final int? layoutPaintOrder; final int minBarLengthPx; // The maximum bar group width in pixels, or null if bars can be arbitrarily // wide. final int? maxBarWidthPx; final FillPatternType? fillPattern; /// The padding between bar stacks. final int stackedBarPaddingPx; /// Stroke width of the target line. final double strokeWidthPx; /// Sets the series weight pattern. This is a pattern of weights used to /// calculate the width of bars within a bar group. If not specified, each bar /// in the group will have an equal width. /// /// The pattern will not repeat. If more series are assigned to the renderer /// than there are segments in the weight pattern, an error will be thrown. /// /// e.g. For the pattern [2, 1], the first bar in a group should be rendered /// twice as wide as the second bar. /// /// If the expected bar width of the chart is 12px, then the first bar will /// render at 16px and the second will render at 8px. The default weight /// pattern of null means that all bars should be the same width, or 12px in /// this case. /// /// Not used for stacked bars. final List? weightPattern; @override final rendererAttributes = RendererAttributes(); BaseBarRendererConfig( {this.customRendererId, this.dashPattern, this.groupingType = BarGroupingType.grouped, this.layoutPaintOrder, this.minBarLengthPx = 0, this.maxBarWidthPx, this.fillPattern, this.stackedBarPaddingPx = 1, this.strokeWidthPx = 0.0, SymbolRenderer? symbolRenderer, this.weightPattern}) : symbolRenderer = symbolRenderer ?? RoundedRectSymbolRenderer(); /// Whether or not the bars should be organized into groups. bool get grouped => groupingType == BarGroupingType.grouped || groupingType == BarGroupingType.groupedStacked; /// Whether or not the bars should be organized into stacks. bool get stacked => groupingType == BarGroupingType.stacked || groupingType == BarGroupingType.groupedStacked; @override bool operator ==(Object other) { if (identical(this, other)) { return true; } return other is BaseBarRendererConfig && other.customRendererId == customRendererId && other.dashPattern == dashPattern && other.fillPattern == fillPattern && other.groupingType == groupingType && other.minBarLengthPx == minBarLengthPx && other.maxBarWidthPx == maxBarWidthPx && other.stackedBarPaddingPx == stackedBarPaddingPx && other.strokeWidthPx == strokeWidthPx && other.symbolRenderer == symbolRenderer && ListEquality().equals(other.weightPattern, weightPattern); } @override int get hashCode { var hash = 1; hash = hash * 31 + customRendererId.hashCode; hash = hash * 31 + dashPattern.hashCode; hash = hash * 31 + fillPattern.hashCode; hash = hash * 31 + groupingType.hashCode; hash = hash * 31 + minBarLengthPx.hashCode; hash = hash * 31 + maxBarWidthPx.hashCode; hash = hash * 31 + stackedBarPaddingPx.hashCode; hash = hash * 31 + strokeWidthPx.hashCode; hash = hash * 31 + symbolRenderer.hashCode; hash = hash * 31 + weightPattern.hashCode; return hash; } } /// Defines the way multiple series of bars are renderered per domain. /// /// * [grouped] - Render bars for each series that shares a domain value /// side-by-side. /// * [stacked] - Render bars for each series that shares a domain value in a /// stack, ordered in the same order as the series list. /// * [groupedStacked]: Render bars for each series that shares a domain value /// in a group of bar stacks. Each stack will contain all the series that /// share a series category. enum BarGroupingType { grouped, groupedStacked, stacked } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/bar/base_bar_renderer_element.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import '../../common/color.dart' show Color; import '../common/chart_canvas.dart' show getAnimatedColor, FillPatternType; import '../common/processed_series.dart' show ImmutableSeries; abstract class BaseBarRendererElement { int? barStackIndex; Color? color; num? cumulativeTotal; List? dashPattern; Color? fillColor; FillPatternType? fillPattern; double? measureAxisPosition; num? measureOffset; num? measureOffsetPlusMeasure; double? strokeWidthPx; bool? measureIsNull; bool? measureIsNegative; BaseBarRendererElement(); BaseBarRendererElement.clone(BaseBarRendererElement other) { barStackIndex = other.barStackIndex; color = other.color != null ? Color.fromOther(color: other.color!) : null; cumulativeTotal = other.cumulativeTotal; dashPattern = other.dashPattern; fillColor = other.fillColor != null ? Color.fromOther(color: other.fillColor!) : null; fillPattern = other.fillPattern; measureAxisPosition = other.measureAxisPosition; measureOffset = other.measureOffset; measureOffsetPlusMeasure = other.measureOffsetPlusMeasure; strokeWidthPx = other.strokeWidthPx; measureIsNull = other.measureIsNull; measureIsNegative = other.measureIsNegative; } void updateAnimationPercent(BaseBarRendererElement previous, BaseBarRendererElement target, double animationPercent) { color = getAnimatedColor(previous.color!, target.color!, animationPercent); fillColor = getAnimatedColor( previous.fillColor!, target.fillColor!, animationPercent); measureIsNull = target.measureIsNull; measureIsNegative = target.measureIsNegative; } } abstract class BaseAnimatedBar { final String key; dynamic datum; ImmutableSeries series; D? domainValue; R? _previousBar; R? _targetBar; R? _currentBar; // Flag indicating whether this bar is being animated out of the chart. bool animatingOut = false; BaseAnimatedBar({ required this.key, required this.datum, required this.series, required this.domainValue, }); /// Animates a bar that was removed from the series out of the view. /// /// This should be called in place of "setNewTarget" for bars that represent /// data that has been removed from the series. /// /// Animates the height of the bar down to the measure axis position (position /// of 0). Animates the width of the bar down to 0, centered in the middle of /// the original bar width. void animateOut() { var newTarget = clone(_currentBar!); animateElementToMeasureAxisPosition(newTarget); setNewTarget(newTarget); animatingOut = true; } /// Sets the bounds for the target to the measure axis position. void animateElementToMeasureAxisPosition(R target); /// Sets a new element to render. void setNewTarget(R newTarget) { animatingOut = false; _currentBar ??= clone(newTarget); _previousBar = clone(_currentBar!); _targetBar = newTarget; } R? get currentBar => _currentBar; R? get previousBar => _previousBar; R? get targetBar => _targetBar; /// Gets the new state of the bar element for painting, updated for a /// transition between the previous state and the new animationPercent. R getCurrentBar(double animationPercent) { assert(_targetBar != null); if (animationPercent == 1.0 || _previousBar == null) { _currentBar = _targetBar; _previousBar = _targetBar; return _currentBar!; } _currentBar! .updateAnimationPercent(_previousBar!, _targetBar!, animationPercent); return _currentBar!; } R clone(R bar); } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/axis.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math' show Rectangle, min, max; import 'package:collection/collection.dart' show IterableExtension; import 'package:meta/meta.dart' show protected, visibleForTesting; import '../../../../common.dart'; import '../../../common/graphics_factory.dart' show GraphicsFactory; import '../../../common/text_element.dart' show TextElement; import '../../../data/series.dart' show AttributeKey; import '../../common/chart_canvas.dart' show ChartCanvas; import '../../common/chart_context.dart' show ChartContext; import '../../layout/layout_view.dart' show LayoutPosition, LayoutView, LayoutViewConfig, LayoutViewPaintOrder, LayoutViewPositionOrder, ViewMeasuredSizes; import 'axis_tick.dart' show AxisTicks; import 'draw_strategy/small_tick_draw_strategy.dart' show SmallTickDrawStrategy; import 'draw_strategy/tick_draw_strategy.dart' show TickDrawStrategy; import 'linear/linear_scale.dart' show LinearScale; import 'numeric_extents.dart' show NumericExtents; import 'numeric_scale.dart' show NumericScale; import 'numeric_tick_provider.dart' show NumericTickProvider; import 'ordinal_scale.dart' show OrdinalScale; import 'ordinal_tick_provider.dart' show OrdinalTickProvider; import 'range_axis_tick.dart' show RangeAxisTicks; import 'range_tick.dart' show RangeTick; import 'scale.dart' show MutableScale, RangeBandConfig, RangeBandType, ScaleOutputExtent, Scale; import 'simple_ordinal_scale.dart' show SimpleOrdinalScale; import 'tick.dart' show Tick; import 'tick_formatter.dart' show TickFormatter, OrdinalTickFormatter, NumericTickFormatter; import 'tick_provider.dart' show TickProvider; const measureAxisIdKey = AttributeKey('Axis.measureAxisId'); const measureAxisKey = AttributeKey>('Axis.measureAxis'); const domainAxisKey = AttributeKey>('Axis.domainAxis'); /// Orientation of an Axis. enum AxisOrientation { top, right, bottom, left } abstract class ImmutableAxis { /// Compare domain to the viewport. /// /// 0 if the domain is in the viewport. /// 1 if the domain is to the right of the viewport. /// -1 if the domain is to the left of the viewport. int compareDomainValueToViewport(D domain); /// Get location for the domain. double? getLocation(D? domain); D getDomain(double location); /// Rangeband for this axis. double get rangeBand; /// Step size for this axis. double get stepSize; /// Output range for this axis. ScaleOutputExtent? get range; } abstract class Axis extends ImmutableAxis implements LayoutView { static const primaryMeasureAxisId = 'primaryMeasureAxisId'; static const secondaryMeasureAxisId = 'secondaryMeasureAxisId'; static const _autoViewportDefault = true; final MutableScale? _defaultScale; /// [Scale] of this axis. MutableScale? scale; /// Previous [Scale] of this axis, used to calculate tick animation. MutableScale? _previousScale; final TickProvider? _defaultTickProvider; /// [TickProvider] for this axis. TickProvider? tickProvider; final TickFormatter? _defaultTickFormatter; TickFormatter? _tickFormatter; set tickFormatter(TickFormatter? formatter) { if (_tickFormatter != formatter) { _tickFormatter = formatter; _formatterValueCache.clear(); } } /// [TickFormatter] for this axis. TickFormatter? get tickFormatter => _tickFormatter; final _formatterValueCache = {}; /// [TickDrawStrategy] for this axis. TickDrawStrategy? tickDrawStrategy; /// [AxisOrientation] for this axis. AxisOrientation? axisOrientation; ChartContext? context; /// If the output range should be reversed. bool reverseOutputRange = false; /// Configures whether the viewport should be reset back to default values /// when the domain is reset. /// /// This should generally be disabled when the viewport will be managed /// externally, e.g. from pan and zoom behaviors. bool autoViewport = _autoViewportDefault; /// If the axis line should always be drawn. bool? forceDrawAxisLine; /// If true, do not allow axis to be modified. /// /// Ticks (including their location) are not updated. /// Viewport changes not allowed. bool lockAxis = false; /// Ticks provided by the tick provider. List>? _providedTicks; /// Ticks used by the axis for drawing. final _axisTicks = >[]; Rectangle? _componentBounds; Rectangle? _drawAreaBounds; /// Order for chart layout painting. /// /// In general, domain axes should be drawn on top of measure axes to ensure /// that the domain axis line appears on top of any measure axis grid lines. int layoutPaintOrder = LayoutViewPaintOrder.measureAxis; /// If true, a collision has occurred between ticks on this axis. bool hasTickCollision = false; Axis({this.tickProvider, TickFormatter? tickFormatter, this.scale}) : _defaultScale = scale, _defaultTickProvider = tickProvider, _defaultTickFormatter = tickFormatter, _tickFormatter = tickFormatter; @protected MutableScale? get mutableScale => scale; /// Rangeband for this axis. @override double get rangeBand => scale!.rangeBand; @override double get stepSize => scale!.stepSize; @override ScaleOutputExtent? get range => scale!.range; void setRangeBandConfig(RangeBandConfig rangeBandConfig) { mutableScale!.rangeBandConfig = rangeBandConfig; } /// For bars to be renderer properly the RangeBandConfig must be set and /// type must not be RangeBandType.none. bool get hasValidBarChartRangeBandConfig => (mutableScale?.rangeBandConfig.type ?? RangeBandType.none) != RangeBandType.none; void addDomainValue(D domain) { if (lockAxis) { return; } scale!.addDomain(domain); } void resetDefaultConfiguration() { forceDrawAxisLine = null; autoViewport = _autoViewportDefault; scale = _defaultScale; _tickFormatter = _defaultTickFormatter; tickProvider = _defaultTickProvider; } void resetDomains() { if (lockAxis) { return; } // If the series list changes, clear the cache. // // There are cases where tick formatter has not "changed", but if measure // formatter provided to the tick formatter uses a closure value, the // formatter cache needs to be cleared. // // This type of use case for the measure formatter surfaced where the series // list also changes. So this is a round about way to also clear the // tick formatter cache. // // TODO: Measure formatter should be changed from a typedef to // a concrete class to force users to create a new tick formatter when // formatting is different, so we can recognize when the tick formatter is // changed and then clear cache accordingly. // // Remove this when bug above is fixed, and verify it did not cause // regression for b/110371453. _formatterValueCache.clear(); final scale = this.scale!; scale.resetDomain(); reverseOutputRange = false; if (autoViewport) { scale.resetViewportSettings(); } // TODO: Reset rangeband and step size when we port over config //scale.rangeBandConfig = get range band config //scale.stepSizeConfig = get step size config } @override double? getLocation(D? domain) { const epsilon = 2e-10; if (domain != null) { final scale = this.scale!; final range = scale.range!; var domainLocation = scale[domain]!.toDouble(); // If domain location is outside of scale range but only outside by less // than epsilon, correct the potential mislocation caused by floating // point computation by moving it inside of scale range. if (domainLocation > range.max && domainLocation - epsilon < range.max) { return domainLocation - epsilon; } else if (domainLocation < range.min && domainLocation + epsilon > range.min) { return domainLocation + epsilon; } return domainLocation; } return null; } @override D getDomain(double location) => scale!.reverse(location); @override int compareDomainValueToViewport(D domain) { return scale!.compareDomainValueToViewport(domain); } void setOutputRange(int start, int end) { scale!.range = ScaleOutputExtent(start, end); } /// Request update ticks from tick provider and update the painted ticks. void updateTicks() { _updateProvidedTicks(); if (_componentBounds != null) { _updateProvidedTickWidth( _componentBounds!.width, _componentBounds!.height); } _updateAxisTicks(); } /// Request ticks from tick provider. void _updateProvidedTicks() { if (lockAxis) { return; } assert( graphicsFactory != null, 'Axis.graphicsFactory must be set first'); assert( tickDrawStrategy != null, 'Axis.tickDrawStrategy must be set first'); // TODO: Ensure that tick providers take manually configured // viewport settings into account, so that we still get the right number. _providedTicks = tickProvider!.getTicks( context: context, graphicsFactory: graphicsFactory!, scale: scale!, formatter: tickFormatter!, formatterValueCache: _formatterValueCache, tickDrawStrategy: tickDrawStrategy!, orientation: axisOrientation, viewportExtensionEnabled: autoViewport); hasTickCollision = tickDrawStrategy! .collides(_providedTicks, axisOrientation) .ticksCollide; } /// Updates the current provided tick labels with a max width. void _updateProvidedTickWidth(int maxWidth, int maxHeight) { if (axisOrientation != null) { tickDrawStrategy!.updateTickWidth( _providedTicks!, maxWidth, maxHeight, axisOrientation!, collision: hasTickCollision, ); } } /// Updates the ticks that are actually used for drawing. void _updateAxisTicks() { if (lockAxis) { return; } final providedTicks = List.of(_providedTicks ?? >[]); final scale = this.scale!; for (final animatedTick in _axisTicks) { final tick = providedTicks.firstWhereOrNull((t) => t.value == animatedTick.value); if (tick != null) { // Swap out the text element only if the settings are different. // This prevents a costly new TextPainter in Flutter. if (!TextElement.elementSettingsSame( animatedTick.textElement!, tick.textElement!)) { animatedTick.textElement = tick.textElement; } var newTarget = scale[tick.value]?.toDouble(); if (scale.isRangeValueWithinViewport(newTarget!)) { // Update target for all existing ticks animatedTick.setNewTarget(newTarget); } else { // Animate out ticks that are outside the viewport. animatedTick.animateOut(animatedTick.locationPx); } providedTicks.remove(tick); } else { // Animate out ticks that do not exist any more. animatedTick.animateOut(scale[animatedTick.value]!.toDouble()); } } // Add new ticks providedTicks.forEach((tick) { AxisTicks animatedTick; if (tick is RangeTick) { animatedTick = RangeAxisTicks(tick); } else { animatedTick = AxisTicks(tick); } if (scale.isRangeValueWithinViewport(animatedTick.locationPx!)) { if (_previousScale != null) { animatedTick.animateInFrom(_previousScale![tick.value]!.toDouble()); } _axisTicks.add(animatedTick); } }); _axisTicks.sort(); // Save a copy of the current scale to be used as the previous scale when // ticks are updated. _previousScale = scale.copy(); } /// Configures the zoom and translate. /// /// [viewportScale] is the zoom factor to use, likely >= 1.0 where 1.0 maps /// the complete data extents to the output range, and 2.0 only maps half the /// data to the output range. /// /// [viewportTranslatePx] is the translate/pan to use in pixel units, /// likely <= 0 which shifts the start of the data before the edge of the /// chart giving us a pan. /// /// [drawAreaWidth] is the width of the draw area for the series data in pixel /// units, at minimum viewport scale level (1.0). When provided, /// [drawAreaHeight] is the height of the draw area for the series data in /// pixel units, at minimum viewport scale level (1.0). When provided, /// [viewportTranslatePx] will be clamped such that the axis cannot be panned /// beyond the bounds of the data. void setViewportSettings(double viewportScale, double viewportTranslatePx, {int? drawAreaWidth, int? drawAreaHeight}) { // Don't let the viewport be panned beyond the bounds of the data. viewportTranslatePx = _clampTranslatePx(viewportScale, viewportTranslatePx, drawAreaWidth: drawAreaWidth, drawAreaHeight: drawAreaHeight); scale!.setViewportSettings(viewportScale, viewportTranslatePx); } /// Returns the current viewport scale. /// /// A scale of 1.0 would map the data directly to the output range, while a /// value of 2.0 would map the data to an output of double the range so you /// only see half the data in the viewport. This is the equivalent to /// zooming. Its value is likely >= 1.0. double get viewportScalingFactor => scale!.viewportScalingFactor; /// Returns the current pixel viewport offset /// /// The translate is used by the scale function when it applies the scale. /// This is the equivalent to panning. Its value is likely <= 0 to pan the /// data to the left. double get viewportTranslatePx => scale!.viewportTranslatePx; /// Clamps a possible change in domain translation to fit within the range of /// the data. double _clampTranslatePx( double viewportScalingFactor, double viewportTranslatePx, {int? drawAreaWidth, int? drawAreaHeight}) { if (isVertical) { if (drawAreaHeight == null) { return viewportTranslatePx; } // Bound the viewport translate to the range of the data. final maxPositiveTranslate = (drawAreaHeight * viewportScalingFactor) - drawAreaHeight; viewportTranslatePx = max(min(viewportTranslatePx, maxPositiveTranslate), 0.0); } else { if (drawAreaWidth == null) { return viewportTranslatePx; } // Bound the viewport translate to the range of the data. final maxNegativeTranslate = -1.0 * ((drawAreaWidth * viewportScalingFactor) - drawAreaWidth); viewportTranslatePx = min(max(viewportTranslatePx, maxNegativeTranslate), 0.0); } return viewportTranslatePx; } // // LayoutView methods. // @override GraphicsFactory? graphicsFactory; @override LayoutViewConfig get layoutConfig => LayoutViewConfig( paintOrder: layoutPaintOrder, position: _layoutPosition, positionOrder: LayoutViewPositionOrder.axis); /// Get layout position from axis orientation. LayoutPosition? get _layoutPosition { LayoutPosition? position; switch (axisOrientation) { case AxisOrientation.top: position = LayoutPosition.Top; break; case AxisOrientation.right: position = LayoutPosition.Right; break; case AxisOrientation.bottom: position = LayoutPosition.Bottom; break; case AxisOrientation.left: position = LayoutPosition.Left; break; case null: break; } return position; } /// The axis is rendered vertically. bool get isVertical => axisOrientation == AxisOrientation.left || axisOrientation == AxisOrientation.right; @override ViewMeasuredSizes measure(int maxWidth, int maxHeight) { return isVertical ? _measureVerticalAxis(maxWidth, maxHeight) : _measureHorizontalAxis(maxWidth, maxHeight); } ViewMeasuredSizes _measureVerticalAxis(int maxWidth, int maxHeight) { setOutputRange(maxHeight, 0); _updateProvidedTicks(); return tickDrawStrategy!.measureVerticallyDrawnTicks( _providedTicks!, maxWidth, maxHeight, collision: hasTickCollision); } ViewMeasuredSizes _measureHorizontalAxis(int maxWidth, int maxHeight) { setOutputRange(0, maxWidth); _updateProvidedTicks(); return tickDrawStrategy!.measureHorizontallyDrawnTicks( _providedTicks!, maxWidth, maxHeight, collision: hasTickCollision); } /// Layout this component. @override void layout(Rectangle componentBounds, Rectangle drawAreaBounds) { _componentBounds = componentBounds; _drawAreaBounds = drawAreaBounds; // Update the output range if it is different than the current one. // This is necessary because during the measure cycle, the output range is // set between zero and the max range available. On layout, the output range // needs to be updated to account of the offset of the axis view. final outputStart = isVertical ? componentBounds.bottom : componentBounds.left; final outputEnd = isVertical ? componentBounds.top : componentBounds.right; final outputRange = reverseOutputRange ? ScaleOutputExtent(outputEnd, outputStart) : ScaleOutputExtent(outputStart, outputEnd); final scale = this.scale!; if (scale.range != outputRange) { scale.range = outputRange; } _updateProvidedTicks(); _updateProvidedTickWidth(_componentBounds!.width, _componentBounds!.height); // Update animated ticks in layout, because updateTicks are called during // measure and we don't want to update the animation at that time. _updateAxisTicks(); } @override bool get isSeriesRenderer => false; @override Rectangle? get componentBounds => _componentBounds; bool get drawAxisLine { if (forceDrawAxisLine != null) { return forceDrawAxisLine!; } return tickDrawStrategy is SmallTickDrawStrategy; } @override void paint(ChartCanvas canvas, double animationPercent) { if (animationPercent == 1.0) { _axisTicks.removeWhere((t) => t.markedForRemoval); } for (var i = 0; i < _axisTicks.length; i++) { final animatedTick = _axisTicks[i]; tickDrawStrategy!.draw( canvas, animatedTick..setCurrentTick(animationPercent), orientation: axisOrientation!, axisBounds: _componentBounds!, collision: hasTickCollision, drawAreaBounds: _drawAreaBounds!, isFirst: i == 0, isLast: i == _axisTicks.length - 1); } if (drawAxisLine) { tickDrawStrategy! .drawAxisLine(canvas, axisOrientation!, _componentBounds!); } } } class NumericAxis extends Axis { NumericAxis({TickProvider? tickProvider}) : super( tickProvider: tickProvider ?? NumericTickProvider(), tickFormatter: NumericTickFormatter(), scale: LinearScale(), ); void setScaleViewport(NumericExtents viewport) { autoViewport = false; (scale as NumericScale).viewportDomain = viewport; } } class OrdinalAxis extends Axis { OrdinalAxis({ TickDrawStrategy? tickDrawStrategy, TickProvider? tickProvider, TickFormatter? tickFormatter, }) : super( tickProvider: tickProvider ?? const OrdinalTickProvider(), tickFormatter: tickFormatter ?? const OrdinalTickFormatter(), scale: SimpleOrdinalScale(), ) { this.tickDrawStrategy = tickDrawStrategy; } void setScaleViewport(OrdinalViewport viewport) { autoViewport = false; (scale as OrdinalScale) .setViewport(viewport.dataSize, viewport.startingDomain); } @override void layout(Rectangle componentBounds, Rectangle drawAreaBounds) { super.layout(componentBounds, drawAreaBounds); // We are purposely clearing the viewport starting domain and data size // post layout. // // Originally we set a flag in [setScaleViewport] to recalculate viewport // settings on next scale update and then reset the flag. This doesn't work // because chart's measure cycle provides different ranges to the scale, // causing the scale to update multiple times before it is finalized after // layout. // // By resetting the viewport after layout, we guarantee the correct range // was used to apply the viewport and behaviors that update the viewport // based on translate and scale changes will not be affected (pan/zoom). (scale as OrdinalScale).setViewport(null, null); } } /// Viewport to cover [dataSize] data points starting at [startingDomain] value. class OrdinalViewport { final String startingDomain; final int dataSize; OrdinalViewport(this.startingDomain, this.dataSize); @override bool operator ==(Object other) { return other is OrdinalViewport && startingDomain == other.startingDomain && dataSize == other.dataSize; } @override int get hashCode { var hashcode = startingDomain.hashCode; hashcode = (hashcode * 37) + dataSize; return hashcode; } } @visibleForTesting class AxisTester { final Axis _axis; AxisTester(this._axis); List> get axisTicks => _axis._axisTicks; MutableScale? get scale => _axis.scale; List get axisValues => axisTicks.map((t) => t.value).toList(); } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/axis_tick.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'tick.dart' show Tick; class AxisTicks extends Tick implements Comparable> { /// This tick is being animated out. bool _markedForRemoval; /// This tick's current location. double? _currentLocation; /// This tick's previous target location. double? _previousLocation; /// This tick's current target location. double? _targetLocation; /// This tick's current opacity. double? _currentOpacity; /// This tick's previous opacity. double? _previousOpacity; /// This tick's target opacity. double? _targetOpacity; AxisTicks(Tick tick) : // Set the initial target for a new animated tick. _markedForRemoval = false, _targetLocation = tick.locationPx, super( value: tick.value, textElement: tick.textElement, locationPx: tick.locationPx, labelOffsetPx: tick.labelOffsetPx); bool get markedForRemoval => _markedForRemoval; /// Animate the tick in from [previousLocation]. void animateInFrom(double previousLocation) { _markedForRemoval = false; _previousLocation = previousLocation; _previousOpacity = 0.0; _targetOpacity = 1.0; } /// Animate out this tick to [newLocation]. void animateOut(double? newLocation) { _markedForRemoval = true; _previousLocation = _currentLocation; _targetLocation = newLocation; _previousOpacity = _currentOpacity; _targetOpacity = 0.0; } /// Set new target for this tick to be [newLocation]. void setNewTarget(double? newLocation) { _markedForRemoval = false; _previousLocation = _currentLocation; _targetLocation = newLocation; _previousOpacity = _currentOpacity; _targetOpacity = 1.0; } /// Update tick's location and opacity based on animation percent. void setCurrentTick(double animationPercent) { if (animationPercent == 1.0) { _currentLocation = _targetLocation; _previousLocation = _targetLocation; _currentOpacity = markedForRemoval ? 0.0 : 1.0; } else if (_previousLocation == null) { _currentLocation = _targetLocation; _currentOpacity = 1.0; } else { _currentLocation = _lerpDouble(_previousLocation, _targetLocation, animationPercent); _currentOpacity = _lerpDouble(_previousOpacity, _targetOpacity, animationPercent); } locationPx = _currentLocation; textElement!.opacity = _currentOpacity; } /// Linearly interpolate between two numbers. /// /// From lerpDouble in dart:ui which is Flutter only. double? _lerpDouble(double? a, double? b, double t) { if (a == null && b == null) return null; a ??= 0.0; b ??= 0.0; return a + (b - a) * t; } @override int compareTo(AxisTicks other) => _targetLocation!.compareTo(other._targetLocation!); } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/collision_report.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'tick.dart' show Tick; /// A report that contains a list of ticks and if they collide. class CollisionReport { /// If [ticks] collide. final bool ticksCollide; final List> ticks; final bool alternateTicksUsed; CollisionReport( {required this.ticksCollide, required List>? ticks, bool? alternateTicksUsed}) : ticks = ticks ?? [], alternateTicksUsed = alternateTicksUsed ?? false; CollisionReport.empty() : ticksCollide = false, ticks = [], alternateTicksUsed = false; } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/draw_strategy/base_tick_draw_strategy.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math'; import 'package:meta/meta.dart' show immutable, protected; import '../../../../common/graphics_factory.dart' show GraphicsFactory; import '../../../../common/line_style.dart' show LineStyle; import '../../../../common/style/style_factory.dart' show StyleFactory; import '../../../../common/text_element.dart' show TextDirection, TextElement, MaxWidthStrategy; import '../../../../common/text_style.dart' show TextStyle; import '../../../common/chart_canvas.dart' show ChartCanvas; import '../../../common/chart_context.dart' show ChartContext; import '../../../layout/layout_view.dart' show ViewMeasuredSizes; import '../axis.dart' show AxisOrientation; import '../collision_report.dart' show CollisionReport; import '../spec/axis_spec.dart' show TextStyleSpec, TickLabelAnchor, TickLabelJustification, LineStyleSpec, RenderSpec; import '../tick.dart' show Tick; import 'tick_draw_strategy.dart' show TickDrawStrategy; @immutable abstract class BaseRenderSpec implements RenderSpec { final TextStyleSpec? labelStyle; final TickLabelAnchor? labelAnchor; final TickLabelJustification? labelJustification; /// Distance from the axis line in px. final int? labelOffsetFromAxisPx; /// Distance from the axis line in px when a collision between ticks has /// occurred. final int? labelCollisionOffsetFromAxisPx; /// Absolute distance from the tick to the text if using start/end final int? labelOffsetFromTickPx; /// Absolute distance from the tick to the text when a collision between ticks /// has occurred. final int? labelCollisionOffsetFromTickPx; final int? minimumPaddingBetweenLabelsPx; /// Angle of rotation for tick labels, in degrees. When set to a non-zero /// value, all labels drawn for this axis will be rotated. final int? labelRotation; /// Angle of rotation for tick labels, in degrees when a collision between /// ticks has occurred. final int? labelCollisionRotation; final LineStyleSpec? axisLineStyle; const BaseRenderSpec({ this.labelStyle, this.labelAnchor, this.labelJustification, this.labelOffsetFromAxisPx, this.labelCollisionOffsetFromAxisPx, this.labelOffsetFromTickPx, this.labelCollisionOffsetFromTickPx, this.minimumPaddingBetweenLabelsPx, this.labelRotation, this.labelCollisionRotation, this.axisLineStyle, }); @override bool operator ==(Object other) { return identical(this, other) || (other is BaseRenderSpec && labelStyle == other.labelStyle && labelAnchor == other.labelAnchor && labelJustification == other.labelJustification && labelOffsetFromTickPx == other.labelOffsetFromTickPx && labelCollisionOffsetFromTickPx == other.labelCollisionOffsetFromTickPx && labelOffsetFromAxisPx == other.labelOffsetFromAxisPx && labelCollisionOffsetFromAxisPx == other.labelCollisionOffsetFromAxisPx && minimumPaddingBetweenLabelsPx == other.minimumPaddingBetweenLabelsPx && labelRotation == other.labelRotation && labelCollisionRotation == other.labelCollisionRotation && axisLineStyle == other.axisLineStyle); } @override int get hashCode { var hashcode = labelStyle.hashCode; hashcode = (hashcode * 37) + labelAnchor.hashCode; hashcode = (hashcode * 37) + labelJustification.hashCode; hashcode = (hashcode * 37) + labelOffsetFromTickPx.hashCode; hashcode = (hashcode * 37) + labelCollisionOffsetFromTickPx.hashCode; hashcode = (hashcode * 37) + labelOffsetFromAxisPx.hashCode; hashcode = (hashcode * 37) + labelCollisionOffsetFromAxisPx.hashCode; hashcode = (hashcode * 37) + minimumPaddingBetweenLabelsPx.hashCode; hashcode = (hashcode * 37) + labelRotation.hashCode; hashcode = (hashcode * 37) + labelCollisionRotation.hashCode; hashcode = (hashcode * 37) + axisLineStyle.hashCode; return hashcode; } } /// Base strategy that draws tick labels and checks for label collisions. abstract class BaseTickDrawStrategy implements TickDrawStrategy { static final _labelSplitPattern = '\n'; static final multiLineLabelPadding = 2; static double _degToRad(double deg) => deg * (pi / 180.0); final ChartContext chartContext; final GraphicsFactory graphicsFactory; LineStyle axisLineStyle; TextStyle labelStyle; TickLabelJustification tickLabelJustification; final TickLabelAnchor _defaultTickLabelAnchor; final int _labelDefaultOffsetFromAxisPx; final int _labelCollisionOffsetFromAxisPx; final int _labelDefaultOffsetFromTickPx; final int _labelCollisionOffsetFromTickPx; final int _labelDefaultRotation; final int _labelCollisionRotation; final bool _rotateOnCollision; int minimumPaddingBetweenLabelsPx; int labelRotation({required bool collision}) => collision && _rotateOnCollision ? _labelCollisionRotation : _labelDefaultRotation; int labelOffsetFromAxisPx({required bool collision}) => collision && _rotateOnCollision ? _labelCollisionOffsetFromAxisPx : _labelDefaultOffsetFromAxisPx; int labelOffsetFromTickPx({required bool collision}) => collision && _rotateOnCollision ? _labelCollisionOffsetFromTickPx : _labelDefaultOffsetFromTickPx; TickLabelAnchor tickLabelAnchor({required bool collision}) => collision && _rotateOnCollision ? TickLabelAnchor.after : _defaultTickLabelAnchor; BaseTickDrawStrategy( this.chartContext, this.graphicsFactory, { TextStyleSpec? labelStyleSpec, LineStyleSpec? axisLineStyleSpec, TickLabelAnchor? labelAnchor, TickLabelJustification? labelJustification, int? labelOffsetFromAxisPx, int? labelCollisionOffsetFromAxisPx, int? labelOffsetFromTickPx, int? labelCollisionOffsetFromTickPx, int? minimumPaddingBetweenLabelsPx, int? labelRotation, int? labelCollisionRotation, }) : labelStyle = graphicsFactory.createTextPaint(), axisLineStyle = graphicsFactory.createLinePaint(), _defaultTickLabelAnchor = labelAnchor ?? TickLabelAnchor.centered, tickLabelJustification = labelJustification ?? TickLabelJustification.inside, _rotateOnCollision = labelCollisionRotation != null, minimumPaddingBetweenLabelsPx = minimumPaddingBetweenLabelsPx ?? 50, _labelDefaultOffsetFromAxisPx = labelOffsetFromAxisPx ?? 5, _labelDefaultOffsetFromTickPx = labelOffsetFromTickPx ?? 5, _labelDefaultRotation = labelRotation ?? 0, _labelCollisionOffsetFromAxisPx = labelCollisionOffsetFromAxisPx ?? 5, _labelCollisionOffsetFromTickPx = labelCollisionOffsetFromTickPx ?? 5, _labelCollisionRotation = labelCollisionRotation ?? 0 { labelStyle ..color = labelStyleSpec?.color ?? StyleFactory.style.tickColor ..fontFamily = labelStyleSpec?.fontFamily ..fontSize = labelStyleSpec?.fontSize ?? 12 ..lineHeight = labelStyleSpec?.lineHeight; axisLineStyle ..color = axisLineStyleSpec?.color ?? labelStyle.color ..dashPattern = axisLineStyleSpec?.dashPattern ..strokeWidth = axisLineStyleSpec?.thickness ?? 1; } @override void decorateTicks(List> ticks) { for (final tick in ticks) { var textElement = tick.textElement; if (textElement == null) { continue; } // If no style at all, set the default style. if (textElement.textStyle == null) { textElement.textStyle = labelStyle; } else { // Fill in whatever is missing var textStyle = textElement.textStyle!; textStyle.color ??= labelStyle.color; textStyle.fontFamily ??= labelStyle.fontFamily; textStyle.fontSize ??= labelStyle.fontSize; textStyle.lineHeight ??= labelStyle.lineHeight; } } } @override void updateTickWidth(List> ticks, int maxWidth, int maxHeight, AxisOrientation orientation, {bool collision = false}) { final isVertical = orientation != null && orientation == AxisOrientation.right || orientation == AxisOrientation.left; final rotationRelativeToAxis = labelRotation(collision: collision).toDouble(); final rotationRads = _degToRad(rotationRelativeToAxis - (isVertical ? 90 : 0)).abs(); final availableSpace = (isVertical ? maxWidth : maxHeight) - labelOffsetFromAxisPx(collision: collision); final maxTextWidth = sin(rotationRads) == 0 ? null : (availableSpace / sin(rotationRads)).floor(); for (final tick in ticks) { if (maxTextWidth != null) { tick.textElement!.maxWidth = maxTextWidth; tick.textElement!.maxWidthStrategy = MaxWidthStrategy.ellipsize; } else { tick.textElement!.maxWidth = null; tick.textElement!.maxWidthStrategy = null; } } } @override CollisionReport collides( List>? ticks, AxisOrientation? orientation) { // TODO: Collision analysis for rotated labels are not // supported yet. // If there are no ticks, they do not collide. if (ticks == null) { return CollisionReport( ticksCollide: false, ticks: ticks, alternateTicksUsed: false); } final vertical = orientation == AxisOrientation.left || orientation == AxisOrientation.right; ticks = [ for (var tick in ticks) if (tick.locationPx != null) tick, ]; // First sort ticks by smallest locationPx first (NOT sorted by value). // This allows us to only check if a tick collides with the previous tick. ticks.sort((a, b) => a.locationPx!.compareTo(b.locationPx!)); var previousEnd = double.negativeInfinity; var collides = false; for (final tick in ticks) { final tickSize = tick.textElement?.measurement; final tickLocationPx = tick.locationPx!; if (vertical) { final adjustedHeight = (tickSize?.verticalSliceWidth ?? 0.0) + minimumPaddingBetweenLabelsPx; if (_defaultTickLabelAnchor == TickLabelAnchor.inside) { if (identical(tick, ticks.first)) { // Top most tick draws down from the location collides = false; previousEnd = tickLocationPx + adjustedHeight; } else if (identical(tick, ticks.last)) { // Bottom most tick draws up from the location collides = previousEnd > tickLocationPx - adjustedHeight; previousEnd = tickLocationPx; } else { // All other ticks is centered. final halfHeight = adjustedHeight / 2; collides = previousEnd > tickLocationPx - halfHeight; previousEnd = tickLocationPx + halfHeight; } } else { collides = previousEnd > tickLocationPx; previousEnd = tickLocationPx + adjustedHeight; } } else { // Use the text direction the ticks specified, unless the label anchor // is set to [TickLabelAnchor.inside]. When 'inside' is set, the text // direction is normalized such that the left most tick is drawn ltr, // the last tick is drawn rtl, and all other ticks are in the center. // This is not set until it is painted, so collision check needs to get // the value also. final textDirection = _normalizeHorizontalAnchor( _defaultTickLabelAnchor, chartContext.isRtl, identical(tick, ticks.first), identical(tick, ticks.last)); final adjustedWidth = (tickSize?.horizontalSliceWidth ?? 0.0) + minimumPaddingBetweenLabelsPx; switch (textDirection) { case TextDirection.ltr: collides = previousEnd > tickLocationPx; previousEnd = tickLocationPx + adjustedWidth; break; case TextDirection.rtl: collides = previousEnd > (tickLocationPx - adjustedWidth); previousEnd = tickLocationPx; break; case TextDirection.center: final halfWidth = adjustedWidth / 2; collides = previousEnd > tickLocationPx - halfWidth; previousEnd = tickLocationPx + halfWidth; break; } } if (collides) { return CollisionReport( ticksCollide: true, ticks: ticks, alternateTicksUsed: false); } } return CollisionReport( ticksCollide: false, ticks: ticks, alternateTicksUsed: false); } @override ViewMeasuredSizes measureVerticallyDrawnTicks( List> ticks, int maxWidth, int maxHeight, {bool collision = false}) { // TODO: Add spacing to account for the distance between the // text and the axis baseline (even if it isn't drawn). final maxHorizontalSliceWidth = ticks.fold(0.0, (double prevMax, tick) { final labelElements = splitLabel(tick.textElement!); return max( prevMax, calculateWidthForRotatedLabel( labelRotation(collision: collision), getLabelHeight(labelElements), getLabelWidth(labelElements), ) + labelOffsetFromAxisPx(collision: collision)); }).round(); return ViewMeasuredSizes( preferredWidth: maxHorizontalSliceWidth, preferredHeight: maxHeight); } @override ViewMeasuredSizes measureHorizontallyDrawnTicks( List> ticks, int maxWidth, int maxHeight, {bool collision = false}) { final maxVerticalSliceWidth = ticks.fold(0.0, (double prevMax, tick) { final labelElements = splitLabel(tick.textElement!); return max( prevMax, calculateHeightForRotatedLabel( labelRotation(collision: collision), getLabelHeight(labelElements), getLabelWidth(labelElements), )); }).round(); return ViewMeasuredSizes( preferredWidth: maxWidth, preferredHeight: min( maxHeight, maxVerticalSliceWidth + labelOffsetFromAxisPx(collision: collision))); } @override void drawAxisLine(ChartCanvas canvas, AxisOrientation orientation, Rectangle axisBounds) { Point start; Point end; switch (orientation) { case AxisOrientation.top: start = axisBounds.bottomLeft; end = axisBounds.bottomRight; break; case AxisOrientation.bottom: start = axisBounds.topLeft; end = axisBounds.topRight; break; case AxisOrientation.right: start = axisBounds.topLeft; end = axisBounds.bottomLeft; break; case AxisOrientation.left: start = axisBounds.topRight; end = axisBounds.bottomRight; break; } canvas.drawLine( points: [start, end], fill: axisLineStyle.color, stroke: axisLineStyle.color, strokeWidthPx: axisLineStyle.strokeWidth.toDouble(), dashPattern: axisLineStyle.dashPattern, ); } // TODO: Why is drawAreaBounds required when it is unused? @protected void drawLabel( ChartCanvas canvas, Tick tick, { required AxisOrientation orientation, required Rectangle axisBounds, required Rectangle? drawAreaBounds, required bool isFirst, required bool isLast, bool collision = false, }) { final locationPx = tick.locationPx ?? 0; final labelOffsetPx = tick.labelOffsetPx ?? 0; final isRtl = chartContext.isRtl; final labelElements = splitLabel(tick.textElement!); final labelHeight = getLabelHeight(labelElements); var multiLineLabelOffset = 0; for (final line in labelElements) { var x = 0; var y = 0; if (orientation == AxisOrientation.bottom || orientation == AxisOrientation.top) { y = orientation == AxisOrientation.bottom ? axisBounds.top + labelOffsetFromAxisPx(collision: collision) : axisBounds.bottom - (labelHeight.toInt() - multiLineLabelOffset) - labelOffsetFromAxisPx(collision: collision); final direction = _normalizeHorizontalAnchor( tickLabelAnchor(collision: collision), isRtl, isFirst, isLast); line.textDirection = direction; switch (direction) { case TextDirection.rtl: x = (locationPx + labelOffsetFromTickPx(collision: collision) + labelOffsetPx) .toInt(); break; case TextDirection.ltr: x = (locationPx - labelOffsetFromTickPx(collision: collision) - labelOffsetPx) .toInt(); break; case TextDirection.center: default: x = (locationPx - labelOffsetPx).toInt(); break; } } else { if (orientation == AxisOrientation.left) { if (tickLabelJustification == TickLabelJustification.inside) { x = axisBounds.right - labelOffsetFromAxisPx(collision: collision); line.textDirection = TextDirection.rtl; } else { x = axisBounds.left; line.textDirection = TextDirection.ltr; } } else { // orientation == right if (tickLabelJustification == TickLabelJustification.inside) { x = axisBounds.left + labelOffsetFromAxisPx(collision: collision); line.textDirection = TextDirection.ltr; } else { x = axisBounds.right; line.textDirection = TextDirection.rtl; } } switch (normalizeVerticalAnchor( tickLabelAnchor(collision: collision), isFirst, isLast)) { case _PixelVerticalDirection.over: y = (locationPx - (labelHeight - multiLineLabelOffset) - labelOffsetFromTickPx(collision: collision) - labelOffsetPx) .toInt(); break; case _PixelVerticalDirection.under: y = (locationPx + labelOffsetFromTickPx(collision: collision) + labelOffsetPx) .toInt(); break; case _PixelVerticalDirection.center: default: y = (locationPx - labelHeight / 2 + labelOffsetPx).toInt(); break; } } canvas.drawText(line, x, y + multiLineLabelOffset, rotation: _degToRad(labelRotation(collision: collision).toDouble())); multiLineLabelOffset += multiLineLabelPadding + line.measurement.verticalSliceWidth.round(); } } TextDirection _normalizeHorizontalAnchor( TickLabelAnchor anchor, bool isRtl, bool isFirst, bool isLast) { switch (anchor) { case TickLabelAnchor.before: return isRtl ? TextDirection.ltr : TextDirection.rtl; case TickLabelAnchor.after: return isRtl ? TextDirection.rtl : TextDirection.ltr; case TickLabelAnchor.inside: if (isFirst) { return TextDirection.ltr; } if (isLast) { return TextDirection.rtl; } return TextDirection.center; case TickLabelAnchor.centered: default: return TextDirection.center; } } @protected _PixelVerticalDirection normalizeVerticalAnchor( TickLabelAnchor anchor, bool isFirst, bool isLast) { switch (anchor) { case TickLabelAnchor.before: return _PixelVerticalDirection.under; case TickLabelAnchor.after: return _PixelVerticalDirection.over; case TickLabelAnchor.inside: if (isFirst) { return _PixelVerticalDirection.over; } if (isLast) { return _PixelVerticalDirection.under; } return _PixelVerticalDirection.center; case TickLabelAnchor.centered: default: return _PixelVerticalDirection.center; } } /// Returns the width of a rotated labels on a domain axis. double calculateWidthForRotatedLabel( int rotation, double labelHeight, double labelLength) { if (rotation == 0) return labelLength; var rotationRadian = _degToRad(rotation.toDouble()); // Imagine a right triangle with a base that is parallel to the axis // baseline. The side of this triangle that is perpendicular to the baseline // is the height of the axis we wish to calculate. The hypotenuse of the // triangle is the given length of the tick labels, labelLength. The angle // between the perpendicular line and the hypotenuse (the tick label) is 90 // - the label rotation angle, since the tick label transformation is // applied relative to the axis baseline. Given this triangle, we can // calculate the height of the axis by using the cosine of this angle. // The triangle assumes a zero-height line for the labels, but the actual // rendered text will be drawn above and below this center line. To account // for this, extend the label length by using a triangle with half the // height of the label. labelLength += labelHeight / 2.0 * tan(rotationRadian); // To compute the label width, we need the angle between the label and a // line perpendicular to the axis baseline, in radians. return labelLength * cos(rotationRadian); } /// Returns the height of a rotated labels on a domain axis. double calculateHeightForRotatedLabel( int rotation, double labelHeight, double labelLength) { if (rotation == 0) return labelHeight; var rotationRadian = _degToRad(rotation.toDouble()); // Imagine a right triangle with a base that is parallel to the axis // baseline. The side of this triangle that is perpendicular to the baseline // is the height of the axis we wish to calculate. The hypotenuse of the // triangle is the given length of the tick labels, labelLength. The angle // between the perpendicular line and the hypotenuse (the tick label) is 90 // - the label rotation angle, since the tick label transformation is // applied relative to the axis baseline. Given this triangle, we can // calculate the height of the axis by using the cosine of this angle. // The triangle assumes a zero-height line for the labels, but the actual // rendered text will be drawn above and below this center line. To account // for this, extend the label length by using a triangle with half the // height of the label. labelLength += labelHeight / 2.0 * tan(rotationRadian); // To compute the label height, we need the angle between the label and a // line perpendicular to the axis baseline, in radians. var angle = pi / 2.0 - rotationRadian.abs(); return max(labelHeight, labelLength * cos(angle)); } /// The [wholeLabel] is split into constituent chunks if it is multiline. List splitLabel(TextElement wholeLabel) => wholeLabel.text .split(_labelSplitPattern) .map((line) => (graphicsFactory.createTextElement(line.trim()) ..textStyle = wholeLabel.textStyle)) .toList(); /// The width of the label (handles labels spanning multiple lines). /// /// If the label spans multiple lines then it returns the width of the /// longest line. double getLabelWidth(Iterable labelElements) => labelElements .map((line) => line.measurement.horizontalSliceWidth) .reduce(max); /// The height of the label (handles labels spanning multiple lines). double getLabelHeight(Iterable labelElements) { if (labelElements.isEmpty) return 0; final textHeight = labelElements.first.measurement.verticalSliceWidth; final numLines = labelElements.length; return (textHeight * numLines) + (multiLineLabelPadding * (numLines - 1)); } } enum _PixelVerticalDirection { over, center, under, } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/draw_strategy/gridline_draw_strategy.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math'; import 'package:meta/meta.dart' show immutable; import '../../../../common/graphics_factory.dart' show GraphicsFactory; import '../../../../common/line_style.dart' show LineStyle; import '../../../../common/style/style_factory.dart' show StyleFactory; import '../../../common/chart_canvas.dart' show ChartCanvas; import '../../../common/chart_context.dart' show ChartContext; import '../axis.dart' show AxisOrientation; import '../spec/axis_spec.dart' show TextStyleSpec, LineStyleSpec, TickLabelAnchor, TickLabelJustification; import '../tick.dart' show Tick; import 'base_tick_draw_strategy.dart' show BaseTickDrawStrategy; import 'small_tick_draw_strategy.dart' show SmallTickRendererSpec; import 'tick_draw_strategy.dart' show TickDrawStrategy; @immutable class GridlineRendererSpec extends SmallTickRendererSpec { const GridlineRendererSpec({ TextStyleSpec? labelStyle, LineStyleSpec? lineStyle, LineStyleSpec? axisLineStyle, TickLabelAnchor? labelAnchor, TickLabelJustification? labelJustification, int? tickLengthPx, int? labelOffsetFromAxisPx, int? labelCollisionOffsetFromAxisPx, int? labelOffsetFromTickPx, int? labelCollisionOffsetFromTickPx, int? minimumPaddingBetweenLabelsPx, int? labelRotation, int? labelCollisionRotation, }) : super( labelStyle: labelStyle, lineStyle: lineStyle, labelAnchor: labelAnchor, labelJustification: labelJustification, labelOffsetFromAxisPx: labelOffsetFromAxisPx, labelCollisionOffsetFromAxisPx: labelCollisionOffsetFromAxisPx, labelOffsetFromTickPx: labelOffsetFromTickPx, labelCollisionOffsetFromTickPx: labelCollisionOffsetFromTickPx, minimumPaddingBetweenLabelsPx: minimumPaddingBetweenLabelsPx, labelRotation: labelRotation, labelCollisionRotation: labelCollisionRotation, tickLengthPx: tickLengthPx, axisLineStyle: axisLineStyle); @override TickDrawStrategy createDrawStrategy( ChartContext context, GraphicsFactory graphicsFactory) => GridlineTickDrawStrategy(context, graphicsFactory, tickLengthPx: tickLengthPx, lineStyleSpec: lineStyle, labelStyleSpec: labelStyle, axisLineStyleSpec: axisLineStyle, labelAnchor: labelAnchor, labelJustification: labelJustification, labelOffsetFromAxisPx: labelOffsetFromAxisPx, labelCollisionOffsetFromAxisPx: labelCollisionOffsetFromAxisPx, labelOffsetFromTickPx: labelOffsetFromTickPx, labelCollisionOffsetFromTickPx: labelCollisionOffsetFromTickPx, minimumPaddingBetweenLabelsPx: minimumPaddingBetweenLabelsPx, labelRotation: labelRotation, labelCollisionRotation: labelCollisionRotation); @override // ignore: hash_and_equals bool operator ==(Object other) { return identical(this, other) || (other is GridlineRendererSpec && super == other); } } /// Draws line across chart draw area for each tick. /// /// Extends [BaseTickDrawStrategy]. class GridlineTickDrawStrategy extends BaseTickDrawStrategy { int tickLength; LineStyle lineStyle; GridlineTickDrawStrategy( ChartContext chartContext, GraphicsFactory graphicsFactory, { int? tickLengthPx, LineStyleSpec? lineStyleSpec, TextStyleSpec? labelStyleSpec, LineStyleSpec? axisLineStyleSpec, TickLabelAnchor? labelAnchor, TickLabelJustification? labelJustification, int? labelOffsetFromAxisPx, int? labelCollisionOffsetFromAxisPx, int? labelOffsetFromTickPx, int? labelCollisionOffsetFromTickPx, int? minimumPaddingBetweenLabelsPx, int? labelRotation, int? labelCollisionRotation, }) : tickLength = tickLengthPx ?? 0, lineStyle = StyleFactory.style .createGridlineStyle(graphicsFactory, lineStyleSpec), super(chartContext, graphicsFactory, labelStyleSpec: labelStyleSpec, axisLineStyleSpec: axisLineStyleSpec ?? lineStyleSpec, labelAnchor: labelAnchor, labelJustification: labelJustification, labelOffsetFromAxisPx: labelOffsetFromAxisPx, labelCollisionOffsetFromAxisPx: labelCollisionOffsetFromAxisPx, labelOffsetFromTickPx: labelOffsetFromTickPx, labelCollisionOffsetFromTickPx: labelCollisionOffsetFromTickPx, minimumPaddingBetweenLabelsPx: minimumPaddingBetweenLabelsPx, labelRotation: labelRotation, labelCollisionRotation: labelCollisionRotation); @override void draw( ChartCanvas canvas, Tick tick, { required AxisOrientation orientation, required Rectangle axisBounds, required Rectangle drawAreaBounds, required bool isFirst, required bool isLast, bool collision = false, }) { Point lineStart; Point lineEnd; final tickLocationPx = tick.locationPx!; switch (orientation) { case AxisOrientation.top: final x = tickLocationPx; lineStart = Point(x, axisBounds.bottom - tickLength); lineEnd = Point(x, drawAreaBounds.bottom); break; case AxisOrientation.bottom: final x = tickLocationPx; lineStart = Point(x, drawAreaBounds.top + tickLength); lineEnd = Point(x, axisBounds.top); break; case AxisOrientation.right: final y = tickLocationPx; if (tickLabelAnchor(collision: collision) == TickLabelAnchor.after || tickLabelAnchor(collision: collision) == TickLabelAnchor.before) { lineStart = Point(axisBounds.right, y); } else { lineStart = Point(axisBounds.left + tickLength, y); } lineEnd = Point(drawAreaBounds.left, y); break; case AxisOrientation.left: final y = tickLocationPx; if (tickLabelAnchor(collision: collision) == TickLabelAnchor.after || tickLabelAnchor(collision: collision) == TickLabelAnchor.before) { lineStart = Point(axisBounds.left, y); } else { lineStart = Point(axisBounds.right - tickLength, y); } lineEnd = Point(drawAreaBounds.right, y); break; } canvas.drawLine( points: [lineStart, lineEnd], dashPattern: lineStyle.dashPattern, fill: lineStyle.color, stroke: lineStyle.color, strokeWidthPx: lineStyle.strokeWidth.toDouble(), ); drawLabel(canvas, tick, orientation: orientation, axisBounds: axisBounds, drawAreaBounds: drawAreaBounds, isFirst: isFirst, isLast: isLast, collision: collision); } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/draw_strategy/none_draw_strategy.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math'; import 'package:meta/meta.dart' show immutable; import '../../../../common/color.dart' show Color; import '../../../../common/graphics_factory.dart' show GraphicsFactory; import '../../../../common/line_style.dart' show LineStyle; import '../../../../common/style/style_factory.dart' show StyleFactory; import '../../../../common/text_style.dart' show TextStyle; import '../../../common/chart_canvas.dart' show ChartCanvas; import '../../../common/chart_context.dart' show ChartContext; import '../../../layout/layout_view.dart' show ViewMeasuredSizes; import '../axis.dart' show AxisOrientation; import '../collision_report.dart' show CollisionReport; import '../spec/axis_spec.dart' show RenderSpec, LineStyleSpec; import '../tick.dart' show Tick; import 'tick_draw_strategy.dart'; /// Renders no ticks no labels, and claims no space in layout. /// However, it does render the axis line if asked to by the axis. @immutable class NoneRenderSpec extends RenderSpec { final LineStyleSpec? axisLineStyle; const NoneRenderSpec({this.axisLineStyle}); @override TickDrawStrategy createDrawStrategy( ChartContext context, GraphicsFactory graphicFactory) => NoneDrawStrategy(context, graphicFactory, axisLineStyleSpec: axisLineStyle); @override bool operator ==(Object other) => identical(this, other) || other is NoneRenderSpec; @override int get hashCode => 0; } class NoneDrawStrategy implements TickDrawStrategy { LineStyle axisLineStyle; TextStyle noneTextStyle; NoneDrawStrategy( ChartContext chartContext, GraphicsFactory graphicsFactory, { LineStyleSpec? axisLineStyleSpec, }) : axisLineStyle = StyleFactory.style .createAxisLineStyle(graphicsFactory, axisLineStyleSpec), noneTextStyle = graphicsFactory.createTextPaint() ..color = Color.transparent ..fontSize = 0; @override void updateTickWidth(List> ticks, int maxWidth, int maxHeight, AxisOrientation orientation, {bool collision = false}) {} @override CollisionReport collides( List>? ticks, AxisOrientation? orientation) => CollisionReport(ticksCollide: false, ticks: ticks); @override void decorateTicks(List> ticks) { // Even though no text is rendered, the text style for each element should // still be set to handle the case of the draw strategy being switched to // a different draw strategy. The new draw strategy will try to animate // the old ticks out and the text style property is used. ticks.forEach((tick) => tick.textElement!.textStyle = noneTextStyle); } @override void drawAxisLine(ChartCanvas canvas, AxisOrientation orientation, Rectangle axisBounds) { Point start; Point end; switch (orientation) { case AxisOrientation.top: start = axisBounds.bottomLeft; end = axisBounds.bottomRight; break; case AxisOrientation.bottom: start = axisBounds.topLeft; end = axisBounds.topRight; break; case AxisOrientation.right: start = axisBounds.topLeft; end = axisBounds.bottomLeft; break; case AxisOrientation.left: start = axisBounds.topRight; end = axisBounds.bottomRight; break; } canvas.drawLine( points: [start, end], dashPattern: axisLineStyle.dashPattern, fill: axisLineStyle.color, stroke: axisLineStyle.color, strokeWidthPx: axisLineStyle.strokeWidth.toDouble(), ); } @override void draw(ChartCanvas canvas, Tick tick, {required AxisOrientation orientation, required Rectangle axisBounds, required Rectangle drawAreaBounds, required bool isFirst, required bool isLast, bool collision = false}) {} @override ViewMeasuredSizes measureHorizontallyDrawnTicks( List> ticks, int maxWidth, int maxHeight, {bool collision = false}) { return ViewMeasuredSizes(preferredWidth: 0, preferredHeight: 0); } @override ViewMeasuredSizes measureVerticallyDrawnTicks( List> ticks, int maxWidth, int maxHeight, {bool collision = false}) { return ViewMeasuredSizes(preferredWidth: 0, preferredHeight: 0); } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/draw_strategy/range_tick_draw_strategy.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math'; import 'package:meta/meta.dart' show immutable; import '../../../../common/graphics_factory.dart' show GraphicsFactory; import '../../../../common/line_style.dart' show LineStyle; import '../../../../common/material_palette.dart' show MaterialPalette; import '../../../../common/style/style_factory.dart' show StyleFactory; import '../../../../common/text_style.dart' show TextStyle; import '../../../common/chart_canvas.dart' show ChartCanvas; import '../../../common/chart_context.dart' show ChartContext; import '../../../layout/layout_view.dart' show ViewMeasuredSizes; import '../axis.dart' show AxisOrientation; import '../range_axis_tick.dart' show RangeAxisTicks; import '../spec/axis_spec.dart' show TextStyleSpec, LineStyleSpec, TickLabelAnchor, TickLabelJustification; import '../tick.dart' show Tick; import 'base_tick_draw_strategy.dart' show BaseTickDrawStrategy; import 'small_tick_draw_strategy.dart' show SmallTickRendererSpec, SmallTickDrawStrategy; import 'tick_draw_strategy.dart' show TickDrawStrategy; /// Displays individual ticks and range ticks and with a shade for ranges. /// Sample ticks looks like: /// ------------------------------------------------------------------- /// | | | | | /// | (Individual tick) | (Individual tick) | /// |///////Range Label/////////|///////////Range Label///////////| @immutable class RangeTickRendererSpec extends SmallTickRendererSpec { // Specifies range shade's style. final LineStyleSpec? rangeShadeStyle; // Specifies range label text style. final TextStyleSpec? rangeLabelStyle; // Specifies range tick's length. final int? rangeTickLengthPx; // Specifies range shade's height. final int? rangeShadeHeightPx; // Specifies the starting offet of range shade from axis in pixels. final int? rangeShadeOffsetFromAxisPx; // A range tick offset from the original location. The start point offset is // toward the origin and end point offset is toward the end of axis. final int? rangeTickOffsetPx; final TextStyleSpec defaultLabelStyleSpec; static const int defaultLabelOffsetFromAxis = 2; static const int defaultLabelOffsetFromTick = -4; RangeTickRendererSpec( {TextStyleSpec? labelStyle, LineStyleSpec? lineStyle, TickLabelAnchor? labelAnchor, TickLabelJustification? labelJustification, int? labelOffsetFromAxisPx, int? labelCollisionOffsetFromAxisPx, int? labelOffsetFromTickPx, int? labelCollisionOffsetFromTickPx, this.rangeShadeHeightPx, this.rangeShadeOffsetFromAxisPx, this.rangeShadeStyle, this.rangeTickLengthPx, this.rangeTickOffsetPx, this.rangeLabelStyle, int? tickLengthPx, int? minimumPaddingBetweenLabelsPx, int? labelRotation, int? labelCollisionRotation}) : defaultLabelStyleSpec = TextStyleSpec(fontSize: 9, color: StyleFactory.style.tickColor), super( axisLineStyle: lineStyle, labelStyle: labelStyle, labelAnchor: labelAnchor, labelJustification: labelJustification, labelOffsetFromAxisPx: labelOffsetFromAxisPx ?? defaultLabelOffsetFromAxis, labelCollisionOffsetFromAxisPx: labelCollisionOffsetFromAxisPx, labelOffsetFromTickPx: labelOffsetFromTickPx ?? defaultLabelOffsetFromTick, labelCollisionOffsetFromTickPx: labelCollisionOffsetFromTickPx, tickLengthPx: tickLengthPx, minimumPaddingBetweenLabelsPx: minimumPaddingBetweenLabelsPx, labelRotation: labelRotation, labelCollisionRotation: labelCollisionRotation, ); @override TickDrawStrategy createDrawStrategy( ChartContext context, GraphicsFactory graphicsFactory) => RangeTickDrawStrategy(context, graphicsFactory, tickLength: tickLengthPx, rangeLabelTextStyleSpec: rangeLabelStyle, rangeTickLength: rangeTickLengthPx, rangeShadeHeight: rangeShadeHeightPx, rangeShadeOffsetFromAxis: rangeShadeOffsetFromAxisPx, rangeTickOffset: rangeTickOffsetPx, lineStyleSpec: lineStyle, labelStyleSpec: labelStyle ?? defaultLabelStyleSpec, axisLineStyleSpec: axisLineStyle, rangeShadeStyleSpec: rangeShadeStyle, labelAnchor: labelAnchor, labelJustification: labelJustification, labelOffsetFromAxisPx: labelOffsetFromAxisPx, labelCollisionOffsetFromAxisPx: labelCollisionOffsetFromAxisPx, labelOffsetFromTickPx: labelOffsetFromTickPx, labelCollisionOffsetFromTickPx: labelCollisionOffsetFromTickPx, minimumPaddingBetweenLabelsPx: minimumPaddingBetweenLabelsPx, labelRotation: labelRotation, labelCollisionRotation: labelCollisionRotation); @override // ignore: hash_and_equals bool operator ==(Object other) { return identical(this, other) || (other is RangeTickRendererSpec && super == other); } } /// Draws small tick lines for each tick. Extends [BaseTickDrawStrategy]. class RangeTickDrawStrategy extends SmallTickDrawStrategy { int rangeTickLengthPx = 24; int rangeShadeHeightPx = 12; int rangeShadeOffsetFromAxisPx = 12; int rangeTickOffsetPx = 12; late LineStyle rangeShadeStyle; late TextStyle rangeLabelStyle; RangeTickDrawStrategy( ChartContext chartContext, GraphicsFactory graphicsFactory, {int? tickLength, int? rangeTickLength, int? rangeShadeHeight, int? rangeShadeOffsetFromAxis, int? rangeTickOffset, TextStyleSpec? rangeLabelTextStyleSpec, LineStyleSpec? lineStyleSpec, LineStyleSpec? rangeShadeStyleSpec, TextStyleSpec? labelStyleSpec, LineStyleSpec? axisLineStyleSpec, TickLabelAnchor? labelAnchor, int? labelOffsetFromAxisPx, int? labelCollisionOffsetFromAxisPx, int? labelOffsetFromTickPx, int? labelCollisionOffsetFromTickPx, TickLabelJustification? labelJustification, int? minimumPaddingBetweenLabelsPx, int? labelRotation, int? labelCollisionRotation}) : super(chartContext, graphicsFactory, tickLengthPx: tickLength, axisLineStyleSpec: axisLineStyleSpec, labelStyleSpec: labelStyleSpec, lineStyleSpec: lineStyleSpec, labelAnchor: labelAnchor ?? TickLabelAnchor.after, labelJustification: labelJustification, labelOffsetFromAxisPx: labelOffsetFromAxisPx, labelCollisionOffsetFromAxisPx: labelCollisionOffsetFromAxisPx, labelOffsetFromTickPx: labelOffsetFromTickPx, labelCollisionOffsetFromTickPx: labelCollisionOffsetFromTickPx, minimumPaddingBetweenLabelsPx: minimumPaddingBetweenLabelsPx, labelRotation: labelRotation, labelCollisionRotation: labelCollisionRotation) { rangeTickOffsetPx = rangeTickOffset ?? rangeTickOffsetPx; rangeTickLengthPx = rangeTickLength ?? rangeTickLengthPx; rangeShadeHeightPx = rangeShadeHeight ?? rangeShadeHeightPx; rangeShadeOffsetFromAxisPx = rangeShadeOffsetFromAxis ?? rangeShadeOffsetFromAxisPx; lineStyle = StyleFactory.style.createTickLineStyle(graphicsFactory, lineStyleSpec); rangeShadeStyleSpec = rangeShadeStyleSpec ?? LineStyleSpec( color: MaterialPalette.gray.shade300, ); rangeShadeStyle = StyleFactory.style .createTickLineStyle(graphicsFactory, rangeShadeStyleSpec); rangeLabelStyle = rangeLabelTextStyleSpec == null ? (graphicsFactory.createTextPaint() ..color = labelStyleSpec?.color ?? StyleFactory.style.tickColor ..fontFamily = labelStyleSpec?.fontFamily ..fontSize = rangeShadeHeightPx - 1) : (graphicsFactory.createTextPaint() ..color = rangeLabelTextStyleSpec.color ..fontFamily = rangeLabelTextStyleSpec.fontFamily ..fontSize = rangeLabelTextStyleSpec.fontSize ..lineHeight = rangeLabelTextStyleSpec.lineHeight); } @override void draw(ChartCanvas canvas, Tick tick, {required AxisOrientation orientation, required Rectangle axisBounds, required Rectangle drawAreaBounds, required bool isFirst, required bool isLast, bool collision = false}) { if (tick is RangeAxisTicks) { drawRangeShadeAndRangeLabel(tick, canvas, orientation, axisBounds, drawAreaBounds, isFirst, isLast); } else { super.draw(canvas, tick, orientation: orientation, axisBounds: axisBounds, drawAreaBounds: drawAreaBounds, isFirst: isFirst, isLast: isLast, collision: collision); } } @override ViewMeasuredSizes measureVerticallyDrawnTicks( List> ticks, int maxWidth, int maxHeight, {bool collision = false}) { // TODO: Add spacing to account for the distance between the // text and the axis baseline (even if it isn't drawn). final maxHorizontalSliceWidth = ticks.fold(0.0, (num prevMax, tick) { assert(tick.textElement != null); final labelElements = splitLabel(tick.textElement!); if (tick is RangeAxisTicks) { // Find the maximum within prevMax, label total height and // labelOffsetFromAxisPx + rangeShadeHeightPx. return max( max( prevMax, calculateWidthForRotatedLabel( labelRotation(collision: collision), getLabelHeight(labelElements), getLabelWidth(labelElements), ) + labelOffsetFromAxisPx(collision: collision)), labelOffsetFromAxisPx(collision: collision) + rangeShadeHeightPx); } else { return max( prevMax, calculateWidthForRotatedLabel( labelRotation(collision: collision), getLabelHeight(labelElements), getLabelWidth(labelElements), ) + labelOffsetFromAxisPx(collision: collision)); } }).round(); return ViewMeasuredSizes( preferredWidth: maxHorizontalSliceWidth, preferredHeight: maxHeight); } @override ViewMeasuredSizes measureHorizontallyDrawnTicks( List> ticks, int maxWidth, int maxHeight, {bool collision = false}) { var maxVerticalSliceWidth = ticks.fold(0.0, (num prevMax, tick) { final labelElements = splitLabel(tick.textElement!); if (tick is RangeAxisTicks) { // Find the maximum within prevMax, label total height and // labelOffsetFromAxisPx + rangeShadeHeightPx. return max( max( prevMax, calculateHeightForRotatedLabel( labelRotation(collision: collision), getLabelHeight(labelElements), getLabelWidth(labelElements), ) + rangeShadeOffsetFromAxisPx, ), rangeShadeOffsetFromAxisPx + rangeShadeHeightPx); } else { return max( prevMax, calculateHeightForRotatedLabel( labelRotation(collision: collision), getLabelHeight(labelElements), getLabelWidth(labelElements), )) + labelOffsetFromAxisPx(collision: collision); } }).round(); return ViewMeasuredSizes( preferredWidth: maxWidth, preferredHeight: maxVerticalSliceWidth); } void drawRangeShadeAndRangeLabel( RangeAxisTicks tick, ChartCanvas canvas, AxisOrientation orientation, Rectangle axisBounds, Rectangle drawAreaBounds, bool isFirst, bool isLast, ) { // Create virtual range start and end ticks for position calculation. var rangeStartTick = Tick( value: tick.rangeStartValue, locationPx: tick.rangeStartLocationPx - rangeTickOffsetPx, textElement: null, ); var rangeEndTick = Tick( value: tick.rangeEndValue, locationPx: isLast ? tick.rangeEndLocationPx + rangeTickOffsetPx : tick.rangeEndLocationPx - rangeTickOffsetPx, textElement: null, ); // Calculate range start positions. var rangeStartPositions = calculateTickPositions(rangeStartTick, orientation, axisBounds, drawAreaBounds, rangeTickLengthPx); var rangeStartTickStart = rangeStartPositions.first; var rangeStartTickEnd = rangeStartPositions.last; // Calculate range end positions. var rangeEndPositions = calculateTickPositions(rangeEndTick, orientation, axisBounds, drawAreaBounds, rangeTickLengthPx); var rangeEndTickStart = rangeEndPositions.first; var rangeEndTickEnd = rangeEndPositions.last; // Draw range shade. Rectangle rangeShade; switch (orientation) { case AxisOrientation.top: case AxisOrientation.bottom: rangeShade = Rectangle( rangeStartTickStart.x, rangeStartTickStart.y + rangeShadeOffsetFromAxisPx, rangeEndTickStart.x - rangeStartTickStart.x, rangeShadeHeightPx); break; case AxisOrientation.right: rangeShade = Rectangle( rangeEndTickStart.x + rangeShadeOffsetFromAxisPx, rangeEndTickStart.y, rangeShadeHeightPx, rangeEndTickStart.y - rangeEndTickStart.y); break; case AxisOrientation.left: rangeShade = Rectangle( rangeEndTickStart.x - rangeShadeOffsetFromAxisPx - rangeShadeHeightPx, rangeEndTickStart.y, rangeShadeHeightPx, rangeEndTickStart.y - rangeEndTickStart.y); break; } canvas.drawRect(rangeShade, fill: rangeShadeStyle.color, stroke: rangeShadeStyle.color, strokeWidthPx: rangeShadeStyle.strokeWidth.toDouble()); // Draw the start and end boundaries of the range. canvas.drawLine( points: [rangeStartTickStart, rangeStartTickEnd], dashPattern: lineStyle.dashPattern, fill: lineStyle.color, stroke: lineStyle.color, strokeWidthPx: lineStyle.strokeWidth.toDouble(), ); canvas.drawLine( points: [rangeEndTickStart, rangeEndTickEnd], dashPattern: lineStyle.dashPattern, fill: lineStyle.color, stroke: lineStyle.color, strokeWidthPx: lineStyle.strokeWidth.toDouble(), ); // Prepare range label. final rangeLabelTextElement = tick.textElement! ..textStyle = rangeLabelStyle; final labelElements = splitLabel(rangeLabelTextElement); final labelWidth = getLabelWidth(labelElements); // Draw range label on top of range shade. var multiLineLabelOffset = 0; for (final line in labelElements) { var x = 0; var y = 0; if (orientation == AxisOrientation.bottom || orientation == AxisOrientation.top) { y = rangeStartTickStart.y.toInt() + rangeShadeOffsetFromAxisPx - 1; x = (rangeStartTickStart.x + (rangeEndTickStart.x - rangeStartTickStart.x - labelWidth) / 2) .round(); } // TODO: add support for orientation left and right. canvas.drawText(line, x, y + multiLineLabelOffset); multiLineLabelOffset += BaseTickDrawStrategy.multiLineLabelPadding + line.measurement.verticalSliceWidth.round(); } } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/draw_strategy/small_tick_draw_strategy.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math'; import 'package:meta/meta.dart' show immutable; import '../../../../common/graphics_factory.dart' show GraphicsFactory; import '../../../../common/line_style.dart' show LineStyle; import '../../../../common/style/style_factory.dart' show StyleFactory; import '../../../common/chart_canvas.dart' show ChartCanvas; import '../../../common/chart_context.dart' show ChartContext; import '../axis.dart' show AxisOrientation; import '../spec/axis_spec.dart' show TextStyleSpec, LineStyleSpec, TickLabelAnchor, TickLabelJustification; import '../tick.dart' show Tick; import 'base_tick_draw_strategy.dart' show BaseRenderSpec, BaseTickDrawStrategy; import 'tick_draw_strategy.dart' show TickDrawStrategy; @immutable class SmallTickRendererSpec extends BaseRenderSpec { final LineStyleSpec? lineStyle; final int? tickLengthPx; const SmallTickRendererSpec({ TextStyleSpec? labelStyle, this.lineStyle, LineStyleSpec? axisLineStyle, TickLabelAnchor? labelAnchor, TickLabelJustification? labelJustification, int? labelOffsetFromAxisPx, int? labelCollisionOffsetFromAxisPx, int? labelOffsetFromTickPx, int? labelCollisionOffsetFromTickPx, this.tickLengthPx, int? minimumPaddingBetweenLabelsPx, int? labelRotation, int? labelCollisionRotation, }) : super( labelStyle: labelStyle, labelAnchor: labelAnchor, labelJustification: labelJustification, labelOffsetFromAxisPx: labelOffsetFromAxisPx, labelCollisionOffsetFromAxisPx: labelCollisionOffsetFromAxisPx, labelOffsetFromTickPx: labelOffsetFromTickPx, labelCollisionOffsetFromTickPx: labelCollisionOffsetFromTickPx, minimumPaddingBetweenLabelsPx: minimumPaddingBetweenLabelsPx, labelRotation: labelRotation, labelCollisionRotation: labelCollisionRotation, axisLineStyle: axisLineStyle); @override TickDrawStrategy createDrawStrategy( ChartContext context, GraphicsFactory graphicsFactory) => SmallTickDrawStrategy( context, graphicsFactory, tickLengthPx: tickLengthPx, lineStyleSpec: lineStyle, labelStyleSpec: labelStyle, axisLineStyleSpec: axisLineStyle, labelAnchor: labelAnchor, labelJustification: labelJustification, labelOffsetFromAxisPx: labelOffsetFromAxisPx, labelCollisionOffsetFromAxisPx: labelCollisionOffsetFromAxisPx, labelOffsetFromTickPx: labelOffsetFromTickPx, labelCollisionOffsetFromTickPx: labelCollisionOffsetFromTickPx, minimumPaddingBetweenLabelsPx: minimumPaddingBetweenLabelsPx, labelRotation: labelRotation, labelCollisionRotation: labelCollisionRotation, ); @override bool operator ==(Object other) { return identical(this, other) || (other is SmallTickRendererSpec && lineStyle == other.lineStyle && tickLengthPx == other.tickLengthPx && super == other); } @override int get hashCode { var hashcode = lineStyle.hashCode; hashcode = (hashcode * 37) + tickLengthPx.hashCode; hashcode = (hashcode * 37) + super.hashCode; return hashcode; } } /// Draws small tick lines for each tick. Extends [BaseTickDrawStrategy]. class SmallTickDrawStrategy extends BaseTickDrawStrategy { int tickLength; LineStyle lineStyle; SmallTickDrawStrategy( ChartContext chartContext, GraphicsFactory graphicsFactory, {int? tickLengthPx, LineStyleSpec? lineStyleSpec, TextStyleSpec? labelStyleSpec, LineStyleSpec? axisLineStyleSpec, TickLabelAnchor? labelAnchor, TickLabelJustification? labelJustification, int? labelOffsetFromAxisPx, int? labelCollisionOffsetFromAxisPx, int? labelOffsetFromTickPx, int? labelCollisionOffsetFromTickPx, int? minimumPaddingBetweenLabelsPx, int? labelRotation, int? labelCollisionRotation}) : tickLength = tickLengthPx ?? StyleFactory.style.tickLength, lineStyle = StyleFactory.style .createTickLineStyle(graphicsFactory, lineStyleSpec), super( chartContext, graphicsFactory, labelStyleSpec: labelStyleSpec, axisLineStyleSpec: axisLineStyleSpec ?? lineStyleSpec, labelAnchor: labelAnchor, labelJustification: labelJustification, labelOffsetFromAxisPx: labelOffsetFromAxisPx, labelCollisionOffsetFromAxisPx: labelCollisionOffsetFromAxisPx, labelOffsetFromTickPx: labelOffsetFromTickPx, labelCollisionOffsetFromTickPx: labelCollisionOffsetFromTickPx, minimumPaddingBetweenLabelsPx: minimumPaddingBetweenLabelsPx, labelRotation: labelRotation, labelCollisionRotation: labelCollisionRotation, ); @override void draw(ChartCanvas canvas, Tick tick, {required AxisOrientation orientation, required Rectangle axisBounds, required Rectangle drawAreaBounds, required bool isFirst, required bool isLast, bool collision = false}) { var tickPositions = calculateTickPositions( tick, orientation, axisBounds, drawAreaBounds, tickLength, ); final tickStart = tickPositions.first; final tickEnd = tickPositions.last; canvas.drawLine( points: [tickStart, tickEnd], dashPattern: lineStyle.dashPattern, fill: lineStyle.color, stroke: lineStyle.color, strokeWidthPx: lineStyle.strokeWidth.toDouble(), ); drawLabel(canvas, tick, orientation: orientation, axisBounds: axisBounds, drawAreaBounds: drawAreaBounds, isFirst: isFirst, isLast: isLast, collision: collision); } List> calculateTickPositions( Tick tick, AxisOrientation orientation, Rectangle axisBounds, Rectangle drawAreaBounds, int tickLength, ) { Point tickStart; Point tickEnd; final tickLocationPx = tick.locationPx!; switch (orientation) { case AxisOrientation.top: final x = tickLocationPx; tickStart = Point(x, axisBounds.bottom - tickLength); tickEnd = Point(x, axisBounds.bottom); break; case AxisOrientation.bottom: final x = tickLocationPx; tickStart = Point(x, axisBounds.top); tickEnd = Point(x, axisBounds.top + tickLength); break; case AxisOrientation.right: final y = tickLocationPx; tickStart = Point(axisBounds.left, y); tickEnd = Point(axisBounds.left + tickLength, y); break; case AxisOrientation.left: final y = tickLocationPx; tickStart = Point(axisBounds.right - tickLength, y); tickEnd = Point(axisBounds.right, y); break; } return [tickStart, tickEnd]; } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/draw_strategy/tick_draw_strategy.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math'; import '../../../common/chart_canvas.dart' show ChartCanvas; import '../../../layout/layout_view.dart' show ViewMeasuredSizes; import '../axis.dart' show AxisOrientation; import '../collision_report.dart' show CollisionReport; import '../tick.dart' show Tick; /// Strategy for drawing ticks and checking for collisions. abstract class TickDrawStrategy { /// Decorate the existing list of ticks. /// /// This can be used to further modify ticks after they have been generated /// with location data and formatted labels. void decorateTicks(List> ticks); /// Returns a [CollisionReport] indicating if there are any collisions. CollisionReport collides( List>? ticks, AxisOrientation? orientation); /// Returns measurement of ticks drawn vertically. ViewMeasuredSizes measureVerticallyDrawnTicks( List> ticks, int maxWidth, int maxHeight, {bool collision = false}); /// Returns measurement of ticks drawn horizontally. ViewMeasuredSizes measureHorizontallyDrawnTicks( List> ticks, int maxWidth, int maxHeight, {bool collision = false}); /// Updates max tick width to match fit max size. void updateTickWidth(List> ticks, int maxWidth, int maxHeight, AxisOrientation orientation, {bool collision = false}); /// Draws tick onto [ChartCanvas]. /// /// [orientation] the orientation of the axis that this [tick] belongs to. /// [axisBounds] the bounds of the axis. /// [drawAreaBounds] the bounds of the chart draw area adjacent to the axis. /// [collision] whether or not this [tick] should be drawn in such a way to /// avoid colliding into other ticks. void draw(ChartCanvas canvas, Tick tick, {required AxisOrientation orientation, required Rectangle axisBounds, required Rectangle drawAreaBounds, required bool isFirst, required bool isLast, bool collision = false}); void drawAxisLine(ChartCanvas canvas, AxisOrientation orientation, Rectangle axisBounds); } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/end_points_tick_provider.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import '../../../common/graphics_factory.dart' show GraphicsFactory; import '../../common/chart_context.dart' show ChartContext; import 'axis.dart' show AxisOrientation; import 'draw_strategy/tick_draw_strategy.dart' show TickDrawStrategy; import 'numeric_scale.dart' show NumericScale; import 'ordinal_scale.dart' show OrdinalScale; import 'scale.dart' show MutableScale; import 'tick.dart' show Tick; import 'tick_formatter.dart' show TickFormatter; import 'tick_provider.dart' show BaseTickProvider, TickHint; import 'time/date_time_scale.dart' show DateTimeScale; /// Tick provider that provides ticks at the two end points of the axis range. class EndPointsTickProvider extends BaseTickProvider { @override List> getTicks({ required ChartContext? context, required GraphicsFactory graphicsFactory, required MutableScale scale, required TickFormatter formatter, required Map formatterValueCache, required TickDrawStrategy tickDrawStrategy, required AxisOrientation? orientation, bool viewportExtensionEnabled = false, TickHint? tickHint, }) { final ticks = >[]; // Check to see if the axis has been configured with some domain values. // // An un-configured axis has no domain step size, and its scale defaults to // infinity. if (scale.domainStepSize.abs() != double.infinity) { final start = _getStartValue(tickHint, scale); final end = _getEndValue(tickHint, scale); final labels = formatter.format([start, end], formatterValueCache, stepSize: scale.domainStepSize); if (start != null) { ticks.add(Tick( value: start, textElement: graphicsFactory.createTextElement(labels[0]), locationPx: scale[start]?.toDouble())); } if (end != null) { ticks.add(Tick( value: end, textElement: graphicsFactory.createTextElement(labels[1]), locationPx: scale[end]?.toDouble())); } // Allow draw strategy to decorate the ticks. tickDrawStrategy.decorateTicks(ticks); } return ticks; } /// Get the start value from the scale. D _getStartValue(TickHint? tickHint, MutableScale scale) { Object? start; if (tickHint != null) { start = tickHint.start; } else { // Upcast to allow type promotion. // See https://github.com/dart-lang/sdk/issues/34018. Object _scale = scale; if (_scale is NumericScale) { start = _scale.viewportDomain.min; } else if (_scale is DateTimeScale) { start = _scale.viewportDomain.start; } else if (_scale is OrdinalScale) { start = _scale.domain.first; } else { throw UnsupportedError('Unrecognized scale: {scale.runtimeType}'); } } return start as D; } /// Get the end value from the scale. D _getEndValue(TickHint? tickHint, MutableScale scale) { Object? end; if (tickHint != null) { end = tickHint.end; } else { // Upcast to allow type promotion. // See https://github.com/dart-lang/sdk/issues/34018. Object _scale = scale; if (_scale is NumericScale) { end = _scale.viewportDomain.max; } else if (_scale is DateTimeScale) { end = _scale.viewportDomain.end; } else if (_scale is OrdinalScale) { end = _scale.domain.last; } else { throw UnsupportedError('Unrecognized scale: {scale.runtimeType}'); } } return end as D; } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/linear/bucketing_numeric_axis.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import '../axis.dart' show NumericAxis; import 'bucketing_numeric_tick_provider.dart' show BucketingNumericTickProvider; /// A numeric [Axis] that positions all values beneath a certain [threshold] /// into a reserved space on the axis range. The label for the bucket line will /// be drawn in the middle of the bucket range, rather than aligned with the /// gridline for that value's position on the scale. /// /// An example illustration of a bucketing measure axis on a point chart /// follows. In this case, values such as "6%" and "3%" are drawn in the bucket /// of the axis, since they are less than the [threshold] value of 10%. /// /// 100% ┠───────────────────────── /// ┃ * /// ┃ * /// 50% ┠──────*────────────────── /// ┃ /// ┠───────────────────────── /// < 10% ┃ * * /// ┗┯━━━━━━━━━━┯━━━━━━━━━━━┯━ /// 0 50 100 /// /// This axis will format numbers as percents by default. class BucketingNumericAxis extends NumericAxis { /// All values smaller than the threshold will be bucketed into the same /// position in the reserved space on the axis. num? _threshold; /// Whether or not measure values bucketed below the [threshold] should be /// visible on the chart, or collapsed. /// /// If this is false, then any data with measure values smaller than /// [threshold] will be rendered at the baseline of the chart. The bool? _showBucket; BucketingNumericAxis() : super(tickProvider: BucketingNumericTickProvider()); set threshold(num threshold) { _threshold = threshold; (tickProvider as BucketingNumericTickProvider).threshold = threshold; } set showBucket(bool showBucket) { _showBucket = showBucket; (tickProvider as BucketingNumericTickProvider).showBucket = showBucket; } /// Gets the location of [domain] on the axis, repositioning any value less /// than [threshold] to the middle of the reserved bucket. @override double? getLocation(num? domain) { if (domain == null) { return null; } final scale = this.scale!; if (_threshold != null && domain < _threshold!) { return (_showBucket! ? scale[_threshold! / 2] : scale[0.0])!.toDouble(); } else { return scale[domain]!.toDouble(); } } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/linear/bucketing_numeric_tick_provider.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import '../../../../common/graphics_factory.dart' show GraphicsFactory; import '../../../common/chart_context.dart' show ChartContext; import '../axis.dart' show AxisOrientation; import '../draw_strategy/tick_draw_strategy.dart' show TickDrawStrategy; import '../numeric_scale.dart' show NumericScale; import '../numeric_tick_provider.dart' show NumericTickProvider; import '../tick.dart' show Tick; import '../tick_formatter.dart' show SimpleTickFormatterBase, TickFormatter; import '../tick_provider.dart' show TickHint; /// Tick provider that generates ticks for a [BucketingNumericAxis]. /// /// An example illustration of a bucketing measure axis on a point chart /// follows. In this case, values such as "6%" and "3%" are drawn in the bucket /// of the axis, since they are less than the [threshold] value of 10%. /// /// 100% ┠───────────────────────── /// ┃ * /// ┃ * /// 50% ┠──────*────────────────── /// ┃ /// ┠───────────────────────── /// < 10% ┃ * * /// ┗┯━━━━━━━━━━┯━━━━━━━━━━━┯━ /// 0 50 100 /// /// This tick provider will generate ticks using the same strategy as /// [NumericTickProvider], except that any ticks that are smaller than /// [threshold] will be hidden with an empty label. A special tick will be added /// at the [threshold] position, with a label offset that moves its label down /// to the middle of the bucket. class BucketingNumericTickProvider extends NumericTickProvider { /// All values smaller than the threshold will be bucketed into the same /// position in the reserved space on the axis. num? _threshold; set threshold(num threshold) { _threshold = threshold; } /// Whether or not measure values bucketed below the [threshold] should be /// visible on the chart, or collapsed. bool? _showBucket; set showBucket(bool showBucket) { _showBucket = showBucket; } @override List> getTicks({ required ChartContext? context, required GraphicsFactory graphicsFactory, required NumericScale scale, required TickFormatter formatter, required Map formatterValueCache, required TickDrawStrategy tickDrawStrategy, required AxisOrientation? orientation, bool viewportExtensionEnabled = false, TickHint? tickHint, }) { final _threshold = this._threshold; final _showBucket = this._showBucket; if (_threshold == null) { throw ArgumentError( 'Bucketing threshold must be set before getting ticks.'); } if (_showBucket == null) { throw ArgumentError( 'The showBucket flag must be set before getting ticks.'); } final localFormatter = _BucketingFormatter( threshold: _threshold, originalFormatter: formatter as SimpleTickFormatterBase); final ticks = super.getTicks( context: context, graphicsFactory: graphicsFactory, scale: scale, formatter: localFormatter, formatterValueCache: formatterValueCache, tickDrawStrategy: tickDrawStrategy, orientation: orientation, viewportExtensionEnabled: viewportExtensionEnabled); // Create a tick for the threshold. final thresholdTick = Tick( value: _threshold, textElement: graphicsFactory .createTextElement(localFormatter.formatValue(_threshold)), locationPx: (_showBucket ? scale[_threshold] : scale[0])!.toDouble(), labelOffsetPx: _showBucket ? -0.5 * (scale[_threshold]! - scale[0]!) : 0.0); tickDrawStrategy.decorateTicks(>[thresholdTick]); // Filter out ticks that sit below the threshold. ticks.removeWhere((Tick tick) => tick.value <= thresholdTick.value && tick.value != 0.0); // Finally, add our threshold tick to the list. ticks.add(thresholdTick); // Make sure they are sorted by increasing value. ticks.sort((a, b) => a.value.compareTo(b.value)); return ticks; } } class _BucketingFormatter extends SimpleTickFormatterBase { _BucketingFormatter( {required this.threshold, required this.originalFormatter}); /// All values smaller than the threshold will be formatted into an empty /// string. final num threshold; final SimpleTickFormatterBase originalFormatter; /// Formats a single tick value. @override String formatValue(num value) { if (value < threshold) { return ''; } else if (value == threshold) { return '< ' + originalFormatter.formatValue(value); } else { return originalFormatter.formatValue(value); } } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/linear/linear_scale.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import '../numeric_extents.dart' show NumericExtents; import '../numeric_scale.dart' show NumericScale; import '../scale.dart' show RangeBandConfig, ScaleOutputExtent, StepSizeConfig; import 'linear_scale_domain_info.dart' show LinearScaleDomainInfo; import 'linear_scale_function.dart' show LinearScaleFunction; import 'linear_scale_viewport.dart' show LinearScaleViewportSettings; /// [NumericScale] that lays out the domain linearly across the range. /// /// A [Scale] which converts numeric domain units to a given numeric range units /// linearly (as opposed to other methods like log scales). This is used to map /// the domain's values to the available pixel range of the chart using the /// apply method. /// ///

The domain extent of the scale are determined by adding all domain /// values to the scale. It can, however, be overwritten by calling /// [domainOverride] to define the extent of the data. /// ///

The scale can be zoomed & panned by calling either [setViewportSettings] /// with a zoom and translate, or by setting [viewportExtent] with the domain /// extent to show in the output range. /// ///

[rangeBandConfig]: By default, this scale will map the domain extent /// exactly to the output range in a simple ratio mapping. If a /// [RangeBandConfig] other than NONE is used to define the width of bar groups, /// then the scale calculation may be altered to that there is a half a stepSize /// at the start and end of the range to ensure that a bar group can be shown /// and centered on the scale's result. /// ///

[stepSizeConfig]: By default, this scale will calculate the stepSize as /// being auto detected using the minimal distance between two consecutive /// datum. If you don't assign a [RangeBandConfig], then changing the /// [stepSizeConfig] is a no-op. class LinearScale implements NumericScale { final LinearScaleDomainInfo _domainInfo; final LinearScaleViewportSettings _viewportSettings; final LinearScaleFunction _scaleFunction = LinearScaleFunction(); @override RangeBandConfig rangeBandConfig = const RangeBandConfig.none(); @override StepSizeConfig stepSizeConfig = const StepSizeConfig.auto(); bool _scaleReady = false; LinearScale() : _domainInfo = LinearScaleDomainInfo(), _viewportSettings = LinearScaleViewportSettings(); LinearScale._copy(LinearScale other) : _domainInfo = LinearScaleDomainInfo.copy(other._domainInfo), _viewportSettings = LinearScaleViewportSettings.copy(other._viewportSettings), rangeBandConfig = other.rangeBandConfig, stepSizeConfig = other.stepSizeConfig; @override LinearScale copy() => LinearScale._copy(this); // // Domain methods // @override void addDomain(num domainValue) { _domainInfo.addDomainValue(domainValue); } @override void resetDomain() { _scaleReady = false; _domainInfo.reset(); } @override void resetViewportSettings() { _viewportSettings.reset(); } @override NumericExtents get dataExtent => NumericExtents(_domainInfo.dataDomainStart, _domainInfo.dataDomainEnd); @override num get minimumDomainStep => _domainInfo.minimumDetectedDomainStep; @override bool canTranslate(_) => true; @override set domainOverride(NumericExtents? domainMaxExtent) { _domainInfo.domainOverride = domainMaxExtent; } @override NumericExtents? get domainOverride => _domainInfo.domainOverride; @override int compareDomainValueToViewport(num domainValue) { final dataExtent = _viewportSettings.domainExtent ?? _domainInfo.extent; return dataExtent.compareValue(domainValue); } // // Viewport methods // @override void setViewportSettings(double viewportScale, double viewportTranslatePx) { _viewportSettings ..scalingFactor = viewportScale ..translatePx = viewportTranslatePx ..domainExtent = null; _scaleReady = false; } @override double get viewportScalingFactor => _viewportSettings.scalingFactor; @override double get viewportTranslatePx => _viewportSettings.translatePx; @override set viewportDomain(NumericExtents extent) { _scaleReady = false; _viewportSettings.domainExtent = extent; } @override NumericExtents get viewportDomain { _configureScale(); return _viewportSettings.domainExtent!; } @override set keepViewportWithinData(bool autoAdjustViewportToNiceValues) { _scaleReady = false; _viewportSettings.keepViewportWithinData = true; } @override bool get keepViewportWithinData => _viewportSettings.keepViewportWithinData; @override double computeViewportScaleFactor(double domainWindow) => _domainInfo.domainDiff / domainWindow; @override set range(ScaleOutputExtent? extent) { _viewportSettings.range = extent; _scaleReady = false; } @override ScaleOutputExtent? get range => _viewportSettings.range; // // Scale application methods // @override num operator [](num domainValue) { _configureScale(); return _scaleFunction[domainValue]; } @override num reverse(double viewPixels) { _configureScale(); final num domain = _scaleFunction.reverse(viewPixels); return domain; } @override double get rangeBand { _configureScale(); return _scaleFunction.rangeBandPixels; } @override double get stepSize { _configureScale(); return _scaleFunction.stepSizePixels; } @override double get domainStepSize => _domainInfo.minimumDetectedDomainStep.toDouble(); @override int get rangeWidth => (range!.end - range!.start).abs().toInt(); @override bool isRangeValueWithinViewport(double rangeValue) => range!.containsValue(rangeValue); // // Private update // void _configureScale() { if (_scaleReady) return; assert(_viewportSettings.range != null); // If the viewport's domainExtent are set, then we can calculate the // viewport's scaleFactor now that the domainInfo has been loaded. // The viewport also has a chance to correct the scaleFactor. _viewportSettings.updateViewportScaleFactor(_domainInfo); // Now that the viewport's scalingFactor is setup, set it on the scale // function. _scaleFunction.updateScaleFactor( _viewportSettings, _domainInfo, rangeBandConfig, stepSizeConfig); // If the viewport's domainExtent are set, then we can calculate the // viewport's translate now that the scaleFactor has been loaded. // The viewport also has a chance to correct the translate. _viewportSettings.updateViewportTranslatePx( _domainInfo, _scaleFunction.scalingFactor); // Now that the viewport has a chance to update the translate, set it on the // scale function. _scaleFunction.updateTranslateAndRangeBand( _viewportSettings, _domainInfo, rangeBandConfig); // Now that the viewport's scaleFactor and translate have been updated // set the effective domainExtent of the viewport. _viewportSettings.updateViewportDomainExtent( _domainInfo, _scaleFunction.scalingFactor); // Cached computed values are updated. _scaleReady = true; } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/linear/linear_scale_domain_info.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import '../numeric_extents.dart' show NumericExtents; /// Encapsulation of all the domain processing logic for the [LinearScale]. class LinearScaleDomainInfo { /// User (or axis) overridden extent in domain units. NumericExtents? domainOverride; /// The minimum added domain value. num _dataDomainStart = double.infinity; num get dataDomainStart => _dataDomainStart; /// The maximum added domain value. num _dataDomainEnd = double.negativeInfinity; num get dataDomainEnd => _dataDomainEnd; /// Previous domain added so we can calculate minimumDetectedDomainStep. num? _previouslyAddedDomain; /// The step size between data points in domain units. /// /// Measured as the minimum distance between consecutive added points. num _minimumDetectedDomainStep = double.infinity; num get minimumDetectedDomainStep => _minimumDetectedDomainStep; ///The diff of the nicedDomain extent. num get domainDiff => extent.width; LinearScaleDomainInfo(); LinearScaleDomainInfo.copy(LinearScaleDomainInfo other) { if (other.domainOverride != null) { domainOverride = other.domainOverride; } _dataDomainStart = other._dataDomainStart; _dataDomainEnd = other._dataDomainEnd; _previouslyAddedDomain = other._previouslyAddedDomain; _minimumDetectedDomainStep = other._minimumDetectedDomainStep; } /// Resets everything back to initial state. void reset() { _previouslyAddedDomain = null; _dataDomainStart = double.infinity; _dataDomainEnd = double.negativeInfinity; _minimumDetectedDomainStep = double.infinity; } /// Updates the domain extent and detected step size given the [domainValue]. void addDomainValue(num? domainValue) { if (domainValue == null || !domainValue.isFinite) { return; } extendDomain(domainValue); if (_previouslyAddedDomain != null) { final domainStep = (domainValue - _previouslyAddedDomain!).abs(); if (domainStep != 0.0 && domainStep < minimumDetectedDomainStep) { _minimumDetectedDomainStep = domainStep; } } _previouslyAddedDomain = domainValue; } /// Extends the data domain extent without modifying step size detection. /// /// Returns whether the the domain interval was extended. If the domain value /// was already contained in the domain interval, the domain interval does not /// change. bool extendDomain(num? domainValue) { if (domainValue == null || !domainValue.isFinite) { return false; } var domainExtended = false; if (domainValue < _dataDomainStart) { _dataDomainStart = domainValue; domainExtended = true; } if (domainValue > _dataDomainEnd) { _dataDomainEnd = domainValue; domainExtended = true; } return domainExtended; } /// Returns the extent based on the current domain range and overrides. NumericExtents get extent { num tmpDomainStart; num tmpDomainEnd; if (domainOverride != null) { // override was set. tmpDomainStart = domainOverride!.min; tmpDomainEnd = domainOverride!.max; } else { // domainEnd is less than domainStart if no domain values have been set. tmpDomainStart = _dataDomainStart.isFinite ? _dataDomainStart : 0.0; tmpDomainEnd = _dataDomainEnd.isFinite ? _dataDomainEnd : 1.0; } return NumericExtents(tmpDomainStart, tmpDomainEnd); } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/linear/linear_scale_function.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import '../scale.dart' show RangeBandConfig, RangeBandType, StepSizeConfig, StepSizeType; import 'linear_scale_domain_info.dart' show LinearScaleDomainInfo; import 'linear_scale_viewport.dart' show LinearScaleViewportSettings; /// Component of the LinearScale which actually handles the apply and reverse /// function of the scale. class LinearScaleFunction { /// Cached rangeBand width in pixels given the RangeBandConfig and the current /// domain & range. double rangeBandPixels = 0.0; /// Cached amount in domain units to shift the input value as a part of /// translation. num domainTranslate = 0.0; /// Cached translation ratio for scale translation. double scalingFactor = 1.0; /// Cached amount in pixel units to shift the output value as a part of /// translation. double rangeTranslate = 0.0; /// The calculated step size given the step size config. double stepSizePixels = 0.0; /// Translates the given domainValue to the range output. double operator [](num domainValue) { return (((domainValue + domainTranslate) * scalingFactor) + rangeTranslate) .toDouble(); } /// Translates the given range output back to a domainValue. double reverse(double viewPixels) { return ((viewPixels - rangeTranslate) / scalingFactor) - domainTranslate; } /// Update the scale function's scaleFactor given the current state of the /// viewport. void updateScaleFactor( LinearScaleViewportSettings viewportSettings, LinearScaleDomainInfo domainInfo, RangeBandConfig rangeBandConfig, StepSizeConfig stepSizeConfig) { final rangeDiff = viewportSettings.range!.diff.toDouble(); // Note: if you provided a nicing function that extends the domain, we won't // muck with the extended side. final hasHalfStepAtStart = domainInfo.extent.min == domainInfo.dataDomainStart; final hasHalfStepAtEnd = domainInfo.extent.max == domainInfo.dataDomainEnd; // Determine the stepSize and reserved range values. // The percentage of the step reserved from the scale's range due to the // possible half step at the start and end. final reservedRangePercentOfStep = getStepReservationPercent(hasHalfStepAtStart, hasHalfStepAtEnd); _updateStepSizeAndScaleFactor(viewportSettings, domainInfo, rangeDiff, reservedRangePercentOfStep, rangeBandConfig, stepSizeConfig); } /// Returns the percentage of the step reserved from the output range due to /// maybe having to hold half stepSizes on the start and end of the output. double getStepReservationPercent( bool hasHalfStepAtStart, bool hasHalfStepAtEnd) { if (!hasHalfStepAtStart && !hasHalfStepAtEnd) { return 0.0; } if (hasHalfStepAtStart && hasHalfStepAtEnd) { return 1.0; } return 0.5; } /// Updates the scale function's translate and rangeBand given the current /// state of the viewport. void updateTranslateAndRangeBand(LinearScaleViewportSettings viewportSettings, LinearScaleDomainInfo domainInfo, RangeBandConfig rangeBandConfig) { // Assign the rangeTranslate using the current viewportSettings.translatePx // and diffs. if (domainInfo.domainDiff == 0) { // Translate it to the center of the range. rangeTranslate = viewportSettings.range!.start + (viewportSettings.range!.diff / 2); } else { final hasHalfStepAtStart = domainInfo.extent.min == domainInfo.dataDomainStart; // The pixel shift of the scale function due to the half a step at the // beginning. final reservedRangePixelShift = hasHalfStepAtStart ? (stepSizePixels / 2.0) : 0.0; rangeTranslate = viewportSettings.range!.start + viewportSettings.translatePx + reservedRangePixelShift; } // We need to subtract the start from any incoming domain to apply the // scale, so flip its sign. domainTranslate = -1 * domainInfo.extent.min; // Update the rangeBand size. rangeBandPixels = _calculateRangeBandSize(rangeBandConfig); } /// Calculates and stores the current rangeBand given the config and current /// step size. double _calculateRangeBandSize(RangeBandConfig rangeBandConfig) { switch (rangeBandConfig.type) { case RangeBandType.fixedDomain: return rangeBandConfig.size * scalingFactor; case RangeBandType.fixedPixel: return rangeBandConfig.size; case RangeBandType.fixedPixelSpaceFromStep: return stepSizePixels - rangeBandConfig.size; case RangeBandType.styleAssignedPercentOfStep: case RangeBandType.fixedPercentOfStep: return stepSizePixels * rangeBandConfig.size; case RangeBandType.none: return 0.0; } } /// Calculates and Stores the current step size and scale factor together, /// given the viewport, domain, and config. /// ///

Scale factor and step size are related closely and should be calculated /// together so that we do not lose accuracy due to double arithmetic. void _updateStepSizeAndScaleFactor( LinearScaleViewportSettings viewportSettings, LinearScaleDomainInfo domainInfo, double rangeDiff, double reservedRangePercentOfStep, RangeBandConfig rangeBandConfig, StepSizeConfig stepSizeConfig) { final domainDiff = domainInfo.domainDiff.toDouble(); // If we are going to have any rangeBands, then ensure that we account for // needed space on the beginning and end of the range. if (rangeBandConfig.type != RangeBandType.none) { switch (stepSizeConfig.type) { case StepSizeType.autoDetect: final minimumDetectedDomainStep = domainInfo.minimumDetectedDomainStep.toDouble(); if (minimumDetectedDomainStep != null && minimumDetectedDomainStep.isFinite) { scalingFactor = viewportSettings.scalingFactor * (rangeDiff / (domainDiff + (minimumDetectedDomainStep * reservedRangePercentOfStep))); stepSizePixels = minimumDetectedDomainStep * scalingFactor; } else { stepSizePixels = rangeDiff.abs(); scalingFactor = 1.0; } return; case StepSizeType.fixedPixels: stepSizePixels = stepSizeConfig.size; final reservedRangeForStepPixels = stepSizePixels * reservedRangePercentOfStep; scalingFactor = domainDiff == 0 ? 1.0 : viewportSettings.scalingFactor * (rangeDiff - reservedRangeForStepPixels) / domainDiff; return; case StepSizeType.fixedDomain: final domainStepWidth = stepSizeConfig.size; final totalDomainDiff = domainDiff + (domainStepWidth * reservedRangePercentOfStep); scalingFactor = totalDomainDiff == 0 ? 1.0 : viewportSettings.scalingFactor * (rangeDiff / totalDomainDiff); stepSizePixels = domainStepWidth * scalingFactor; return; } } // If no cases matched, use zero step size. stepSizePixels = 0.0; scalingFactor = domainDiff == 0 ? 1.0 : viewportSettings.scalingFactor * rangeDiff / domainDiff; } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/linear/linear_scale_viewport.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math' as math show max, min; import '../numeric_extents.dart' show NumericExtents; import '../scale.dart' show ScaleOutputExtent; import 'linear_scale_domain_info.dart' show LinearScaleDomainInfo; /// Component of the LinearScale responsible for the configuration and /// calculations of the viewport. class LinearScaleViewportSettings { /// Output extent for the scale, typically set by the axis as the pixel /// output. ScaleOutputExtent? range; /// Determines whether the scale should be extended to the nice values /// provided by the tick provider. If true, we wont touch the viewport config /// since the axis will configure it, if false, we will still ensure sane zoom /// and translates. bool keepViewportWithinData = true; /// User configured viewport scale as a zoom multiplier where 1.0 is /// 100% (default) and 2.0 is 200% zooming in making the data take up twice /// the space (showing half as much data in the viewport). double scalingFactor = 1.0; /// User configured viewport translate in pixel units. double translatePx = 0.0; /// The current extent of the viewport in domain units. NumericExtents? _domainExtent; set domainExtent(NumericExtents? extent) { _domainExtent = extent; _manualDomainExtent = extent != null; } NumericExtents? get domainExtent => _domainExtent; /// Indicates that the viewportExtends are to be read from to determine the /// internal scaleFactor and rangeTranslate. bool _manualDomainExtent = false; LinearScaleViewportSettings(); LinearScaleViewportSettings.copy(LinearScaleViewportSettings other) { range = other.range; keepViewportWithinData = other.keepViewportWithinData; scalingFactor = other.scalingFactor; translatePx = other.translatePx; _manualDomainExtent = other._manualDomainExtent; _domainExtent = other._domainExtent; } /// Resets the viewport calculated fields back to their initial settings. void reset() { // Likely an auto assigned viewport (niced), so reset it between draws. scalingFactor = 1.0; translatePx = 0.0; domainExtent = null; } int get rangeWidth => range!.diff.abs().toInt(); bool isRangeValueWithinViewport(double rangeValue) => range!.containsValue(rangeValue); /// Updates the viewport's internal scalingFactor given the current /// domainInfo. void updateViewportScaleFactor(LinearScaleDomainInfo domainInfo) { // If we are loading from the viewport, then update the scalingFactor given // the viewport size compared to the data size. if (_manualDomainExtent) { final viewportDomainDiff = _domainExtent?.width.toDouble(); if (domainInfo.domainDiff != 0.0) { scalingFactor = domainInfo.domainDiff / viewportDomainDiff!; } else { scalingFactor = 1.0; // The domain claims to have no date, extend it to the viewport's domainInfo.extendDomain(_domainExtent?.min); domainInfo.extendDomain(_domainExtent?.max); } } // Make sure that the viewportSettings.scalingFactor is sane if desired. if (!keepViewportWithinData) { // Make sure we don't zoom out beyond the max domain extent. scalingFactor = math.max(1.0, scalingFactor); } } /// Updates the viewport's internal translate given the current domainInfo and /// main scalingFactor from LinearScaleFunction (not internal scalingFactor). void updateViewportTranslatePx( LinearScaleDomainInfo domainInfo, double scaleScalingFactor) { // If we are loading from the viewport, then update the translate now that // the scaleFactor has been setup. if (_manualDomainExtent) { translatePx = -scaleScalingFactor * (_domainExtent!.min - domainInfo.extent.min); } // Make sure that the viewportSettings.translatePx is sane if desired. if (!keepViewportWithinData) { // Make sure we don't translate beyond the max domain extent. translatePx = math.min(0.0, translatePx); translatePx = math.max(range!.diff * (1.0 - scalingFactor), translatePx); } } /// Calculates and stores the viewport's domainExtent if we did not load from /// them in the first place. void updateViewportDomainExtent( LinearScaleDomainInfo domainInfo, double scaleScalingFactor) { // If we didn't load from the viewport extent, then update them given the // current scale configuration. if (!_manualDomainExtent) { final viewportDomainDiff = domainInfo.domainDiff / scalingFactor; final viewportStart = (-translatePx / scaleScalingFactor) + domainInfo.extent.min; _domainExtent = NumericExtents(viewportStart, viewportStart + viewportDomainDiff); } } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/numeric_extents.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'scale.dart' show Extents; /// Represents the starting and ending extent of a dataset. class NumericExtents implements Extents { final num min; final num max; /// Precondition: [min] <= [max]. // TODO: When initializer list asserts are supported everywhere, // add the precondition as an initializer list assert. This is supported in // Flutter only. const NumericExtents(this.min, this.max); /// Returns [Extents] based on the min and max of the given values. /// Returns [NumericExtents.empty] if [values] are empty factory NumericExtents.fromValues(Iterable values) { if (values.isEmpty) { return NumericExtents.empty; } var min = values.first; var max = values.first; for (final value in values) { if (value < min) { min = value; } else if (max < value) { max = value; } } return NumericExtents(min, max); } /// Returns the union of this and other. NumericExtents plus(NumericExtents other) { if (min <= other.min) { if (max >= other.max) { return this; } else { return NumericExtents(min, other.max); } } else { if (other.max >= max) { return other; } else { return NumericExtents(other.min, max); } } } /// Compares the given [value] against the extents. /// /// Returns -1 if the value is less than the extents. /// Returns 0 if the value is within the extents inclusive. /// Returns 1 if the value is greater than the extents. int compareValue(num value) { if (value < min) { return -1; } if (value > max) { return 1; } return 0; } bool _containsValue(double value) => compareValue(value) == 0; // Returns true if these [NumericExtents] collides with [other]. bool overlaps(NumericExtents other) { return _containsValue(other.min.toDouble()) || _containsValue(other.max.toDouble()) || other._containsValue(min.toDouble()) || other._containsValue(max.toDouble()); } @override bool operator ==(Object other) { return other is NumericExtents && min == other.min && max == other.max; } @override int get hashCode => min.hashCode + (max.hashCode * 31); num get width => max - min; @override String toString() => 'Extent($min, $max)'; static const NumericExtents unbounded = NumericExtents(double.negativeInfinity, double.infinity); static const NumericExtents empty = NumericExtents(0.0, 0.0); } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/numeric_scale.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'numeric_extents.dart' show NumericExtents; import 'scale.dart' show MutableScale; /// Scale used to convert numeric domain input units to output range units. /// /// The input represents a continuous numeric domain which maps to a given range /// output. This is used to map the domain's values to the available pixel /// range of the chart. abstract class NumericScale extends MutableScale { /// Keeps the scale and translate sane if true (default). /// /// Setting this to false disables some pan/zoom protections that prevent you /// from going beyond the data extent. bool get keepViewportWithinData; set keepViewportWithinData(bool keep); /// Returns the extent of the actual data (not the viewport max). NumericExtents get dataExtent; /// Returns the minimum step size of the actual data. num get minimumDomainStep; /// Overrides the domain extent if set, null otherwise. /// /// Overrides the extent of the actual data to lie about the range of the /// data so that panning has a start and end point to go between beyond the /// received data. This allows lazy loading of data into the gaps in the /// expanded lied about areas. NumericExtents? get domainOverride; set domainOverride(NumericExtents? extent); /// Returns the domain extent visible in the viewport of the drawArea. NumericExtents get viewportDomain; /// Sets the domain extent visible in the viewport of the drawArea. /// /// Invalidates the viewportScale & viewportTranslatePx. set viewportDomain(NumericExtents extent); /// Returns the viewportScaleFactor needed to present the given domainWindow. double computeViewportScaleFactor(double domainWindow); } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/numeric_tick_provider.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math' show log, log10e, max, min, pow; import '../../../common/graphics_factory.dart' show GraphicsFactory; import '../../common/chart_context.dart' show ChartContext; import '../../common/unitconverter/identity_converter.dart' show IdentityConverter; import '../../common/unitconverter/unit_converter.dart' show UnitConverter; import 'axis.dart' show AxisOrientation; import 'draw_strategy/tick_draw_strategy.dart' show TickDrawStrategy; import 'numeric_extents.dart' show NumericExtents; import 'numeric_scale.dart' show NumericScale; import 'tick.dart' show Tick; import 'tick_formatter.dart' show TickFormatter; import 'tick_provider.dart' show BaseTickProvider, TickHint; /// Tick provider that allows you to specify how many ticks to present while /// also choosing tick values that appear "nice" or "rounded" to the user. By /// default it will try to guess an appropriate number of ticks given the size /// of the range available, but the min and max tick counts can be set by /// calling setTickCounts(). /// /// You can control whether the axis is bound to zero (default) or follows the /// data by calling setZeroBound(). /// /// This provider will choose "nice" ticks with the following priority order. /// * Ticks do not collide with each other. /// * Alternate rendering is not used to avoid collisions. /// * Provide the least amount of domain range covering all data points (while /// still selecting "nice" ticks values. class NumericTickProvider extends BaseTickProvider { /// Used to determine the automatic tick count calculation. static const MIN_DIPS_BETWEEN_TICKS = 25; /// Potential steps available to the baseTen value of the data. static const DEFAULT_STEPS = [ 0.01, 0.02, 0.025, 0.03, 0.04, 0.05, 0.06, 0.07, 0.08, 0.09, 0.1, 0.2, 0.25, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 2.0, 2.50, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0 ]; // Settings /// Sets whether the the tick provider should always include a zero tick. /// /// If set the data range may be extended to include zero. /// /// Note that the zero value in axis units is chosen, which may be different /// than zero value in data units if a data to axis unit converter is set. bool zeroBound = true; /// If your data can only be in whole numbers, then set this to true. /// /// It should prevent the scale from choosing fractional ticks. For example, /// if you had a office head count, don't generate a tick for 1.5, instead /// jump to 2. /// /// Note that the provider will choose whole number ticks in the axis units, /// not data units if a data to axis unit converter is set. bool dataIsInWholeNumbers = true; // Desired min and max tick counts are set by [setFixedTickCount] and // [setTickCount]. These are not guaranteed tick counts. int? _desiredMaxTickCount; int? _desiredMinTickCount; /// Allowed steps the tick provider can choose from. var _allowedSteps = DEFAULT_STEPS; /// Convert input data units to the desired units on the axis. /// If not set no conversion will take place. /// /// Combining this with an appropriate [TickFormatter] would result in axis /// ticks that are in different unit than the actual data units. UnitConverter dataToAxisUnitConverter = const IdentityConverter(); // Internal tick calculation state for [getTicks]. // // [_low] and [_high] are valid only after calling [_updateDomainExtents]. // // [_minTickCount] and [_maxTickCount] are valid only after calling // [_updateTickCounts]. late num _low; late num _high; int? _minTickCount; int? _maxTickCount; /// Sets the desired tick count. /// /// While the provider will try to satisfy the requirement, it is not /// guaranteed, such as cases where ticks may overlap or are insufficient. /// /// [tickCount] the fixed number of major (labeled) ticks to draw for the axis /// Passing null will result in falling back on the automatic tick count /// assignment. void setFixedTickCount(int? tickCount) { // Don't allow a single tick, it doesn't make sense. so tickCount > 1 _desiredMinTickCount = tickCount != null && tickCount > 1 ? tickCount : null; _desiredMaxTickCount = _desiredMinTickCount; } /// Sets the desired min and max tick count when providing ticks. /// /// The values are suggested requirements but are not guaranteed to be the /// actual tick count in cases where it is not possible. /// /// [maxTickCount] The max tick count must be greater than 1. /// [minTickCount] The min tick count must be greater than 1. void setTickCount(int maxTickCount, int minTickCount) { // Don't allow a single tick, it doesn't make sense. so tickCount > 1 if (maxTickCount != null && maxTickCount > 1) { _desiredMaxTickCount = maxTickCount; if (minTickCount != null && minTickCount > 1 && minTickCount <= _desiredMaxTickCount!) { _desiredMinTickCount = minTickCount; } else { _desiredMinTickCount = 2; } } else { _desiredMaxTickCount = null; _desiredMinTickCount = null; } assert((_desiredMinTickCount == null) == (_desiredMaxTickCount == null)); } /// Sets the allowed step sizes this tick provider can choose from. /// /// All ticks will be a power of 10 multiple of the given step sizes. /// /// Note that if only very few step sizes are allowed the tick range maybe /// much bigger than the data range. /// /// The step sizes setup here apply in axis units, which is different than /// input units if a data to axis unit converter is set. /// /// [steps] allowed step sizes in the [1, 10) range. set allowedSteps(List steps) { assert(steps != null && steps.isNotEmpty); steps.sort(); final stepSet = Set.of(steps); _allowedSteps = List.filled(stepSet.length * 3, 0); var stepIndex = 0; for (final step in stepSet) { assert(1.0 <= step && step < 10.0); _allowedSteps[stepIndex] = _removeRoundingErrors(step / 100); _allowedSteps[stepSet.length + stepIndex] = _removeRoundingErrors(step / 10); _allowedSteps[2 * stepSet.length + stepIndex] = _removeRoundingErrors(step); stepIndex++; } } List> _getTicksFromHint({ required ChartContext? context, required GraphicsFactory graphicsFactory, required NumericScale scale, required TickFormatter formatter, required Map formatterValueCache, required TickDrawStrategy tickDrawStrategy, required TickHint tickHint, }) { final stepSize = (tickHint.end - tickHint.start) / (tickHint.tickCount - 1); // Find the first tick that is greater than or equal to the min // viewportDomain. final tickZeroShift = tickHint.start.toDouble() - (stepSize * (tickHint.start >= 0 ? (tickHint.start / stepSize).floor() : (tickHint.start / stepSize).ceil())); final tickStart = (scale.viewportDomain.min / stepSize).ceil() * stepSize + tickZeroShift; final stepInfo = _TickStepInfo(stepSize.abs(), tickStart); final tickValues = _getTickValues(stepInfo, tickHint.tickCount); // Create ticks from domain values. return createTicks(tickValues, context: context, graphicsFactory: graphicsFactory, scale: scale, formatter: formatter, formatterValueCache: formatterValueCache, tickDrawStrategy: tickDrawStrategy, stepSize: stepInfo.stepSize); } @override List> getTicks({ required ChartContext? context, required GraphicsFactory graphicsFactory, required NumericScale scale, required TickFormatter formatter, required Map formatterValueCache, required TickDrawStrategy tickDrawStrategy, required AxisOrientation? orientation, bool viewportExtensionEnabled = false, TickHint? tickHint, }) { _updateDomainExtents(scale.viewportDomain); // Bypass searching for a tick range since we are getting ticks using // information in [tickHint]. if (tickHint != null) { return _getTicksFromHint( context: context, graphicsFactory: graphicsFactory, scale: scale, formatter: formatter, formatterValueCache: formatterValueCache, tickDrawStrategy: tickDrawStrategy, tickHint: tickHint, ); } // TODO: Recalculate ticks only if something changed. var selectedTicksRange = double.maxFinite; var foundPreferredTicks = false; var viewportDomain = scale.viewportDomain; final axisUnitsHigh = dataToAxisUnitConverter.convert(_high); final axisUnitsLow = dataToAxisUnitConverter.convert(_low); _updateTickCounts( high: axisUnitsHigh, low: axisUnitsLow, rangeWidth: scale.rangeWidth); // Only create a copy of the scale if [viewportExtensionEnabled]. final mutableScale = viewportExtensionEnabled ? scale.copy() as NumericScale : null; // Walk to available tick count from max to min looking for the first one // that gives you the least amount of range used. If a non colliding tick // count is not found use the min tick count to generate ticks. var ticks = >[]; for (var tickCount = _maxTickCount!; tickCount >= _minTickCount!; tickCount--) { final stepInfo = _getStepsForTickCount(tickCount, axisUnitsHigh, axisUnitsLow); final firstTick = dataToAxisUnitConverter.invert(stepInfo.tickStart).toDouble(); final lastTick = dataToAxisUnitConverter .invert(stepInfo.tickStart + stepInfo.stepSize * (tickCount - 1)) .toDouble(); final range = lastTick - firstTick; // Calculate ticks if it is a better range or if preferred ticks have // not been found yet. if (range < selectedTicksRange || !foundPreferredTicks) { final tickValues = _getTickValues(stepInfo, tickCount); if (mutableScale != null) { mutableScale.viewportDomain = NumericExtents(firstTick, lastTick); } // Create ticks from domain values. final preferredTicks = createTicks(tickValues, context: context, graphicsFactory: graphicsFactory, scale: mutableScale ?? scale, formatter: formatter, formatterValueCache: formatterValueCache, tickDrawStrategy: tickDrawStrategy, stepSize: stepInfo.stepSize); // Request collision check from draw strategy. final collisionReport = tickDrawStrategy.collides(preferredTicks, orientation); // Don't choose colliding ticks unless it was our last resort if (collisionReport.ticksCollide && tickCount > _minTickCount!) { continue; } // Only choose alternate ticks if preferred ticks is not found. if (foundPreferredTicks && collisionReport.alternateTicksUsed) { continue; } ticks = collisionReport.alternateTicksUsed ? collisionReport.ticks : preferredTicks; foundPreferredTicks = !collisionReport.alternateTicksUsed; selectedTicksRange = range; // If viewport extended, save the viewport used. viewportDomain = mutableScale?.viewportDomain ?? scale.viewportDomain; } } // If [viewportExtensionEnabled] and has changed, then set the scale's // viewport to what was used to generate ticks. By only setting viewport // when it has changed, we do not trigger the flag to recalculate scale. if (viewportExtensionEnabled && scale.viewportDomain != viewportDomain) { scale.viewportDomain = viewportDomain; } return ticks; } /// Calculates the domain extents that this provider will cover based on the /// axis extents passed in and the settings in the numeric tick provider. /// Stores the domain extents in [_low] and [_high]. void _updateDomainExtents(NumericExtents axisExtents) { _low = axisExtents.min; _high = axisExtents.max; // Correct the extents for zero bound if (zeroBound) { _low = _low > 0.0 ? 0.0 : _low; _high = _high < 0.0 ? 0.0 : _high; } // Correct cases where high and low equal to give the tick provider an // actual range to go off of when picking ticks. if (_high == _low) { if (_high == 0.0) { // Corner case: the only values we've seen are zero, so lets just say // the high is 1 and leave the low at zero. _high = 1.0; } else { // The values are all the same, so assume a range of -5% to +5% from the // single value. if (_high > 0.0) { _high *= 1.05; _low *= 0.95; } else { // (_high == _low) < 0 _high *= 0.95; _low *= 1.05; } } } } /// Given [tickCount] and the domain range, finds the smallest tick increment, /// chosen from power of 10 multiples of allowed steps, that covers the whole /// data range. _TickStepInfo _getStepsForTickCount(int tickCount, num high, num low) { // A region is the space between ticks. final regionCount = tickCount - 1; // If the range contains zero, ensure that zero is a tick. if (high >= 0 && low <= 0) { // determine the ratio of regions that are above the zero axis. final posRegionRatio = high > 0 ? min(1.0, high / (high - low)) : 0.0; var positiveRegionCount = (regionCount * posRegionRatio).ceil(); var negativeRegionCount = regionCount - positiveRegionCount; // Ensure that negative regions are not excluded, unless there are no // regions to spare. if (negativeRegionCount == 0 && low < 0 && regionCount > 1) { positiveRegionCount--; negativeRegionCount++; } // If we have positive and negative values, ensure that we have ticks in // both regions. // // This should not happen unless the axis is manually configured with a // tick count. [_updateTickCounts] should ensure that we have do not try // to generate fewer than three. assert( !(low < 0 && high > 0 && (negativeRegionCount == 0 || positiveRegionCount == 0)), 'Numeric tick provider cannot generate ${tickCount} ' 'ticks when the axis range contains both positive and negative ' 'values. A minimum of three ticks are required to include zero.'); // Determine the "favored" axis direction (the one which will control the // ticks based on having a greater value / regions). // // Example: 13 / 3 (4.33 per tick) vs -5 / 1 (5 per tick) // making -5 the favored number. A step size that includes this number // ensures the other is also includes in the opposite direction. final favorPositive = (high > 0 ? high / positiveRegionCount : 0).abs() > (low < 0 ? low / negativeRegionCount : 0).abs(); final favoredNum = (favorPositive ? high : low).abs(); final favoredRegionCount = favorPositive ? positiveRegionCount : negativeRegionCount; final favoredTensBase = (_getEnclosingPowerOfTen(favoredNum)).abs(); // Check each step size and see if it would contain the "favored" value for (final step in _allowedSteps) { final tmpStepSize = _removeRoundingErrors(step * favoredTensBase); // If prefer whole number, then don't allow a step that isn't one. if (dataIsInWholeNumbers && tmpStepSize.round() != tmpStepSize) { continue; } // TODO: Skip steps that format to the same string. // But wait until the last step to prevent the cost of the formatter. // Potentially store the formatted strings in TickStepInfo? if (tmpStepSize * favoredRegionCount >= favoredNum) { final stepStart = negativeRegionCount > 0 ? (-1 * tmpStepSize * negativeRegionCount) : 0.0; return _TickStepInfo(tmpStepSize, stepStart); } } } else { // Find the range base to calculate step sizes. final diffTensBase = _getEnclosingPowerOfTen(high - low); // Walk the step sizes calculating a starting point and seeing if the high // end is included in the range given that step size. for (final step in _allowedSteps) { final tmpStepSize = _removeRoundingErrors(step * diffTensBase); // If prefer whole number, then don't allow a step that isn't one. if (dataIsInWholeNumbers && tmpStepSize.round() != tmpStepSize) { continue; } // TODO: Skip steps that format to the same string. // But wait until the last step to prevent the cost of the formatter. final tmpStepStart = _getStepLessThan(low.toDouble(), tmpStepSize); if (tmpStepStart + (tmpStepSize * regionCount) >= high) { return _TickStepInfo(tmpStepSize, tmpStepStart); } } } return _TickStepInfo(1.0, low.floorToDouble()); } List _getTickValues(_TickStepInfo steps, int tickCount) { // We have our size and start, assign all the tick values to the given array. return [ for (int i = 0; i < tickCount; i++) dataToAxisUnitConverter .invert( _removeRoundingErrors(steps.tickStart + (i * steps.stepSize))) .toDouble(), ]; } /// Given the axisDimensions update the tick counts given they are not fixed. void _updateTickCounts({ required num high, required num low, required int rangeWidth, }) { int tmpMaxNumMajorTicks; int tmpMinNumMajorTicks; // If the domain range contains both positive and negative values, then we // need a minimum of three ticks to include zero as a tick. Otherwise, we // only need an upper and lower tick. final absoluteMinTicks = (low < 0 && 0 < high) ? 3 : 2; // If there is a desired tick range use it, if not calculate one. if (_desiredMaxTickCount != null) { tmpMinNumMajorTicks = max(_desiredMinTickCount!, absoluteMinTicks); tmpMaxNumMajorTicks = max(_desiredMaxTickCount!, tmpMinNumMajorTicks); } else { final minPixelsPerTick = MIN_DIPS_BETWEEN_TICKS.toDouble(); tmpMinNumMajorTicks = absoluteMinTicks; tmpMaxNumMajorTicks = max(absoluteMinTicks, (rangeWidth / minPixelsPerTick).floor()); } // Don't blow away the previous array if it hasn't changed. if (tmpMaxNumMajorTicks != _maxTickCount || tmpMinNumMajorTicks != _minTickCount) { _maxTickCount = tmpMaxNumMajorTicks; _minTickCount = tmpMinNumMajorTicks; } } /// Returns the power of 10 which contains the [number]. /// /// If [number] is 0 returns 1. /// Examples: /// [number] of 63 returns 100 /// [number] of -63 returns -100 /// [number] of 0.63 returns 1 static double _getEnclosingPowerOfTen(num number) { if (number == 0) { return 1.0; } return pow(10, (log10e * log(number.abs())).ceil()).toDouble() * (number < 0.0 ? -1.0 : 1.0); } /// Returns the step numerically less than the number by step increments. static double _getStepLessThan(double number, double stepSize) { if (number == 0.0 || stepSize == 0.0) { return 0.0; } return (stepSize > 0.0 ? (number / stepSize).floor() : (number / stepSize).ceil()) * stepSize; } /// Attempts to slice off very small floating point rounding effects for the /// given number. /// /// @param number the number to round. /// @return the rounded number. static double _removeRoundingErrors(double number) { // sufficiently large multiplier to handle generating ticks on the order // of 10^-9. const multiplier = 1.0e9; return number > 100.0 ? number.roundToDouble() : (number * multiplier).roundToDouble() / multiplier; } } class _TickStepInfo { double stepSize; double tickStart; _TickStepInfo(this.stepSize, this.tickStart); } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/ordinal_extents.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:collection' show HashSet; import 'scale.dart' show Extents; /// A range of ordinals. class OrdinalExtents extends Extents { final List _range; /// The extents representing the ordinal values in [range]. /// /// The elements of [range] must all be unique. /// /// [D] is the domain class type for the elements in the extents. OrdinalExtents(List range) : _range = range { // This asserts that all elements in [range] are unique. assert(() { final uniqueValueCount = HashSet.of(_range).length; return uniqueValueCount == range.length; }()); } factory OrdinalExtents.all(List range) => OrdinalExtents(range); bool get isEmpty => _range.isEmpty; /// The number of values inside this extent. int get length => _range.length; String? operator [](int index) => _range[index]; int indexOf(String value) => _range.indexOf(value); } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/ordinal_scale.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'ordinal_scale_domain_info.dart' show OrdinalScaleDomainInfo; import 'scale.dart' show MutableScale; abstract class OrdinalScale extends MutableScale { /// The current domain collection with all added unique values. OrdinalScaleDomainInfo get domain; /// Sets the viewport of the scale based on the number of data points to show /// and the starting domain value. /// /// [viewportDataSize] How many ordinal domain values to show in the viewport. /// [startingDomain] The starting domain value of the viewport. Note that if /// the starting domain is in terms of position less than [domainValuesToShow] /// from the last domain value the viewport will be fixed to the last value /// and not guaranteed that this domain value is the first in the viewport. void setViewport(int? viewportDataSize, String? startingDomain); /// The number of full ordinal steps that fit in the viewport. int get viewportDataSize; /// The first fully visible ordinal step within the viewport. /// /// Start is defined by the leftmost domain for horizontal axes, and /// topmost domain for vertical axes. /// /// Null if no domains exist. String? get viewportStartingDomain; } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/ordinal_scale_domain_info.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:collection' show HashMap; import 'ordinal_extents.dart' show OrdinalExtents; /// A domain processor for [OrdinalScale]. /// /// [D] domain class type of the values being tracked. /// /// Unique domain values are kept, so duplicates will not increase the extent. class OrdinalScaleDomainInfo { int _index = 0; /// A map of domain value and the order it was added. final _domainsToOrder = HashMap(); /// A list of domain values kept to support [getDomainAtIndex]. final _domainList = []; OrdinalScaleDomainInfo(); OrdinalScaleDomainInfo copy() { return OrdinalScaleDomainInfo() .._domainsToOrder.addAll(_domainsToOrder) .._index = _index .._domainList.addAll(_domainList); } void add(String domain) { if (!_domainsToOrder.containsKey(domain)) { _domainsToOrder[domain] = _index; _index += 1; _domainList.add(domain); } } int? indexOf(String domain) => _domainsToOrder[domain]; String getDomainAtIndex(int index) { assert(index >= 0); assert(index < _index); return _domainList[index]; } List get domains => _domainList; String? get first => _domainList.isEmpty ? null : _domainList.first; String? get last => _domainList.isEmpty ? null : _domainList.last; bool get isEmpty => _index == 0; bool get isNotEmpty => !isEmpty; OrdinalExtents get extent => OrdinalExtents.all(_domainList); int get size => _index; /// Clears all domain values. void clear() { _domainsToOrder.clear(); _domainList.clear(); _index = 0; } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/ordinal_tick_provider.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import '../../../common/graphics_factory.dart' show GraphicsFactory; import '../../common/chart_context.dart' show ChartContext; import 'axis.dart' show AxisOrientation; import 'draw_strategy/tick_draw_strategy.dart' show TickDrawStrategy; import 'ordinal_scale.dart' show OrdinalScale; import 'tick.dart' show Tick; import 'tick_formatter.dart' show TickFormatter; import 'tick_provider.dart' show BaseTickProvider, TickHint; /// A strategy for selecting ticks to draw given ordinal domain values. class OrdinalTickProvider extends BaseTickProvider { const OrdinalTickProvider(); @override List> getTicks({ required ChartContext? context, required GraphicsFactory graphicsFactory, required OrdinalScale scale, required TickFormatter formatter, required Map formatterValueCache, required TickDrawStrategy tickDrawStrategy, required AxisOrientation? orientation, bool viewportExtensionEnabled = false, TickHint? tickHint, }) { return createTicks(scale.domain.domains, context: context, graphicsFactory: graphicsFactory, scale: scale, formatter: formatter, formatterValueCache: formatterValueCache, tickDrawStrategy: tickDrawStrategy); } @override bool operator ==(Object other) => other is OrdinalTickProvider; @override int get hashCode => 31; } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/range_axis_tick.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'axis_tick.dart' show AxisTicks; import 'range_tick.dart' show RangeTick; class RangeAxisTicks extends AxisTicks { /// The value that this range tick starting point represents final D rangeStartValue; /// Position of the range tick starting point. double rangeStartLocationPx; /// The value that this range tick ending point represents. final D rangeEndValue; /// Position of the range tick ending point. double rangeEndLocationPx; RangeAxisTicks(RangeTick tick) : rangeStartValue = tick.rangeStartValue, rangeStartLocationPx = tick.rangeStartLocationPx, rangeEndValue = tick.rangeEndValue, rangeEndLocationPx = tick.rangeEndLocationPx, super(tick); } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/range_tick.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import '../../../common/text_element.dart'; import 'tick.dart' show Tick; /// A labeled range on an axis. /// /// [D] is the type of the value this tick is associated with. class RangeTick extends Tick { /// The value that this range tick starting point represents final D rangeStartValue; /// Position of the range tick starting point. double rangeStartLocationPx; /// The value that this range tick ending point represents. final D rangeEndValue; /// Position of the range tick ending point. double rangeEndLocationPx; RangeTick( {required D value, required TextElement textElement, double? locationPx, double? labelOffsetPx, required this.rangeStartValue, required this.rangeStartLocationPx, required this.rangeEndValue, required this.rangeEndLocationPx}) : super( value: value, locationPx: locationPx, textElement: textElement, labelOffsetPx: labelOffsetPx); @override String toString() => 'RangeTick(value: $value, locationPx: $locationPx, ' 'labelOffsetPx: $labelOffsetPx, rangeStartValue: $rangeStartValue, ' 'rangeStartLocationPx: $rangeStartLocationPx, ' 'rangeEndValue: $rangeEndValue, rangeEndLocationPx: $rangeEndLocationPx)'; } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/range_tick_provider.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import '../../../common/graphics_factory.dart' show GraphicsFactory; import '../../common/chart_context.dart' show ChartContext; import 'axis.dart' show AxisOrientation; import 'draw_strategy/tick_draw_strategy.dart' show TickDrawStrategy; import 'numeric_scale.dart' show NumericScale; import 'range_tick.dart' show RangeTick; import 'scale.dart' show MutableScale; import 'spec/range_tick_spec.dart' show RangeTickSpec; import 'spec/tick_spec.dart' show TickSpec; import 'tick.dart' show Tick; import 'tick_formatter.dart' show TickFormatter; import 'tick_provider.dart' show TickProvider, TickHint; import 'time/date_time_scale.dart' show DateTimeScale; /// A strategy that provides normal ticks and range ticks. class RangeTickProvider extends TickProvider { final List> tickSpec; RangeTickProvider(this.tickSpec); @override List> getTicks({ required ChartContext? context, required GraphicsFactory graphicsFactory, required MutableScale scale, required TickFormatter formatter, required Map formatterValueCache, required TickDrawStrategy tickDrawStrategy, required AxisOrientation? orientation, bool viewportExtensionEnabled = false, TickHint? tickHint, }) { final ticks = >[]; var allTicksHaveLabels = true; for (final spec in tickSpec) { // When static ticks are being used with a numeric axis, extend the axis // with the values specified. if (scale is NumericScale || scale is DateTimeScale) { scale.addDomain(spec.value); if (spec is RangeTickSpec) { scale.addDomain(spec.rangeStartValue); scale.addDomain(spec.rangeEndValue); } } // Save off whether all ticks have labels. allTicksHaveLabels &= spec.label != null; } // Use the formatter's label if the tick spec does not provide one. List? formattedValues; if (!allTicksHaveLabels) { formattedValues = formatter.format( tickSpec.map((spec) => spec.value).toList(), formatterValueCache, stepSize: scale.domainStepSize); } for (var i = 0; i < tickSpec.length; i++) { final spec = tickSpec[i]; Tick? tick; if (spec is RangeTickSpec) { // If it is a range tick, we still check if the spec's start and end // points are within the viewport because we do not extend the axis for // OrdinalScale. if (scale.compareDomainValueToViewport(spec.rangeStartValue) == 0 && scale.compareDomainValueToViewport(spec.rangeEndValue) == 0) { tick = RangeTick( value: spec.value, textElement: graphicsFactory .createTextElement(spec.label ?? formattedValues![i]), locationPx: (scale[spec.rangeStartValue]! + (scale[spec.rangeEndValue]! - scale[spec.rangeStartValue]!) / 2) .toDouble(), rangeStartValue: spec.rangeStartValue, rangeStartLocationPx: scale[spec.rangeStartValue]!.toDouble(), rangeEndValue: spec.rangeEndValue, rangeEndLocationPx: scale[spec.rangeEndValue]!.toDouble(), ); } } else { // If it is a normal tick, we still check if the spec is within the // viewport because we do not extend the axis for OrdinalScale. if (scale.compareDomainValueToViewport(spec.value) == 0) { tick = Tick( value: spec.value, textElement: graphicsFactory .createTextElement(spec.label ?? formattedValues![i]), locationPx: scale[spec.value]?.toDouble(), ); } } if (tick != null) { final style = spec.style; if (style != null) { tick.textElement!.textStyle = graphicsFactory.createTextPaint() ..fontFamily = style.fontFamily ..fontSize = style.fontSize ..color = style.color ..lineHeight = style.lineHeight; } ticks.add(tick); } } // Allow draw strategy to decorate the ticks. tickDrawStrategy.decorateTicks(ticks); return ticks; } @override bool operator ==(Object other) => other is RangeTickProvider && tickSpec == other.tickSpec; @override int get hashCode => tickSpec.hashCode; } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/scale.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math' as math show max, min; import 'package:charts_common/src/common/math.dart'; import '../../../common/style/style_factory.dart' show StyleFactory; /// Scale used to convert data input domain units to output range units. /// /// This is the immutable portion of the Scale definition. Used for converting /// data from the dataset in domain units to an output in range units (likely /// pixel range of the area to draw on). /// ///

The Scale/MutableScale split is to show the intention of what you can or /// should be doing with the scale during different stages of chart draw /// process. /// /// [D] is the domain class type for the values passed in. abstract class Scale { /// Applies the scale function to the [domainValue]. /// /// Returns the pixel location for the given [domainValue] or null if the /// domainValue could not be found/translated by this scale. /// Non-numeric scales should be the only ones that can return null. num? operator [](D domainValue); /// Reverse application of the scale. D reverse(double pixelLocation); /// Tests a [domainValue] to see if the scale can translate it. /// /// Returns true if the scale can translate the given domainValue. /// (Ex: linear scales can translate any number, but ordinal scales can only /// translate values previously passed in.) bool canTranslate(D domainValue); /// Returns the previously set output range for the scale function. ScaleOutputExtent? get range; /// Returns the absolute width between the max and min range values. int get rangeWidth; /// Returns the configuration used to determine the rangeBand. /// /// This is most often used to define the bar group width. RangeBandConfig get rangeBandConfig; /// Returns the rangeBand width in pixels. /// /// The rangeBand is determined using the RangeBandConfig potentially with the /// measured step size. This value is used as the bar group width. If /// StepSizeConfig is set to auto detect, then you must wait until after /// the chart's onPostLayout phase before you'll get a valid number. double get rangeBand; /// Returns the stepSize width in pixels. /// /// The step size is determined using the [StepSizeConfig]. double get stepSize; /// Returns the stepSize domain value. double get domainStepSize; /// Tests whether the given [domainValue] is within the axis' range. /// /// Returns < 0 if the [domainValue] would plot before the viewport, 0 if it /// would plot within the viewport and > 0 if it would plot beyond the /// viewport of the axis. int compareDomainValueToViewport(D domainValue); /// Returns true if the given [rangeValue] point is within the output range. /// /// Not to be confused with the start and end of the domain. bool isRangeValueWithinViewport(double rangeValue); /// Returns the current viewport scale. /// /// A scale of 1.0 would map the data directly to the output range, while a /// value of 2.0 would map the data to an output of double the range so you /// only see half the data in the viewport. This is the equivalent to /// zooming. Its value is likely >= 1.0. double get viewportScalingFactor; /// Returns the current pixel viewport offset /// /// The translate is used by the scale function when it applies the scale. /// This is the equivalent to panning. Its value is likely <= 0 to pan the /// data to the left. double get viewportTranslatePx; /// Returns a mutable copy of the scale. /// /// Mutating the returned scale will not effect the original one. MutableScale copy(); } /// Mutable extension of the [Scale] definition. /// /// Used for converting data from the dataset to some range (likely pixel range) /// of the area to draw on. /// /// [D] the domain class type for the values passed in. abstract class MutableScale extends Scale { /// Reset the domain for this [Scale]. void resetDomain(); /// Reset the viewport settings for this [Scale]. void resetViewportSettings(); /// Add [domainValue] to this [Scale]'s domain. /// /// Domains should be added in order to allow proper stepSize detection. /// [domainValue] is the data value to add to the scale used to update the /// domain extent. void addDomain(D domainValue); /// Sets the output range to use for the scale's conversion. /// /// The range start is mapped to the domain's min and the range end is /// mapped to the domain's max for the conversion using the domain nicing /// function. /// /// [extent] is the extent of the range which will likely be the pixel /// range of the drawing area to convert to. set range(ScaleOutputExtent? extent); /// Configures the zoom and translate. /// /// [viewportScale] is the zoom factor to use, likely >= 1.0 where 1.0 maps /// the complete data extents to the output range, and 2.0 only maps half the /// data to the output range. /// /// [viewportTranslatePx] is the translate/pan to use in pixel units, /// likely <= 0 which shifts the start of the data before the edge of the /// chart giving us a pan. void setViewportSettings(double viewportScale, double viewportTranslatePx); /// Sets the configuration used to determine the rangeBand (bar group width). set rangeBandConfig(RangeBandConfig barGroupWidthConfig); /// Sets the method for determining the step size. /// /// This is the domain space between data points. StepSizeConfig get stepSizeConfig; set stepSizeConfig(StepSizeConfig config); } /// Tuple of the output for a scale in pixels from [start] to [end] inclusive. /// /// It is different from [Extent] because it focuses on start and end and not /// min and max, meaning that start could be greater or less than end. class ScaleOutputExtent { final int start; final int end; const ScaleOutputExtent(this.start, this.end); int get min => math.min(start, end); int get max => math.max(start, end); bool containsValue(double value) => withinBounds(value, min, max); /// Returns the difference between the extents. /// /// If the [end] is less than the [start] (think vertical measure axis), then /// this will correctly return a negative value. int get diff => end - start; /// Returns the width of the extent. int get width => diff.abs(); @override bool operator ==(Object other) => other is ScaleOutputExtent && start == other.start && end == other.end; @override int get hashCode => start.hashCode + (end.hashCode * 31); @override String toString() => 'ScaleOutputRange($start, $end)'; } /// Type of RangeBand used to determine the rangeBand size units. enum RangeBandType { /// No rangeBand (not suitable for bars or step line charts). none, /// Size is specified in pixel units. fixedPixel, /// Size is specified domain scale units. fixedDomain, /// Size is a percentage of the minimum step size between points. fixedPercentOfStep, /// Size is a style pack assigned percentage of the minimum step size between /// points. styleAssignedPercentOfStep, /// Size is subtracted from the minimum step size between points in pixel /// units. fixedPixelSpaceFromStep, } /// Defines the method for calculating the rangeBand of the Scale. /// /// The rangeBand is used to determine the width of a group of bars. The term /// rangeBand comes from the d3 JavaScript library which the JS library uses /// internally. /// ///

RangeBandConfig is immutable, See factory methods for creating one. class RangeBandConfig { final RangeBandType type; /// The width of the band in units specified by the bandType. final double size; /// Creates a rangeBand definition of zero, no rangeBand. const RangeBandConfig.none() : type = RangeBandType.none, size = 0.0; /// Creates a fixed rangeBand definition in pixel width. /// /// Used to determine a bar width or a step width in the line renderer. const RangeBandConfig.fixedPixel(double pixels) : type = RangeBandType.fixedPixel, size = pixels; /// Creates a fixed rangeBand definition in domain unit width. /// /// Used to determine a bar width or a step width in the line renderer. const RangeBandConfig.fixedDomain(double domainSize) : type = RangeBandType.fixedDomain, size = domainSize; /// Creates a config that defines the rangeBand as equal to the stepSize. const RangeBandConfig.stepChartBand() : type = RangeBandType.fixedPercentOfStep, size = 1.0; /// Creates a config that defines the rangeBand as percentage of the stepSize. /// /// [percentOfStepWidth] is the percentage of the step from 0.0 - 1.0. RangeBandConfig.percentOfStep(double percentOfStepWidth) : type = RangeBandType.fixedPercentOfStep, size = percentOfStepWidth { assert(percentOfStepWidth >= 0 && percentOfStepWidth <= 1.0); } /// Creates a config that assigns the rangeBand according to the stylepack. /// ///

Note: renderers can detect this setting and update the percent based on /// the number of series in their preprocess. RangeBandConfig.styleAssignedPercent([int seriesCount = 1]) : type = RangeBandType.styleAssignedPercentOfStep, size = StyleFactory.style.rangeBandSize; /// Creates a config that defines the rangeBand as the stepSize - pixels. /// /// Where fixedPixels() gave you a constant rangBand in pixels, this will give /// you a constant space between rangeBands in pixels. const RangeBandConfig.fixedPixelSpaceBetweenStep(double pixels) : type = RangeBandType.fixedPixelSpaceFromStep, size = pixels; } /// Type of step size calculation to use. enum StepSizeType { autoDetect, fixedDomain, fixedPixels } /// Defines the method for calculating the stepSize between points. /// /// Typically auto will work fine in most cases, but if your data is /// irregular or you only have one data point, then you may want to override the /// stepSize detection specifying the exact expected stepSize. class StepSizeConfig { final StepSizeType type; final double size; /// Creates a StepSizeConfig that calculates step size based on incoming data. /// /// The stepSize is determined is calculated by detecting the smallest /// distance between two adjacent data points. This may not be suitable if /// you have irregular data or just a single data point. const StepSizeConfig.auto() : type = StepSizeType.autoDetect, size = 0.0; /// Creates a StepSizeConfig specifying the exact step size in pixel units. const StepSizeConfig.fixedPixels(double pixels) : type = StepSizeType.fixedPixels, size = pixels; /// Creates a StepSizeConfig specifying the exact step size in domain units. const StepSizeConfig.fixedDomain(double domainSize) : type = StepSizeType.fixedDomain, size = domainSize; } // TODO: make other extent subclasses plural. abstract class Extents {} ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/simple_ordinal_scale.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math' show min, max; import 'package:charts_common/src/common/math.dart'; import 'ordinal_scale.dart' show OrdinalScale; import 'ordinal_scale_domain_info.dart' show OrdinalScaleDomainInfo; import 'scale.dart' show RangeBandConfig, RangeBandType, StepSizeConfig, StepSizeType, ScaleOutputExtent; /// Scale that converts ordinal values of type [D] to a given range output. /// /// A `SimpleOrdinalScale` is used to map values from its domain to the /// available pixel range of the chart. Typically used for bar charts where the /// width of the bar is [rangeBand] and the position of the bar is retrieved /// by [[]]. class SimpleOrdinalScale implements OrdinalScale { final _stepSizeConfig = StepSizeConfig.auto(); final OrdinalScaleDomainInfo _domain; ScaleOutputExtent _range = ScaleOutputExtent(0, 1); double _viewportScale = 1.0; double _viewportTranslatePx = 0.0; RangeBandConfig _rangeBandConfig = RangeBandConfig.styleAssignedPercent(); bool _scaleChanged = true; late double _cachedStepSizePixels; late double _cachedRangeBandShift; late double _cachedRangeBandSize; int? _viewportDataSize; String? _viewportStartingDomain; // TODO: When there are horizontal bars increasing from where // the domain and measure axis intersects but the desired behavior is // flipped. The plan is to fix this by fixing code to flip the range in the // code. // // If range start is less than range end, then the domain is calculated by // adding the band width. If range start is greater than range end, then the // domain is calculated by subtracting from the band width (ex. horizontal // bar charts where first series is at the bottom of the chart). bool get _isVertical => range.start > range.end; SimpleOrdinalScale() : _domain = OrdinalScaleDomainInfo(); SimpleOrdinalScale._copy(SimpleOrdinalScale other) : _domain = other._domain.copy(), _range = ScaleOutputExtent(other._range.start, other._range.end), _viewportScale = other._viewportScale, _viewportTranslatePx = other._viewportTranslatePx, _rangeBandConfig = other._rangeBandConfig; @override double get rangeBand { if (_scaleChanged) { _updateScale(); } return _cachedRangeBandSize; } @override double get stepSize { if (_scaleChanged) { _updateScale(); } return _cachedStepSizePixels; } @override double get domainStepSize => 1.0; @override set rangeBandConfig(RangeBandConfig barGroupWidthConfig) { if (barGroupWidthConfig == null) { throw ArgumentError.notNull('RangeBandConfig must not be null.'); } if (barGroupWidthConfig.type == RangeBandType.fixedDomain || barGroupWidthConfig.type == RangeBandType.none) { throw ArgumentError( 'barGroupWidthConfig must not be NONE or FIXED_DOMAIN'); } _rangeBandConfig = barGroupWidthConfig; _scaleChanged = true; } @override RangeBandConfig get rangeBandConfig => _rangeBandConfig; @override set stepSizeConfig(StepSizeConfig? config) { if (config != null && config.type != StepSizeType.autoDetect) { throw ArgumentError( 'Ordinal scales only support StepSizeConfig of type Auto'); } // Nothing is set because only auto is supported. } @override StepSizeConfig get stepSizeConfig => _stepSizeConfig; /// Converts [domainValue] to the position to place the band/bar. /// /// Returns 0 if not found. @override double operator [](String domainValue) { if (_scaleChanged) { _updateScale(); } final i = _domain.indexOf(domainValue); if (i != null) { return viewportTranslatePx + _range.start + _cachedRangeBandShift + (_cachedStepSizePixels * i); } // If it wasn't found return 0.0; } @override String reverse(double pixelLocation) { final index = (pixelLocation - viewportTranslatePx - _range.start - _cachedRangeBandShift) / _cachedStepSizePixels; // The last pixel belongs in the last step even if it tries to round up. // // Index may be less than 0 when [pixelLocation] is less than the width of // the range band shift. This may happen on the far left side of the chart, // where we want the first datum anyways. Wrapping the result in "max(0, x)" // cuts off these negative values. return _domain .getDomainAtIndex(max(0, min(index.round(), domain.size - 1))); } @override bool canTranslate(String domainValue) => _domain.indexOf(domainValue) != null; @override OrdinalScaleDomainInfo get domain => _domain; /// Update the scale to include [domainValue]. @override void addDomain(String domainValue) { _domain.add(domainValue); _scaleChanged = true; } @override set range(ScaleOutputExtent? extent) { _range = extent!; _scaleChanged = true; } @override ScaleOutputExtent get range => _range; @override void resetDomain() { _domain.clear(); _scaleChanged = true; } @override void resetViewportSettings() { _viewportScale = 1.0; _viewportTranslatePx = 0.0; _scaleChanged = true; } @override int get rangeWidth => (range.start - range.end).abs().toInt(); @override double get viewportScalingFactor => _viewportScale; @override double get viewportTranslatePx => _viewportTranslatePx; @override void setViewportSettings(double viewportScale, double viewportTranslatePx) { _viewportScale = viewportScale; if (_isVertical) { _viewportTranslatePx = max( min(-(rangeWidth * (1.0 - viewportScale)), viewportTranslatePx), 0); } else { _viewportTranslatePx = min(max(rangeWidth * (1.0 - viewportScale), viewportTranslatePx), 0); } _scaleChanged = true; } @override void setViewport(int? viewportDataSize, String? startingDomain) { if (startingDomain != null && viewportDataSize != null && viewportDataSize <= 0) { throw ArgumentError('viewportDataSize cannot be less than 1.'); } _scaleChanged = true; _viewportDataSize = viewportDataSize; _viewportStartingDomain = startingDomain; } /// Update this scale's viewport using settings [_viewportDataSize] and /// [_viewportStartingDomain]. void _updateViewport() { setViewportSettings(1.0, 0.0); _recalculateScale(); if (_domain.isEmpty) { return; } // Update the scale with zoom level to help find the correct translate. setViewportSettings(_domain.size / min(_viewportDataSize!, _domain.size), _isVertical ? double.maxFinite : 0.0); _recalculateScale(); final domainIndex = _domain.indexOf(_viewportStartingDomain!); if (domainIndex != null) { var viewportTranslatePx = 0.0; if (_isVertical) { // Account for the domain values being reversed. viewportTranslatePx = (_viewportDataSize! - domainIndex - 1) * _cachedStepSizePixels; } else { viewportTranslatePx = -(_cachedStepSizePixels * domainIndex); } setViewportSettings(_viewportScale, viewportTranslatePx); } } @override int get viewportDataSize { if (_scaleChanged) { _updateScale(); } return _domain.isEmpty ? 0 : (rangeWidth ~/ _cachedStepSizePixels.abs()); } @override String? get viewportStartingDomain { if (_scaleChanged) { _updateScale(); } if (_domain.isEmpty) { return null; } if (_isVertical) { // Get topmost visible index. var index = (-(rangeWidth + _viewportTranslatePx) / _cachedStepSizePixels) .ceil() .toInt() - 1; return _domain.getDomainAtIndex(index); } else { return _domain.getDomainAtIndex( (-_viewportTranslatePx / _cachedStepSizePixels).ceil().toInt()); } } @override bool isRangeValueWithinViewport(double rangeValue) { return withinBounds(rangeValue, range.min, range.max); } @override int compareDomainValueToViewport(String domainValue) { // TODO: This currently works because range defaults to 0-1 // This needs to be looked into further. var i = _domain.indexOf(domainValue); if (i != null && range != null) { var domainPx = this[domainValue]; if (domainPx < range.min) { return -1; } if (domainPx > range.max) { return 1; } return 0; } return -1; } @override SimpleOrdinalScale copy() => SimpleOrdinalScale._copy(this); void _updateCachedFields( double stepSizePixels, double rangeBandPixels, double rangeBandShift) { _cachedStepSizePixels = stepSizePixels; _cachedRangeBandSize = rangeBandPixels; _cachedRangeBandShift = rangeBandShift; if (_isVertical) { _cachedStepSizePixels *= -1; _cachedRangeBandShift *= -1; } _scaleChanged = false; } void _updateScale() { if (_viewportStartingDomain != null && _viewportDataSize != null) { // Update viewport recalculates the scale. _updateViewport(); } _recalculateScale(); } void _recalculateScale() { final stepSizePixels = _domain.isEmpty ? 0.0 : _viewportScale * (rangeWidth.toDouble() / _domain.size.toDouble()); double rangeBandPixels; switch (rangeBandConfig.type) { case RangeBandType.fixedPixel: rangeBandPixels = rangeBandConfig.size.toDouble(); break; case RangeBandType.fixedPixelSpaceFromStep: var spaceInPixels = rangeBandConfig.size.toDouble(); rangeBandPixels = max(0.0, stepSizePixels - spaceInPixels); break; case RangeBandType.styleAssignedPercentOfStep: case RangeBandType.fixedPercentOfStep: var percent = rangeBandConfig.size.toDouble(); rangeBandPixels = stepSizePixels * percent; break; case RangeBandType.fixedDomain: case RangeBandType.none: throw StateError('RangeBandType must not be NONE or FIXED_DOMAIN'); } _updateCachedFields(stepSizePixels, rangeBandPixels, stepSizePixels / 2.0); } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/spec/axis_spec.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'package:meta/meta.dart' show immutable; import '../../../../common/color.dart' show Color; import '../../../../common/graphics_factory.dart' show GraphicsFactory; import '../../../common/chart_context.dart' show ChartContext; import '../axis.dart' show Axis; import '../scale.dart' show Scale, MutableScale; import '../draw_strategy/tick_draw_strategy.dart' show TickDrawStrategy; import '../tick_formatter.dart' show TickFormatter; import '../tick_provider.dart' show TickProvider; @immutable class AxisSpec { final bool? showAxisLine; final RenderSpec? renderSpec; final TickProviderSpec? tickProviderSpec; final TickFormatterSpec? tickFormatterSpec; final ScaleSpec? scaleSpec; const AxisSpec({ this.renderSpec, this.tickProviderSpec, this.tickFormatterSpec, this.showAxisLine, this.scaleSpec, }); factory AxisSpec.from( AxisSpec other, { RenderSpec? renderSpec, TickProviderSpec? tickProviderSpec, TickFormatterSpec? tickFormatterSpec, bool? showAxisLine, ScaleSpec? scaleSpec, }) { return AxisSpec( renderSpec: renderSpec ?? other.renderSpec, tickProviderSpec: tickProviderSpec ?? other.tickProviderSpec, tickFormatterSpec: tickFormatterSpec ?? other.tickFormatterSpec, showAxisLine: showAxisLine ?? other.showAxisLine, scaleSpec: scaleSpec ?? other.scaleSpec, ); } void configure( Axis axis, ChartContext context, GraphicsFactory graphicsFactory) { axis.resetDefaultConfiguration(); if (showAxisLine != null) { axis.forceDrawAxisLine = showAxisLine; } if (renderSpec != null) { axis.tickDrawStrategy = renderSpec!.createDrawStrategy(context, graphicsFactory); } if (tickProviderSpec != null) { axis.tickProvider = tickProviderSpec!.createTickProvider(context); } if (tickFormatterSpec != null) { axis.tickFormatter = tickFormatterSpec!.createTickFormatter(context); } if (scaleSpec != null) { axis.scale = scaleSpec!.createScale() as MutableScale; } } /// Creates an appropriately typed [Axis]. Axis? createAxis() => null; @override bool operator ==(Object other) => identical(this, other) || (other is AxisSpec && renderSpec == other.renderSpec && tickProviderSpec == other.tickProviderSpec && tickFormatterSpec == other.tickFormatterSpec && showAxisLine == other.showAxisLine && scaleSpec == other.scaleSpec); @override int get hashCode { var hashcode = renderSpec.hashCode; hashcode = (hashcode * 37) + tickProviderSpec.hashCode; hashcode = (hashcode * 37) + tickFormatterSpec.hashCode; hashcode = (hashcode * 37) + showAxisLine.hashCode; hashcode = (hashCode * 37) + scaleSpec.hashCode; return hashcode; } } @immutable abstract class TickProviderSpec { TickProvider createTickProvider(ChartContext context); } @immutable abstract class TickFormatterSpec { TickFormatter createTickFormatter(ChartContext context); } @immutable abstract class ScaleSpec { Scale createScale(); } @immutable abstract class RenderSpec { const RenderSpec(); TickDrawStrategy createDrawStrategy( ChartContext context, GraphicsFactory graphicFactory); } @immutable class TextStyleSpec { final String? fontFamily; final int? fontSize; final double? lineHeight; final Color? color; final String? fontWeight; const TextStyleSpec( {this.fontFamily, this.fontSize, this.lineHeight, this.color, this.fontWeight}); @override bool operator ==(Object other) { return identical(this, other) || (other is TextStyleSpec && fontFamily == other.fontFamily && fontSize == other.fontSize && lineHeight == other.lineHeight && color == other.color && fontWeight == other.fontWeight); } @override int get hashCode { var hashcode = fontFamily.hashCode; hashcode = (hashcode * 37) + fontSize.hashCode; hashcode = (hashcode * 37) + lineHeight.hashCode; hashcode = (hashcode * 37) + color.hashCode; hashcode = (hashcode * 37) + fontWeight.hashCode; return hashcode; } } @immutable class LineStyleSpec { final Color? color; final List? dashPattern; final int? thickness; const LineStyleSpec({this.color, this.dashPattern, this.thickness}); @override bool operator ==(Object other) { return identical(this, other) || (other is LineStyleSpec && color == other.color && dashPattern == other.dashPattern && thickness == other.thickness); } @override int get hashCode { var hashcode = color.hashCode; hashcode = (hashcode * 37) + dashPattern.hashCode; hashcode = (hashcode * 37) + thickness.hashCode; return hashcode; } } enum TickLabelAnchor { before, centered, after, /// The top most tick draws all text under the location. /// The bottom most tick draws all text above the location. /// The rest of the ticks are centered. inside, } enum TickLabelJustification { inside, outside, } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/spec/bucketing_axis_spec.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'package:intl/intl.dart'; import 'package:meta/meta.dart' show immutable; import '../../../../common/graphics_factory.dart' show GraphicsFactory; import '../../../common/chart_context.dart' show ChartContext; import '../axis.dart' show Axis, NumericAxis; import '../linear/bucketing_numeric_axis.dart' show BucketingNumericAxis; import '../linear/bucketing_numeric_tick_provider.dart' show BucketingNumericTickProvider; import '../numeric_extents.dart' show NumericExtents; import 'axis_spec.dart' show AxisSpec, RenderSpec; import 'numeric_axis_spec.dart' show BasicNumericTickFormatterSpec, BasicNumericTickProviderSpec, NumericAxisSpec, NumericTickProviderSpec, NumericTickFormatterSpec; /// A numeric [AxisSpec] that positions all values beneath a certain [threshold] /// into a reserved space on the axis range. The label for the bucket line will /// be drawn in the middle of the bucket range, rather than aligned with the /// gridline for that value's position on the scale. /// /// An example illustration of a bucketing measure axis on a point chart /// follows. In this case, values such as "6%" and "3%" are drawn in the bucket /// of the axis, since they are less than the [threshold] value of 10%. /// /// 100% ┠───────────────────────── /// ┃ * /// ┃ * /// 50% ┠──────*────────────────── /// ┃ /// ┠───────────────────────── /// < 10% ┃ * * /// ┗┯━━━━━━━━━━┯━━━━━━━━━━━┯━ /// 0 50 100 /// /// This axis will format numbers as percents by default. @immutable class BucketingAxisSpec extends NumericAxisSpec { /// All values smaller than the threshold will be bucketed into the same /// position in the reserved space on the axis. final num? threshold; /// Whether or not measure values bucketed below the [threshold] should be /// visible on the chart, or collapsed. /// /// If this is false, then any data with measure values smaller than /// [threshold] will not be rendered on the chart. final bool showBucket; /// Creates a [NumericAxisSpec] that is specialized for percentage data. BucketingAxisSpec({ RenderSpec? renderSpec, NumericTickProviderSpec? tickProviderSpec, NumericTickFormatterSpec? tickFormatterSpec, bool? showAxisLine, bool? showBucket, this.threshold, NumericExtents? viewport, }) : showBucket = showBucket ?? true, super( renderSpec: renderSpec, tickProviderSpec: tickProviderSpec ?? const BucketingNumericTickProviderSpec(), tickFormatterSpec: tickFormatterSpec ?? BasicNumericTickFormatterSpec.fromNumberFormat( NumberFormat.percentPattern()), showAxisLine: showAxisLine, viewport: viewport ?? const NumericExtents(0.0, 1.0)); @override void configure( Axis axis, ChartContext context, GraphicsFactory graphicsFactory) { super.configure(axis, context, graphicsFactory); if (axis is NumericAxis && viewport != null) { axis.setScaleViewport(viewport!); } if (axis is BucketingNumericAxis && threshold != null) { axis.threshold = threshold!; } if (axis is BucketingNumericAxis) { axis.showBucket = showBucket; } } @override BucketingNumericAxis createAxis() => BucketingNumericAxis(); @override bool operator ==(Object other) => identical(this, other) || (other is BucketingAxisSpec && showBucket == other.showBucket && threshold == other.threshold && super == other); @override int get hashCode { var hashcode = super.hashCode; hashcode = (hashcode * 37) + showBucket.hashCode; hashcode = (hashcode * 37) + threshold.hashCode; return hashcode; } } @immutable class BucketingNumericTickProviderSpec extends BasicNumericTickProviderSpec { /// Creates a [TickProviderSpec] that generates ticks for a bucketing axis. /// /// [zeroBound] automatically include zero in the data range. /// [dataIsInWholeNumbers] skip over ticks that would produce /// fractional ticks that don't make sense for the domain (ie: headcount). /// [desiredTickCount] the fixed number of ticks to try to make. Convenience /// that sets [desiredMinTickCount] and [desiredMaxTickCount] the same. /// Both min and max win out if they are set along with /// [desiredTickCount]. /// [desiredMinTickCount] automatically choose the best tick /// count to produce the 'nicest' ticks but make sure we have this many. /// [desiredMaxTickCount] automatically choose the best tick /// count to produce the 'nicest' ticks but make sure we don't have more /// than this many. const BucketingNumericTickProviderSpec( {bool? zeroBound, bool? dataIsInWholeNumbers, int? desiredTickCount, int? desiredMinTickCount, int? desiredMaxTickCount}) : super( zeroBound: zeroBound ?? true, dataIsInWholeNumbers: dataIsInWholeNumbers ?? false, desiredTickCount: desiredTickCount, desiredMinTickCount: desiredMinTickCount, desiredMaxTickCount: desiredMaxTickCount, ); @override BucketingNumericTickProvider createTickProvider(ChartContext context) { final provider = BucketingNumericTickProvider() ..zeroBound = zeroBound! ..dataIsInWholeNumbers = dataIsInWholeNumbers!; if (desiredMinTickCount != null || desiredMaxTickCount != null || desiredTickCount != null) { provider.setTickCount(desiredMaxTickCount ?? desiredTickCount ?? 10, desiredMinTickCount ?? desiredTickCount ?? 2); } return provider; } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/spec/date_time_axis_spec.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'package:meta/meta.dart' show immutable; import 'package:intl/intl.dart' show DateFormat; import '../../../../common/date_time_factory.dart' show DateTimeFactory; import '../../../../common/graphics_factory.dart' show GraphicsFactory; import '../../../common/chart_context.dart' show ChartContext; import '../axis.dart' show Axis; import '../end_points_tick_provider.dart' show EndPointsTickProvider; import '../static_tick_provider.dart' show StaticTickProvider; import '../time/auto_adjusting_date_time_tick_provider.dart' show AutoAdjustingDateTimeTickProvider; import '../time/date_time_axis.dart' show DateTimeAxis; import '../time/date_time_extents.dart' show DateTimeExtents; import '../time/date_time_tick_formatter.dart' show DateTimeTickFormatter; import '../time/day_time_stepper.dart' show DayTimeStepper; import '../time/hour_tick_formatter.dart' show HourTickFormatter; import '../time/simple_time_tick_formatter.dart' show DateTimeFormatterFunction, SimpleTimeTickFormatter; import '../time/time_range_tick_provider_impl.dart' show TimeRangeTickProviderImpl; import '../time/time_tick_formatter.dart' show TimeTickFormatter; import '../time/time_tick_formatter_impl.dart' show CalendarField, TimeTickFormatterImpl; import 'axis_spec.dart' show AxisSpec, TickProviderSpec, TickFormatterSpec, RenderSpec; import 'tick_spec.dart' show TickSpec; /// Generic [AxisSpec] specialized for Timeseries charts. @immutable class DateTimeAxisSpec extends AxisSpec { /// Sets viewport for this Axis. /// /// If pan / zoom behaviors are set, this is the initial viewport. final DateTimeExtents? viewport; /// Creates a [AxisSpec] that specialized for timeseries charts. /// /// [renderSpec] spec used to configure how the ticks and labels /// actually render. Possible values are [GridlineRendererSpec], /// [SmallTickRendererSpec] & [NoneRenderSpec]. Make sure that the /// given to the RenderSpec is of type [DateTime] for Timeseries. /// [tickProviderSpec] spec used to configure what ticks are generated. /// [tickFormatterSpec] spec used to configure how the tick labels /// are formatted. /// [showAxisLine] override to force the axis to draw the axis /// line. const DateTimeAxisSpec({ RenderSpec? renderSpec, DateTimeTickProviderSpec? tickProviderSpec, DateTimeTickFormatterSpec? tickFormatterSpec, bool? showAxisLine, this.viewport, }) : super( renderSpec: renderSpec, tickProviderSpec: tickProviderSpec, tickFormatterSpec: tickFormatterSpec, showAxisLine: showAxisLine); @override void configure(Axis axis, ChartContext context, GraphicsFactory graphicsFactory) { super.configure(axis, context, graphicsFactory); if (axis is DateTimeAxis && viewport != null) { axis.setScaleViewport(viewport!); } } @override Axis? createAxis() { assert(false, 'Call createDateTimeAxis() to create a DateTimeAxis.'); return null; } /// Creates a [DateTimeAxis]. This should be called in place of createAxis. DateTimeAxis createDateTimeAxis(DateTimeFactory dateTimeFactory) => DateTimeAxis(dateTimeFactory); @override bool operator ==(Object other) => other is DateTimeAxisSpec && viewport == other.viewport && super == other; @override int get hashCode { var hashcode = super.hashCode; hashcode = (hashcode * 37) + viewport.hashCode; return hashcode; } } abstract class DateTimeTickProviderSpec extends TickProviderSpec {} abstract class DateTimeTickFormatterSpec extends TickFormatterSpec {} /// [TickProviderSpec] that sets up the automatically assigned time ticks based /// on the extents of your data. @immutable class AutoDateTimeTickProviderSpec implements DateTimeTickProviderSpec { final bool includeTime; /// Creates a [TickProviderSpec] that dynamically chooses ticks based on the /// extents of the data. /// /// [includeTime] - flag that indicates whether the time should be /// included when choosing appropriate tick intervals. const AutoDateTimeTickProviderSpec({this.includeTime = true}); @override AutoAdjustingDateTimeTickProvider createTickProvider(ChartContext context) { if (includeTime) { return AutoAdjustingDateTimeTickProvider.createDefault( context.dateTimeFactory); } else { return AutoAdjustingDateTimeTickProvider.createWithoutTime( context.dateTimeFactory); } } @override bool operator ==(Object other) => other is AutoDateTimeTickProviderSpec && includeTime == other.includeTime; @override int get hashCode => includeTime.hashCode; } /// [TickProviderSpec] that sets up time ticks with days increments only. @immutable class DayTickProviderSpec implements DateTimeTickProviderSpec { final List? increments; const DayTickProviderSpec({this.increments}); /// Creates a [TickProviderSpec] that dynamically chooses ticks based on the /// extents of the data, limited to day increments. /// /// [increments] specify the number of day increments that can be chosen from /// when searching for the appropriate tick intervals. @override AutoAdjustingDateTimeTickProvider createTickProvider(ChartContext context) { return AutoAdjustingDateTimeTickProvider.createWith([ TimeRangeTickProviderImpl(DayTimeStepper(context.dateTimeFactory, allowedTickIncrements: increments)) ]); } @override bool operator ==(Object other) => other is DayTickProviderSpec && increments == other.increments; @override int get hashCode => increments.hashCode; } /// [TickProviderSpec] that sets up time ticks at the two end points of the axis /// range. @immutable class DateTimeEndPointsTickProviderSpec implements DateTimeTickProviderSpec { const DateTimeEndPointsTickProviderSpec(); /// Creates a [TickProviderSpec] that dynamically chooses time ticks at the /// two end points of the axis range @override EndPointsTickProvider createTickProvider(ChartContext context) { return EndPointsTickProvider(); } @override // ignore: hash_and_equals bool operator ==(Object other) => other is DateTimeEndPointsTickProviderSpec; } /// [TickProviderSpec] that allows you to specific the ticks to be used. @immutable class StaticDateTimeTickProviderSpec implements DateTimeTickProviderSpec { final List> tickSpecs; const StaticDateTimeTickProviderSpec(this.tickSpecs); @override StaticTickProvider createTickProvider(ChartContext context) => StaticTickProvider(tickSpecs); @override bool operator ==(Object other) => other is StaticDateTimeTickProviderSpec && tickSpecs == other.tickSpecs; @override int get hashCode => tickSpecs.hashCode; } /// Formatters for a single level of the [DateTimeTickFormatterSpec]. @immutable class TimeFormatterSpec { final String? format; final String? transitionFormat; final String? noonFormat; /// Creates a formatter for a particular granularity of data. /// /// [format] [DateFormat] format string used to format non-transition ticks. /// The string is given to the dateTimeFactory to support i18n formatting. /// [transitionFormat] [DateFormat] format string used to format transition /// ticks. Examples of transition ticks: /// Day ticks would have a transition tick at month boundaries. /// Hour ticks would have a transition tick at day boundaries. /// The first tick is typically a transition tick. /// [noonFormat] [DateFormat] format string used only for formatting hours /// in the event that you want to format noon differently than other /// hours (ie: [10, 11, 12p, 1, 2, 3]). const TimeFormatterSpec( {this.format, this.transitionFormat, this.noonFormat}); @override bool operator ==(Object other) => other is TimeFormatterSpec && format == other.format && transitionFormat == other.transitionFormat && noonFormat == other.noonFormat; @override int get hashCode { var hashcode = format.hashCode; hashcode = (hashcode * 37) + transitionFormat.hashCode; hashcode = (hashcode * 37) + noonFormat.hashCode; return hashcode; } } /// A [DateTimeTickFormatterSpec] that accepts a [DateFormat] or a /// [DateTimeFormatterFunction]. @immutable class BasicDateTimeTickFormatterSpec implements DateTimeTickFormatterSpec { final DateTimeFormatterFunction? formatter; final DateFormat? dateFormat; const BasicDateTimeTickFormatterSpec(DateTimeFormatterFunction formatter) : formatter = formatter, dateFormat = null; const BasicDateTimeTickFormatterSpec.fromDateFormat(DateFormat dateFormat) : formatter = null, dateFormat = dateFormat; /// A formatter will be created with the [DateFormat] if it is not null. /// Otherwise, it will create one with the provided /// [DateTimeFormatterFunction]. @override DateTimeTickFormatter createTickFormatter(ChartContext context) { assert(dateFormat != null || formatter != null); return DateTimeTickFormatter.uniform(SimpleTimeTickFormatter( formatter: dateFormat != null ? dateFormat!.format : formatter!)); } @override bool operator ==(Object other) { return identical(this, other) || (other is BasicDateTimeTickFormatterSpec && formatter == other.formatter && dateFormat == other.dateFormat); } @override int get hashCode { var hash = formatter.hashCode; hash = (hash * 37) * dateFormat.hashCode; return hash; } } /// [TickFormatterSpec] that automatically chooses the appropriate level of /// formatting based on the tick stepSize. Each level of date granularity has /// its own [TimeFormatterSpec] used to specify the formatting strings at that /// level. @immutable class AutoDateTimeTickFormatterSpec implements DateTimeTickFormatterSpec { final TimeFormatterSpec? minute; final TimeFormatterSpec? hour; final TimeFormatterSpec? day; final TimeFormatterSpec? month; final TimeFormatterSpec? year; /// Creates a [TickFormatterSpec] that automatically chooses the formatting /// given the individual [TimeFormatterSpec] formatters that are set. /// /// There is a default formatter for each level that is configurable, but /// by specifying a level here it replaces the default for that particular /// granularity. This is useful for swapping out one or all of the formatters. const AutoDateTimeTickFormatterSpec( {this.minute, this.hour, this.day, this.month, this.year}); @override DateTimeTickFormatter createTickFormatter(ChartContext context) { final map = {}; if (minute != null) { map[DateTimeTickFormatter.MINUTE] = _makeFormatter(minute!, CalendarField.hourOfDay, context); } if (hour != null) { map[DateTimeTickFormatter.HOUR] = _makeFormatter(hour!, CalendarField.date, context); } if (day != null) { map[23 * DateTimeTickFormatter.HOUR] = _makeFormatter(day!, CalendarField.month, context); } if (month != null) { map[28 * DateTimeTickFormatter.DAY] = _makeFormatter(month!, CalendarField.year, context); } if (year != null) { map[364 * DateTimeTickFormatter.DAY] = _makeFormatter(year!, CalendarField.year, context); } return DateTimeTickFormatter(context.dateTimeFactory, overrides: map); } TimeTickFormatterImpl _makeFormatter(TimeFormatterSpec spec, CalendarField transitionField, ChartContext context) { if (spec.noonFormat != null) { return HourTickFormatter( dateTimeFactory: context.dateTimeFactory, simpleFormat: spec.format, transitionFormat: spec.transitionFormat, noonFormat: spec.noonFormat); } else { return TimeTickFormatterImpl( dateTimeFactory: context.dateTimeFactory, simpleFormat: spec.format, transitionFormat: spec.transitionFormat, transitionField: transitionField); } } @override bool operator ==(Object other) => identical(this, other) || (other is AutoDateTimeTickFormatterSpec && minute == other.minute && hour == other.hour && day == other.day && month == other.month && year == other.year); @override int get hashCode { var hashcode = minute.hashCode; hashcode = (hashcode * 37) + hour.hashCode; hashcode = (hashcode * 37) + day.hashCode; hashcode = (hashcode * 37) + month.hashCode; hashcode = (hashcode * 37) + year.hashCode; return hashcode; } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/spec/end_points_time_axis_spec.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'package:meta/meta.dart' show immutable; import '../draw_strategy/small_tick_draw_strategy.dart' show SmallTickRendererSpec; import '../time/date_time_extents.dart' show DateTimeExtents; import 'axis_spec.dart' show AxisSpec, RenderSpec, TickLabelAnchor; import 'date_time_axis_spec.dart' show DateTimeAxisSpec, DateTimeEndPointsTickProviderSpec, DateTimeTickFormatterSpec, DateTimeTickProviderSpec; /// Default [AxisSpec] used for Timeseries charts. @immutable class EndPointsTimeAxisSpec extends DateTimeAxisSpec { /// Creates a [AxisSpec] that specialized for timeseries charts. /// /// [renderSpec] spec used to configure how the ticks and labels /// actually render. Possible values are [GridlineRendererSpec], /// [SmallTickRendererSpec] & [NoneRenderSpec]. Make sure that the /// given to the RenderSpec is of type [DateTime] for Timeseries. /// [tickProviderSpec] spec used to configure what ticks are generated. /// [tickFormatterSpec] spec used to configure how the tick labels /// are formatted. /// [showAxisLine] override to force the axis to draw the axis /// line. const EndPointsTimeAxisSpec({ RenderSpec? renderSpec, DateTimeTickProviderSpec? tickProviderSpec, DateTimeTickFormatterSpec? tickFormatterSpec, bool? showAxisLine, DateTimeExtents? viewport, }) : super( renderSpec: renderSpec ?? const SmallTickRendererSpec( labelAnchor: TickLabelAnchor.inside, labelOffsetFromTickPx: 0), tickProviderSpec: tickProviderSpec ?? const DateTimeEndPointsTickProviderSpec(), tickFormatterSpec: tickFormatterSpec, showAxisLine: showAxisLine, viewport: viewport); @override bool operator ==(Object other) => identical(this, other) || (other is EndPointsTimeAxisSpec && super == other); @override int get hashCode { var hashcode = super.hashCode; hashcode = (hashcode * 37) + runtimeType.hashCode; return hashcode; } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/spec/numeric_axis_spec.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'package:charts_common/src/chart/cartesian/axis/tick_formatter.dart'; import 'package:meta/meta.dart' show immutable; import 'package:intl/intl.dart'; import '../../../../common/graphics_factory.dart' show GraphicsFactory; import '../../../common/chart_context.dart' show ChartContext; import '../../../common/datum_details.dart' show MeasureFormatter; import '../axis.dart' show Axis, NumericAxis; import '../end_points_tick_provider.dart' show EndPointsTickProvider; import '../numeric_extents.dart' show NumericExtents; import '../numeric_tick_provider.dart' show NumericTickProvider; import '../static_tick_provider.dart' show StaticTickProvider; import '../tick_formatter.dart' show NumericTickFormatter; import 'axis_spec.dart' show AxisSpec, TickProviderSpec, TickFormatterSpec, RenderSpec; import 'tick_spec.dart' show TickSpec; /// [AxisSpec] specialized for numeric/continuous axes like the measure axis. @immutable class NumericAxisSpec extends AxisSpec { /// Sets viewport for this Axis. /// /// If pan / zoom behaviors are set, this is the initial viewport. final NumericExtents? viewport; /// Creates a [AxisSpec] that specialized for numeric data. /// /// [renderSpec] spec used to configure how the ticks and labels /// actually render. Possible values are [GridlineRendererSpec], /// [SmallTickRendererSpec] & [NoneRenderSpec]. Make sure that the /// given to the RenderSpec is of type [num] when using this spec. /// [tickProviderSpec] spec used to configure what ticks are generated. /// [tickFormatterSpec] spec used to configure how the tick labels are /// formatted. /// [showAxisLine] override to force the axis to draw the axis line. const NumericAxisSpec({ RenderSpec? renderSpec, NumericTickProviderSpec? tickProviderSpec, NumericTickFormatterSpec? tickFormatterSpec, bool? showAxisLine, this.viewport, }) : super( renderSpec: renderSpec, tickProviderSpec: tickProviderSpec, tickFormatterSpec: tickFormatterSpec, showAxisLine: showAxisLine); factory NumericAxisSpec.from( NumericAxisSpec other, { RenderSpec? renderSpec, TickProviderSpec? tickProviderSpec, TickFormatterSpec? tickFormatterSpec, bool? showAxisLine, NumericExtents? viewport, }) { return NumericAxisSpec( renderSpec: renderSpec ?? other.renderSpec, tickProviderSpec: (tickProviderSpec ?? other.tickProviderSpec) as NumericTickProviderSpec?, tickFormatterSpec: (tickFormatterSpec ?? other.tickFormatterSpec) as NumericTickFormatterSpec?, showAxisLine: showAxisLine ?? other.showAxisLine, viewport: viewport ?? other.viewport, ); } @override void configure( Axis axis, ChartContext context, GraphicsFactory graphicsFactory) { super.configure(axis, context, graphicsFactory); if (axis is NumericAxis && viewport != null) { axis.setScaleViewport(viewport!); } } @override NumericAxis createAxis() => NumericAxis(); @override bool operator ==(Object other) => other is NumericAxisSpec && viewport == other.viewport && super == other; @override int get hashCode { var hashcode = super.hashCode; hashcode = (hashcode * 37) + viewport.hashCode; hashcode = (hashcode * 37) + super.hashCode; return hashcode; } } abstract class NumericTickProviderSpec extends TickProviderSpec {} abstract class NumericTickFormatterSpec extends TickFormatterSpec {} @immutable class BasicNumericTickProviderSpec implements NumericTickProviderSpec { final bool? zeroBound; final bool? dataIsInWholeNumbers; final int? desiredTickCount; final int? desiredMinTickCount; final int? desiredMaxTickCount; /// Creates a [TickProviderSpec] that dynamically chooses the number of /// ticks based on the extents of the data. /// /// [zeroBound] automatically include zero in the data range. /// [dataIsInWholeNumbers] skip over ticks that would produce /// fractional ticks that don't make sense for the domain (ie: headcount). /// [desiredTickCount] the fixed number of ticks to try to make. Convenience /// that sets [desiredMinTickCount] and [desiredMaxTickCount] the same. /// Both min and max win out if they are set along with /// [desiredTickCount]. /// [desiredMinTickCount] automatically choose the best tick /// count to produce the 'nicest' ticks but make sure we have this many. /// [desiredMaxTickCount] automatically choose the best tick /// count to produce the 'nicest' ticks but make sure we don't have more /// than this many. const BasicNumericTickProviderSpec( {this.zeroBound, this.dataIsInWholeNumbers, this.desiredTickCount, this.desiredMinTickCount, this.desiredMaxTickCount}); @override NumericTickProvider createTickProvider(ChartContext context) { final provider = NumericTickProvider(); if (zeroBound != null) { provider.zeroBound = zeroBound!; } if (dataIsInWholeNumbers != null) { provider.dataIsInWholeNumbers = dataIsInWholeNumbers!; } if (desiredMinTickCount != null || desiredMaxTickCount != null || desiredTickCount != null) { provider.setTickCount(desiredMaxTickCount ?? desiredTickCount ?? 10, desiredMinTickCount ?? desiredTickCount ?? 2); } return provider; } @override bool operator ==(Object other) => other is BasicNumericTickProviderSpec && zeroBound == other.zeroBound && dataIsInWholeNumbers == other.dataIsInWholeNumbers && desiredTickCount == other.desiredTickCount && desiredMinTickCount == other.desiredMinTickCount && desiredMaxTickCount == other.desiredMaxTickCount; @override int get hashCode { var hashcode = zeroBound.hashCode; hashcode = (hashcode * 37) + dataIsInWholeNumbers.hashCode; hashcode = (hashcode * 37) + desiredTickCount.hashCode; hashcode = (hashcode * 37) + desiredMinTickCount.hashCode; hashcode = (hashcode * 37) + desiredMaxTickCount.hashCode; return hashcode; } } /// [TickProviderSpec] that sets up numeric ticks at the two end points of the /// axis range. @immutable class NumericEndPointsTickProviderSpec implements NumericTickProviderSpec { /// Creates a [TickProviderSpec] that dynamically chooses numeric ticks at the /// two end points of the axis range const NumericEndPointsTickProviderSpec(); @override EndPointsTickProvider createTickProvider(ChartContext context) { return EndPointsTickProvider(); } @override // ignore: hash_and_equals bool operator ==(Object other) => other is NumericEndPointsTickProviderSpec; } /// [TickProviderSpec] that allows you to specific the ticks to be used. @immutable class StaticNumericTickProviderSpec implements NumericTickProviderSpec { final List> tickSpecs; const StaticNumericTickProviderSpec(this.tickSpecs); @override StaticTickProvider createTickProvider(ChartContext context) => StaticTickProvider(tickSpecs); @override bool operator ==(Object other) => identical(this, other) || (other is StaticNumericTickProviderSpec && tickSpecs == other.tickSpecs); @override int get hashCode => tickSpecs.hashCode; } @immutable class BasicNumericTickFormatterSpec implements NumericTickFormatterSpec { final MeasureFormatter? formatter; final NumberFormat? numberFormat; /// Simple [TickFormatterSpec] that delegates formatting to the given /// [NumberFormat]. const BasicNumericTickFormatterSpec(this.formatter) : numberFormat = null; const BasicNumericTickFormatterSpec.fromNumberFormat(this.numberFormat) : formatter = null; /// A formatter will be created with the number format if it is not null. /// Otherwise, it will create one with the [MeasureFormatter] callback. @override NumericTickFormatter createTickFormatter(ChartContext context) { return numberFormat != null ? NumericTickFormatter.fromNumberFormat(numberFormat!) : NumericTickFormatter(formatter: formatter); } @override bool operator ==(Object other) { return identical(this, other) || (other is BasicNumericTickFormatterSpec && formatter == other.formatter && numberFormat == other.numberFormat); } @override int get hashCode { var hashcode = formatter.hashCode; hashcode = (hashcode * 37) * numberFormat.hashCode; return hashcode; } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/spec/ordinal_axis_spec.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'package:charts_common/src/chart/cartesian/axis/scale.dart' show RangeBandConfig; import 'package:meta/meta.dart' show immutable; import '../../../../common/graphics_factory.dart' show GraphicsFactory; import '../../../common/chart_context.dart' show ChartContext; import '../axis.dart' show Axis, OrdinalAxis, OrdinalViewport; import '../ordinal_scale.dart' show OrdinalScale; import '../ordinal_tick_provider.dart' show OrdinalTickProvider; import '../range_tick_provider.dart' show RangeTickProvider; import '../simple_ordinal_scale.dart' show SimpleOrdinalScale; import '../static_tick_provider.dart' show StaticTickProvider; import '../tick_formatter.dart' show OrdinalTickFormatter; import 'axis_spec.dart' show AxisSpec, TickProviderSpec, TickFormatterSpec, ScaleSpec, RenderSpec; import 'tick_spec.dart' show TickSpec; /// [AxisSpec] specialized for ordinal/non-continuous axes typically for bars. @immutable class OrdinalAxisSpec extends AxisSpec { /// Sets viewport for this Axis. /// /// If pan / zoom behaviors are set, this is the initial viewport. final OrdinalViewport? viewport; /// Creates a [AxisSpec] that specialized for ordinal domain charts. /// /// [renderSpec] spec used to configure how the ticks and labels /// actually render. Possible values are [GridlineRendererSpec], /// [SmallTickRendererSpec] & [NoneRenderSpec]. Make sure that the /// given to the RenderSpec is of type [String] when using this spec. /// [tickProviderSpec] spec used to configure what ticks are generated. /// [tickFormatterSpec] spec used to configure how the tick labels are /// formatted. /// [showAxisLine] override to force the axis to draw the axis line. const OrdinalAxisSpec({ RenderSpec? renderSpec, OrdinalTickProviderSpec? tickProviderSpec, OrdinalTickFormatterSpec? tickFormatterSpec, bool? showAxisLine, OrdinalScaleSpec? scaleSpec, this.viewport, }) : super( renderSpec: renderSpec, tickProviderSpec: tickProviderSpec, tickFormatterSpec: tickFormatterSpec, showAxisLine: showAxisLine, scaleSpec: scaleSpec, ); @override void configure(Axis axis, ChartContext context, GraphicsFactory graphicsFactory) { super.configure(axis, context, graphicsFactory); if (axis is OrdinalAxis && viewport != null) { axis.setScaleViewport(viewport!); } } @override OrdinalAxis createAxis() => OrdinalAxis(); @override bool operator ==(Object other) { return identical(this, other) || (other is OrdinalAxisSpec && viewport == other.viewport && super == other); } @override int get hashCode { var hashcode = super.hashCode; hashcode = (hashcode * 37) + viewport.hashCode; return hashcode; } } abstract class OrdinalTickProviderSpec extends TickProviderSpec {} abstract class OrdinalTickFormatterSpec extends TickFormatterSpec {} abstract class OrdinalScaleSpec extends ScaleSpec {} @immutable class BasicOrdinalTickProviderSpec implements OrdinalTickProviderSpec { const BasicOrdinalTickProviderSpec(); @override OrdinalTickProvider createTickProvider(ChartContext context) => OrdinalTickProvider(); @override bool operator ==(Object other) => other is BasicOrdinalTickProviderSpec; @override int get hashCode => 37; } /// [TickProviderSpec] that allows you to specify the ticks to be used. @immutable class StaticOrdinalTickProviderSpec implements OrdinalTickProviderSpec { final List> tickSpecs; const StaticOrdinalTickProviderSpec(this.tickSpecs); @override StaticTickProvider createTickProvider(ChartContext context) => StaticTickProvider(tickSpecs); @override bool operator ==(Object other) => identical(this, other) || (other is StaticOrdinalTickProviderSpec && tickSpecs == other.tickSpecs); @override int get hashCode => tickSpecs.hashCode; } /// [TickProviderSpec] that allows you to provide range ticks and normal ticks. @immutable class RangeOrdinalTickProviderSpec implements OrdinalTickProviderSpec { final List> tickSpecs; const RangeOrdinalTickProviderSpec(this.tickSpecs); @override RangeTickProvider createTickProvider(ChartContext context) => RangeTickProvider(tickSpecs); @override bool operator ==(Object other) => identical(this, other) || (other is RangeOrdinalTickProviderSpec && tickSpecs == other.tickSpecs); @override int get hashCode => tickSpecs.hashCode; } @immutable class BasicOrdinalTickFormatterSpec implements OrdinalTickFormatterSpec { const BasicOrdinalTickFormatterSpec(); @override OrdinalTickFormatter createTickFormatter(ChartContext context) => OrdinalTickFormatter(); @override bool operator ==(Object other) => other is BasicOrdinalTickFormatterSpec; @override int get hashCode => 37; } @immutable class SimpleOrdinalScaleSpec implements OrdinalScaleSpec { const SimpleOrdinalScaleSpec(); @override OrdinalScale createScale() => SimpleOrdinalScale(); @override bool operator ==(Object other) => other is SimpleOrdinalScaleSpec; @override int get hashCode => 37; } /// [OrdinalScaleSpec] which allows setting space between bars to be a fixed /// pixel size. @immutable class FixedPixelSpaceOrdinalScaleSpec implements OrdinalScaleSpec { final double pixelSpaceBetweenBars; const FixedPixelSpaceOrdinalScaleSpec(this.pixelSpaceBetweenBars); @override OrdinalScale createScale() => SimpleOrdinalScale() ..rangeBandConfig = RangeBandConfig.fixedPixelSpaceBetweenStep(pixelSpaceBetweenBars); @override bool operator ==(Object other) => other is SimpleOrdinalScaleSpec; @override int get hashCode => 37; } /// [OrdinalScaleSpec] which allows setting bar width to be a fixed pixel size. @immutable class FixedPixelOrdinalScaleSpec implements OrdinalScaleSpec { final double pixels; const FixedPixelOrdinalScaleSpec(this.pixels); @override OrdinalScale createScale() => SimpleOrdinalScale() ..rangeBandConfig = RangeBandConfig.fixedPixel(pixels); @override bool operator ==(Object other) => other is SimpleOrdinalScaleSpec; @override int get hashCode => 37; } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/spec/percent_axis_spec.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'package:meta/meta.dart' show immutable; import 'package:intl/intl.dart'; import '../numeric_extents.dart' show NumericExtents; import 'axis_spec.dart' show AxisSpec, RenderSpec; import 'numeric_axis_spec.dart' show BasicNumericTickFormatterSpec, BasicNumericTickProviderSpec, NumericAxisSpec, NumericTickProviderSpec, NumericTickFormatterSpec; /// Convenience [AxisSpec] specialized for numeric percentage axes. @immutable class PercentAxisSpec extends NumericAxisSpec { /// Creates a [NumericAxisSpec] that is specialized for percentage data. PercentAxisSpec({ RenderSpec? renderSpec, NumericTickProviderSpec? tickProviderSpec, NumericTickFormatterSpec? tickFormatterSpec, bool? showAxisLine, NumericExtents? viewport, }) : super( renderSpec: renderSpec, tickProviderSpec: tickProviderSpec ?? const BasicNumericTickProviderSpec(dataIsInWholeNumbers: false), tickFormatterSpec: tickFormatterSpec ?? BasicNumericTickFormatterSpec.fromNumberFormat( NumberFormat.percentPattern()), showAxisLine: showAxisLine, viewport: viewport ?? const NumericExtents(0.0, 1.0)); @override bool operator ==(Object other) => other is PercentAxisSpec && super == other; @override int get hashCode { var hashcode = super.hashCode; hashcode = (hashcode * 37) + runtimeType.hashCode; return hashcode; } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/spec/range_tick_spec.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'axis_spec.dart' show TextStyleSpec; import 'tick_spec.dart' show TickSpec; /// Definition for a range tick. /// /// Used to define a tick that is used by range tick provider. class RangeTickSpec extends TickSpec { final D rangeStartValue; final D rangeEndValue; /// Creates a range tick for [value]. /// A [label] optionally labels this tick. If not set, the tick formatter /// formatter of the axis is used. /// A [style] optionally sets the style for this tick. If not set, the style /// of the axis is used. /// A [rangeStartValue] represents value of this range tick's starting point. /// A [rangeEndValue] represents the value of this range tick's ending point. const RangeTickSpec( D value, { String? label, TextStyleSpec? style, required this.rangeStartValue, required this.rangeEndValue, }) : super(value, label: label, style: style); } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/spec/tick_spec.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'axis_spec.dart' show TextStyleSpec; /// Definition for a tick. /// /// Used to define a tick that is used by static tick provider. class TickSpec { final D value; final String? label; final TextStyleSpec? style; /// [value] the value of this tick /// [label] optional label for this tick. If not set, uses the tick formatter /// of the axis. /// [style] optional style for this tick. If not set, uses the style of the /// axis. const TickSpec(this.value, {this.label, this.style}); } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/static_tick_provider.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import '../../../common/graphics_factory.dart' show GraphicsFactory; import '../../common/chart_context.dart' show ChartContext; import 'axis.dart' show AxisOrientation; import 'draw_strategy/tick_draw_strategy.dart' show TickDrawStrategy; import 'numeric_scale.dart' show NumericScale; import 'scale.dart' show MutableScale; import 'spec/tick_spec.dart' show TickSpec; import 'tick.dart' show Tick; import 'tick_formatter.dart' show TickFormatter; import 'tick_provider.dart' show TickProvider, TickHint; import 'time/date_time_scale.dart' show DateTimeScale; /// A strategy that uses the ticks provided and only assigns positioning. /// /// The [TextStyle] is not overridden during [TickDrawStrategy.decorateTicks]. /// If the [TickSpec] style is null, then the default [TextStyle] is used. class StaticTickProvider extends TickProvider { final List> tickSpec; StaticTickProvider(this.tickSpec); @override List> getTicks({ required ChartContext? context, required GraphicsFactory graphicsFactory, required MutableScale scale, required TickFormatter formatter, required Map formatterValueCache, required TickDrawStrategy tickDrawStrategy, required AxisOrientation? orientation, bool viewportExtensionEnabled = false, TickHint? tickHint, }) { final ticks = >[]; var allTicksHaveLabels = true; for (final spec in tickSpec) { // When static ticks are being used with a numeric axis, extend the axis // with the values specified. if (scale is NumericScale || scale is DateTimeScale) { scale.addDomain(spec.value); } // Save off whether all ticks have labels. allTicksHaveLabels = allTicksHaveLabels && (spec.label != null); } // Use the formatter's label if the tick spec does not provide one. late List formattedValues; if (!allTicksHaveLabels) { formattedValues = formatter.format( tickSpec.map((spec) => spec.value).toList(), formatterValueCache, stepSize: scale.domainStepSize); } for (var i = 0; i < tickSpec.length; i++) { final spec = tickSpec[i]; // We still check if the spec is within the viewport because we do not // extend the axis for OrdinalScale. if (scale.compareDomainValueToViewport(spec.value) == 0) { final tick = Tick( value: spec.value, textElement: graphicsFactory .createTextElement(spec.label ?? formattedValues[i]), locationPx: scale[spec.value]?.toDouble()); final style = spec.style; if (style != null) { tick.textElement!.textStyle = graphicsFactory.createTextPaint() ..fontFamily = style.fontFamily ..fontSize = style.fontSize ..color = style.color ..lineHeight = style.lineHeight; } ticks.add(tick); } } // Allow draw strategy to decorate the ticks. tickDrawStrategy.decorateTicks(ticks); return ticks; } @override bool operator ==(Object other) => other is StaticTickProvider && tickSpec == other.tickSpec; @override int get hashCode => tickSpec.hashCode; } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/tick.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import '../../../common/text_element.dart'; /// A labeled point on an axis. /// /// [D] is the type of the value this tick is associated with. class Tick { /// The value that this tick represents final D value; /// [TextElement] for this tick. TextElement? textElement; /// Location on the axis where this tick is rendered (in canvas coordinates). double? locationPx; /// Offset of the label for this tick from its location. /// /// This is a vertical offset for ticks on a vertical axis, or horizontal /// offset for ticks on a horizontal axis. double? labelOffsetPx; Tick( {required this.value, required this.textElement, this.locationPx, this.labelOffsetPx}); @override String toString() => 'Tick(value: $value, locationPx: $locationPx, ' 'labelOffsetPx: $labelOffsetPx)'; } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/tick_formatter.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'package:intl/intl.dart'; import '../../common/datum_details.dart' show MeasureFormatter; // TODO: Break out into separate files. /// A strategy used for converting domain values of the ticks into Strings. /// /// [D] is the domain type. abstract class TickFormatter { const TickFormatter(); /// Formats a list of tick values. List format(List tickValues, Map cache, {num? stepSize}); } abstract class SimpleTickFormatterBase implements TickFormatter { const SimpleTickFormatterBase(); @override List format(List tickValues, Map cache, {num? stepSize}) => tickValues.map((value) { // Try to use the cached formats first. var formattedString = cache[value]; if (formattedString == null) { formattedString = formatValue(value); cache[value] = formattedString; } return formattedString; }).toList(); /// Formats a single tick value. String formatValue(D value); } /// A strategy that converts tick labels using toString(). class OrdinalTickFormatter extends SimpleTickFormatterBase { const OrdinalTickFormatter(); @override String formatValue(String value) => value; @override bool operator ==(Object other) => other is OrdinalTickFormatter; @override int get hashCode => 31; } /// A strategy for formatting the labels on numeric ticks using [NumberFormat]. /// /// The default format is [NumberFormat.decimalPattern]. class NumericTickFormatter extends SimpleTickFormatterBase { final MeasureFormatter formatter; NumericTickFormatter._internal(this.formatter); /// Construct a a new [NumericTickFormatter]. /// /// [formatter] optionally specify a formatter to be used. Defaults to using /// [NumberFormat.decimalPattern] if none is specified. factory NumericTickFormatter({MeasureFormatter? formatter}) { formatter ??= _getFormatter(NumberFormat.decimalPattern()); return NumericTickFormatter._internal(formatter); } /// Constructs a new [NumericTickFormatter] that formats using [numberFormat]. factory NumericTickFormatter.fromNumberFormat(NumberFormat numberFormat) { return NumericTickFormatter._internal(_getFormatter(numberFormat)); } /// Constructs a new formatter that uses [NumberFormat.compactCurrency]. factory NumericTickFormatter.compactSimpleCurrency() { return NumericTickFormatter._internal( _getFormatter(NumberFormat.compactCurrency())); } /// Returns a [MeasureFormatter] that calls format on [numberFormat]. static MeasureFormatter _getFormatter(NumberFormat numberFormat) { return (num? value) => (value == null) ? '' : numberFormat.format(value); } @override String formatValue(num value) => formatter(value); @override bool operator ==(Object other) => other is NumericTickFormatter && formatter == other.formatter; @override int get hashCode => formatter.hashCode; } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/tick_provider.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import '../../../common/graphics_factory.dart' show GraphicsFactory; import '../../common/chart_context.dart' show ChartContext; import 'axis.dart' show AxisOrientation; import 'draw_strategy/tick_draw_strategy.dart' show TickDrawStrategy; import 'scale.dart' show MutableScale; import 'tick.dart' show Tick; import 'tick_formatter.dart' show TickFormatter; /// A strategy for selecting values for axis ticks based on the domain values. /// /// [D] is the domain type. abstract class TickProvider { /// Returns a list of ticks in value order that should be displayed. /// /// If no ticks are desired an empty list should be returned. /// /// [graphicsFactory] The graphics factory used for text measurement. /// [scale] The scale of the data. /// [formatter] The formatter to use for generating tick labels. /// [orientation] Orientation of this axis ticks. /// [tickDrawStrategy] Draw strategy for ticks. /// [viewportExtensionEnabled] allow extending the viewport for 'niced' ticks. /// [tickHint] tick values for provider to calculate a desired tick range. List> getTicks({ required ChartContext? context, required GraphicsFactory graphicsFactory, required covariant MutableScale scale, required TickFormatter formatter, required Map formatterValueCache, required TickDrawStrategy tickDrawStrategy, required AxisOrientation? orientation, bool viewportExtensionEnabled = false, TickHint? tickHint, }); } /// A base tick provider. abstract class BaseTickProvider implements TickProvider { const BaseTickProvider(); /// Create ticks from [domainValues]. List> createTicks( List domainValues, { required ChartContext? context, required GraphicsFactory graphicsFactory, required MutableScale scale, required TickFormatter formatter, required Map formatterValueCache, required TickDrawStrategy tickDrawStrategy, num? stepSize, }) { final ticks = >[]; final labels = formatter.format(domainValues, formatterValueCache, stepSize: stepSize); for (var i = 0; i < domainValues.length; i++) { final value = domainValues[i]; final tick = Tick( value: value, textElement: graphicsFactory.createTextElement(labels[i]), locationPx: scale[value]?.toDouble()); ticks.add(tick); } // Allow draw strategy to decorate the ticks. tickDrawStrategy.decorateTicks(ticks); return ticks; } } /// A hint for the tick provider to determine step size and tick count. class TickHint { /// The starting hint tick value. final D start; /// The ending hint tick value. final D end; /// Number of ticks. final int tickCount; TickHint(this.start, this.end, {required this.tickCount}); } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/time/auto_adjusting_date_time_tick_provider.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import '../../../../common/date_time_factory.dart' show DateTimeFactory; import '../../../../common/graphics_factory.dart' show GraphicsFactory; import '../../../common/chart_context.dart' show ChartContext; import '../axis.dart' show AxisOrientation; import '../draw_strategy/tick_draw_strategy.dart' show TickDrawStrategy; import '../tick.dart' show Tick; import '../tick_formatter.dart' show TickFormatter; import '../tick_provider.dart' show TickProvider, TickHint; import 'date_time_scale.dart' show DateTimeScale; import 'day_time_stepper.dart' show DayTimeStepper; import 'hour_time_stepper.dart' show HourTimeStepper; import 'minute_time_stepper.dart' show MinuteTimeStepper; import 'month_time_stepper.dart' show MonthTimeStepper; import 'time_range_tick_provider.dart' show TimeRangeTickProvider; import 'time_range_tick_provider_impl.dart' show TimeRangeTickProviderImpl; import 'year_time_stepper.dart' show YearTimeStepper; /// Tick provider for date and time. /// /// When determining the ticks for a given domain, the provider will use choose /// one of the internal tick providers appropriate to the size of the data's /// domain range. It does this in an attempt to ensure there are at least 3 /// ticks, before jumping to the next more fine grain provider. The 3 tick /// minimum is not a hard rule as some of the ticks might be eliminated because /// of collisions, but the data was within the targeted range. /// /// Once a tick provider is chosen the selection of ticks is done by the child /// tick provider. class AutoAdjustingDateTimeTickProvider implements TickProvider { /// List of tick providers to be selected from. final List _potentialTickProviders; AutoAdjustingDateTimeTickProvider._internal( List tickProviders) : assert(tickProviders.isNotEmpty), _potentialTickProviders = tickProviders; /// Creates a default [AutoAdjustingDateTimeTickProvider] for day and time. factory AutoAdjustingDateTimeTickProvider.createDefault( DateTimeFactory dateTimeFactory) { return AutoAdjustingDateTimeTickProvider._internal([ createYearTickProvider(dateTimeFactory), createMonthTickProvider(dateTimeFactory), createDayTickProvider(dateTimeFactory), createHourTickProvider(dateTimeFactory), createMinuteTickProvider(dateTimeFactory) ]); } /// Creates a default [AutoAdjustingDateTimeTickProvider] for day only. factory AutoAdjustingDateTimeTickProvider.createWithoutTime( DateTimeFactory dateTimeFactory) { return AutoAdjustingDateTimeTickProvider._internal([ createYearTickProvider(dateTimeFactory), createMonthTickProvider(dateTimeFactory), createDayTickProvider(dateTimeFactory) ]); } /// Creates [AutoAdjustingDateTimeTickProvider] with custom tick providers. /// /// [potentialTickProviders] must have at least one [TimeRangeTickProvider] /// and this list of tick providers are used in the order they are provided. factory AutoAdjustingDateTimeTickProvider.createWith( List potentialTickProviders) { if (potentialTickProviders == null || potentialTickProviders.isEmpty) { throw ArgumentError('At least one TimeRangeTickProvider is required'); } return AutoAdjustingDateTimeTickProvider._internal(potentialTickProviders); } /// Generates a list of ticks for the given data which should not collide /// unless the range is not large enough. @override List> getTicks({ required ChartContext? context, required GraphicsFactory graphicsFactory, required DateTimeScale scale, required TickFormatter formatter, required Map formatterValueCache, required TickDrawStrategy tickDrawStrategy, required AxisOrientation? orientation, bool viewportExtensionEnabled = false, TickHint? tickHint, }) { List tickProviders; /// If tick hint is provided, use the closest tick provider, otherwise /// look through the tick providers for one that provides sufficient ticks /// for the viewport. if (tickHint != null) { tickProviders = [_getClosestTickProvider(tickHint)]; } else { tickProviders = _potentialTickProviders; } final lastTickProvider = tickProviders.last; final viewport = scale.viewportDomain; for (final tickProvider in tickProviders) { final isLastProvider = tickProvider == lastTickProvider; if (isLastProvider || tickProvider.providesSufficientTicksForRange(viewport)) { return tickProvider.getTicks( context: context, graphicsFactory: graphicsFactory, scale: scale, formatter: formatter, formatterValueCache: formatterValueCache, tickDrawStrategy: tickDrawStrategy, orientation: orientation, ); } } return >[]; } /// Find the closest tick provider based on the tick hint. TimeRangeTickProvider _getClosestTickProvider(TickHint tickHint) { final stepSize = ((tickHint.end.difference(tickHint.start).inMilliseconds) / (tickHint.tickCount - 1)) .round(); int? minDifference; late TimeRangeTickProvider closestTickProvider; assert(_potentialTickProviders.isNotEmpty); for (final tickProvider in _potentialTickProviders) { final difference = (stepSize - tickProvider.getClosestStepSize(stepSize)).abs(); if (minDifference == null || minDifference > difference) { minDifference = difference; closestTickProvider = tickProvider; } } assert(minDifference != null); return closestTickProvider; } static TimeRangeTickProvider createYearTickProvider( DateTimeFactory dateTimeFactory) => TimeRangeTickProviderImpl(YearTimeStepper(dateTimeFactory)); static TimeRangeTickProvider createMonthTickProvider( DateTimeFactory dateTimeFactory) => TimeRangeTickProviderImpl(MonthTimeStepper(dateTimeFactory)); static TimeRangeTickProvider createDayTickProvider( DateTimeFactory dateTimeFactory) => TimeRangeTickProviderImpl(DayTimeStepper(dateTimeFactory)); static TimeRangeTickProvider createHourTickProvider( DateTimeFactory dateTimeFactory) => TimeRangeTickProviderImpl(HourTimeStepper(dateTimeFactory)); static TimeRangeTickProvider createMinuteTickProvider( DateTimeFactory dateTimeFactory) => TimeRangeTickProviderImpl(MinuteTimeStepper(dateTimeFactory)); } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/time/base_time_stepper.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import '../../../../common/date_time_factory.dart'; import 'date_time_extents.dart' show DateTimeExtents; import 'time_stepper.dart' show TimeStepper, TimeStepIteratorFactory, TimeStepIterator; /// A base stepper for operating with DateTimeFactory and time range steps. abstract class BaseTimeStepper implements TimeStepper { /// The factory to generate a DateTime object. /// /// This is needed because Dart's DateTime does not handle time zone. /// There is a time zone aware library that we could use that implements the /// DateTime interface. final DateTimeFactory dateTimeFactory; _TimeStepIteratorFactoryImpl? _stepsIterable; BaseTimeStepper(this.dateTimeFactory) { // Must have at least one increment option. assert(allowedTickIncrements.isNotEmpty); } /// Get the step time before or on the given [time] from [tickIncrement]. DateTime getStepTimeBeforeInclusive(DateTime time, int tickIncrement); /// Get the next step time after [time] from [tickIncrement]. DateTime getNextStepTime(DateTime time, int tickIncrement); @override int getStepCountBetween(DateTimeExtents timeExtent, int tickIncrement) { checkTickIncrement(tickIncrement); final min = timeExtent.start; final max = timeExtent.end; var time = getStepTimeAfterInclusive(min, tickIncrement); var cnt = 0; while (time.compareTo(max) <= 0) { cnt++; time = getNextStepTime(time, tickIncrement); } return cnt; } @override TimeStepIteratorFactory getSteps(DateTimeExtents timeExtent) { // Keep the steps iterable unless time extent changes, so the same iterator // can be used and reset for different increments. if (_stepsIterable == null || _stepsIterable!.timeExtent != timeExtent) { _stepsIterable = _TimeStepIteratorFactoryImpl(timeExtent, this); } return _stepsIterable!; } @override DateTimeExtents updateBoundingSteps(DateTimeExtents timeExtent) { final stepBefore = getStepTimeBeforeInclusive(timeExtent.start, 1); final stepAfter = getStepTimeAfterInclusive(timeExtent.end, 1); return DateTimeExtents(start: stepBefore, end: stepAfter); } DateTime getStepTimeAfterInclusive(DateTime time, int tickIncrement) { final boundedStart = getStepTimeBeforeInclusive(time, tickIncrement); if (boundedStart.isAtSameMomentAs(time)) { return boundedStart; } return getNextStepTime(boundedStart, tickIncrement); } } class _TimeStepIteratorImpl implements TimeStepIterator { final DateTime extentStartTime; final DateTime extentEndTime; final BaseTimeStepper stepper; DateTime? _current; int _tickIncrement = 1; _TimeStepIteratorImpl( this.extentStartTime, this.extentEndTime, this.stepper) { reset(_tickIncrement); } @override bool moveNext() { if (_current == null) { _current = stepper.getStepTimeAfterInclusive(extentStartTime, _tickIncrement); } else { _current = stepper.getNextStepTime(_current!, _tickIncrement); } return _current!.compareTo(extentEndTime) <= 0; } @override DateTime get current => _current!; @override TimeStepIterator reset(int tickIncrement) { checkTickIncrement(tickIncrement); _tickIncrement = tickIncrement; _current = null; return this; } } class _TimeStepIteratorFactoryImpl extends TimeStepIteratorFactory { final DateTimeExtents timeExtent; final _TimeStepIteratorImpl _timeStepIterator; _TimeStepIteratorFactoryImpl._internal( _TimeStepIteratorImpl timeStepIterator, this.timeExtent) : _timeStepIterator = timeStepIterator; factory _TimeStepIteratorFactoryImpl( DateTimeExtents timeExtent, BaseTimeStepper stepper) { final startTime = timeExtent.start; final endTime = timeExtent.end; return _TimeStepIteratorFactoryImpl._internal( _TimeStepIteratorImpl(startTime, endTime, stepper), timeExtent); } @override TimeStepIterator get iterator => _timeStepIterator; } void checkTickIncrement(int tickIncrement) { /// tickIncrement must be greater than 0 assert(tickIncrement > 0); } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/time/date_time_axis.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import '../../../../common/date_time_factory.dart' show DateTimeFactory; import '../axis.dart' show Axis; import '../tick_formatter.dart' show TickFormatter; import '../tick_provider.dart' show TickProvider; import 'auto_adjusting_date_time_tick_provider.dart' show AutoAdjustingDateTimeTickProvider; import 'date_time_extents.dart' show DateTimeExtents; import 'date_time_scale.dart' show DateTimeScale; import 'date_time_tick_formatter.dart' show DateTimeTickFormatter; class DateTimeAxis extends Axis { DateTimeAxis(DateTimeFactory dateTimeFactory, {TickProvider? tickProvider, TickFormatter? tickFormatter}) : super( tickProvider: tickProvider ?? AutoAdjustingDateTimeTickProvider.createDefault(dateTimeFactory), tickFormatter: tickFormatter ?? DateTimeTickFormatter(dateTimeFactory), scale: DateTimeScale(dateTimeFactory), ); void setScaleViewport(DateTimeExtents viewport) { autoViewport = false; (mutableScale as DateTimeScale).viewportDomain = viewport; } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/time/date_time_extents.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import '../scale.dart' show Extents; class DateTimeExtents extends Extents { final DateTime start; final DateTime end; DateTimeExtents({required this.start, required this.end}); @override bool operator ==(Object other) { return other is DateTimeExtents && start == other.start && end == other.end; } @override int get hashCode => start.hashCode + (end.hashCode * 37); } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/time/date_time_scale.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import '../../../../common/date_time_factory.dart' show DateTimeFactory; import '../linear/linear_scale.dart' show LinearScale; import '../numeric_extents.dart' show NumericExtents; import '../scale.dart' show MutableScale, StepSizeConfig, RangeBandConfig, ScaleOutputExtent; import 'date_time_extents.dart' show DateTimeExtents; /// [DateTimeScale] is a wrapper for [LinearScale]. /// [DateTime] values are converted to millisecondsSinceEpoch and passed to the /// [LinearScale]. class DateTimeScale extends MutableScale { final DateTimeFactory dateTimeFactory; final LinearScale _linearScale; DateTimeScale(this.dateTimeFactory) : _linearScale = LinearScale(); DateTimeScale._copy(DateTimeScale other) : dateTimeFactory = other.dateTimeFactory, _linearScale = other._linearScale.copy(); @override num operator [](DateTime domainValue) => _linearScale[domainValue.millisecondsSinceEpoch]; @override DateTime reverse(double pixelLocation) => dateTimeFactory.createDateTimeFromMilliSecondsSinceEpoch( _linearScale.reverse(pixelLocation).round()); @override void resetDomain() { _linearScale.resetDomain(); } @override set stepSizeConfig(StepSizeConfig config) { _linearScale.stepSizeConfig = config; } @override StepSizeConfig get stepSizeConfig => _linearScale.stepSizeConfig; @override set rangeBandConfig(RangeBandConfig barGroupWidthConfig) { _linearScale.rangeBandConfig = barGroupWidthConfig; } @override void setViewportSettings(double viewportScale, double viewportTranslatePx) { _linearScale.setViewportSettings(viewportScale, viewportTranslatePx); } @override set range(ScaleOutputExtent? extent) { _linearScale.range = extent; } @override void addDomain(DateTime domainValue) { _linearScale.addDomain(domainValue.millisecondsSinceEpoch); } @override void resetViewportSettings() { _linearScale.resetViewportSettings(); } DateTimeExtents get viewportDomain { final extents = _linearScale.viewportDomain; return DateTimeExtents( start: dateTimeFactory .createDateTimeFromMilliSecondsSinceEpoch(extents.min.toInt()), end: dateTimeFactory .createDateTimeFromMilliSecondsSinceEpoch(extents.max.toInt())); } set viewportDomain(DateTimeExtents extents) { _linearScale.viewportDomain = NumericExtents( extents.start.millisecondsSinceEpoch, extents.end.millisecondsSinceEpoch); } @override DateTimeScale copy() => DateTimeScale._copy(this); @override double get viewportTranslatePx => _linearScale.viewportTranslatePx; @override double get viewportScalingFactor => _linearScale.viewportScalingFactor; @override bool isRangeValueWithinViewport(double rangeValue) => _linearScale.isRangeValueWithinViewport(rangeValue); @override int compareDomainValueToViewport(DateTime domainValue) => _linearScale .compareDomainValueToViewport(domainValue.millisecondsSinceEpoch); @override double get rangeBand => _linearScale.rangeBand; @override double get stepSize => _linearScale.stepSize; @override double get domainStepSize => _linearScale.domainStepSize; @override RangeBandConfig get rangeBandConfig => _linearScale.rangeBandConfig; @override int get rangeWidth => _linearScale.rangeWidth; @override ScaleOutputExtent? get range => _linearScale.range; @override bool canTranslate(DateTime domainValue) => _linearScale.canTranslate(domainValue.millisecondsSinceEpoch); NumericExtents get dataExtent => _linearScale.dataExtent; } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/time/date_time_tick_formatter.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import '../../../../common/date_time_factory.dart' show DateTimeFactory; import '../tick_formatter.dart' show TickFormatter; import 'hour_tick_formatter.dart' show HourTickFormatter; import 'time_tick_formatter.dart' show TimeTickFormatter; import 'time_tick_formatter_impl.dart' show CalendarField, TimeTickFormatterImpl; /// A [TickFormatter] that formats date/time values based on minimum difference /// between subsequent ticks. /// /// This formatter assumes that the Tick values passed in are sorted in /// increasing order. /// /// This class is setup with a list of formatters that format the input ticks at /// a given time resolution. The time resolution which will accurately display /// the difference between 2 subsequent ticks is picked. Each time resolution /// can be setup with a [TimeTickFormatter], which is used to format ticks as /// regular or transition ticks based on whether the tick has crossed the time /// boundary defined in the [TimeTickFormatter]. class DateTimeTickFormatter implements TickFormatter { static const int SECOND = 1000; static const int MINUTE = 60 * SECOND; static const int HOUR = 60 * MINUTE; static const int DAY = 24 * HOUR; /// Used for the case when there is only one formatter. static const int ANY = -1; final Map _timeFormatters; /// Creates a [DateTimeTickFormatter] that works well with time tick provider /// classes. /// /// The default formatter makes assumptions on border cases that time tick /// providers will still provide ticks that make sense. Example: Tick provider /// does not provide ticks with 23 hour intervals. For custom tick providers /// where these assumptions are not correct, please create a custom /// [TickFormatter]. factory DateTimeTickFormatter(DateTimeFactory dateTimeFactory, {Map? overrides}) { final map = { MINUTE: TimeTickFormatterImpl( dateTimeFactory: dateTimeFactory, simpleFormat: 'mm', transitionFormat: 'h mm', transitionField: CalendarField.hourOfDay), HOUR: HourTickFormatter( dateTimeFactory: dateTimeFactory, simpleFormat: 'h', transitionFormat: 'MMM d ha', noonFormat: 'ha'), 23 * HOUR: TimeTickFormatterImpl( dateTimeFactory: dateTimeFactory, simpleFormat: 'd', transitionFormat: 'MMM d', transitionField: CalendarField.month), 28 * DAY: TimeTickFormatterImpl( dateTimeFactory: dateTimeFactory, simpleFormat: 'MMM', transitionFormat: 'MMM yyyy', transitionField: CalendarField.year), 364 * DAY: TimeTickFormatterImpl( dateTimeFactory: dateTimeFactory, simpleFormat: 'yyyy', transitionFormat: 'yyyy', transitionField: CalendarField.year), }; // Allow the user to override some of the defaults. if (overrides != null) { map.addAll(overrides); } return DateTimeTickFormatter._internal(map); } /// Creates a [DateTimeTickFormatter] without the time component. factory DateTimeTickFormatter.withoutTime(DateTimeFactory dateTimeFactory) { return DateTimeTickFormatter._internal({ 23 * HOUR: TimeTickFormatterImpl( dateTimeFactory: dateTimeFactory, simpleFormat: 'd', transitionFormat: 'MMM d', transitionField: CalendarField.month), 28 * DAY: TimeTickFormatterImpl( dateTimeFactory: dateTimeFactory, simpleFormat: 'MMM', transitionFormat: 'MMM yyyy', transitionField: CalendarField.year), 365 * DAY: TimeTickFormatterImpl( dateTimeFactory: dateTimeFactory, simpleFormat: 'yyyy', transitionFormat: 'yyyy', transitionField: CalendarField.year), }); } /// Creates a [DateTimeTickFormatter] that formats all ticks the same. /// /// Only use this formatter for data with fixed intervals, otherwise use the /// default, or build from scratch. /// /// [formatter] The format for all ticks. factory DateTimeTickFormatter.uniform(TimeTickFormatter formatter) { return DateTimeTickFormatter._internal({ANY: formatter}); } /// Creates a [DateTimeTickFormatter] that formats ticks with [formatters]. /// /// The formatters are expected to be provided with keys in increasing order. factory DateTimeTickFormatter.withFormatters( Map formatters) { // Formatters must be non empty. if (formatters == null || formatters.isEmpty) { throw ArgumentError('At least one TimeTickFormatter is required.'); } return DateTimeTickFormatter._internal(formatters); } DateTimeTickFormatter._internal(this._timeFormatters) { // If there is only one formatter, just use this one and skip this check. if (_timeFormatters.length == 1) { return; } _checkPositiveAndSorted(_timeFormatters.keys); } @override List format(List tickValues, Map cache, {num? stepSize}) { final tickLabels = []; if (tickValues.isEmpty) { return tickLabels; } // Find the formatter that is the largest interval that has enough // resolution to describe the difference between ticks. If no such formatter // exists pick the highest res one. var formatter = _timeFormatters[_timeFormatters.keys.first]!; var formatterFound = false; if (_timeFormatters.keys.first == ANY) { formatterFound = true; } else { final minTimeBetweenTicks = stepSize?.toInt() ?? 0; // TODO: Skip the formatter if the formatter's step size is // smaller than the minimum step size of the data. var keys = _timeFormatters.keys.iterator; while (keys.moveNext() && !formatterFound) { if (keys.current > minTimeBetweenTicks) { formatterFound = true; } else { formatter = _timeFormatters[keys.current]!; } } } // Format the ticks. final tickValuesIt = tickValues.iterator; var tickValue = (tickValuesIt..moveNext()).current; var prevTickValue = tickValue; tickLabels.add(formatter.formatFirstTick(tickValue)); while (tickValuesIt.moveNext()) { tickValue = tickValuesIt.current; if (formatter.isTransition(tickValue, prevTickValue)) { tickLabels.add(formatter.formatTransitionTick(tickValue)); } else { tickLabels.add(formatter.formatSimpleTick(tickValue)); } prevTickValue = tickValue; } return tickLabels; } static void _checkPositiveAndSorted(Iterable values) { final valuesIterator = values.iterator; var prev = (valuesIterator..moveNext()).current; var isSorted = true; // Only need to check the first value, because the values after are expected // to be greater. if (prev <= 0) { throw ArgumentError('Formatter keys must be positive'); } while (valuesIterator.moveNext() && isSorted) { isSorted = prev < valuesIterator.current; prev = valuesIterator.current; } if (!isSorted) { throw ArgumentError( 'Formatters must be sorted with keys in increasing order'); } } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/time/day_time_stepper.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import '../../../../common/date_time_factory.dart' show DateTimeFactory; import 'base_time_stepper.dart' show BaseTimeStepper; /// Day stepper. class DayTimeStepper extends BaseTimeStepper { // TODO: Remove the 14 day increment if we add week stepper. static const _defaultIncrements = [1, 2, 3, 7, 14]; static const _hoursInDay = 24; final List _allowedTickIncrements; DayTimeStepper._internal( DateTimeFactory dateTimeFactory, List increments) : _allowedTickIncrements = increments, super(dateTimeFactory); factory DayTimeStepper(DateTimeFactory dateTimeFactory, {List? allowedTickIncrements}) { // Set the default increments if null. allowedTickIncrements ??= _defaultIncrements; assert(allowedTickIncrements.every((increment) => increment > 0)); return DayTimeStepper._internal(dateTimeFactory, allowedTickIncrements); } @override int get typicalStepSizeMs => _hoursInDay * 3600 * 1000; @override List get allowedTickIncrements => _allowedTickIncrements; /// Get the step time before or on the given [time] from [tickIncrement]. /// /// Increments are based off the beginning of the month. /// Ex. 5 day increments in a month is 1,6,11,16,21,26,31 /// Ex. Time is Aug 20, increment is 1 day. Returns Aug 20. /// Ex. Time is Aug 20, increment is 2 days. Returns Aug 19 because 2 day /// increments in a month is 1,3,5,7,9,11,13,15,17,19,21.... @override DateTime getStepTimeBeforeInclusive(DateTime time, int tickIncrement) { final dayRemainder = (time.day - 1) % tickIncrement; // Subtract an extra hour in case stepping through a daylight saving change. final dayBefore = dayRemainder > 0 ? time.subtract(Duration(hours: (_hoursInDay * dayRemainder) - 1)) : time; // Explicitly leaving off hours and beyond to truncate to start of day. final stepBefore = dateTimeFactory.createDateTime( dayBefore.year, dayBefore.month, dayBefore.day); return stepBefore; } @override DateTime getNextStepTime(DateTime time, int tickIncrement) { // Add an extra hour in case stepping through a daylight saving change. final stepAfter = time.add(Duration(hours: (_hoursInDay * tickIncrement) + 1)); // Explicitly leaving off hours and beyond to truncate to start of day. return dateTimeFactory.createDateTime( stepAfter.year, stepAfter.month, stepAfter.day); } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/time/hour_tick_formatter.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'package:intl/intl.dart' show DateFormat; import '../../../../common/date_time_factory.dart'; import 'time_tick_formatter_impl.dart' show CalendarField, TimeTickFormatterImpl; /// Hour specific tick formatter which will format noon differently. class HourTickFormatter extends TimeTickFormatterImpl { late final DateFormat _noonFormat; HourTickFormatter( {required DateTimeFactory dateTimeFactory, required String? simpleFormat, required String? transitionFormat, required String? noonFormat}) : super( dateTimeFactory: dateTimeFactory, simpleFormat: simpleFormat, transitionFormat: transitionFormat, transitionField: CalendarField.date) { _noonFormat = dateTimeFactory.createDateFormat(noonFormat); } @override String formatSimpleTick(DateTime date) { return (date.hour == 12) ? _noonFormat.format(date) : super.formatSimpleTick(date); } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/time/hour_time_stepper.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import '../../../../common/date_time_factory.dart' show DateTimeFactory; import 'base_time_stepper.dart' show BaseTimeStepper; /// Hour stepper. class HourTimeStepper extends BaseTimeStepper { static const _defaultIncrements = [1, 2, 3, 4, 6, 12, 24]; static const _hoursInDay = 24; static const _millisecondsInHour = 3600 * 1000; final List _allowedTickIncrements; HourTimeStepper._internal( DateTimeFactory dateTimeFactory, List increments) : _allowedTickIncrements = increments, super(dateTimeFactory); factory HourTimeStepper(DateTimeFactory dateTimeFactory, {List? allowedTickIncrements}) { // Set the default increments if null. allowedTickIncrements ??= _defaultIncrements; assert(allowedTickIncrements .every((increment) => increment >= 1 && increment <= 24)); return HourTimeStepper._internal(dateTimeFactory, allowedTickIncrements); } @override int get typicalStepSizeMs => _millisecondsInHour; @override List get allowedTickIncrements => _allowedTickIncrements; /// Get the step time before or on the given [time] from [tickIncrement]. /// /// Guarantee a step at the start of the next day. /// Ex. Time is Aug 20 10 AM, increment is 1 hour. Returns 10 AM. /// Ex. Time is Aug 20 6 AM, increment is 4 hours. Returns 4 AM. @override DateTime getStepTimeBeforeInclusive(DateTime time, int tickIncrement) { final nextDay = dateTimeFactory .createDateTime(time.year, time.month, time.day) .add(Duration(hours: _hoursInDay + 1)); final nextDayStart = dateTimeFactory.createDateTime( nextDay.year, nextDay.month, nextDay.day); final hoursToNextDay = ((nextDayStart.millisecondsSinceEpoch - time.millisecondsSinceEpoch) / _millisecondsInHour) .ceil(); final hoursRemainder = hoursToNextDay % tickIncrement; final rewindHours = hoursRemainder == 0 ? 0 : tickIncrement - hoursRemainder; final stepBefore = dateTimeFactory.createDateTime( time.year, time.month, time.day, time.hour - rewindHours); return stepBefore; } /// Get next step time. /// /// [time] is expected to be a [DateTime] with the hour at start of the hour. @override DateTime getNextStepTime(DateTime time, int tickIncrement) { return time.add(Duration(hours: tickIncrement)); } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/time/minute_time_stepper.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import '../../../../common/date_time_factory.dart' show DateTimeFactory; import 'base_time_stepper.dart'; /// Minute stepper where ticks generated aligns with the hour. class MinuteTimeStepper extends BaseTimeStepper { static const _defaultIncrements = [5, 10, 15, 20, 30]; static const _millisecondsInMinute = 60 * 1000; final List _allowedTickIncrements; MinuteTimeStepper._internal( DateTimeFactory dateTimeFactory, List increments) : _allowedTickIncrements = increments, super(dateTimeFactory); factory MinuteTimeStepper(DateTimeFactory dateTimeFactory, {List? allowedTickIncrements}) { // Set the default increments if null. allowedTickIncrements ??= _defaultIncrements; assert(allowedTickIncrements .every((increment) => increment >= 1 && increment <= 60)); return MinuteTimeStepper._internal(dateTimeFactory, allowedTickIncrements); } @override int get typicalStepSizeMs => _millisecondsInMinute; @override List get allowedTickIncrements => _allowedTickIncrements; /// Picks a tick start time that guarantees the start of the hour is included. /// /// Ex. Time is 3:46, increments is 5 minutes, step before is 3:45, because /// we can guarantee a step at 4:00. @override DateTime getStepTimeBeforeInclusive(DateTime time, int tickIncrement) { final nextHourStart = time.millisecondsSinceEpoch + (60 - time.minute) * _millisecondsInMinute; final minutesToNextHour = ((nextHourStart - time.millisecondsSinceEpoch) / _millisecondsInMinute) .ceil(); final minRemainder = minutesToNextHour % tickIncrement; final rewindMinutes = minRemainder == 0 ? 0 : tickIncrement - minRemainder; final stepBefore = dateTimeFactory.createDateTimeFromMilliSecondsSinceEpoch( time.millisecondsSinceEpoch - rewindMinutes * _millisecondsInMinute); return stepBefore; } @override DateTime getNextStepTime(DateTime time, int tickIncrement) { return time.add(Duration(minutes: tickIncrement)); } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/time/month_time_stepper.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import '../../../../common/date_time_factory.dart' show DateTimeFactory; import 'base_time_stepper.dart' show BaseTimeStepper; /// Month stepper. class MonthTimeStepper extends BaseTimeStepper { static const _defaultIncrements = [1, 2, 3, 4, 6, 12]; final List _allowedTickIncrements; MonthTimeStepper._internal( DateTimeFactory dateTimeFactory, List increments) : _allowedTickIncrements = increments, super(dateTimeFactory); factory MonthTimeStepper(DateTimeFactory dateTimeFactory, {List? allowedTickIncrements}) { // Set the default increments if null. allowedTickIncrements ??= _defaultIncrements; assert(allowedTickIncrements.every((increment) => increment > 0)); return MonthTimeStepper._internal(dateTimeFactory, allowedTickIncrements); } @override int get typicalStepSizeMs => 30 * 24 * 3600 * 1000; @override List get allowedTickIncrements => _allowedTickIncrements; /// Guarantee a step ending in the last month of the year. /// /// If date is 2017 Oct and increments is 6, the step before is 2017 June. @override DateTime getStepTimeBeforeInclusive(DateTime time, int tickIncrement) { final monthRemainder = time.month % tickIncrement; var newMonth = (time.month - monthRemainder) % DateTime.monthsPerYear; // Handles the last month of the year (December) edge case. // Ex. When month is December and increment is 1 if (time.month == DateTime.monthsPerYear && newMonth == 0) { newMonth = DateTime.monthsPerYear; } final newYear = time.year - (monthRemainder / DateTime.monthsPerYear).floor(); return dateTimeFactory.createDateTime(newYear, newMonth); } @override DateTime getNextStepTime(DateTime time, int tickIncrement) { final incrementedMonth = time.month + tickIncrement; final newMonth = incrementedMonth % DateTime.monthsPerYear; final newYear = time.year + (incrementedMonth / DateTime.monthsPerYear).floor(); return dateTimeFactory.createDateTime(newYear, newMonth); } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/time/simple_time_tick_formatter.dart ================================================ // Copyright 2019 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'time_tick_formatter.dart' show TimeTickFormatter; typedef DateTimeFormatterFunction = String Function(DateTime datetime); /// Formatter that formats all ticks using a single [DateTimeFormatterFunction]. class SimpleTimeTickFormatter implements TimeTickFormatter { DateTimeFormatterFunction formatter; SimpleTimeTickFormatter({required this.formatter}); @override String formatFirstTick(DateTime date) => formatter(date); @override String formatSimpleTick(DateTime date) => formatter(date); @override String formatTransitionTick(DateTime date) => formatter(date); // Transition fields don't matter here. @override bool isTransition(DateTime tickValue, DateTime prevTickValue) => false; } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/time/time_range_tick_provider.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import '../tick_provider.dart' show BaseTickProvider; import '../time/date_time_extents.dart' show DateTimeExtents; /// Provides ticks for a particular time unit. /// /// Used by [AutoAdjustingDateTimeTickProvider]. abstract class TimeRangeTickProvider extends BaseTickProvider { /// Returns if this tick provider will produce a sufficient number of ticks /// for [domainExtents]. bool providesSufficientTicksForRange(DateTimeExtents domainExtents); /// Find the closet step size, from provided step size, in milliseconds. int getClosestStepSize(int stepSize); } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/time/time_range_tick_provider_impl.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import '../../../../common/graphics_factory.dart' show GraphicsFactory; import '../../../common/chart_context.dart' show ChartContext; import '../axis.dart' show AxisOrientation; import '../draw_strategy/tick_draw_strategy.dart' show TickDrawStrategy; import '../tick.dart' show Tick; import '../tick_formatter.dart' show TickFormatter; import '../tick_provider.dart' show TickHint; import 'date_time_extents.dart' show DateTimeExtents; import 'date_time_scale.dart' show DateTimeScale; import 'time_range_tick_provider.dart' show TimeRangeTickProvider; import 'time_stepper.dart' show TimeStepper; // Contains all the common code for the time range tick providers. class TimeRangeTickProviderImpl extends TimeRangeTickProvider { final int requiredMinimumTicks; final TimeStepper timeStepper; TimeRangeTickProviderImpl(this.timeStepper, {this.requiredMinimumTicks = 3}); @override bool providesSufficientTicksForRange(DateTimeExtents domainExtents) { final cnt = timeStepper.getStepCountBetween(domainExtents, 1); return cnt >= requiredMinimumTicks; } /// Find the closet step size, from provided step size, in milliseconds. @override int getClosestStepSize(int stepSize) { return timeStepper.typicalStepSizeMs * _getClosestIncrementFromStepSize(stepSize); } // Find the increment that is closest to the step size. int _getClosestIncrementFromStepSize(int stepSize) { int? minDifference; late int closestIncrement; assert(timeStepper.allowedTickIncrements.isNotEmpty); for (final increment in timeStepper.allowedTickIncrements) { final difference = (stepSize - (timeStepper.typicalStepSizeMs * increment)).abs(); if (minDifference == null || minDifference > difference) { minDifference = difference; closestIncrement = increment; } } return closestIncrement; } @override List> getTicks({ required ChartContext? context, required GraphicsFactory graphicsFactory, required DateTimeScale scale, required TickFormatter formatter, required Map formatterValueCache, required TickDrawStrategy tickDrawStrategy, required AxisOrientation? orientation, bool viewportExtensionEnabled = false, TickHint? tickHint, }) { late List> currentTicks; final tickValues = []; final timeStepIt = timeStepper.getSteps(scale.viewportDomain).iterator; // Try different tickIncrements and choose the first that has no collisions. // If none exist use the last one which should have the fewest ticks and // hope that the renderer will resolve collisions. // // If a tick hint was provided, use the tick hint to search for the closest // increment and use that. List allowedTickIncrements; if (tickHint != null) { final stepSize = tickHint.end.difference(tickHint.start).inMilliseconds; allowedTickIncrements = [_getClosestIncrementFromStepSize(stepSize)]; } else { allowedTickIncrements = timeStepper.allowedTickIncrements; } assert(allowedTickIncrements.isNotEmpty); for (final tickIncrement in allowedTickIncrements) { // Create tick values with a specified increment. tickValues.clear(); timeStepIt.reset(tickIncrement); while (timeStepIt.moveNext()) { tickValues.add(timeStepIt.current); } // Create ticks currentTicks = createTicks(tickValues, context: context, graphicsFactory: graphicsFactory, scale: scale, formatter: formatter, formatterValueCache: formatterValueCache, tickDrawStrategy: tickDrawStrategy, stepSize: timeStepper.typicalStepSizeMs * tickIncrement); // Request collision check from draw strategy. final collisionReport = tickDrawStrategy.collides(currentTicks, orientation); if (!collisionReport.ticksCollide) { // Return the first non colliding ticks. return currentTicks; } } // If all ticks collide, return the last generated ticks. return currentTicks; } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/time/time_stepper.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'date_time_extents.dart' show DateTimeExtents; /// Represents the step/tick information for the given time range. abstract class TimeStepper { /// Get new bounding extents to the ticks that would contain the given /// timeExtents. DateTimeExtents updateBoundingSteps(DateTimeExtents timeExtents); /// Returns the number steps/ticks are between the given extents inclusive. /// /// Does not extend the extents to the bounding ticks. int getStepCountBetween(DateTimeExtents timeExtents, int tickIncrement); /// Generates an Iterable for iterating over the time steps bounded by the /// given timeExtents. The desired tickIncrement can be set on the returned /// [TimeStepIteratorFactory]. TimeStepIteratorFactory getSteps(DateTimeExtents timeExtents); /// Returns the typical stepSize for this stepper assuming increment by 1. int get typicalStepSizeMs; /// An ordered list of step increments that makes sense given the step. /// /// Should never be empty. /// /// Example: hours may increment by 1, 2, 3, 4, 6, 12. It doesn't make sense /// to increment hours by 7. List get allowedTickIncrements; } /// Iterator with a reset function that can be used multiple times to avoid /// object instantiation during the Android layout/draw phases. abstract class TimeStepIterator extends Iterator { /// Reset the iterator and set the tickIncrement to the specified value. /// /// This method is provided so that the same iterator instance can be used for /// different tick increments, avoiding object allocation during Android /// layout/draw phases. TimeStepIterator reset(int tickIncrement); } /// Factory that creates TimeStepIterator with the set tickIncrement value. abstract class TimeStepIteratorFactory extends Iterable { /// Get iterator and optionally set the tickIncrement. @override TimeStepIterator get iterator; } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/time/time_tick_formatter.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /// Formatter of [DateTime] ticks abstract class TimeTickFormatter { /// Format for tick that is the first in a set of ticks. String formatFirstTick(DateTime date); /// Format for a 'simple' tick. /// /// Ex. Not a first tick or transition tick. String formatSimpleTick(DateTime date); /// Format for a transitional tick. String formatTransitionTick(DateTime date); /// Returns true if tick is a transitional tick. bool isTransition(DateTime tickValue, DateTime prevTickValue); } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/time/time_tick_formatter_impl.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'package:intl/intl.dart' show DateFormat; import '../../../../common/date_time_factory.dart' show DateTimeFactory; import 'time_tick_formatter.dart' show TimeTickFormatter; /// Formatter that can format simple and transition time ticks differently. class TimeTickFormatterImpl implements TimeTickFormatter { final DateFormat _simpleFormat; final DateFormat _transitionFormat; final CalendarField? transitionField; /// Create time tick formatter. /// /// [dateTimeFactory] factory to use to generate the [DateFormat]. /// [simpleFormat] format to use for most ticks. /// [transitionFormat] format to use when the time unit transitions. /// For example showing the month with the date for Jan 1. /// [transitionField] the calendar field that indicates transition. TimeTickFormatterImpl({ required DateTimeFactory dateTimeFactory, required String? simpleFormat, required String? transitionFormat, this.transitionField, }) : _simpleFormat = dateTimeFactory.createDateFormat(simpleFormat), _transitionFormat = dateTimeFactory.createDateFormat(transitionFormat); @override String formatFirstTick(DateTime date) => _transitionFormat.format(date); @override String formatSimpleTick(DateTime date) => _simpleFormat.format(date); @override String formatTransitionTick(DateTime date) => _transitionFormat.format(date); @override bool isTransition(DateTime tickValue, DateTime prevTickValue) { // Transition is always false if no transition field is specified. final transitionField = this.transitionField; if (transitionField == null) { return false; } final prevTransitionFieldValue = getCalendarField(prevTickValue, transitionField); final transitionFieldValue = getCalendarField(tickValue, transitionField); return prevTransitionFieldValue != transitionFieldValue; } /// Gets the calendar field for [dateTime]. int getCalendarField(DateTime dateTime, CalendarField field) { switch (field) { case CalendarField.year: return dateTime.year; case CalendarField.month: return dateTime.month; case CalendarField.date: return dateTime.day; case CalendarField.hourOfDay: return dateTime.hour; case CalendarField.minute: return dateTime.minute; case CalendarField.second: return dateTime.second; } } } enum CalendarField { year, month, date, hourOfDay, minute, second, } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/axis/time/year_time_stepper.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import '../../../../common/date_time_factory.dart' show DateTimeFactory; import 'base_time_stepper.dart' show BaseTimeStepper; /// Year stepper. class YearTimeStepper extends BaseTimeStepper { static const _defaultIncrements = [1, 2, 5, 10, 50, 100, 500, 1000]; final List _allowedTickIncrements; YearTimeStepper._internal( DateTimeFactory dateTimeFactory, List increments) : _allowedTickIncrements = increments, super(dateTimeFactory); factory YearTimeStepper(DateTimeFactory dateTimeFactory, {List? allowedTickIncrements}) { // Set the default increments if null. allowedTickIncrements ??= _defaultIncrements; assert(allowedTickIncrements.every((increment) => increment > 0)); return YearTimeStepper._internal(dateTimeFactory, allowedTickIncrements); } @override int get typicalStepSizeMs => 365 * 24 * 3600 * 1000; @override List get allowedTickIncrements => _allowedTickIncrements; /// Guarantees the increment is a factor of the tick value. /// /// Example: 2017, tick increment of 10, step before is 2010. @override DateTime getStepTimeBeforeInclusive(DateTime time, int tickIncrement) { final yearRemainder = time.year % tickIncrement; return dateTimeFactory.createDateTime(time.year - yearRemainder); } @override DateTime getNextStepTime(DateTime time, int tickIncrement) { return dateTimeFactory.createDateTime(time.year + tickIncrement); } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/cartesian_chart.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:collection' show LinkedHashMap; import 'package:meta/meta.dart' show protected; import '../../common/graphics_factory.dart' show GraphicsFactory; import '../../data/series.dart' show Series; import '../bar/bar_renderer.dart' show BarRenderer; import '../common/base_chart.dart' show BaseChart; import '../common/chart_context.dart' show ChartContext; import '../common/datum_details.dart' show DatumDetails; import '../common/processed_series.dart' show MutableSeries; import '../common/selection_model/selection_model.dart' show SelectionModelType; import '../common/series_renderer.dart' show SeriesRenderer, rendererIdKey; import '../layout/layout_config.dart' show LayoutConfig, MarginSpec; import '../layout/layout_view.dart' show LayoutViewPaintOrder; import 'axis/axis.dart' show Axis, AxisOrientation, OrdinalAxis, NumericAxis, domainAxisKey, measureAxisIdKey, measureAxisKey; import 'axis/draw_strategy/gridline_draw_strategy.dart' show GridlineRendererSpec; import 'axis/draw_strategy/none_draw_strategy.dart' show NoneDrawStrategy; import 'axis/draw_strategy/small_tick_draw_strategy.dart' show SmallTickRendererSpec; import 'axis/spec/axis_spec.dart' show AxisSpec; import 'axis/spec/numeric_axis_spec.dart' show NumericAxisSpec; class NumericCartesianChart extends CartesianChart { NumericCartesianChart( {bool? vertical, LayoutConfig? layoutConfig, NumericAxis? primaryMeasureAxis, NumericAxis? secondaryMeasureAxis, LinkedHashMap? disjointMeasureAxes}) : super( vertical: vertical, layoutConfig: layoutConfig, domainAxis: NumericAxis(), primaryMeasureAxis: primaryMeasureAxis, secondaryMeasureAxis: secondaryMeasureAxis, disjointMeasureAxes: disjointMeasureAxes); @protected @override void initDomainAxis() { _domainAxis!.tickDrawStrategy = SmallTickRendererSpec() .createDrawStrategy(context, graphicsFactory!); } } class OrdinalCartesianChart extends CartesianChart { OrdinalCartesianChart( {bool? vertical, LayoutConfig? layoutConfig, NumericAxis? primaryMeasureAxis, NumericAxis? secondaryMeasureAxis, LinkedHashMap? disjointMeasureAxes}) : super( vertical: vertical, layoutConfig: layoutConfig, domainAxis: OrdinalAxis(), primaryMeasureAxis: primaryMeasureAxis, secondaryMeasureAxis: secondaryMeasureAxis, disjointMeasureAxes: disjointMeasureAxes); @protected @override void initDomainAxis() { _domainAxis!.tickDrawStrategy = SmallTickRendererSpec() .createDrawStrategy(context, graphicsFactory!); } } abstract class CartesianChart extends BaseChart { static final _defaultLayoutConfig = LayoutConfig( topSpec: MarginSpec.fromPixel(minPixel: 20), bottomSpec: MarginSpec.fromPixel(minPixel: 20), leftSpec: MarginSpec.fromPixel(minPixel: 20), rightSpec: MarginSpec.fromPixel(minPixel: 20), ); bool vertical; /// The current domain axis for this chart. Axis? _domainAxis; /// Temporarily stores the new domain axis that is passed in the constructor /// and the new domain axis created when [domainAxisSpec] is set to a new /// spec. /// /// This step is necessary because the axis cannot be fully configured until /// [context] is available. [configurationChanged] is called after [context] /// is available and [_newDomainAxis] will be set to [_domainAxis] and then /// reset back to null. Axis? _newDomainAxis; /// The current domain axis spec that was used to configure [_domainAxis]. /// /// This is kept to check if the axis spec has changed when [domainAxisSpec] /// is set. AxisSpec? _domainAxisSpec; /// Temporarily stores the new domain axis spec that is passed in when /// [domainAxisSpec] is set and is different from [_domainAxisSpec]. This spec /// is then applied to the new domain axis when [configurationChanged] is /// called. AxisSpec? _newDomainAxisSpec; NumericAxisSpec? _primaryMeasureAxisSpec; NumericAxisSpec? _newPrimaryMeasureAxisSpec; NumericAxis _primaryMeasureAxis; NumericAxisSpec? _secondaryMeasureAxisSpec; NumericAxisSpec? _newSecondaryMeasureAxisSpec; NumericAxis _secondaryMeasureAxis; LinkedHashMap? _disjointMeasureAxesSpec; LinkedHashMap? _newDisjointMeasureAxesSpec; LinkedHashMap _disjointMeasureAxes; /// If set to true, the vertical axis will render the opposite of the default /// direction. bool flipVerticalAxisOutput = false; bool _usePrimaryMeasureAxis = false; bool _useSecondaryMeasureAxis = false; CartesianChart( {bool? vertical, LayoutConfig? layoutConfig, Axis? domainAxis, NumericAxis? primaryMeasureAxis, NumericAxis? secondaryMeasureAxis, LinkedHashMap? disjointMeasureAxes}) : vertical = vertical ?? true, // [domainAxis] will be set to the new axis in [configurationChanged]. _newDomainAxis = domainAxis, _primaryMeasureAxis = primaryMeasureAxis ?? NumericAxis(), _secondaryMeasureAxis = secondaryMeasureAxis ?? NumericAxis(), _disjointMeasureAxes = // ignore: prefer_collection_literals disjointMeasureAxes ?? LinkedHashMap(), super(layoutConfig: layoutConfig ?? _defaultLayoutConfig); @override void init(ChartContext context, GraphicsFactory graphicsFactory) { super.init(context, graphicsFactory); _primaryMeasureAxis.context = context; _primaryMeasureAxis.tickDrawStrategy = GridlineRendererSpec() .createDrawStrategy(context, graphicsFactory); _secondaryMeasureAxis.context = context; _secondaryMeasureAxis.tickDrawStrategy = GridlineRendererSpec() .createDrawStrategy(context, graphicsFactory); _disjointMeasureAxes.forEach((String axisId, NumericAxis axis) { axis.context = context; axis.tickDrawStrategy = NoneDrawStrategy(context, graphicsFactory); }); } @override void updateConfig(LayoutConfig? layoutConfig) { super.updateConfig(layoutConfig ?? _defaultLayoutConfig); } Axis? get domainAxis => _domainAxis; /// Allows the chart to configure the domain axis when it is created. @protected void initDomainAxis(); /// Create a new domain axis and save the new spec to be applied during /// [configurationChanged]. set domainAxisSpec(AxisSpec axisSpec) { if (_domainAxisSpec != axisSpec) { _newDomainAxis = createDomainAxisFromSpec(axisSpec); _newDomainAxisSpec = axisSpec; } } /// Creates the domain axis from a provided axis spec. @protected Axis? createDomainAxisFromSpec(AxisSpec axisSpec) { return axisSpec.createAxis(); } @override void configurationChanged() { if (_newDomainAxis != null) { markChartDirty(); if (_domainAxis != null) { removeView(_domainAxis!); } _domainAxis = _newDomainAxis; _domainAxis! ..context = context ..layoutPaintOrder = LayoutViewPaintOrder.domainAxis; initDomainAxis(); addView(_domainAxis!); _newDomainAxis = null; } if (_newDomainAxisSpec != null) { markChartDirty(); _domainAxisSpec = _newDomainAxisSpec; _newDomainAxisSpec!.configure(_domainAxis!, context, graphicsFactory!); _newDomainAxisSpec = null; } if (_primaryMeasureAxisSpec != _newPrimaryMeasureAxisSpec) { markChartDirty(); _primaryMeasureAxisSpec = _newPrimaryMeasureAxisSpec; removeView(_primaryMeasureAxis); _primaryMeasureAxis = _primaryMeasureAxisSpec?.createAxis() ?? NumericAxis(); _primaryMeasureAxis.tickDrawStrategy = GridlineRendererSpec() .createDrawStrategy(context, graphicsFactory!); _primaryMeasureAxisSpec?.configure( _primaryMeasureAxis, context, graphicsFactory!); addView(_primaryMeasureAxis); } if (_secondaryMeasureAxisSpec != _newSecondaryMeasureAxisSpec) { markChartDirty(); _secondaryMeasureAxisSpec = _newSecondaryMeasureAxisSpec; removeView(_secondaryMeasureAxis); _secondaryMeasureAxis = _secondaryMeasureAxisSpec?.createAxis() ?? NumericAxis(); _secondaryMeasureAxis.tickDrawStrategy = GridlineRendererSpec() .createDrawStrategy(context, graphicsFactory!); _secondaryMeasureAxisSpec?.configure( _secondaryMeasureAxis, context, graphicsFactory!); addView(_secondaryMeasureAxis); } if (_disjointMeasureAxesSpec != _newDisjointMeasureAxesSpec) { markChartDirty(); _disjointMeasureAxesSpec = _newDisjointMeasureAxesSpec; _disjointMeasureAxes.forEach((String axisId, NumericAxis axis) { removeView(axis); }); // ignore: prefer_collection_literals, https://github.com/dart-lang/linter/issues/1649 _disjointMeasureAxes = LinkedHashMap(); _disjointMeasureAxesSpec?.forEach((axisId, axisSpec) { _disjointMeasureAxes[axisId] = axisSpec.createAxis(); _disjointMeasureAxes[axisId]!.tickDrawStrategy = NoneDrawStrategy(context, graphicsFactory!); axisSpec.configure( _disjointMeasureAxes[axisId]!, context, graphicsFactory!); addView(_disjointMeasureAxes[axisId]!); }); } } /// Gets the measure axis matching the provided id. /// /// If none is provided, this returns the primary measure axis. NumericAxis getMeasureAxis({String? axisId}) { NumericAxis? axis; if (axisId == Axis.secondaryMeasureAxisId) { axis = _secondaryMeasureAxis; } else if (axisId == Axis.primaryMeasureAxisId) { axis = _primaryMeasureAxis; } else if (axisId != null && _disjointMeasureAxes[axisId] != null) { axis = _disjointMeasureAxes[axisId]; } // If no valid axisId was provided, fall back to primary axis. axis ??= _primaryMeasureAxis; return axis; } /// Sets the primary measure axis for the chart, rendered on the start side of /// the domain axis. set primaryMeasureAxisSpec(NumericAxisSpec? axisSpec) { _newPrimaryMeasureAxisSpec = axisSpec; // Must set the spec to the current axis instance in the case of // errant reads that expect the spec to be changed. axisSpec?.configure(_primaryMeasureAxis, context, graphicsFactory!); } /// Sets the secondary measure axis for the chart, rendered on the end side of /// the domain axis. set secondaryMeasureAxisSpec(NumericAxisSpec? axisSpec) { _newSecondaryMeasureAxisSpec = axisSpec; // Must set the spec to the current axis instance in the case of // errant reads that expect the spec to be changed. axisSpec?.configure(_secondaryMeasureAxis, context, graphicsFactory!); } /// Sets a map of disjoint measure axes for the chart. /// /// Disjoint measure axes can be used to scale a sub-set of series on the /// chart independently from the primary and secondary axes. The general use /// case for this type of chart is to show differences in the trends of the /// data, without comparing their absolute values. /// /// Disjoint axes will not render any tick or gridline elements. With /// independent scales, there would be a lot of collision in labels were they /// to do so. /// /// If any series is rendered with a disjoint axis, it is highly recommended /// to render all series with disjoint axes. Otherwise, the chart may be /// visually misleading. /// /// A [LinkedHashMap] is used to ensure consistent ordering when painting the /// axes. set disjointMeasureAxisSpecs( LinkedHashMap? axisSpecs) { _newDisjointMeasureAxesSpec = axisSpecs; // Must set the spec to the current axis instance in the case of // errant reads that expect the spec to be changed. axisSpecs?.forEach((axisId, axisSpec) { if (_disjointMeasureAxes.containsKey(axisId)) { axisSpec.configure( _disjointMeasureAxes[axisId]!, context, graphicsFactory!); } }); } @override MutableSeries makeSeries(Series series) { final s = super.makeSeries(series); s.measureOffsetFn ??= (_) => 0; // Setup the Axes s.setAttr(domainAxisKey, domainAxis); s.setAttr(measureAxisKey, getMeasureAxis(axisId: series.getAttribute(measureAxisIdKey))); return s; } @override SeriesRenderer makeDefaultRenderer() { return BarRenderer()..rendererId = SeriesRenderer.defaultRendererId; } @override Map>> preprocessSeries( List> seriesList) { var rendererToSeriesList = super.preprocessSeries(seriesList); _useSecondaryMeasureAxis = false; // Check if primary or secondary measure axis is being used. for (final series in seriesList) { final measureAxisId = series.getAttr(measureAxisIdKey); _usePrimaryMeasureAxis = _usePrimaryMeasureAxis || (measureAxisId == null || measureAxisId == Axis.primaryMeasureAxisId); _useSecondaryMeasureAxis = _useSecondaryMeasureAxis || (measureAxisId == Axis.secondaryMeasureAxisId); } // Add or remove the primary axis view. if (_usePrimaryMeasureAxis) { addView(_primaryMeasureAxis); } else { removeView(_primaryMeasureAxis); } // Add or remove the secondary axis view. if (_useSecondaryMeasureAxis) { addView(_secondaryMeasureAxis); } else { removeView(_secondaryMeasureAxis); } // Add all disjoint axis views so that their range will be configured. _disjointMeasureAxes.forEach((String axisId, NumericAxis axis) { addView(axis); }); final domainAxis = this.domainAxis!; // Reset stale values from previous draw cycles. domainAxis.resetDomains(); _primaryMeasureAxis.resetDomains(); _secondaryMeasureAxis.resetDomains(); _disjointMeasureAxes.forEach((String axisId, NumericAxis axis) { axis.resetDomains(); }); final reverseAxisDirection = context != null && context.isRtl; if (vertical) { domainAxis ..axisOrientation = AxisOrientation.bottom ..reverseOutputRange = reverseAxisDirection; _primaryMeasureAxis ..axisOrientation = (reverseAxisDirection ? AxisOrientation.right : AxisOrientation.left) ..reverseOutputRange = flipVerticalAxisOutput; _secondaryMeasureAxis ..axisOrientation = (reverseAxisDirection ? AxisOrientation.left : AxisOrientation.right) ..reverseOutputRange = flipVerticalAxisOutput; _disjointMeasureAxes.forEach((String axisId, NumericAxis axis) { axis ..axisOrientation = (reverseAxisDirection ? AxisOrientation.left : AxisOrientation.right) ..reverseOutputRange = flipVerticalAxisOutput; }); } else { domainAxis ..axisOrientation = (reverseAxisDirection ? AxisOrientation.right : AxisOrientation.left) ..reverseOutputRange = flipVerticalAxisOutput; _primaryMeasureAxis ..axisOrientation = AxisOrientation.bottom ..reverseOutputRange = reverseAxisDirection; _secondaryMeasureAxis ..axisOrientation = AxisOrientation.top ..reverseOutputRange = reverseAxisDirection; _disjointMeasureAxes.forEach((String axisId, NumericAxis axis) { axis ..axisOrientation = AxisOrientation.top ..reverseOutputRange = reverseAxisDirection; }); } // Have each renderer configure the axes with their domain and measure // values. rendererToSeriesList .forEach((String rendererId, List> seriesList) { getSeriesRenderer(rendererId).configureDomainAxes(seriesList); getSeriesRenderer(rendererId).configureMeasureAxes(seriesList); }); return rendererToSeriesList; } @override void onSkipLayout() { // Update ticks only when skipping layout. domainAxis!.updateTicks(); if (_usePrimaryMeasureAxis) { _primaryMeasureAxis.updateTicks(); } if (_useSecondaryMeasureAxis) { _secondaryMeasureAxis.updateTicks(); } _disjointMeasureAxes.forEach((String axisId, NumericAxis axis) { axis.updateTicks(); }); super.onSkipLayout(); } @override void onPostLayout(Map>> rendererToSeriesList) { fireOnAxisConfigured(); super.onPostLayout(rendererToSeriesList); } /// Returns a list of datum details from selection model of [type]. @override List> getDatumDetails(SelectionModelType type) { final entries = >[]; getSelectionModel(type).selectedDatum.forEach((seriesDatum) { final series = seriesDatum.series; final Object? datum = seriesDatum.datum; final datumIndex = seriesDatum.index; final domain = series.domainFn(datumIndex); final domainFormatterFn = series.domainFormatterFn; final measure = series.measureFn(datumIndex); final measureFormatterFn = series.measureFormatterFn; final measureOffset = series.measureOffsetFn!(datumIndex); final rawMeasure = series.rawMeasureFn(datumIndex); final color = series.colorFn!(datumIndex); final renderer = getSeriesRenderer(series.getAttr(rendererIdKey)); final datumDetails = renderer.addPositionToDetailsForSeriesDatum( DatumDetails( datum: datum, domain: domain, domainFormatter: domainFormatterFn?.call(datumIndex), index: datumIndex, measure: measure, measureFormatter: measureFormatterFn?.call(datumIndex), measureOffset: measureOffset, rawMeasure: rawMeasure, series: series, color: color), seriesDatum); entries.add(datumDetails); }); return entries; } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/cartesian/cartesian_renderer.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'package:meta/meta.dart'; import '../../common/symbol_renderer.dart' show SymbolRenderer; import '../../data/series.dart' show AccessorFn; import '../common/base_chart.dart' show BaseChart; import '../common/processed_series.dart' show MutableSeries; import '../common/series_renderer.dart' show BaseSeriesRenderer, SeriesRenderer; import 'axis/axis.dart' show Axis, domainAxisKey, measureAxisKey; import 'cartesian_chart.dart' show CartesianChart; abstract class CartesianRenderer extends SeriesRenderer { @override void configureDomainAxes(List> seriesList); @override void configureMeasureAxes(List> seriesList); } abstract class BaseCartesianRenderer extends BaseSeriesRenderer implements CartesianRenderer { BaseCartesianRenderer({ required String rendererId, required int layoutPaintOrder, SymbolRenderer? symbolRenderer, }) : super( rendererId: rendererId, layoutPaintOrder: layoutPaintOrder, symbolRenderer: symbolRenderer); @protected late CartesianChart chart; @override void onAttach(BaseChart chart) { super.onAttach(chart); // Save a reference to the parent chart so that we can access properties // that are not set until a later state (e.g. isRtl), or that might change // dynamically (e.g. vertical). this.chart = chart as CartesianChart; } // True when the chart should be rendered in vertical mode, false when in // horizontal mode. bool get renderingVertically => chart.vertical; @override void configureDomainAxes(List> seriesList) { seriesList.forEach((MutableSeries series) { if (series.data.isEmpty) { return; } final domainAxis = series.getAttr(domainAxisKey); final domainFn = series.domainFn; final domainLowerBoundFn = series.domainLowerBoundFn; final domainUpperBoundFn = series.domainUpperBoundFn; if (domainAxis == null) { return; } if (renderingVertically) { for (var i = 0; i < series.data.length; i++) { domainAxis.addDomainValue(domainFn(i)!); if (domainLowerBoundFn != null && domainUpperBoundFn != null) { final domainLowerBound = domainLowerBoundFn(i); final domainUpperBound = domainUpperBoundFn(i); if (domainLowerBound != null && domainUpperBound != null) { domainAxis.addDomainValue(domainLowerBound); domainAxis.addDomainValue(domainUpperBound); } } } } else { // When rendering horizontally, domains are displayed from top to bottom // in order to match visual display in legend. for (var i = series.data.length - 1; i >= 0; i--) { domainAxis.addDomainValue(domainFn(i)!); if (domainLowerBoundFn != null && domainUpperBoundFn != null) { final domainLowerBound = domainLowerBoundFn(i); final domainUpperBound = domainUpperBoundFn(i); if (domainLowerBound != null && domainUpperBound != null) { domainAxis.addDomainValue(domainLowerBound); domainAxis.addDomainValue(domainUpperBound); } } } } }); } @override void configureMeasureAxes(List> seriesList) { seriesList.forEach((MutableSeries series) { if (series.data.isEmpty) { return; } final domainAxis = series.getAttr(domainAxisKey) as Axis?; final domainFn = series.domainFn; if (domainAxis == null) { return; } final measureAxis = series.getAttr(measureAxisKey) as Axis?; if (measureAxis == null) { return; } // Only add the measure values for datum who's domain is within the // domainAxis viewport. final startIndex = findNearestViewportStart(domainAxis, domainFn, series.data); final endIndex = findNearestViewportEnd(domainAxis, domainFn, series.data); addMeasureValuesFor(series, measureAxis, startIndex, endIndex); }); } void addMeasureValuesFor( MutableSeries series, Axis measureAxis, int startIndex, int endIndex, ) { final measureFn = series.measureFn; final measureOffsetFn = series.measureOffsetFn!; final measureLowerBoundFn = series.measureLowerBoundFn; final measureUpperBoundFn = series.measureUpperBoundFn; for (var i = startIndex; i <= endIndex; i++) { final measure = measureFn(i); final measureOffset = measureOffsetFn(i); if (measure != null && measureOffset != null) { measureAxis.addDomainValue(measure + measureOffset); if (measureLowerBoundFn != null && measureUpperBoundFn != null) { measureAxis .addDomainValue((measureLowerBoundFn(i) ?? 0) + measureOffset); measureAxis .addDomainValue((measureUpperBoundFn(i) ?? 0) + measureOffset); } } } } @visibleForTesting int findNearestViewportStart( Axis domainAxis, AccessorFn domainFn, List data) { assert(data.isNotEmpty); // Quick optimization for full viewport (likely). if (domainAxis.compareDomainValueToViewport(domainFn(0)) == 0) { return 0; } var start = 1; // Index zero was already checked for above. var end = data.length - 1; // Binary search for the start of the viewport. while (end >= start) { final searchIndex = ((end - start) / 2).floor() + start; final prevIndex = searchIndex - 1; var comparisonValue = domainAxis.compareDomainValueToViewport(domainFn(searchIndex)); var prevComparisonValue = domainAxis.compareDomainValueToViewport(domainFn(prevIndex)); // Found start? if (prevComparisonValue == -1 && comparisonValue == 0) { return searchIndex; } // Straddling viewport? // Return previous index as the nearest start of the viewport. if (comparisonValue == 1 && prevComparisonValue == -1) { return searchIndex - 1; } // Before start? Update startIndex if (comparisonValue == -1) { start = searchIndex + 1; } else { // Middle or after viewport? Update endIndex end = searchIndex - 1; } } // Binary search would reach this point for the edge cases where the domain // specified is prior or after the domain viewport. // If domain is prior to the domain viewport, return the first index as the // nearest viewport start. // If domain is after the domain viewport, return the last index as the // nearest viewport start. final lastComparison = domainAxis.compareDomainValueToViewport(domainFn(data.length - 1)); return lastComparison == 1 ? (data.length - 1) : 0; } @visibleForTesting int findNearestViewportEnd( Axis domainAxis, AccessorFn domainFn, List data) { assert(data.isNotEmpty); var start = 1; var end = data.length - 1; // Quick optimization for full viewport (likely). if (domainAxis.compareDomainValueToViewport(domainFn(end)) == 0) { return end; } end = end - 1; // Last index was already checked for above. // Binary search for the start of the viewport. while (end >= start) { final searchIndex = ((end - start) / 2).floor() + start; final prevIndex = searchIndex - 1; final comparisonValue = domainAxis.compareDomainValueToViewport(domainFn(searchIndex)); final prevComparisonValue = domainAxis.compareDomainValueToViewport(domainFn(prevIndex)); // Found end? if (prevComparisonValue == 0 && comparisonValue == 1) { return prevIndex; } // Straddling viewport? // Return the current index as the start of the viewport. if (comparisonValue == 1 && prevComparisonValue == -1) { return searchIndex; } // After end? Update endIndex if (comparisonValue == 1) { end = searchIndex - 1; } else { // Middle or before viewport? Update startIndex start = searchIndex + 1; } } // Binary search would reach this point for the edge cases where the domain // specified is prior or after the domain viewport. // If domain is prior to the domain viewport, return the first index as the // nearest viewport end. // If domain is after the domain viewport, return the last index as the // nearest viewport end. final lastComparison = domainAxis.compareDomainValueToViewport(domainFn(data.length - 1)); return lastComparison == 1 ? (data.length - 1) : 0; } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/common/base_chart.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math' show Rectangle, Point; import 'package:meta/meta.dart' show protected; import '../../common/gesture_listener.dart' show GestureListener; import '../../common/graphics_factory.dart' show GraphicsFactory; import '../../common/proxy_gesture_listener.dart' show ProxyGestureListener; import '../../data/series.dart' show Series; import '../layout/layout_config.dart' show LayoutConfig; import '../layout/layout_manager.dart' show LayoutManager; import '../layout/layout_manager_impl.dart' show LayoutManagerImpl; import '../layout/layout_view.dart' show LayoutView; import 'behavior/chart_behavior.dart' show ChartBehavior; import 'chart_canvas.dart' show ChartCanvas; import 'chart_context.dart' show ChartContext; import 'datum_details.dart' show DatumDetails; import 'processed_series.dart' show MutableSeries; import 'selection_model/selection_model.dart' show MutableSelectionModel, SelectionModelType; import 'series_datum.dart' show SeriesDatum; import 'series_renderer.dart' show SeriesRenderer, rendererIdKey, rendererKey; typedef BehaviorCreator = ChartBehavior Function(); abstract class BaseChart { late ChartContext context; /// Internal use only. GraphicsFactory? graphicsFactory; final LayoutManager _layoutManager; int? _chartWidth; int? _chartHeight; Duration transition = const Duration(milliseconds: 300); /// A value in the range \[0.0, 1.0\] that indicates the animation progress. double animationPercent = 0; bool _animationsTemporarilyDisabled = false; /// List of series that were passed into the previous draw call. /// /// This list will be used when redraw is called, to reset the state of all /// behaviors to the original list. late List> _originalSeriesList; /// List of series that are currently drawn on the chart. /// /// This list should be used by interactive behaviors between chart draw /// cycles. It may be filtered or modified by some behaviors during the /// initial draw cycle (e.g. a [Legend] may hide some series). List>? _currentSeriesList; Set _usingRenderers = {}; Map>>? _rendererToSeriesList; final _seriesRenderers = >{}; /// Map of named chart behaviors attached to this chart. final _behaviorRoleMap = >{}; final _behaviorStack = >[]; final _behaviorTappableMap = >{}; /// Whether or not the chart will respond to tap events. /// /// This will generally be true if there is a behavior attached to the chart /// that does something with tap events, such as "click to select data." bool get isTappable => _behaviorTappableMap.isNotEmpty; final _gestureProxy = ProxyGestureListener(); final _selectionModels = >{}; /// Whether selected data should be restricted to only have points that /// cover this event location. /// /// When this is true, selection logic would ignore points that are close to /// event location but does not cover event location. bool get selectExactEventLocation => false; /// Whether data should be selected by nearest domain distance, or by relative /// distance. /// /// This should generally be true for chart types that are intended to be /// aggregated by domain, and false for charts that plot arbitrary x,y data. /// Scatter plots, for example, may have many overlapping data with the same /// domain value. bool get selectNearestByDomain => true; /// Whether data should be expanded by to include all points overlapping the /// selection point /// /// Should be true for Scatter plots. bool get selectOverlappingPoints => false; final _lifecycleListeners = >[]; BaseChart({LayoutConfig? layoutConfig}) : _layoutManager = LayoutManagerImpl(config: layoutConfig); void init(ChartContext context, GraphicsFactory graphicsFactory) { this.context = context; // When graphics factory is updated, update all the views. if (this.graphicsFactory != graphicsFactory) { this.graphicsFactory = graphicsFactory; _layoutManager.applyToViews( (LayoutView view) => view.graphicsFactory = graphicsFactory); } configurationChanged(); } /// Updates the layout configuration used inside the layout manager. void updateConfig(LayoutConfig layoutConfig) { _layoutManager.updateConfig(layoutConfig); } bool _chartIsDirty = false; /// If the chart configuration has changed and requires a redraw. bool get chartIsDirty => _chartIsDirty; /// Resets the chart dirty flag to `false`. void resetChartDirtyFlag() { _chartIsDirty = false; } /// Marks the chart as dirty. /// /// When a chart axis or configurable is changed and will require a redraw /// next frame the chart must be marked dirty. void markChartDirty() { _chartIsDirty = true; } /// Finish configuring components that require context and graphics factory. /// /// Some components require context and graphics factory to be set again when /// configuration has changed but the configuration is set prior to the /// chart first calling init with the context. void configurationChanged() {} int? get chartWidth => _chartWidth; int? get chartHeight => _chartHeight; // // Gesture proxy methods // ProxyGestureListener get gestureProxy => _gestureProxy; /// Add a [GestureListener] to this chart. GestureListener addGestureListener(GestureListener listener) { _gestureProxy.add(listener); return listener; } /// Remove a [GestureListener] from this chart. void removeGestureListener(GestureListener listener) { _gestureProxy.remove(listener); } LifecycleListener addLifecycleListener(LifecycleListener listener) { _lifecycleListeners.add(listener); return listener; } bool removeLifecycleListener(LifecycleListener listener) => _lifecycleListeners.remove(listener); /// Returns MutableSelectionModel for the given type. Lazy creates one upon first /// request. MutableSelectionModel getSelectionModel(SelectionModelType type) { return _selectionModels.putIfAbsent(type, () => MutableSelectionModel()); } /// Returns a list of datum details from selection model of [type]. List> getDatumDetails(SelectionModelType type); // // Renderer methods // set defaultRenderer(SeriesRenderer renderer) { renderer.rendererId = SeriesRenderer.defaultRendererId; addSeriesRenderer(renderer); } SeriesRenderer get defaultRenderer => getSeriesRenderer(SeriesRenderer.defaultRendererId); void addSeriesRenderer(SeriesRenderer renderer) { final rendererId = renderer.rendererId; final previousRenderer = _seriesRenderers[rendererId]; if (previousRenderer != null) { removeView(previousRenderer); previousRenderer.onDetach(this); } addView(renderer); renderer.onAttach(this); _seriesRenderers[rendererId] = renderer; } SeriesRenderer getSeriesRenderer(String? rendererId) { var renderer = _seriesRenderers[rendererId]; // Special case, if we are asking for the default and we haven't made it // yet, then make it now. if (renderer == null) { // TODO: Throw an error if // `rendererId != SeriesRenderer.defaultRendererId`? renderer = makeDefaultRenderer(); defaultRenderer = renderer; } return renderer; } SeriesRenderer makeDefaultRenderer(); bool pointWithinRenderer(Point chartPosition) { return _usingRenderers.any((String rendererId) => getSeriesRenderer(rendererId) .componentBounds! .containsPoint(chartPosition)); } /// Retrieves the datum details that are nearest to the given [drawAreaPoint]. /// /// [drawAreaPoint] represents a point in the chart, such as a point that was /// clicked/tapped on by a user. /// /// [selectAcrossAllDrawAreaComponents] specifies whether nearest data /// selection should be done across the combined draw area of all components /// with series draw areas, or just the chart's primary draw area bounds. List> getNearestDatumDetailPerSeries( Point drawAreaPoint, bool selectAcrossAllDrawAreaComponents) { // Optionally grab the combined draw area bounds of all components. If this // is disabled, then we expect each series renderer to filter out the event // if [chartPoint] is located outside of its own component bounds. final boundsOverride = selectAcrossAllDrawAreaComponents ? drawableLayoutAreaBounds : null; final details = >[]; _usingRenderers.forEach((String rendererId) { details .addAll(getSeriesRenderer(rendererId).getNearestDatumDetailPerSeries( drawAreaPoint, selectNearestByDomain, boundsOverride, selectOverlappingPoints: selectOverlappingPoints, selectExactEventLocation: selectExactEventLocation, )); }); details.sort((DatumDetails a, DatumDetails b) { // Sort so that the nearest one is first. // Special sort, sort by domain distance first, then by measure distance. if (selectNearestByDomain) { final domainDiff = a.domainDistance!.compareTo(b.domainDistance!); if (domainDiff == 0) { return a.measureDistance!.compareTo(b.measureDistance!); } return domainDiff; } else { return a.relativeDistance!.compareTo(b.relativeDistance!); } }); return details; } /// Retrieves the datum details for the current chart selection. /// /// [selectionModelType] specifies the type of the selection model to use. List> getSelectedDatumDetails( SelectionModelType selectionModelType) { final details = >[]; if (_currentSeriesList == null) { return details; } final selectionModel = getSelectionModel(selectionModelType); if (!selectionModel.hasDatumSelection) { return details; } // Pass each selected datum to the appropriate series renderer to get full // details appropriate to its series type. for (final seriesDatum in selectionModel.selectedDatum) { final rendererId = seriesDatum.series.getAttr(rendererIdKey); details.add( getSeriesRenderer(rendererId).getDetailsForSeriesDatum(seriesDatum)); } return details; } /// Retrieves the datum details for all data on the chart. List> getAllDatumDetails( {bool includeOverlaySeries = false}) { final details = >[]; if (_currentSeriesList == null) { return details; } for (final series in _currentSeriesList!) { final rendererId = series.getAttr(rendererIdKey); if (!includeOverlaySeries && series.overlaySeries) { continue; } for (dynamic datum in series.data) { details.add(getSeriesRenderer(rendererId) .getDetailsForSeriesDatum(SeriesDatum(series, datum))); } } return details; } // // Behavior methods // /// Helper method to create a behavior with congruent types. /// /// This invokes the provides helper with type parameters that match this /// chart. ChartBehavior createBehavior(BehaviorCreator creator) => creator(); /// Attaches a behavior to the chart. /// /// Setting a behavior with the same role as a behavior already attached /// to the chart will replace the old behavior. The old behavior's removeFrom /// method will be called before we attach the behavior. void addBehavior(ChartBehavior behavior) { final role = behavior.role; if (role != null && _behaviorRoleMap[role] != behavior) { // Remove any old behavior with the same role. removeBehavior(_behaviorRoleMap[role]); // Add the behavior. _behaviorRoleMap[role] = behavior; } // Add the behavior if it wasn't already added. if (!_behaviorStack.contains(behavior)) { _behaviorStack.add(behavior); behavior.attachTo(this); } } /// Removes a behavior from the chart. /// /// Returns true if a behavior was removed, otherwise returns false. bool removeBehavior(ChartBehavior? behavior) { if (behavior == null) { return false; } final role = behavior.role; if (role != null && _behaviorRoleMap[role] == behavior) { _behaviorRoleMap.remove(role); } // Make sure the removed behavior is no longer registered for tap events. unregisterTappable(behavior); final wasAttached = _behaviorStack.remove(behavior); behavior.removeFrom(this); return wasAttached; } /// Tells the chart that this behavior responds to tap events. /// /// This should only be called after [behavior] has been attached to the chart /// via [addBehavior]. void registerTappable(ChartBehavior behavior) { final role = behavior.role; if (role != null && _behaviorRoleMap[role] == behavior && _behaviorTappableMap[role] != behavior) { _behaviorTappableMap[role] = behavior; } } /// Tells the chart that this behavior no longer responds to tap events. void unregisterTappable(ChartBehavior behavior) { final role = behavior.role; if (role != null && _behaviorTappableMap[role] == behavior) { _behaviorTappableMap.remove(role); } } /// Returns a list of behaviors that have been added. List> get behaviors => List.unmodifiable(_behaviorStack); // // Layout methods // void measure(int width, int height) { if (_rendererToSeriesList != null) { _layoutManager.measure(width, height); } } void layout(int width, int height) { if (_rendererToSeriesList != null) { layoutInternal(width, height); onPostLayout(_rendererToSeriesList!); } } void layoutInternal(int width, int height) { _chartWidth = width; _chartHeight = height; _layoutManager.layout(width, height); } void addView(LayoutView view) { if (!_layoutManager.isAttached(view)) { view.graphicsFactory = graphicsFactory; _layoutManager.addView(view); } } void removeView(LayoutView view) { _layoutManager.removeView(view); } /// Returns whether or not [point] is within the draw area bounds. bool withinDrawArea(Point point) { return _layoutManager.withinDrawArea(point); } /// Returns the bounds of the chart draw area. Rectangle get drawAreaBounds => _layoutManager.drawAreaBounds; int get marginBottom => _layoutManager.marginBottom; int get marginLeft => _layoutManager.marginLeft; int get marginRight => _layoutManager.marginRight; int get marginTop => _layoutManager.marginTop; /// Returns the combined bounds of the chart draw area and all layout /// components that draw series data. Rectangle get drawableLayoutAreaBounds => _layoutManager.drawableLayoutAreaBounds; // // Draw methods // void draw(List> seriesList) { // Clear the selection model when [seriesList] changes. for (final selectionModel in _selectionModels.values) { selectionModel.clearSelection(notifyListeners: false); } var processedSeriesList = List.of(seriesList.map>(makeSeries)); // Allow listeners to manipulate the seriesList. fireOnDraw(processedSeriesList); // Set an index on the series list. // This can be used by listeners of selection to determine the order of // series, because the selection details are not returned in this order. var seriesIndex = 0; processedSeriesList.forEach((series) => series.seriesIndex = seriesIndex++); // Initially save a reference to processedSeriesList. After drawInternal // finishes, we expect _currentSeriesList to contain a new, possibly // modified list. _currentSeriesList = processedSeriesList; // Store off processedSeriesList for use later during redraw calls. This // list will not reflect any modifications that were made to // _currentSeriesList by behaviors during the draw cycle. _originalSeriesList = processedSeriesList; drawInternal(processedSeriesList, skipAnimation: false, skipLayout: false); } /// Redraws and re-lays-out the chart using the previously rendered layout /// dimensions. void redraw({bool skipAnimation = false, bool skipLayout = false}) { drawInternal(_originalSeriesList, skipAnimation: skipAnimation, skipLayout: skipLayout); // Trigger layout and actually redraw the chart. if (!skipLayout) { measure(_chartWidth!, _chartHeight!); layout(_chartWidth!, _chartHeight!); } else { onSkipLayout(); } } void drawInternal(List> seriesList, {bool? skipAnimation, bool? skipLayout}) { seriesList = seriesList .map((MutableSeries series) => MutableSeries.clone(series)) .toList(); // TODO: Handle exiting renderers. if (skipAnimation != null) { _animationsTemporarilyDisabled = skipAnimation; } configureSeries(seriesList); // Allow listeners to manipulate the processed seriesList. fireOnPreprocess(seriesList); _rendererToSeriesList = preprocessSeries(seriesList); // Allow listeners to manipulate the processed seriesList. fireOnPostprocess(seriesList); _currentSeriesList = seriesList; } List> get currentSeriesList => _currentSeriesList!; MutableSeries makeSeries(Series series) { final s = MutableSeries(series); // Setup the Renderer final rendererId = series.getAttribute(rendererIdKey) ?? SeriesRenderer.defaultRendererId; s.setAttr(rendererIdKey, rendererId); s.setAttr(rendererKey, getSeriesRenderer(rendererId)); return s; } /// Preprocess series to assign missing color functions. void configureSeries(List> seriesList) { final rendererToSeriesList = >>{}; // Build map of rendererIds to SeriesLists. This map can't be re-used later // in the preprocessSeries call because some behaviors might alter the // seriesList. seriesList.forEach((MutableSeries series) { final rendererId = series.getAttr(rendererIdKey); rendererToSeriesList.putIfAbsent(rendererId, () => []).add(series); }); // Have each renderer add missing color functions to their seriesLists. rendererToSeriesList .forEach((String? rendererId, List> seriesList) { getSeriesRenderer(rendererId).configureSeries(seriesList); }); } /// Preprocess series to allow stacking and other mutations. /// /// Build a map of rendererId to series. Map>> preprocessSeries( List> seriesList) { final rendererToSeriesList = >>{}; var unusedRenderers = _usingRenderers; _usingRenderers = {}; // Build map of rendererIds to SeriesLists. seriesList.forEach((MutableSeries series) { final rendererId = series.getAttr(rendererIdKey)!; rendererToSeriesList.putIfAbsent(rendererId, () => []).add(series); _usingRenderers.add(rendererId); unusedRenderers.remove(rendererId); }); // Allow unused renderers to render out content. unusedRenderers .forEach((rendererId) => rendererToSeriesList[rendererId] = []); // Have each renderer preprocess their seriesLists. rendererToSeriesList.forEach((rendererId, seriesList) { getSeriesRenderer(rendererId).preprocessSeries(seriesList); }); return rendererToSeriesList; } void onSkipLayout() { onPostLayout(_rendererToSeriesList!); } void onPostLayout(Map>> rendererToSeriesList) { // Update each renderer with rendererToSeriesList .forEach((String rendererId, List> seriesList) { getSeriesRenderer(rendererId).update(seriesList, animatingThisDraw); }); // Request animation if (animatingThisDraw) { animationPercent = 0.0; context.requestAnimation(transition); } else { animationPercent = 1.0; context.requestPaint(); } _animationsTemporarilyDisabled = false; } void paint(ChartCanvas canvas) { canvas.drawingView = 'BaseView'; _layoutManager.paintOrderedViews.forEach((LayoutView view) { canvas.drawingView = view.runtimeType.toString(); view.paint(canvas, animatingThisDraw ? animationPercent : 1.0); }); canvas.drawingView = 'PostRender'; fireOnPostrender(canvas); canvas.drawingView = null; if (animationPercent == 1.0) { fireOnAnimationComplete(); } } bool get animatingThisDraw => transition != null && transition.inMilliseconds > 0 && !_animationsTemporarilyDisabled; @protected void fireOnDraw(List> seriesList) { _lifecycleListeners.forEach((LifecycleListener listener) { listener.onData?.call(seriesList); }); } @protected void fireOnPreprocess(List> seriesList) { _lifecycleListeners.forEach((LifecycleListener listener) { listener.onPreprocess?.call(seriesList); }); } @protected void fireOnPostprocess(List> seriesList) { _lifecycleListeners.forEach((LifecycleListener listener) { listener.onPostprocess?.call(seriesList); }); } @protected void fireOnAxisConfigured() { _lifecycleListeners.forEach((LifecycleListener listener) { listener.onAxisConfigured?.call(); }); } @protected void fireOnPostrender(ChartCanvas canvas) { _lifecycleListeners.forEach((LifecycleListener listener) { listener.onPostrender?.call(canvas); }); } @protected void fireOnAnimationComplete() { _lifecycleListeners.forEach((LifecycleListener listener) { listener.onAnimationComplete?.call(); }); } /// Called to free up any resources due to chart going away. void destroy() { // Walk them in add order to support behaviors that remove other behaviors. for (var i = 0; i < _behaviorStack.length; i++) { _behaviorStack[i].removeFrom(this); } _behaviorStack.clear(); _behaviorRoleMap.clear(); _selectionModels.values .forEach((selectionModel) => selectionModel.clearAllListeners()); } } class LifecycleListener { /// Called when data is drawn to the chart (not a redraw). /// /// This step is good for processing the data (running averages, percentage of /// first, etc). It can also be used to add Series of data (trend line) or /// remove a line as mentioned above, removing Series. final LifecycleSeriesListCallback? onData; /// Called for every redraw given the original SeriesList resulting from the /// previous onData. /// /// This step is good for injecting default attributes on the Series before /// the renderers process the data (ex: before stacking measures). final LifecycleSeriesListCallback? onPreprocess; /// Called after the chart and renderers get a chance to process the data but /// before the axes process them. /// /// This step is good if you need to alter the Series measure values after the /// renderers have processed them (ex: after stacking measures). final LifecycleSeriesListCallback? onPostprocess; /// Called after the Axes have been configured. /// This step is good if you need to use the axes to get any cartesian /// location information. At this point Axes should be immutable and stable. final LifecycleEmptyCallback? onAxisConfigured; /// Called after the chart is done rendering passing along the canvas allowing /// a behavior or other listener to render on top of the chart. /// /// This is a convenience callback, however if there is any significant canvas /// interaction or stacking needs, it is preferred that a AplosView/ChartView /// is added to the chart instead to fully participate in the view stacking. final LifecycleCanvasCallback? onPostrender; /// Called after animation hits 100%. This allows a behavior or other listener /// to chain animations to create a multiple step animation transition. final LifecycleEmptyCallback? onAnimationComplete; LifecycleListener( {this.onData, this.onPreprocess, this.onPostprocess, this.onAxisConfigured, this.onPostrender, this.onAnimationComplete}); } typedef LifecycleSeriesListCallback = void Function( List> seriesList); typedef LifecycleCanvasCallback = void Function(ChartCanvas canvas); typedef LifecycleEmptyCallback = void Function(); ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/common/behavior/a11y/a11y_explore_behavior.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math' show Point; import '../../../../common/gesture_listener.dart' show GestureListener; import '../../base_chart.dart' show BaseChart; import '../chart_behavior.dart' show ChartBehavior; import 'a11y_node.dart' show A11yNode; /// The gesture to use for triggering explore mode. enum ExploreModeTrigger { pressHold, tap, } /// Chart behavior for adding A11y information. abstract class A11yExploreBehavior implements ChartBehavior { /// The gesture that activates explore mode. Defaults to long press. /// /// Turning on explore mode asks this [A11yExploreBehavior] to generate nodes within /// this chart. final ExploreModeTrigger exploreModeTrigger; /// Minimum width of the bounding box for the a11y focus. /// /// Must be 1 or higher because invisible semantic nodes should not be added. final double minimumWidth; /// Optionally notify the OS when explore mode is enabled. final String? exploreModeEnabledAnnouncement; /// Optionally notify the OS when explore mode is disabled. final String? exploreModeDisabledAnnouncement; BaseChart? _chart; late GestureListener _listener; bool _exploreModeOn = false; A11yExploreBehavior({ ExploreModeTrigger? exploreModeTrigger, double? minimumWidth, this.exploreModeEnabledAnnouncement, this.exploreModeDisabledAnnouncement, }) : exploreModeTrigger = exploreModeTrigger ?? ExploreModeTrigger.pressHold, minimumWidth = minimumWidth ?? 1.0 { assert(this.minimumWidth >= 1.0); switch (this.exploreModeTrigger) { case ExploreModeTrigger.pressHold: _listener = GestureListener(onLongPress: _toggleExploreMode); break; case ExploreModeTrigger.tap: _listener = GestureListener(onTap: _toggleExploreMode); break; } } bool _toggleExploreMode(Point _) { if (_exploreModeOn) { _exploreModeOn = false; // Ask native platform to turn off explore mode. _chart!.context.disableA11yExploreMode( announcement: exploreModeDisabledAnnouncement); } else { _exploreModeOn = true; // Ask native platform to turn on explore mode. _chart!.context.enableA11yExploreMode(createA11yNodes(), announcement: exploreModeEnabledAnnouncement); } return true; } /// Returns a list of A11yNodes for this chart. List createA11yNodes(); @override void attachTo(BaseChart chart) { _chart = chart; chart.addGestureListener(_listener); } @override void removeFrom(BaseChart chart) { chart.removeGestureListener(_listener); } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/common/behavior/a11y/a11y_node.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math' show Rectangle; typedef OnFocus = void Function(); /// Container for accessibility data. class A11yNode { /// The bounding box for this node. final Rectangle boundingBox; /// The textual description of this node. final String label; /// Callback when the A11yNode is focused by the native platform OnFocus? onFocus; A11yNode(this.label, this.boundingBox, {this.onFocus}); } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/common/behavior/a11y/domain_a11y_explore_behavior.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math' show Rectangle; import '../../../cartesian/axis/axis.dart' show ImmutableAxis, domainAxisKey; import '../../../cartesian/cartesian_chart.dart' show CartesianChart; import '../../base_chart.dart' show BaseChart, LifecycleListener; import '../../processed_series.dart' show MutableSeries; import '../../selection_model/selection_model.dart' show SelectionModelType; import '../../series_datum.dart' show SeriesDatum; import 'a11y_explore_behavior.dart' show A11yExploreBehavior, ExploreModeTrigger; import 'a11y_node.dart' show A11yNode, OnFocus; /// Returns a string for a11y vocalization from a list of series datum. typedef VocalizationCallback = String Function( List> seriesDatums); /// A simple vocalization that returns the domain value to string. String domainVocalization(List> seriesDatums) { final datumIndex = seriesDatums.first.index; final domainFn = seriesDatums.first.series.domainFn; final domain = domainFn(datumIndex); return domain.toString(); } /// Behavior that generates semantic nodes for each domain. class DomainA11yExploreBehavior extends A11yExploreBehavior { final VocalizationCallback _vocalizationCallback; late final LifecycleListener _lifecycleListener; late CartesianChart _chart; late List> _seriesList; DomainA11yExploreBehavior( {VocalizationCallback? vocalizationCallback, ExploreModeTrigger? exploreModeTrigger, double? minimumWidth, String? exploreModeEnabledAnnouncement, String? exploreModeDisabledAnnouncement}) : _vocalizationCallback = vocalizationCallback ?? domainVocalization, super( exploreModeTrigger: exploreModeTrigger, minimumWidth: minimumWidth, exploreModeEnabledAnnouncement: exploreModeEnabledAnnouncement, exploreModeDisabledAnnouncement: exploreModeDisabledAnnouncement) { _lifecycleListener = LifecycleListener(onPostprocess: _updateSeriesList); } @override List createA11yNodes() { final nodes = <_DomainA11yNode>[]; // Update the selection model when the a11y node has focus. final selectionModel = _chart.getSelectionModel(SelectionModelType.info); final domainSeriesDatum = >>{}; for (final series in _seriesList) { for (var index = 0; index < series.data.length; index++) { final Object? datum = series.data[index]; final domain = series.domainFn(index); domainSeriesDatum[domain] ??= >[]; domainSeriesDatum[domain]!.add(SeriesDatum(series, datum)); } } domainSeriesDatum.forEach((D domain, List> seriesDatums) { final a11yDescription = _vocalizationCallback(seriesDatums); final firstSeries = seriesDatums.first.series; final domainAxis = firstSeries.getAttr(domainAxisKey) as ImmutableAxis; final location = domainAxis.getLocation(domain)!; /// If the step size is smaller than the minimum width, use minimum. final stepSize = (domainAxis.stepSize > minimumWidth) ? domainAxis.stepSize : minimumWidth; nodes.add(_DomainA11yNode(a11yDescription, location: location, stepSize: stepSize, chartDrawBounds: _chart.drawAreaBounds, isRtl: _chart.context.isRtl, renderVertically: _chart.vertical, onFocus: () => selectionModel.updateSelection(seriesDatums, []))); }); // The screen reader navigates the nodes based on the order it is returned. // So if the chart is RTL, then the nodes should be ordered with the right // most domain first. // // If the chart has multiple series and one series is missing the domain // and it was added later, we still want the domains to be in order. nodes.sort(); return nodes; } void _updateSeriesList(List> seriesList) { _seriesList = seriesList; } @override void attachTo(BaseChart chart) { // Domain selection behavior only works for cartesian charts. assert(chart is CartesianChart); _chart = chart as CartesianChart; chart.addLifecycleListener(_lifecycleListener); super.attachTo(chart); } @override void removeFrom(BaseChart chart) { chart.removeLifecycleListener(_lifecycleListener); } @override String get role => 'DomainA11yExplore-${exploreModeTrigger}'; } /// A11yNode with domain specific information. class _DomainA11yNode extends A11yNode implements Comparable<_DomainA11yNode> { // Save location, RTL, and is render vertically for sorting final double location; final bool isRtl; final bool renderVertically; factory _DomainA11yNode(String label, {required double location, required double stepSize, required Rectangle chartDrawBounds, required bool isRtl, required bool renderVertically, OnFocus? onFocus}) { Rectangle boundingBox; if (renderVertically) { var left = (location - stepSize / 2).round(); var top = chartDrawBounds.top; var width = stepSize.round(); var height = chartDrawBounds.height; boundingBox = Rectangle(left, top, width, height); } else { var left = chartDrawBounds.left; var top = (location - stepSize / 2).round(); var width = chartDrawBounds.width; var height = stepSize.round(); boundingBox = Rectangle(left, top, width, height); } return _DomainA11yNode._internal(label, boundingBox, location: location, isRtl: isRtl, renderVertically: renderVertically, onFocus: onFocus); } _DomainA11yNode._internal(String label, Rectangle boundingBox, {required this.location, required this.isRtl, required this.renderVertically, OnFocus? onFocus}) : super(label, boundingBox, onFocus: onFocus); @override int compareTo(_DomainA11yNode other) { // Ordered by smaller location first, unless rendering vertically and RTL, // then flip to sort by larger location first. var result = location.compareTo(other.location); if (renderVertically && isRtl && result != 0) { result = -result; } return result; } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/common/behavior/a11y/keyboard_domain_navigator.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'package:meta/meta.dart' show protected; import '../../../cartesian/cartesian_chart.dart' show CartesianChart; import '../../base_chart.dart' show BaseChart, LifecycleListener; import '../../processed_series.dart' show MutableSeries; import '../../selection_model/selection_model.dart' show SelectionModelType; import '../../series_datum.dart' show SeriesDatum; import '../chart_behavior.dart' show ChartBehavior; /// Enable keyboard navigation of the chart when focused using the directional /// keys. /// /// This behavior enables keyboard navigation over the domains of the chart when /// focused using the following keys: /// - Arrow left/right keys will move the hover selection over the chart /// domains. /// - Escape will clear both hover and click selections. /// - Enter/space will update the click selection to the hover selection. /// /// This behavior does not add any visual cues or accessibility text, so it is /// ideally used along with other behaviors that handle hover/click selections /// and add these types of visual and/or accessibility cues. /// /// Note that using this behavior requires configuring the tabIndex of your /// chart component. Using the default value of 0 makes the chart focusable in /// the natural order of the page, but you have the option to use whatever /// fine-tuned order works best. abstract class KeyboardDomainNavigator implements ChartBehavior { late BaseChart _chart; late final LifecycleListener _lifecycleListener; /// An ordered list of the available domains. List? _domains; /// An ordered list of selectable domains, the domains will be selected based /// on the order in this list, going back and fort with right and left keys. Map>>? _datumPairs; /// Currently selected domain index. int _currentIndex = NO_SELECTION; KeyboardDomainNavigator() { _lifecycleListener = LifecycleListener(onData: onData); } @override void attachTo(BaseChart chart) { _chart = chart; chart.addLifecycleListener(_lifecycleListener); } @override void removeFrom(BaseChart chart) { chart.removeLifecycleListener(_lifecycleListener); } /// Resets any hidden series data when new data is drawn on the chart. @protected void onData(List> _) { _domains = null; _datumPairs = null; _currentIndex = NO_SELECTION; } @protected bool handleEscape() { _currentIndex = NO_SELECTION; clearSelection(); return true; } @protected bool handleEnter() { _currentIndex = _getActiveHoverDomainIndex(); selectDomain(_currentIndex); return true; } @protected bool handlePreviousDomain() { // Lazily initialize selection domains when a key is pressed after a draw. if (_datumPairs == null) { _generateSelectionDomains(); } final domainsLength = _datumPairs!.length; if (domainsLength == 0) { return false; } _currentIndex = _getActiveHoverDomainIndex(); // Navigate to the last domain when current index is NO_SELECTION. if (_currentIndex == NO_SELECTION) { _currentIndex = domainsLength - 1; } else { // Navigate to the previous index, or to NO_SELECTION when it would // outreach the domain index. _currentIndex = _currentIndex == 0 ? NO_SELECTION : _currentIndex - 1; } _doNavigate(_currentIndex); return true; } @protected bool handleNextDomain() { // Lazily initialize selection domains when a key is pressed after a draw. if (_datumPairs == null) { _generateSelectionDomains(); } final domainsLength = _datumPairs!.length; if (domainsLength == 0) { return false; } _currentIndex = _getActiveHoverDomainIndex(); // Navigate to the first domain when current index is NO_SELECTION. if (_currentIndex == NO_SELECTION) { _currentIndex = 0; } else { // Set to NO_SELECTION when the next index would outreach the domains. _currentIndex = _currentIndex == domainsLength - 1 ? NO_SELECTION : _currentIndex + 1; } _doNavigate(_currentIndex); return true; } /// Triggers when the left or right arrow keys are pressed. void _doNavigate(int domainIndex) { _selectDomainIndex(SelectionModelType.info, domainIndex); } /// Triggers when the Enter or Space key is pressed. void selectDomain(int domainIndex) { _selectDomainIndex(SelectionModelType.action, domainIndex); } /// Triggers when the Escape key is pressed or the chart loses focus. void clearSelection() { _selectDomainIndex(SelectionModelType.info, NO_SELECTION); } /// Updates the selection of the attached chart with the data at the given /// domain index. If the chart doesn't support the given model, this is a /// no-op. @protected bool _selectDomainIndex( SelectionModelType selectionModelType, int domainIndex) { final selectionModel = _chart.getSelectionModel(selectionModelType); if (selectionModel == null) { return false; } if (domainIndex == NO_SELECTION) { selectionModel.clearSelection(); } else { final datumPairs = _getDatumPairs(domainIndex); final seriesDatumList = >[]; final seriesList = >[]; for (final seriesDatum in datumPairs) { seriesDatumList .add(SeriesDatum(seriesDatum.series, seriesDatum.datum)); if (!seriesList.contains(seriesDatum.series)) { seriesList.add(seriesDatum.series as MutableSeries); } } selectionModel.updateSelection(seriesDatumList, seriesList); } return true; } /// Reads the current active index of the hover selection. int _getActiveHoverDomainIndex() { // If enter is pressed before an arrow key, we don't have any selection // domains available. Bail out. final _domains = this._domains; if (_domains == null || _domains.isEmpty) { return NO_SELECTION; } final selectionModel = _chart.getSelectionModel(SelectionModelType.info); if (!selectionModel.hasAnySelection) { return NO_SELECTION; } final details = _chart.getSelectedDatumDetails(SelectionModelType.info); if (details.isEmpty) { return NO_SELECTION; } // If the currentIndex is the same as the firstSelectedDetail we don't have // to do a linear seach to find the domain. final firstDomain = details.first.domain!; if (0 <= _currentIndex && _currentIndex <= _domains.length - 1 && _domains[_currentIndex] == firstDomain) { return _currentIndex; } return _domains.indexOf(firstDomain); } /// Processes chart data and generates a mapping of domain index to datum /// details at that domain. void _generateSelectionDomains() { _domains = []; final allSeriesDatum = _chart.getAllDatumDetails(); if (_chart is CartesianChart) { final localChart = _chart as CartesianChart; if (localChart.vertical) { allSeriesDatum.sort((a, b) { if (a.chartPosition!.x == b.chartPosition!.x) { return a.series!.seriesIndex.compareTo(b.series!.seriesIndex); } return a.chartPosition!.x!.compareTo(b.chartPosition!.x!); }); } else { allSeriesDatum.sort((a, b) { if (a.chartPosition!.y == b.chartPosition!.y) { return a.series!.seriesIndex.compareTo(b.series!.seriesIndex); } return a.chartPosition!.y!.compareTo(b.chartPosition!.y!); }); } } final detailsByDomain = >>{}; for (final datumDetails in allSeriesDatum) { // The hovercard is closed when the closest detail has a null measure. // Also, on hovercard close the current selection is cleared, so unless // the details with null measure are skipped, the next domain visited // after a datum with null measure will always be the first one, making // all data after a datum with null measure not accessible by keyboard. // LINT.IfChange if (datumDetails.measure != null) { final domain = datumDetails.domain!; if (detailsByDomain[domain] == null) { _domains!.add(domain); detailsByDomain[domain] = []; } detailsByDomain[domain]! .add(SeriesDatum(datumDetails.series!, datumDetails.datum)); } // LINT.ThenChange(//depot/google3/third_party/dart/charts_web/lib/src/common/behaviors/hovercard/hovercard.dart) } _datumPairs = >>{}; var i = 0; detailsByDomain.forEach((key, value) { _datumPairs!.putIfAbsent(i, () => value); i++; }); _currentIndex = NO_SELECTION; } /// Gets the datum/series pairs for the given domainIndex. List> _getDatumPairs(int domainIndex) => _datumPairs![domainIndex] ?? >[]; @override String get role => 'keyboard-domain-navigator'; } const NO_SELECTION = -1; ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/common/behavior/calculation/percent_injector.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import '../../../../data/series.dart' show AttributeKey; import '../../base_chart.dart' show BaseChart, LifecycleListener; import '../../behavior/chart_behavior.dart' show ChartBehavior; import '../../processed_series.dart' show MutableSeries; const percentInjectedKey = AttributeKey('PercentInjector.percentInjected'); /// Chart behavior that can inject series or domain percentages into each datum. /// /// [totalType] configures the type of total to be calculated. /// /// The measure values of each datum will be replaced by the percent of the /// total measure value that each represents. The "raw" measure accessor /// function on [MutableSeries] can still be used to get the original values. /// /// Note that the results for measureLowerBound and measureUpperBound are not /// currently well defined when converted into percentage values. This behavior /// will replace them as percents to prevent bad axis results, but no effort is /// made to bound them to within a "0 to 100%" data range. /// /// Note that if the chart has a [Legend] that is capable of hiding series data, /// then this behavior must be added after the [Legend] to ensure that it /// calculates values after series have been potentially removed from the list. class PercentInjector implements ChartBehavior { late final LifecycleListener _lifecycleListener; /// The type of data total to be calculated. final PercentInjectorTotalType totalType; /// Constructs a [PercentInjector]. /// /// [totalType] configures the type of data total to be calculated. PercentInjector({this.totalType = PercentInjectorTotalType.domain}) { // Set up chart draw cycle listeners. _lifecycleListener = LifecycleListener(onPreprocess: _preProcess, onData: _onData); } @override void attachTo(BaseChart chart) { chart.addLifecycleListener(_lifecycleListener); } @override void removeFrom(BaseChart chart) { chart.removeLifecycleListener(_lifecycleListener); } /// Resets the state of the behavior when new data is drawn on the chart. void _onData(List> seriesList) { // Reset tracking of percentage injection for new data. seriesList.forEach((series) { series.setAttr(percentInjectedKey, false); }); } /// Injects percent of domain and/or series accessor functions into each /// series. /// /// These are injected in the preProcess phase in case other behaviors modify /// the [seriesList] between chart redraws. void _preProcess(List> seriesList) { var percentInjected = true; seriesList.forEach((series) { percentInjected = percentInjected && series.getAttr(percentInjectedKey)!; }); if (percentInjected) { return; } switch (totalType) { case PercentInjectorTotalType.domain: case PercentInjectorTotalType.domainBySeriesCategory: final totalsByDomain = {}; final useSeriesCategory = totalType == PercentInjectorTotalType.domainBySeriesCategory; // Walk the series and compute the domain total. Series total is // automatically computed by [MutableSeries]. for (final series in seriesList) { final seriesCategory = series.seriesCategory; final rawMeasureFn = series.rawMeasureFn; final domainFn = series.domainFn; for (var index = 0; index < series.data.length; index++) { final domain = domainFn(index); var measure = rawMeasureFn(index) ?? 0.0; final key = useSeriesCategory ? '${seriesCategory}__$domain' : '$domain'; totalsByDomain[key] = (totalsByDomain[key] ?? 0.0) + measure; } // Add percent of domain and series accessor functions. // // Replace the default measure accessor with one that computes the // percentage. series.measureFn = (int? index) { final measure = rawMeasureFn(index); if (measure == null || measure == 0.0) { return 0.0; } final domain = domainFn(index); final key = useSeriesCategory ? '${series.seriesCategory}__$domain' : '$domain'; return measure / totalsByDomain[key]!; }; // Replace the default measure lower bound accessor with one that // computes the percentage. if (series.measureLowerBoundFn != null) { series.measureLowerBoundFn = (int? index) { final measureLowerBound = series.rawMeasureLowerBoundFn!(index); if (measureLowerBound == null || measureLowerBound == 0.0) { return 0.0; } final domain = domainFn(index); final key = useSeriesCategory ? '${series.seriesCategory}__$domain' : '$domain'; return measureLowerBound / totalsByDomain[key]!; }; } // Replace the default measure upper bound accessor with one that // computes the percentage. if (series.measureUpperBoundFn != null) { series.measureUpperBoundFn = (int? index) { final measureUpperBound = series.rawMeasureUpperBoundFn!(index); if (measureUpperBound == null || measureUpperBound == 0.0) { return 0.0; } final domain = domainFn(index); final key = useSeriesCategory ? '${series.seriesCategory}__$domain' : '$domain'; return measureUpperBound / totalsByDomain[key]!; }; } series.setAttr(percentInjectedKey, true); } break; case PercentInjectorTotalType.series: seriesList.forEach((series) { // Replace the default measure accessor with one that computes the // percentage. series.measureFn = (int? index) => series.rawMeasureFn(index)! / series.seriesMeasureTotal; // Replace the default measure lower bound accessor with one that // computes the percentage. if (series.measureLowerBoundFn != null) { series.measureLowerBoundFn = (int? index) => (series.rawMeasureLowerBoundFn!(index) ?? 0) / series.seriesMeasureTotal; } // Replace the default measure upper bound accessor with one that // computes the percentage. if (series.measureUpperBoundFn != null) { series.measureUpperBoundFn = (int? index) => (series.rawMeasureUpperBoundFn!(index) ?? 0) / series.seriesMeasureTotal; } series.setAttr(percentInjectedKey, true); }); break; default: throw ArgumentError('Unsupported totalType: ${totalType}'); } } @override String get role => 'PercentInjector'; } /// Describes the type of data total that will be calculated by PercentInjector. /// /// [domain] calculates the percentage of each datum's measure value out of the /// total measure values for all data that share the same domain value. /// /// [domainBySeriesCategory] calculates the percentage of each datum's measure /// value out of the total measure values for all data that share the same /// domain value and seriesCategory value. This should be enabled if the data /// will be rendered by a series renderer that groups data by both domain and /// series category, such as the "grouped stacked" mode of [BarRenderer]. /// /// [series] calculates the percentage of each datum's measure value out of the /// total measure values for all data in that datum's series. enum PercentInjectorTotalType { domain, domainBySeriesCategory, series } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/common/behavior/chart_behavior.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import '../base_chart.dart'; /// Interface for adding behavior to a chart. /// /// For example pan and zoom are implemented via behavior strategies. abstract class ChartBehavior { String get role; /// Injects the behavior into a chart. void attachTo(BaseChart chart); /// Removes the behavior from a chart. void removeFrom(BaseChart chart); } /// Position of a component within the chart layout. /// /// Outside positions are [top], [bottom], [start], and [end]. /// /// [top] component positioned at the top, with the chart positioned below the /// component and height reduced by the height of the component. /// [bottom] component positioned below the chart, and the chart's height is /// reduced by the height of the component. /// [start] component is positioned at the left of the chart (or the right if /// RTL), the chart's width is reduced by the width of the component. /// [end] component is positioned at the right of the chart (or the left if /// RTL), the chart's width is reduced by the width of the component. /// [inside] component is layered on top of the chart. enum BehaviorPosition { top, bottom, start, end, inside, } /// Justification for components positioned outside [BehaviorPosition]. enum OutsideJustification { startDrawArea, start, middleDrawArea, middle, endDrawArea, end, } /// Justification for components positioned [BehaviorPosition.inside]. enum InsideJustification { topStart, topEnd, } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/common/behavior/chart_title/chart_title.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math'; import '../../../../common/graphics_factory.dart' show GraphicsFactory; import '../../../../common/style/style_factory.dart' show StyleFactory; import '../../../../common/text_element.dart' show MaxWidthStrategy, TextDirection, TextElement; import '../../../../common/text_style.dart' show TextStyle; import '../../../cartesian/axis/spec/axis_spec.dart' show TextStyleSpec; import '../../../layout/layout_view.dart' show LayoutPosition, LayoutView, LayoutViewConfig, LayoutViewPaintOrder, layoutPosition, LayoutViewPositionOrder, ViewMeasuredSizes; import '../../base_chart.dart' show BaseChart, LifecycleListener; import '../../behavior/chart_behavior.dart' show BehaviorPosition, ChartBehavior, OutsideJustification; import '../../chart_canvas.dart' show ChartCanvas; /// Chart behavior that adds title text to a chart. An optional second line of /// text may be rendered as a sub-title. /// /// Titles will by default be rendered as the outermost component in the chart /// margin. class ChartTitle implements ChartBehavior { static const _defaultBehaviorPosition = BehaviorPosition.top; static const _defaultMaxWidthStrategy = MaxWidthStrategy.ellipsize; static const _defaultTitleDirection = ChartTitleDirection.auto; static const _defaultTitleOutsideJustification = OutsideJustification.middle; static final _defaultTitleStyle = TextStyleSpec(fontSize: 18, color: StyleFactory.style.tickColor); static final _defaultSubTitleStyle = TextStyleSpec(fontSize: 14, color: StyleFactory.style.tickColor); static const _defaultInnerPadding = 10; static const _defaultTitlePadding = 18; static const _defaultOuterPadding = 10; /// Stores all of the configured properties of the behavior. final _ChartTitleConfig _config; BaseChart? _chart; _ChartTitleLayoutView? _view; late final LifecycleListener _lifecycleListener; /// Constructs a [ChartTitle]. /// /// [title] contains the text for the chart title. ChartTitle(String title, {BehaviorPosition? behaviorPosition, int? innerPadding, int? layoutMinSize, int? layoutPreferredSize, int? outerPadding, MaxWidthStrategy? maxWidthStrategy, ChartTitleDirection? titleDirection, OutsideJustification? titleOutsideJustification, int? titlePadding, TextStyleSpec? titleStyleSpec, String? subTitle, TextStyleSpec? subTitleStyleSpec}) : _config = _ChartTitleConfig( behaviorPosition: behaviorPosition ?? _defaultBehaviorPosition, innerPadding: innerPadding ?? _defaultInnerPadding, layoutMinSize: layoutMinSize, layoutPreferredSize: layoutPreferredSize, outerPadding: outerPadding ?? _defaultOuterPadding, maxWidthStrategy: maxWidthStrategy ?? _defaultMaxWidthStrategy, title: title, titleDirection: titleDirection ?? _defaultTitleDirection, titleOutsideJustification: titleOutsideJustification ?? _defaultTitleOutsideJustification, titlePadding: titlePadding ?? _defaultTitlePadding, titleStyleSpec: titleStyleSpec ?? _defaultTitleStyle, subTitle: subTitle, subTitleStyleSpec: subTitleStyleSpec ?? _defaultSubTitleStyle, ) { _lifecycleListener = LifecycleListener(onAxisConfigured: _updateViewData); } /// Layout position for the title. BehaviorPosition get behaviorPosition => _config.behaviorPosition; set behaviorPosition(BehaviorPosition behaviorPosition) { _config.behaviorPosition = behaviorPosition; } /// Minimum size of the legend component. Optional. /// /// If the legend is positioned in the top or bottom margin, then this /// configures the legend's height. If positioned in the start or end /// position, this configures the legend's width. int? get layoutMinSize => _config.layoutMinSize; set layoutMinSize(int? layoutMinSize) { _config.layoutMinSize = layoutMinSize; } /// Preferred size of the legend component. Defaults to 0. /// /// If the legend is positioned in the top or bottom margin, then this /// configures the legend's height. If positioned in the start or end /// position, this configures the legend's width. int? get layoutPreferredSize => _config.layoutPreferredSize; set layoutPreferredSize(int? layoutPreferredSize) { _config.layoutPreferredSize = layoutPreferredSize; } /// Strategy for handling title text that is too large to fit. Defaults to /// truncating the text with ellipses. MaxWidthStrategy get maxWidthStrategy => _config.maxWidthStrategy; set maxWidthStrategy(MaxWidthStrategy maxWidthStrategy) { _config.maxWidthStrategy = maxWidthStrategy; } /// Primary text for the title. String get title => _config.title; set title(String title) { _config.title = title; } /// Direction of the chart title text. /// /// This defaults to horizontal for a title in the top or bottom /// [behaviorPosition], or vertical for start or end [behaviorPosition]. ChartTitleDirection get titleDirection => _config.titleDirection; set titleDirection(ChartTitleDirection titleDirection) { _config.titleDirection = titleDirection; } /// Justification of the title text if it is positioned outside of the draw /// area. OutsideJustification get titleOutsideJustification => _config.titleOutsideJustification; set titleOutsideJustification( OutsideJustification titleOutsideJustification) { _config.titleOutsideJustification = titleOutsideJustification; } /// Space between the title and sub-title text, if defined. /// /// This padding is not used if no sub-title is provided. int get titlePadding => _config.titlePadding; set titlePadding(int titlePadding) { _config.titlePadding = titlePadding; } /// Style of the [title] text. TextStyleSpec get titleStyleSpec => _config.titleStyleSpec; set titleStyleSpec(TextStyleSpec titleStyleSpec) { _config.titleStyleSpec = titleStyleSpec; } /// Secondary text for the sub-title. /// /// [subTitle] is rendered on a second line below the [title], and may be /// styled differently. String? get subTitle => _config.subTitle; set subTitle(String? subTitle) { _config.subTitle = subTitle; } /// Style of the [subTitle] text. TextStyleSpec get subTitleStyleSpec => _config.subTitleStyleSpec; set subTitleStyleSpec(TextStyleSpec subTitleStyleSpec) { _config.subTitleStyleSpec = subTitleStyleSpec; } /// Space between the "inside" of the chart, and the title behavior itself. /// /// This padding is applied to all the edge of the title that is in the /// direction of the draw area. For a top positioned title, this is applied /// to the bottom edge. [outerPadding] is applied to the top, left, and right /// edges. /// /// If a sub-title is defined, this is the space between the sub-title text /// and the inside of the chart. Otherwise, it is the space between the title /// text and the inside of chart. int get innerPadding => _config.innerPadding; set innerPadding(int innerPadding) { _config.innerPadding = innerPadding; } /// Space between the "outside" of the chart, and the title behavior itself. /// /// This padding is applied to all 3 edges of the title that are not in the /// direction of the draw area. For a top positioned title, this is applied /// to the top, left, and right edges. [innerPadding] is applied to the /// bottom edge. int get outerPadding => _config.outerPadding; set outerPadding(int outerPadding) { _config.outerPadding = outerPadding; } @override void attachTo(BaseChart chart) { _chart = chart; _view = _ChartTitleLayoutView( layoutPaintOrder: LayoutViewPaintOrder.chartTitle, config: _config, chart: _chart); chart.addView(_view!); chart.addLifecycleListener(_lifecycleListener); } @override void removeFrom(BaseChart chart) { chart.removeView(_view!); chart.removeLifecycleListener(_lifecycleListener); _chart = null; } void _updateViewData() { _view!.config = _config; } @override String get role => 'ChartTitle-${_config.behaviorPosition}'; bool get isRtl => _chart!.context.isRtl; } /// Layout view component for [ChartTitle]. class _ChartTitleLayoutView extends LayoutView { late final LayoutViewConfig _layoutConfig; @override LayoutViewConfig get layoutConfig => _layoutConfig; /// Stores all of the configured properties of the behavior. _ChartTitleConfig _config; BaseChart? chart; bool get isRtl => chart?.context.isRtl ?? false; late Rectangle _componentBounds; late Rectangle _drawAreaBounds; @override GraphicsFactory? graphicsFactory; /// Cached layout element for the title text. /// /// This is used to prevent expensive Flutter painter layout calls on every /// animation frame during the paint cycle. It should never be cached during /// layout measurement. TextElement? _titleTextElement; /// Cached layout element for the sub-title text. /// /// This is used to prevent expensive Flutter painter layout calls on every /// animation frame during the paint cycle. It should never be cached during /// layout measurement. TextElement? _subTitleTextElement; _ChartTitleLayoutView( {required int layoutPaintOrder, required _ChartTitleConfig config, required this.chart}) : _config = config { // Set inside body to resolve [_layoutPosition]. _layoutConfig = LayoutViewConfig( paintOrder: layoutPaintOrder, position: _layoutPosition, positionOrder: LayoutViewPositionOrder.chartTitle); } /// Sets the configuration for the title behavior. set config(_ChartTitleConfig config) { _config = config; layoutConfig.position = _layoutPosition; } @override ViewMeasuredSizes measure(int maxWidth, int maxHeight) { int? minWidth; int? minHeight; var preferredWidth = 0; var preferredHeight = 0; // Always assume that we need outer padding and title padding, but only add // in the sub-title padding if we have one. Title is required, but sub-title // is optional. final totalPadding = _config.outerPadding + _config.innerPadding + (_config.subTitle != null ? _config.titlePadding : 0.0); final graphicsFactory = this.graphicsFactory!; // Create [TextStyle] from [TextStyleSpec] to be used by all the elements. // The [GraphicsFactory] is needed so it can't be created earlier. final textStyle = _getTextStyle(graphicsFactory, _config.titleStyleSpec); final textElement = graphicsFactory.createTextElement(_config.title) ..maxWidthStrategy = _config.maxWidthStrategy ..textStyle = textStyle; final subTitleTextStyle = _getTextStyle(graphicsFactory, _config.subTitleStyleSpec); final subTitleTextElement = _config.subTitle == null ? null : (graphicsFactory.createTextElement(_config.subTitle!) ..maxWidthStrategy = _config.maxWidthStrategy ..textStyle = subTitleTextStyle); final resolvedTitleDirection = _resolvedTitleDirection; switch (_config.behaviorPosition) { case BehaviorPosition.bottom: case BehaviorPosition.top: final textHeight = (resolvedTitleDirection == ChartTitleDirection.vertical ? textElement.measurement.horizontalSliceWidth : textElement.measurement.verticalSliceWidth) .round(); final subTitleTextHeight = subTitleTextElement != null ? (resolvedTitleDirection == ChartTitleDirection.vertical ? subTitleTextElement.measurement.horizontalSliceWidth : subTitleTextElement.measurement.verticalSliceWidth) .round() : 0; final measuredHeight = (textHeight + subTitleTextHeight + totalPadding).round(); minHeight = _config.layoutMinSize != null ? min(_config.layoutMinSize!, measuredHeight) : measuredHeight; preferredWidth = maxWidth; preferredHeight = _config.layoutPreferredSize != null ? min(_config.layoutPreferredSize!, maxHeight) : measuredHeight; break; case BehaviorPosition.end: case BehaviorPosition.start: final textWidth = (resolvedTitleDirection == ChartTitleDirection.vertical ? textElement.measurement.verticalSliceWidth : textElement.measurement.horizontalSliceWidth) .round(); final subTitleTextWidth = subTitleTextElement != null ? (resolvedTitleDirection == ChartTitleDirection.vertical ? subTitleTextElement.measurement.verticalSliceWidth : subTitleTextElement.measurement.horizontalSliceWidth) .round() : 0; final measuredWidth = (textWidth + subTitleTextWidth + totalPadding).round(); minWidth = _config.layoutMinSize != null ? min(_config.layoutMinSize!, measuredWidth) : measuredWidth; preferredWidth = _config.layoutPreferredSize != null ? min(_config.layoutPreferredSize!, maxWidth) : measuredWidth; preferredHeight = maxHeight; break; case BehaviorPosition.inside: preferredWidth = _drawAreaBounds != null ? min(_drawAreaBounds.width, maxWidth) : maxWidth; preferredHeight = _drawAreaBounds != null ? min(_drawAreaBounds.height, maxHeight) : maxHeight; break; } // Reset the cached text elements used during the paint step. _resetTextElementCache(); return ViewMeasuredSizes( minWidth: minWidth, minHeight: minHeight, preferredWidth: preferredWidth, preferredHeight: preferredHeight); } @override void layout(Rectangle componentBounds, Rectangle drawAreaBounds) { _componentBounds = componentBounds; _drawAreaBounds = drawAreaBounds; // Reset the cached text elements used during the paint step. _resetTextElementCache(); } @override void paint(ChartCanvas canvas, double animationPercent) { final resolvedTitleDirection = _resolvedTitleDirection; var titleHeight = 0.0; var subTitleHeight = 0.0; // First, measure the height of the title and sub-title. if (_config.title != null) { // Chart titles do not animate. As an optimization for Flutter, cache the // [TextElement] to avoid an expensive painter layout operation on // subsequent animation frames. if (_titleTextElement == null) { // Create [TextStyle] from [TextStyleSpec] to be used by all the // elements. The [GraphicsFactory] is needed so it can't be created // earlier. final textStyle = _getTextStyle(graphicsFactory!, _config.titleStyleSpec); _titleTextElement = graphicsFactory!.createTextElement(_config.title) ..maxWidthStrategy = _config.maxWidthStrategy ..textStyle = textStyle; _titleTextElement!.maxWidth = resolvedTitleDirection == ChartTitleDirection.horizontal ? _componentBounds.width : _componentBounds.height; } // Get the height of the title so that we can off-set both text elements. titleHeight = _titleTextElement!.measurement.verticalSliceWidth; } if (_config.subTitle != null) { // Chart titles do not animate. As an optimization for Flutter, cache the // [TextElement] to avoid an expensive painter layout operation on // subsequent animation frames. if (_subTitleTextElement == null) { // Create [TextStyle] from [TextStyleSpec] to be used by all the // elements. The [GraphicsFactory] is needed so it can't be created // earlier. final textStyle = _getTextStyle(graphicsFactory!, _config.subTitleStyleSpec); _subTitleTextElement = graphicsFactory!.createTextElement(_config.subTitle!) ..maxWidthStrategy = _config.maxWidthStrategy ..textStyle = textStyle; _subTitleTextElement!.maxWidth = resolvedTitleDirection == ChartTitleDirection.horizontal ? _componentBounds.width : _componentBounds.height; } // Get the height of the sub-title so that we can off-set both text // elements. subTitleHeight = _subTitleTextElement!.measurement.verticalSliceWidth; } // Draw a title if the text is not empty. if (_config.title != null) { final labelPoint = _getLabelPosition( true, _componentBounds, resolvedTitleDirection, _titleTextElement!, titleHeight, subTitleHeight); if (labelPoint != null) { final rotation = resolvedTitleDirection == ChartTitleDirection.vertical ? -pi / 2 : 0.0; canvas.drawText(_titleTextElement!, labelPoint.x, labelPoint.y, rotation: rotation); } } // Draw a sub-title if the text is not empty. if (_config.subTitle != null) { final labelPoint = _getLabelPosition( false, _componentBounds, resolvedTitleDirection, _subTitleTextElement!, titleHeight, subTitleHeight); if (labelPoint != null) { final rotation = resolvedTitleDirection == ChartTitleDirection.vertical ? -pi / 2 : 0.0; canvas.drawText(_subTitleTextElement!, labelPoint.x, labelPoint.y, rotation: rotation); } } } /// Resets the cached text elements used during the paint step. void _resetTextElementCache() { _titleTextElement = null; _subTitleTextElement = null; } /// Get the direction of the title, resolving "auto" position into the /// appropriate direction for the position of the behavior. ChartTitleDirection get _resolvedTitleDirection { var resolvedTitleDirection = _config.titleDirection; if (resolvedTitleDirection == ChartTitleDirection.auto) { switch (_config.behaviorPosition) { case BehaviorPosition.bottom: case BehaviorPosition.inside: case BehaviorPosition.top: resolvedTitleDirection = ChartTitleDirection.horizontal; break; case BehaviorPosition.end: case BehaviorPosition.start: resolvedTitleDirection = ChartTitleDirection.vertical; break; } } return resolvedTitleDirection; } /// Get layout position from chart title position. LayoutPosition get _layoutPosition { return layoutPosition( _config.behaviorPosition, _config.titleOutsideJustification, isRtl); } /// Gets the resolved location for a label element. Point? _getLabelPosition( bool isPrimaryTitle, Rectangle bounds, ChartTitleDirection titleDirection, TextElement textElement, double titleHeight, double subTitleHeight) { switch (_config.behaviorPosition) { case BehaviorPosition.bottom: case BehaviorPosition.top: return _getHorizontalLabelPosition(isPrimaryTitle, bounds, titleDirection, textElement, titleHeight, subTitleHeight); case BehaviorPosition.start: case BehaviorPosition.end: return _getVerticalLabelPosition(isPrimaryTitle, bounds, titleDirection, textElement, titleHeight, subTitleHeight); case BehaviorPosition.inside: return null; } } /// Gets the resolved location for a title in the top or bottom margin. Point _getHorizontalLabelPosition( bool isPrimaryTitle, Rectangle bounds, ChartTitleDirection titleDirection, TextElement textElement, double titleHeight, double subTitleHeight) { var labelX = 0; var labelY = 0; switch (_config.titleOutsideJustification) { case OutsideJustification.middle: case OutsideJustification.middleDrawArea: final textWidth = (isRtl ? 1 : -1) * textElement.measurement.horizontalSliceWidth / 2; labelX = (bounds.left + bounds.width / 2 + textWidth).round(); textElement.textDirection = isRtl ? TextDirection.rtl : TextDirection.ltr; break; case OutsideJustification.end: case OutsideJustification.endDrawArea: case OutsideJustification.start: case OutsideJustification.startDrawArea: final alignLeft = isRtl ? (_config.titleOutsideJustification == OutsideJustification.end || _config.titleOutsideJustification == OutsideJustification.endDrawArea) : (_config.titleOutsideJustification == OutsideJustification.start || _config.titleOutsideJustification == OutsideJustification.startDrawArea); // Don't apply outer padding if we are aligned to the draw area. final padding = (_config.titleOutsideJustification == OutsideJustification.endDrawArea || _config.titleOutsideJustification == OutsideJustification.startDrawArea) ? 0.0 : _config.outerPadding; if (alignLeft) { labelX = (bounds.left + padding).round(); textElement.textDirection = TextDirection.ltr; } else { labelX = (bounds.right - padding).round(); textElement.textDirection = TextDirection.rtl; } break; } // labelY is always relative to the component bounds. if (_config.behaviorPosition == BehaviorPosition.bottom) { final padding = _config.innerPadding + (isPrimaryTitle ? 0 : _config.titlePadding + titleHeight); labelY = (bounds.top + padding).round(); } else { var padding = 0.0 + _config.innerPadding; if (isPrimaryTitle) { padding += (subTitleHeight > 0 ? _config.titlePadding + subTitleHeight : 0) + titleHeight; } else { padding += subTitleHeight; } labelY = (bounds.bottom - padding).round(); } return Point(labelX, labelY); } /// Gets the resolved location for a title in the left or right margin. Point _getVerticalLabelPosition( bool isPrimaryTitle, Rectangle bounds, ChartTitleDirection titleDirection, TextElement textElement, double titleHeight, double subTitleHeight) { var labelX = 0; var labelY = 0; switch (_config.titleOutsideJustification) { case OutsideJustification.middle: case OutsideJustification.middleDrawArea: final textWidth = (isRtl ? -1 : 1) * textElement.measurement.horizontalSliceWidth / 2; labelY = (bounds.top + bounds.height / 2 + textWidth).round(); textElement.textDirection = isRtl ? TextDirection.rtl : TextDirection.ltr; break; case OutsideJustification.end: case OutsideJustification.endDrawArea: case OutsideJustification.start: case OutsideJustification.startDrawArea: final alignLeft = isRtl ? (_config.titleOutsideJustification == OutsideJustification.end || _config.titleOutsideJustification == OutsideJustification.endDrawArea) : (_config.titleOutsideJustification == OutsideJustification.start || _config.titleOutsideJustification == OutsideJustification.startDrawArea); // Don't apply outer padding if we are aligned to the draw area. final padding = (_config.titleOutsideJustification == OutsideJustification.endDrawArea || _config.titleOutsideJustification == OutsideJustification.startDrawArea) ? 0.0 : _config.outerPadding; if (alignLeft) { labelY = (bounds.bottom - padding).round(); textElement.textDirection = TextDirection.ltr; } else { labelY = (bounds.top + padding).round(); textElement.textDirection = TextDirection.rtl; } break; } // labelX is always relative to the component bounds. if (_layoutPosition == LayoutPosition.Right || _layoutPosition == LayoutPosition.FullRight) { final padding = _config.outerPadding + (isPrimaryTitle ? 0 : _config.titlePadding + titleHeight); labelX = (bounds.left + padding).round(); } else { final padding = _config.outerPadding + titleHeight + (isPrimaryTitle ? (subTitleHeight > 0 ? _config.titlePadding + subTitleHeight : 0) : 0.0); labelX = (bounds.right - padding).round(); } return Point(labelX, labelY); } // Helper function that converts [TextStyleSpec] to [TextStyle]. TextStyle _getTextStyle( GraphicsFactory graphicsFactory, TextStyleSpec labelSpec) { return graphicsFactory.createTextPaint() ..color = labelSpec.color ?? StyleFactory.style.tickColor ..fontFamily = labelSpec.fontFamily ..fontSize = labelSpec.fontSize ?? 18 ..lineHeight = labelSpec.lineHeight; } @override Rectangle get componentBounds => _drawAreaBounds; @override bool get isSeriesRenderer => false; } /// Configuration object for [ChartTitle]. class _ChartTitleConfig { _ChartTitleConfig({ required this.behaviorPosition, required this.layoutMinSize, required this.layoutPreferredSize, required this.maxWidthStrategy, required this.title, required this.titleDirection, required this.titleOutsideJustification, required this.titleStyleSpec, required this.subTitle, required this.subTitleStyleSpec, required this.innerPadding, required this.titlePadding, required this.outerPadding, }); BehaviorPosition behaviorPosition; int? layoutMinSize; int? layoutPreferredSize; MaxWidthStrategy maxWidthStrategy; String title; ChartTitleDirection titleDirection; OutsideJustification titleOutsideJustification; TextStyleSpec titleStyleSpec; String? subTitle; TextStyleSpec subTitleStyleSpec; int innerPadding; int titlePadding; int outerPadding; } /// Direction of the title text on the chart. enum ChartTitleDirection { /// Automatically assign a direction based on the [RangeAnnotationAxisType]. /// /// [horizontal] for measure axes, or [vertical] for domain axes. auto, /// Text flows parallel to the x axis. horizontal, /// Text flows parallel to the y axis. vertical, } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/common/behavior/domain_highlighter.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import '../base_chart.dart' show BaseChart, LifecycleListener; import '../processed_series.dart' show MutableSeries; import '../selection_model/selection_model.dart' show SelectionModel, SelectionModelType; import 'chart_behavior.dart' show ChartBehavior; /// Chart behavior that monitors the specified [SelectionModel] and darkens the /// color for selected data. /// /// This is typically used for bars and pies to highlight segments. /// /// It is used in combination with SelectNearest to update the selection model /// and expand selection out to the domain value. class DomainHighlighter implements ChartBehavior { final SelectionModelType selectionModelType; late BaseChart _chart; late LifecycleListener _lifecycleListener; DomainHighlighter([this.selectionModelType = SelectionModelType.info]) { _lifecycleListener = LifecycleListener(onPostprocess: _updateColorFunctions); } void _selectionChanged(SelectionModel selectionModel) { _chart.redraw(skipLayout: true, skipAnimation: true); } void _updateColorFunctions(List> seriesList) { SelectionModel selectionModel = _chart.getSelectionModel(selectionModelType); seriesList.forEach((MutableSeries series) { final origColorFn = series.colorFn; if (origColorFn != null) { series.colorFn = (int? index) { final origColor = origColorFn(index); if (selectionModel.isDatumSelected(series, index)) { return origColor.darker; } else { return origColor; } }; } }); } @override void attachTo(BaseChart chart) { _chart = chart; chart.addLifecycleListener(_lifecycleListener); chart .getSelectionModel(selectionModelType) .addSelectionChangedListener(_selectionChanged); } @override void removeFrom(BaseChart chart) { chart .getSelectionModel(selectionModelType) .removeSelectionChangedListener(_selectionChanged); chart.removeLifecycleListener(_lifecycleListener); } @override String get role => 'domainHighlight-$selectionModelType'; } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/common/behavior/domain_outliner.dart ================================================ // Copyright 2019 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'package:charts_common/src/chart/common/base_chart.dart'; import 'package:charts_common/src/chart/common/processed_series.dart'; import 'package:charts_common/src/chart/common/selection_model/selection_model.dart'; import 'chart_behavior.dart' show ChartBehavior; /// Chart behavior that monitors the specified [SelectionModel] and outlines the /// selected data. /// /// This is typically used for treemap charts to highlight nodes. /// For bars and pies, prefers to use [DomainHighlighter] for UX consistency. class DomainOutliner implements ChartBehavior { final SelectionModelType selectionType; /// Default stroke width of the outline if the series has no stroke width /// function. /// /// When no stroke width function is provided, this value will be used as /// is. [strokePaddingPx] will not be added to [defaultStrokePx]. final double defaultStrokePx; /// Additional stroke width added to the outline of the selected data. /// /// This value is only used when the series has a stroke width function /// defined. final double strokePaddingPx; late BaseChart _chart; late LifecycleListener _lifecycleListener; DomainOutliner({ this.selectionType = SelectionModelType.info, double? defaultStrokePx, double? strokePaddingPx, }) : defaultStrokePx = defaultStrokePx ?? 2.0, strokePaddingPx = strokePaddingPx ?? 1.0 { _lifecycleListener = LifecycleListener(onPostprocess: _outline); } void _selectionChange(SelectionModel selectionModel) { _chart.redraw(skipLayout: true, skipAnimation: true); } void _outline(List> seriesList) { final selectionModel = _chart.getSelectionModel(selectionType); for (var series in seriesList) { final strokeWidthPxFn = series.strokeWidthPxFn; final colorFn = series.colorFn; if (colorFn != null) { series.colorFn = (int? index) { final color = colorFn(index); return selectionModel.isDatumSelected(series, index) ? color.darker : color; }; } if (strokeWidthPxFn != null) { series.strokeWidthPxFn = (int? index) { final strokeWidthPx = strokeWidthPxFn(index); if (!selectionModel.isDatumSelected(series, index)) { return strokeWidthPx; } return strokeWidthPx == null ? defaultStrokePx : strokeWidthPx + strokePaddingPx; }; } } } @override void attachTo(BaseChart chart) { _chart = chart; chart.addLifecycleListener(_lifecycleListener); chart .getSelectionModel(selectionType) .addSelectionChangedListener(_selectionChange); } @override void removeFrom(BaseChart chart) { chart .getSelectionModel(selectionType) .removeSelectionChangedListener(_selectionChange); chart.removeLifecycleListener(_lifecycleListener); } @override String get role => 'domainOutliner-$selectionType'; } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/common/behavior/initial_selection.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import '../base_chart.dart' show BaseChart, LifecycleListener; import '../processed_series.dart' show MutableSeries; import '../selection_model/selection_model.dart' show SelectionModel, SelectionModelType; import '../series_datum.dart' show SeriesDatumConfig; import 'chart_behavior.dart' show ChartBehavior; /// Behavior that sets initial selection. class InitialSelection implements ChartBehavior { final SelectionModelType selectionModelType; /// List of series id of initially selected series. final List? selectedSeriesConfig; /// List of [SeriesDatumConfig] that represents the initially selected datums. final List>? selectedDataConfig; BaseChart? _chart; late LifecycleListener _lifecycleListener; bool _firstDraw = true; // TODO : When the series changes, if the user does not also // change the index the wrong item could be highlighted. InitialSelection( {this.selectionModelType = SelectionModelType.info, this.selectedDataConfig, this.selectedSeriesConfig}) { _lifecycleListener = LifecycleListener(onData: _setInitialSelection); } void _setInitialSelection(List> seriesList) { if (!_firstDraw) { return; } _firstDraw = false; final immutableModel = SelectionModel.fromConfig( selectedDataConfig, selectedSeriesConfig, seriesList); _chart!.getSelectionModel(selectionModelType).updateSelection( immutableModel.selectedDatum, immutableModel.selectedSeries, notifyListeners: false); } @override void attachTo(BaseChart chart) { _chart = chart; chart.addLifecycleListener(_lifecycleListener); } @override void removeFrom(BaseChart chart) { chart.removeLifecycleListener(_lifecycleListener); _chart = null; } @override String get role => 'InitialSelection-$selectionModelType'; } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/common/behavior/legend/datum_legend.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import '../../../cartesian/axis/spec/axis_spec.dart' show TextStyleSpec; import '../../datum_details.dart' show MeasureFormatter; import '../../selection_model/selection_model.dart' show SelectionModelType; import 'legend.dart'; import 'legend_entry_generator.dart'; import 'per_datum_legend_entry_generator.dart'; /// Datum legend behavior for charts. /// /// By default this behavior creates one legend entry per datum in the first /// series rendered on the chart. /// /// TODO: Allows for hovering over a datum in legend to highlight /// corresponding datum in draw area. /// /// TODO: Implement tap to hide individual data in the series. class DatumLegend extends Legend { /// Whether or not the series legend should show measures on datum selection. late bool _showMeasures; DatumLegend({ SelectionModelType? selectionModelType, LegendEntryGenerator? legendEntryGenerator, MeasureFormatter? measureFormatter, MeasureFormatter? secondaryMeasureFormatter, bool? showMeasures, LegendDefaultMeasure? legendDefaultMeasure, TextStyleSpec? entryTextStyle, }) : super( selectionModelType: selectionModelType ?? SelectionModelType.info, legendEntryGenerator: legendEntryGenerator ?? PerDatumLegendEntryGenerator(), entryTextStyle: entryTextStyle) { // Calling the setters will automatically use non-null default values. this.showMeasures = showMeasures; this.legendDefaultMeasure = legendDefaultMeasure; this.measureFormatter = measureFormatter; this.secondaryMeasureFormatter = secondaryMeasureFormatter; } /// Whether or not the legend should show measures. /// /// By default this is false, measures are not shown. When set to true, the /// default behavior is to show measure only if there is selected data. /// Please set [legendDefaultMeasure] to something other than none to enable /// showing measures when there is no selection. /// /// If [showMeasures] is set to null, it is changed to the default of false. bool get showMeasures => _showMeasures; set showMeasures(bool? showMeasures) { _showMeasures = showMeasures ?? false; } /// Option to show measures when selection is null. /// /// By default this is set to none, so no measures are shown when there is /// no selection. /// /// If [legendDefaultMeasure] is set to null, it is changed to the default of /// none. LegendDefaultMeasure get legendDefaultMeasure => legendEntryGenerator.legendDefaultMeasure; set legendDefaultMeasure(LegendDefaultMeasure? legendDefaultMeasure) { legendEntryGenerator.legendDefaultMeasure = legendDefaultMeasure ?? LegendDefaultMeasure.none; } /// Formatter for measure values. /// /// This is optional. The default formatter formats measure values with /// NumberFormat.decimalPattern. If the measure value is null, a dash is /// returned. set measureFormatter(MeasureFormatter? formatter) { legendEntryGenerator.measureFormatter = formatter ?? defaultLegendMeasureFormatter; } /// Formatter for measure values of series that uses the secondary axis. /// /// This is optional. The default formatter formats measure values with /// NumberFormat.decimalPattern. If the measure value is null, a dash is /// returned. set secondaryMeasureFormatter(MeasureFormatter? formatter) { legendEntryGenerator.secondaryMeasureFormatter = formatter ?? defaultLegendMeasureFormatter; } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/common/behavior/legend/legend.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math' show Rectangle; import 'package:meta/meta.dart' show protected; import 'package:intl/intl.dart'; import '../../../../common/graphics_factory.dart' show GraphicsFactory; import '../../../cartesian/axis/spec/axis_spec.dart' show TextStyleSpec; import '../../../layout/layout_view.dart' show LayoutPosition, LayoutView, LayoutViewConfig, LayoutViewPositionOrder, LayoutViewPaintOrder, ViewMeasuredSizes, layoutPosition; import '../../base_chart.dart' show BaseChart, LifecycleListener; import '../../chart_canvas.dart' show ChartCanvas; import '../../chart_context.dart' show ChartContext; import '../../processed_series.dart' show MutableSeries; import '../../selection_model/selection_model.dart' show SelectionModel, SelectionModelType; import '../chart_behavior.dart' show BehaviorPosition, ChartBehavior, InsideJustification, OutsideJustification; import 'legend_entry.dart'; import 'legend_entry_generator.dart'; /// Legend behavior for charts. /// /// Since legends are desired to be customizable, building and displaying the /// visual content of legends is done on the native platforms. This allows users /// to specify customized content for legends using the native platform (ex. for /// Flutter, using widgets). abstract class Legend implements ChartBehavior, LayoutView { final SelectionModelType selectionModelType; final legendState = LegendState(); final LegendEntryGenerator legendEntryGenerator; /// The title text to display before legend entries. late String title; late BaseChart _chart; late final LifecycleListener _lifecycleListener; Rectangle? _componentBounds; Rectangle? _drawAreaBounds; @override GraphicsFactory? graphicsFactory; BehaviorPosition behaviorPosition = BehaviorPosition.end; OutsideJustification outsideJustification = OutsideJustification.startDrawArea; InsideJustification insideJustification = InsideJustification.topStart; LegendCellPadding? cellPadding; LegendCellPadding? legendPadding; /// Text style of the legend title text. TextStyleSpec? titleTextStyle; /// Configures the behavior of the legend when the user taps/clicks on an /// entry. Defaults to no behavior. /// /// Tapping on a legend entry will update the data visible on the chart. For /// example, when [LegendTapHandling.hide] is configured, the series or datum /// associated with that entry will be removed from the chart. Tapping on that /// entry a second time will make the data visible again. LegendTapHandling legendTapHandling = LegendTapHandling.hide; late List> _currentSeriesList; /// List of series IDs in the order the series should appear in the legend. /// Series that are not specified in the ordering will be sorted /// alphabetically at the bottom. List? _customEntryOrder; /// Save this in order to check if series list have changed and regenerate /// the legend entries. List>? _postProcessSeriesList; static final _decimalPattern = NumberFormat.decimalPattern(); /// Default measure formatter for legends. @protected String defaultLegendMeasureFormatter(num? value) { return (value == null) ? '' : _decimalPattern.format(value); } Legend({ required this.selectionModelType, required this.legendEntryGenerator, TextStyleSpec? entryTextStyle, }) { _lifecycleListener = LifecycleListener( onPostprocess: _postProcess, onPreprocess: _preProcess, onData: onData); legendEntryGenerator.entryTextStyle = entryTextStyle; // Calling the setter will automatically use a non-null default value. showOverlaySeries = null; } /// Text style of the legend entry text. TextStyleSpec? get entryTextStyle => legendEntryGenerator.entryTextStyle; set entryTextStyle(TextStyleSpec? entryTextStyle) { legendEntryGenerator.entryTextStyle = entryTextStyle; } set customEntryOrder(List? customEntryOrder) { _customEntryOrder = customEntryOrder; } /// Whether or not the legend show overlay series. /// /// By default this is false, the overlay series are not shown on the legend. /// /// if [showOverlaySeries] is set to null, it is changed to the default of /// false. bool get showOverlaySeries => legendEntryGenerator.showOverlaySeries; set showOverlaySeries(bool? showOverlaySeries) { legendEntryGenerator.showOverlaySeries = showOverlaySeries ?? false; } /// Resets any hidden series data when new data is drawn on the chart. @protected void onData(List> seriesList) {} /// Store off a copy of the series list for use when we render the legend. void _preProcess(List> seriesList) { _currentSeriesList = List.of(seriesList); preProcessSeriesList(seriesList); } /// Overridable method that may be used by concrete [Legend] instances to /// manipulate the series list. @protected void preProcessSeriesList(List> seriesList) {} /// Build LegendEntries from list of series. void _postProcess(List> seriesList) { // Get the selection model directly from chart on post process. // // This is because if initial selection is set as a behavior, it will be // handled during onData. onData is prior to this behavior's postProcess // call, so the selection will have changed prior to the entries being // generated. final selectionModel = chart.getSelectionModel(selectionModelType); // Update entries if the selection model is different because post // process is called on each draw cycle, so this is called on each animation // frame and we don't want to update and request the native platform to // rebuild if nothing has changed. // // Also update legend entries if the series list has changed. if (legendState._selectionModel != selectionModel || _postProcessSeriesList != seriesList) { final _customEntryOrder = this._customEntryOrder; if (_customEntryOrder != null) { _currentSeriesList.sort((a, b) { final a_index = _customEntryOrder.indexOf(a.id); final b_index = _customEntryOrder.indexOf(b.id); if (a_index == -1) { if (a_index == b_index) { return a.displayName!.compareTo(b.displayName!); } return 1; } else if (b_index == -1) { return -1; } return a_index.compareTo(b_index); }); } legendState._legendEntries = legendEntryGenerator.getLegendEntries(_currentSeriesList); legendState._selectionModel = selectionModel; _postProcessSeriesList = seriesList; _updateLegendEntries(seriesList: seriesList); } } // need to handle when series data changes, selection should be reset /// Update the legend state with [selectionModel] and request legend update. void _selectionChanged(SelectionModel selectionModel) { legendState._selectionModel = selectionModel; _updateLegendEntries(); } ChartContext get chartContext => _chart.context; /// Internally update legend entries, before calling [updateLegend] that /// notifies the native platform. void _updateLegendEntries({List>? seriesList}) { legendEntryGenerator.updateLegendEntries(legendState._legendEntries, legendState._selectionModel!, seriesList ?? chart.currentSeriesList); updateLegend(); } /// Requires override to show in native platform void updateLegend() {} @override void attachTo(BaseChart chart) { _chart = chart; chart.addLifecycleListener(_lifecycleListener); chart .getSelectionModel(selectionModelType) .addSelectionChangedListener(_selectionChanged); chart.addView(this); } @override void removeFrom(BaseChart chart) { chart .getSelectionModel(selectionModelType) .removeSelectionChangedListener(_selectionChanged); chart.removeLifecycleListener(_lifecycleListener); chart.removeView(this); } @protected BaseChart get chart => _chart; @override String get role => 'legend-$selectionModelType'; bool get isRtl => _chart.context.chartContainerIsRtl; bool get isAxisFlipped => _chart.context.isRtl; @override LayoutViewConfig get layoutConfig { return LayoutViewConfig( position: _layoutPosition, positionOrder: LayoutViewPositionOrder.legend, paintOrder: LayoutViewPaintOrder.legend); } /// Get layout position from legend position. LayoutPosition get _layoutPosition { return layoutPosition(behaviorPosition, outsideJustification, isRtl); } @override ViewMeasuredSizes measure(int maxWidth, int maxHeight) { // Native child classes should override this method to return real // measurements. return ViewMeasuredSizes(preferredWidth: 0, preferredHeight: 0); } @override void layout(Rectangle componentBounds, Rectangle drawAreaBounds) { _componentBounds = componentBounds; _drawAreaBounds = drawAreaBounds; updateLegend(); } @override void paint(ChartCanvas canvas, double animationPercent) {} @override Rectangle? get componentBounds => _componentBounds; @override bool get isSeriesRenderer => false; // Gets the draw area bounds for native legend content to position itself // accordingly. Rectangle? get drawAreaBounds => _drawAreaBounds; } /// Stores legend data used by native legend content builder. class LegendState { late List> _legendEntries; SelectionModel? _selectionModel; List> get legendEntries => _legendEntries; SelectionModel? get selectionModel => _selectionModel; } /// Stores legend cell padding, in percents or pixels. /// /// If a percent is specified, it takes precedence over a flat pixel value. class LegendCellPadding { final double? bottomPct; final double? bottomPx; final double? leftPct; final double? leftPx; final double? rightPct; final double? rightPx; final double? topPct; final double? topPx; /// Creates padding in percents from the left, top, right, and bottom. const LegendCellPadding.fromLTRBPct( this.leftPct, this.topPct, this.rightPct, this.bottomPct) : leftPx = null, topPx = null, rightPx = null, bottomPx = null; /// Creates padding in pixels from the left, top, right, and bottom. const LegendCellPadding.fromLTRBPx( this.leftPx, this.topPx, this.rightPx, this.bottomPx) : leftPct = null, topPct = null, rightPct = null, bottomPct = null; /// Creates padding in percents from the top, right, bottom, and left. const LegendCellPadding.fromTRBLPct( this.topPct, this.rightPct, this.bottomPct, this.leftPct) : topPx = null, rightPx = null, bottomPx = null, leftPx = null; /// Creates padding in pixels from the top, right, bottom, and left. const LegendCellPadding.fromTRBLPx( this.topPx, this.rightPx, this.bottomPx, this.leftPx) : topPct = null, rightPct = null, bottomPct = null, leftPct = null; /// Creates cell padding where all the offsets are `value` in percent. /// /// ## Sample code /// /// Typical eight percent margin on all sides: /// /// ```dart /// const LegendCellPadding.allPct(8.0) /// ``` const LegendCellPadding.allPct(double value) : this.fromLTRBPct(value, value, value, value); /// Creates cell padding where all the offsets are `value` in pixels. /// /// ## Sample code /// /// Typical eight-pixel margin on all sides: /// /// ```dart /// const LegendCellPadding.allPx(8.0) /// ``` const LegendCellPadding.allPx(double value) : this.fromLTRBPx(value, value, value, value); double bottom(num height) => bottomPct != null ? bottomPct! * height : bottomPx!; double left(num width) => leftPct != null ? leftPct! * width : leftPx!; double right(num width) => rightPct != null ? rightPct! * width : rightPx!; double top(num height) => topPct != null ? topPct! * height : topPx!; } /// Options for behavior of tapping/clicking on entries in the legend. enum LegendTapHandling { /// No associated behavior. none, /// Hide elements on the chart associated with this legend entry. hide, } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/common/behavior/legend/legend_entry.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import '../../../../common/color.dart'; import '../../../../common/symbol_renderer.dart'; import '../../../cartesian/axis/spec/axis_spec.dart' show TextStyleSpec; import '../../processed_series.dart' show ImmutableSeries; import '../../series_renderer.dart' show rendererKey; /// The most basic possible legend entry - just a display name and positioning. class LegendEntryBase { final String label; final TextStyleSpec? textStyle; /// Zero based index for the row where this legend appears in the legend. int? rowNumber; /// Zero based index for the column where this legend appears in the legend. int? columnNumber; /// Total number of rows in the legend. int? rowCount; /// Total number of columns in the legend. int? columnCount; /// Indicates whether this is in the first row of a tabular layout. bool? inFirstRow; /// Indicates whether this is in the first column of a tabular layout. bool? inFirstColumn; /// Indicates whether this is in the last row of a tabular layout. bool? inLastRow; /// Indicates whether this is in the last column of a tabular layout. bool? inLastColumn; LegendEntryBase(this.label, {this.textStyle, this.rowNumber, this.columnNumber, this.rowCount, this.columnCount, this.inFirstRow, this.inFirstColumn, this.inLastRow, this.inLastColumn}); } /// When the legend groups by category it will create additional legend entries /// that track styling and grouping on a per category basis. class LegendCategory extends LegendEntryBase { /// The list of entries that should be displayed within this category. final List>? entries; LegendCategory( String label, this.entries, { TextStyleSpec? textStyle, int? rowNumber, int? columnNumber, int? rowCount, int? columnCount, bool? inFirstRow, bool? inFirstColumn, bool? inLastRow, bool? inLastColumn, }) : super(label, textStyle: textStyle, rowNumber: rowNumber, columnNumber: columnNumber, rowCount: rowCount, columnCount: columnCount, inFirstRow: inFirstRow, inFirstColumn: inFirstColumn, inLastRow: inLastRow, inLastColumn: inLastColumn); } /// Holder for the information used for a legend row. /// /// [T] the datum class type for the series passed in. /// [D] the domain class type for the datum. class LegendEntry extends LegendEntryBase { final ImmutableSeries series; final dynamic datum; final int? datumIndex; final D? domain; final Color? color; double? value; List? selectedDataIndexes; String? formattedValue; bool isSelected; // TODO: Forward the default formatters from series and allow for // native legends to provide separate formatters. LegendEntry( this.series, String label, { this.datum, this.datumIndex, this.domain, this.value, this.selectedDataIndexes, this.color, this.isSelected = false, TextStyleSpec? textStyle, int? rowNumber, int? columnNumber, int? rowCount, int? columnCount, bool? inFirstRow, bool? inFirstColumn, bool? inLastRow, bool? inLastColumn, }) : super(label, textStyle: textStyle, rowNumber: rowNumber, columnNumber: columnNumber, rowCount: rowCount, columnCount: columnCount, inFirstRow: inFirstRow, inFirstColumn: inFirstColumn, inLastRow: inLastRow, inLastColumn: inLastColumn); /// Get the native symbol renderer stored in the series. SymbolRenderer? get symbolRenderer => series.getAttr(rendererKey)!.symbolRenderer; /// Gets the dash pattern for the symbol from the given datum and series. /// /// Use the dash pattern from the datum if available, otherwise fall back to /// generic series dash pattern. List? get dashPattern => series.dashPatternFn?.call(datumIndex ?? 0); } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/common/behavior/legend/legend_entry_generator.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import '../../../cartesian/axis/spec/axis_spec.dart' show TextStyleSpec; import '../../datum_details.dart' show MeasureFormatter; import '../../processed_series.dart' show MutableSeries; import '../../selection_model/selection_model.dart'; import 'legend_entry.dart'; /// A strategy for generating a list of [LegendEntry] based on the series drawn. /// /// [D] the domain class type for the datum. abstract class LegendEntryGenerator { /// Generates a list of legend entries based on the series drawn on the chart. /// /// [seriesList] Processed series list. List> getLegendEntries(List> seriesList); /// Update the list of legend entries based on the selection model. /// /// [legendEntries] Existing legend entries to update. /// [selectionModel] Selection model to query selected state. /// [seriesList] Processed series list. void updateLegendEntries(List> legendEntries, SelectionModel selectionModel, List> seriesList); MeasureFormatter? get measureFormatter; set measureFormatter(MeasureFormatter? formatter); MeasureFormatter? get secondaryMeasureFormatter; set secondaryMeasureFormatter(MeasureFormatter? formatter); LegendDefaultMeasure get legendDefaultMeasure; set legendDefaultMeasure(LegendDefaultMeasure noSelectionMeasure); TextStyleSpec? get entryTextStyle; set entryTextStyle(TextStyleSpec? entryTextStyle); bool get showOverlaySeries; set showOverlaySeries(bool showOverlaySeries); } /// Options for calculating what measures are shown when there is no selection. enum LegendDefaultMeasure { // No measures are shown where there is no selection. none, // Sum of all measure values for the series. sum, // Average of all measure values for the series. average, // The first measure value of the series. firstValue, // The last measure value of the series. lastValue, } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/common/behavior/legend/per_datum_legend_entry_generator.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //import 'dart:collection' show HashSet; import '../../../cartesian/axis/axis.dart' show Axis, measureAxisIdKey; import '../../../cartesian/axis/spec/axis_spec.dart' show TextStyleSpec; import '../../datum_details.dart' show MeasureFormatter; import '../../processed_series.dart' show ImmutableSeries, MutableSeries; import '../../selection_model/selection_model.dart'; import 'legend_entry.dart'; import 'legend_entry_generator.dart'; /// A strategy for generating a list of [LegendEntry] per series data drawn. /// /// [D] the domain class type for the datum. class PerDatumLegendEntryGenerator implements LegendEntryGenerator { @override TextStyleSpec? entryTextStyle; @override MeasureFormatter? measureFormatter; @override MeasureFormatter? secondaryMeasureFormatter; @override late bool showOverlaySeries; /// Option for showing measures when there is no selection. @override late LegendDefaultMeasure legendDefaultMeasure; @override List> getLegendEntries(List> seriesList) { final legendEntries = >[]; final series = seriesList[0]; for (var i = 0; i < series.data.length; i++) { legendEntries.add(LegendEntry(series, series.domainFn(i).toString(), color: series.colorFn!(i), datum: series.data[i], datumIndex: i, textStyle: entryTextStyle)); } // Update with measures only if showing measure on no selection. if (legendDefaultMeasure != LegendDefaultMeasure.none) { _updateEntries(legendEntries); } return legendEntries; } @override void updateLegendEntries(List> legendEntries, SelectionModel selectionModel, List> seriesList) { if (selectionModel.hasAnySelection) { _updateFromSelection(legendEntries, selectionModel); } else { // Update with measures only if showing measure on no selection. if (legendDefaultMeasure != LegendDefaultMeasure.none) { _updateEntries(legendEntries); } else { _resetLegendEntryMeasures(legendEntries); } } } /// Update legend entries with measures of the selected datum void _updateFromSelection( List> legendEntries, SelectionModel selectionModel) { // Given that each legend entry only has one datum associated with it, any // option for [legendDefaultMeasure] essentially boils down to just showing // the measure value. if (legendDefaultMeasure != LegendDefaultMeasure.none) { for (var entry in legendEntries) { final series = entry.series; final measure = series.measureFn(entry.datumIndex); entry.value = measure!.toDouble(); entry.formattedValue = _getFormattedMeasureValue(series, measure); entry.isSelected = selectionModel.selectedSeries .any((selectedSeries) => series.id == selectedSeries.id); } } } void _resetLegendEntryMeasures(List> legendEntries) { for (final entry in legendEntries) { entry.value = null; entry.formattedValue = null; entry.isSelected = false; } } /// Update each legend entry by calculating measure values for its series. /// /// This method calculates the legend's measure value to show when there is no /// selection. The type of calculation is based on the [legendDefaultMeasure] /// value. void _updateEntries(List> legendEntries) { // Given that each legend entry only has one datum associated with it, any // option for [legendDefaultMeasure] essentially boils down to just showing // the measure value. if (legendDefaultMeasure != LegendDefaultMeasure.none) { for (var entry in legendEntries) { final series = entry.series; final measure = series.measureFn(entry.datumIndex); entry.value = measure!.toDouble(); entry.formattedValue = _getFormattedMeasureValue(series, measure); entry.isSelected = false; } } } /// Formats the measure value using the appropriate measure formatter /// function for the series. String _getFormattedMeasureValue(ImmutableSeries series, num measure) { return (series.getAttr(measureAxisIdKey) == Axis.secondaryMeasureAxisId) ? secondaryMeasureFormatter!(measure) : measureFormatter!(measure); } @override bool operator ==(Object other) { return other is PerDatumLegendEntryGenerator && measureFormatter == other.measureFormatter && secondaryMeasureFormatter == other.secondaryMeasureFormatter && legendDefaultMeasure == other.legendDefaultMeasure && entryTextStyle == other.entryTextStyle; } @override int get hashCode { var hashcode = measureFormatter.hashCode; hashcode = (hashcode * 37) + secondaryMeasureFormatter.hashCode; hashcode = (hashcode * 37) + legendDefaultMeasure.hashCode; hashcode = (hashcode * 37) + entryTextStyle.hashCode; return hashcode; } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/common/behavior/legend/per_series_legend_entry_generator.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:collection' show HashSet; import '../../../cartesian/axis/axis.dart' show Axis, measureAxisIdKey; import '../../../cartesian/axis/spec/axis_spec.dart' show TextStyleSpec; import '../../datum_details.dart' show MeasureFormatter; import '../../processed_series.dart' show MutableSeries; import '../../selection_model/selection_model.dart'; import 'legend_entry.dart'; import 'legend_entry_generator.dart'; /// A strategy for generating a list of [LegendEntry] per series drawn. /// /// [D] the domain class type for the datum. class PerSeriesLegendEntryGenerator implements LegendEntryGenerator { @override TextStyleSpec? entryTextStyle; @override MeasureFormatter? measureFormatter; @override MeasureFormatter? secondaryMeasureFormatter; @override bool showOverlaySeries = false; /// Option for showing measures when there is no selection. @override late LegendDefaultMeasure legendDefaultMeasure; @override List> getLegendEntries(List> seriesList) { final legendEntries = seriesList .where((series) => showOverlaySeries || !series.overlaySeries) .map((series) => LegendEntry( series, series.displayName!, // TODO: Should this use series.colorFn if seriesColor is null? color: series.seriesColor!, textStyle: entryTextStyle, )) .toList(); // Update with measures only if showing measure on no selection. if (legendDefaultMeasure != LegendDefaultMeasure.none) { _updateFromSeriesList(legendEntries, seriesList); } return legendEntries; } @override void updateLegendEntries(List> legendEntries, SelectionModel selectionModel, List> seriesList) { if (selectionModel.hasAnySelection) { _updateFromSelection(legendEntries, selectionModel); } else { // Update with measures only if showing measure on no selection. if (legendDefaultMeasure != LegendDefaultMeasure.none) { _updateFromSeriesList(legendEntries, seriesList); } else { _resetLegendEntryMeasures(legendEntries); } } } /// Update legend entries with measures of the selected datum void _updateFromSelection( List> legendEntries, SelectionModel selectionModel) { // Map of series ID to the total selected measure value for that series. final seriesAndMeasure = {}; // Hash set of series ID's that use the secondary measure axis final secondaryAxisSeriesIDs = HashSet(); for (final selectedDatum in selectionModel.selectedDatum) { final series = selectedDatum.series; final seriesId = series.id; final measure = series.measureFn(selectedDatum.index) ?? 0; seriesAndMeasure[seriesId] = (seriesAndMeasure[seriesId] ?? 0) + measure; if (series.getAttr(measureAxisIdKey) == Axis.secondaryMeasureAxisId) { secondaryAxisSeriesIDs.add(seriesId); } } for (var entry in legendEntries) { final seriesId = entry.series.id; final measureValue = seriesAndMeasure[seriesId]?.toDouble(); final formattedValue = secondaryAxisSeriesIDs.contains(seriesId) ? secondaryMeasureFormatter!(measureValue) : measureFormatter!(measureValue); entry.value = measureValue; entry.formattedValue = formattedValue; entry.isSelected = selectionModel.selectedSeries .any((selectedSeries) => entry.series.id == selectedSeries.id); // Set the current selected model index for legend entry. entry.selectedDataIndexes = selectionModel.selectedDatum.map((datum) => datum.index).toList(); } } void _resetLegendEntryMeasures(List> legendEntries) { for (final entry in legendEntries) { entry.value = null; entry.formattedValue = null; entry.isSelected = false; } } /// Update each legend entry by calculating measure values in [seriesList]. /// /// This method calculates the legend's measure value to show when there is no /// selection. The type of calculation is based on the [legendDefaultMeasure] /// value. void _updateFromSeriesList( List> legendEntries, List> seriesList) { // Helper function to sum up the measure values num getMeasureTotal(MutableSeries series) { var measureTotal = 0.0; for (var i = 0; i < series.data.length; i++) { measureTotal += series.measureFn(i) ?? 0.0; } return measureTotal; } // Map of series ID to the calculated measure for that series. final seriesAndMeasure = {}; // Map of series ID and the formatted measure for that series. final seriesAndFormattedMeasure = {}; for (final series in seriesList) { final seriesId = series.id; num? calculatedMeasure; switch (legendDefaultMeasure) { case LegendDefaultMeasure.sum: calculatedMeasure = getMeasureTotal(series); break; case LegendDefaultMeasure.average: calculatedMeasure = getMeasureTotal(series) / series.data.length; break; case LegendDefaultMeasure.firstValue: calculatedMeasure = series.measureFn(0); break; case LegendDefaultMeasure.lastValue: calculatedMeasure = series.measureFn(series.data.length - 1); break; case LegendDefaultMeasure.none: // [calculatedMeasure] intentionally left null, since we do not want // to show any measures. break; } seriesAndMeasure[seriesId] = calculatedMeasure?.toDouble(); seriesAndFormattedMeasure[seriesId] = (series.getAttr(measureAxisIdKey) == Axis.secondaryMeasureAxisId) ? secondaryMeasureFormatter!(calculatedMeasure) : measureFormatter!(calculatedMeasure); } for (var entry in legendEntries) { final seriesId = entry.series.id; entry.value = seriesAndMeasure[seriesId]; entry.formattedValue = seriesAndFormattedMeasure[seriesId]; entry.isSelected = false; } } @override bool operator ==(Object other) { return other is PerSeriesLegendEntryGenerator && measureFormatter == other.measureFormatter && secondaryMeasureFormatter == other.secondaryMeasureFormatter && legendDefaultMeasure == other.legendDefaultMeasure && entryTextStyle == other.entryTextStyle; } @override int get hashCode { var hashcode = measureFormatter.hashCode; hashcode = (hashcode * 37) + secondaryMeasureFormatter.hashCode; hashcode = (hashcode * 37) + legendDefaultMeasure.hashCode; hashcode = (hashcode * 37) + entryTextStyle.hashCode; return hashcode; } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/common/behavior/legend/series_legend.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'package:meta/meta.dart' show protected; import '../../../cartesian/axis/spec/axis_spec.dart' show TextStyleSpec; import '../../datum_details.dart' show MeasureFormatter; import '../../processed_series.dart' show MutableSeries; import '../../selection_model/selection_model.dart' show SelectionModelType; import 'legend.dart'; import 'legend_entry_generator.dart'; import 'per_series_legend_entry_generator.dart'; // TODO: Allows for hovering over a series in legend to highlight // corresponding series in draw area. /// Series legend behavior for charts. /// /// By default this behavior creates a legend entry per series. class SeriesLegend extends Legend { /// List of currently hidden series, by ID. final _hiddenSeriesList = {}; /// List of series IDs that should be hidden by default. List? _defaultHiddenSeries; /// List of series IDs that should not be hideable. List? _alwaysVisibleSeries; /// Whether or not the series legend should show measures on datum selection. late bool _showMeasures; SeriesLegend({ SelectionModelType? selectionModelType, LegendEntryGenerator? legendEntryGenerator, MeasureFormatter? measureFormatter, MeasureFormatter? secondaryMeasureFormatter, bool? showMeasures, LegendDefaultMeasure? legendDefaultMeasure, TextStyleSpec? entryTextStyle, }) : super( selectionModelType: selectionModelType ?? SelectionModelType.info, legendEntryGenerator: legendEntryGenerator ?? PerSeriesLegendEntryGenerator(), entryTextStyle: entryTextStyle) { // Calling the setters will automatically use non-null default values. this.showMeasures = showMeasures; this.legendDefaultMeasure = legendDefaultMeasure; this.measureFormatter = measureFormatter; this.secondaryMeasureFormatter = secondaryMeasureFormatter; } /// Sets a list of series IDs that should be hidden by default on first chart /// draw. /// /// This will also reset the current list of hidden series, filling it in with /// the new default list. set defaultHiddenSeries(List? defaultHiddenSeries) { _defaultHiddenSeries = defaultHiddenSeries; _hiddenSeriesList.clear(); _defaultHiddenSeries?.forEach(hideSeries); } /// Gets a list of series IDs that should be hidden by default on first chart /// draw. List? get defaultHiddenSeries => _defaultHiddenSeries; /// Sets a list of series IDs that should always be visible and therefore /// cannot be hidden. /// /// This also shows any series that should always be visible in case /// it was previously hidden. set alwaysVisibleSeries(List? alwaysVisibleSeries) { _alwaysVisibleSeries = alwaysVisibleSeries; _alwaysVisibleSeries?.forEach(showSeries); } /// Gets a list of series IDs that should always be visible. List? get alwaysVisibleSeries => _alwaysVisibleSeries; /// Whether or not the legend should show measures. /// /// By default this is false, measures are not shown. When set to true, the /// default behavior is to show measure only if there is selected data. /// Please set [legendDefaultMeasure] to something other than none to enable /// showing measures when there is no selection. /// /// If [showMeasures] is set to null, it is changed to the default of false. bool get showMeasures => _showMeasures; set showMeasures(bool? showMeasures) { _showMeasures = showMeasures ?? false; } /// Option to show measures when selection is null. /// /// By default this is set to none, so no measures are shown when there is /// no selection. /// /// If [legendDefaultMeasure] is set to null, it is changed to the default of /// none. LegendDefaultMeasure get legendDefaultMeasure => legendEntryGenerator.legendDefaultMeasure; set legendDefaultMeasure(LegendDefaultMeasure? legendDefaultMeasure) { legendEntryGenerator.legendDefaultMeasure = legendDefaultMeasure ?? LegendDefaultMeasure.none; } /// Formatter for measure values. /// /// This is optional. The default formatter formats measure values with /// NumberFormat.decimalPattern. If the measure value is null, a dash is /// returned. set measureFormatter(MeasureFormatter? formatter) { legendEntryGenerator.measureFormatter = formatter ?? defaultLegendMeasureFormatter; } /// Formatter for measure values of series that uses the secondary axis. /// /// This is optional. The default formatter formats measure values with /// NumberFormat.decimalPattern. If the measure value is null, a dash is /// returned. set secondaryMeasureFormatter(MeasureFormatter? formatter) { legendEntryGenerator.secondaryMeasureFormatter = formatter ?? defaultLegendMeasureFormatter; } /// Remove series IDs from the currently hidden list if those series have been /// removed from the chart data. The goal is to allow any metric that is /// removed from a chart, and later re-added to it, to be visible to the user. @override void onData(List> seriesList) { // If a series was removed from the chart, remove it from our current list // of hidden series. final seriesIds = seriesList.map((MutableSeries series) => series.id); _hiddenSeriesList.removeWhere((String id) => !seriesIds.contains(id)); } @override void preProcessSeriesList(List> seriesList) { seriesList.removeWhere((MutableSeries series) { return _hiddenSeriesList.contains(series.id); }); } /// Hides the data for a series on the chart by [seriesId]. /// /// The entry in the legend for this series will be grayed out to indicate /// that it is hidden. @protected void hideSeries(String seriesId) { if (!isSeriesAlwaysVisible(seriesId)) { _hiddenSeriesList.add(seriesId); } } /// Shows the data for a series on the chart by [seriesId]. /// /// The entry in the legend for this series will be returned to its normal /// color if it was previously hidden. @protected void showSeries(String seriesId) { _hiddenSeriesList.removeWhere((String id) => id == seriesId); } /// Returns whether or not a given series [seriesId] is currently hidden. bool isSeriesHidden(String seriesId) { return _hiddenSeriesList.contains(seriesId); } /// Returns whether or not a given series is always visible. bool isSeriesAlwaysVisible(String seriesId) { return _alwaysVisibleSeries != null && _alwaysVisibleSeries!.contains(seriesId); } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/common/behavior/line_point_highlighter.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:collection' show LinkedHashMap; import 'dart:math' show max, min, Point, Rectangle; import 'package:meta/meta.dart'; import '../../../common/color.dart' show Color; import '../../../common/graphics_factory.dart' show GraphicsFactory; import '../../../common/math.dart' show NullablePoint; import '../../../common/style/style_factory.dart' show StyleFactory; import '../../../common/symbol_renderer.dart' show CircleSymbolRenderer, SymbolRenderer; import '../../cartesian/axis/axis.dart' show ImmutableAxis, domainAxisKey, measureAxisKey; import '../../cartesian/cartesian_chart.dart' show CartesianChart; import '../../layout/layout_view.dart' show LayoutPosition, LayoutView, LayoutViewConfig, LayoutViewPaintOrder, ViewMeasuredSizes; import '../base_chart.dart' show BaseChart, LifecycleListener; import '../chart_canvas.dart' show ChartCanvas, getAnimatedColor; import '../processed_series.dart' show ImmutableSeries; import '../selection_model/selection_model.dart' show SelectionModel, SelectionModelType; import 'chart_behavior.dart' show ChartBehavior; /// Chart behavior that monitors the specified [SelectionModel] and renders a /// dot for selected data. /// /// Vertical or horizontal follow lines can optionally be drawn underneath the /// rendered dots. Follow lines will be drawn in the combined area of the chart /// draw area, and the draw area for any layout components that provide a /// series draw area (e.g. [SymbolAnnotationRenderer]). /// /// This is typically used for line charts to highlight segments. /// /// It is used in combination with SelectNearest to update the selection model /// and expand selection out to the domain value. class LinePointHighlighter implements ChartBehavior { final SelectionModelType selectionModelType; /// Default radius of the dots if the series has no radius mapping function. /// /// When no radius mapping function is provided, this value will be used as /// is. [radiusPaddingPx] will not be added to [defaultRadiusPx]. final double defaultRadiusPx; /// Additional radius value added to the radius of the selected data. /// /// This value is only used when the series has a radius mapping function /// defined. final double radiusPaddingPx; /// Whether or not to draw horizontal follow lines through the selected /// points. /// /// Defaults to drawing no horizontal follow lines. final LinePointHighlighterFollowLineType showHorizontalFollowLine; /// Whether or not to draw vertical follow lines through the selected points. /// /// Defaults to drawing a vertical follow line only for the nearest datum. final LinePointHighlighterFollowLineType showVerticalFollowLine; /// The dash pattern to be used for drawing the line. /// /// To disable dash pattern (to draw a solid line), pass in an empty list. /// This is because if dashPattern is null or not set, it defaults to [1,3]. final List? dashPattern; /// Whether or not follow lines should be drawn across the entire chart draw /// area, or just from the axis to the point. /// /// When disabled, measure follow lines will be drawn from the primary measure /// axis to the point. In RTL mode, this means from the right-hand axis. In /// LTR mode, from the left-hand axis. final bool drawFollowLinesAcrossChart; /// Renderer used to draw the highlighted points. final SymbolRenderer symbolRenderer; late BaseChart _chart; late _LinePointLayoutView _view; late LifecycleListener _lifecycleListener; /// Store a map of data drawn on the chart, mapped by series name. /// /// [LinkedHashMap] is used to render the series on the canvas in the same /// order as the data was provided by the selection model. // ignore: prefer_collection_literals, https://github.com/dart-lang/linter/issues/1649 var _seriesPointMap = LinkedHashMap>(); // Store a list of points that exist in the series data. // // This list will be used to remove any [_AnimatedPoint] that were rendered in // previous draw cycles, but no longer have a corresponding datum in the new // data. final _currentKeys = []; LinePointHighlighter( {SelectionModelType? selectionModelType, double? defaultRadiusPx, double? radiusPaddingPx, LinePointHighlighterFollowLineType? showHorizontalFollowLine, LinePointHighlighterFollowLineType? showVerticalFollowLine, List? dashPattern, bool? drawFollowLinesAcrossChart, SymbolRenderer? symbolRenderer}) : selectionModelType = selectionModelType ?? SelectionModelType.info, defaultRadiusPx = defaultRadiusPx ?? 4.0, radiusPaddingPx = radiusPaddingPx ?? 2.0, showHorizontalFollowLine = showHorizontalFollowLine ?? LinePointHighlighterFollowLineType.none, showVerticalFollowLine = showVerticalFollowLine ?? LinePointHighlighterFollowLineType.nearest, dashPattern = dashPattern ?? [1, 3], drawFollowLinesAcrossChart = drawFollowLinesAcrossChart ?? true, symbolRenderer = symbolRenderer ?? CircleSymbolRenderer() { _lifecycleListener = LifecycleListener(onAxisConfigured: _updateViewData); } @override void attachTo(BaseChart chart) { _chart = chart; _view = _LinePointLayoutView( chart: chart, layoutPaintOrder: LayoutViewPaintOrder.linePointHighlighter, showHorizontalFollowLine: showHorizontalFollowLine, showVerticalFollowLine: showVerticalFollowLine, dashPattern: dashPattern, drawFollowLinesAcrossChart: drawFollowLinesAcrossChart, symbolRenderer: symbolRenderer); if (chart is CartesianChart) { // Only vertical rendering is supported by this behavior. assert((chart as CartesianChart).vertical); } chart.addView(_view); chart.addLifecycleListener(_lifecycleListener); chart .getSelectionModel(selectionModelType) .addSelectionChangedListener(_selectionChanged); } @override void removeFrom(BaseChart chart) { chart.removeView(_view); chart .getSelectionModel(selectionModelType) .removeSelectionChangedListener(_selectionChanged); chart.removeLifecycleListener(_lifecycleListener); } void _selectionChanged(SelectionModel selectionModel) { _chart.redraw(skipLayout: true, skipAnimation: true); } void _updateViewData() { _currentKeys.clear(); final selectedDatumDetails = _chart.getSelectedDatumDetails(selectionModelType); // Create a new map each time to ensure that we have it sorted in the // selection model order. This preserves the "nearestDetail" ordering, so // that we render follow lines in the proper place. // ignore: prefer_collection_literals final newSeriesMap = LinkedHashMap>(); for (final detail in selectedDatumDetails) { if (detail == null) { continue; } final series = detail.series!; final Object? datum = detail.datum; final domainAxis = series.getAttr(domainAxisKey) as ImmutableAxis; final measureAxis = series.getAttr(measureAxisKey) as ImmutableAxis; final lineKey = series.id; final radiusPx = (detail.radiusPx != null) ? detail.radiusPx!.toDouble() + radiusPaddingPx : defaultRadiusPx; final pointKey = '${lineKey}::${detail.domain}::${detail.measure}'; // If we already have a point for that key, use it. _AnimatedPoint animatingPoint; if (_seriesPointMap.containsKey(pointKey)) { animatingPoint = _seriesPointMap[pointKey]!; } else { // Create a new point and have it animate in from axis. final point = _DatumPoint( datum: datum, domain: detail.domain, series: series, x: domainAxis.getLocation(detail.domain), y: measureAxis.getLocation(0.0)); animatingPoint = _AnimatedPoint( key: pointKey, overlaySeries: series.overlaySeries) ..setNewTarget(_PointRendererElement( point: point, color: detail.color, fillColor: detail.fillColor, radiusPx: radiusPx, measureAxisPosition: measureAxis.getLocation(0.0), strokeWidthPx: detail.strokeWidthPx, symbolRenderer: detail.symbolRenderer, )); } newSeriesMap[pointKey] = animatingPoint; // Create a new line using the final point locations. final point = _DatumPoint( datum: datum, domain: detail.domain, series: series, x: detail.chartPosition!.x, y: detail.chartPosition!.y); // Update the set of points that still exist in the series data. _currentKeys.add(pointKey); // Get the point element we are going to setup. final pointElement = _PointRendererElement( point: point, color: detail.color, fillColor: detail.fillColor, radiusPx: radiusPx, measureAxisPosition: measureAxis.getLocation(0.0), strokeWidthPx: detail.strokeWidthPx, symbolRenderer: detail.symbolRenderer, ); animatingPoint.setNewTarget(pointElement); } // Animate out points that don't exist anymore. _seriesPointMap.forEach((String key, _AnimatedPoint point) { if (_currentKeys.contains(point.key) != true) { point.animateOut(); newSeriesMap[point.key] = point; } }); _seriesPointMap = newSeriesMap; _view.seriesPointMap = _seriesPointMap; } @override String get role => 'LinePointHighlighter-$selectionModelType'; } class _LinePointLayoutView extends LayoutView { @override final LayoutViewConfig layoutConfig; final LinePointHighlighterFollowLineType showHorizontalFollowLine; final LinePointHighlighterFollowLineType showVerticalFollowLine; final BaseChart chart; final List? dashPattern; late Rectangle _drawAreaBounds; Rectangle get drawBounds => _drawAreaBounds; final bool drawFollowLinesAcrossChart; final SymbolRenderer symbolRenderer; @override GraphicsFactory? graphicsFactory; /// Store a map of series drawn on the chart, mapped by series name. /// /// [LinkedHashMap] is used to render the series on the canvas in the same /// order as the data was given to the chart. LinkedHashMap>? _seriesPointMap; _LinePointLayoutView({ required this.chart, required int layoutPaintOrder, required this.showHorizontalFollowLine, required this.showVerticalFollowLine, required this.symbolRenderer, required this.dashPattern, required this.drawFollowLinesAcrossChart, }) : layoutConfig = LayoutViewConfig( paintOrder: LayoutViewPaintOrder.linePointHighlighter, position: LayoutPosition.DrawArea, positionOrder: layoutPaintOrder); set seriesPointMap(LinkedHashMap>? value) { _seriesPointMap = value; } @override ViewMeasuredSizes? measure(int maxWidth, int maxHeight) { return null; } @override void layout(Rectangle componentBounds, Rectangle drawAreaBounds) { _drawAreaBounds = drawAreaBounds; } @override void paint(ChartCanvas canvas, double animationPercent) { final _seriesPointMap = this._seriesPointMap; if (_seriesPointMap == null) { return; } // Clean up the lines that no longer exist. if (animationPercent == 1.0) { final keysToRemove = []; _seriesPointMap.forEach((String key, _AnimatedPoint point) { if (point.animatingOut) { keysToRemove.add(key); } }); keysToRemove.forEach(_seriesPointMap.remove); } final points = <_PointRendererElement>[]; _seriesPointMap.forEach((String key, _AnimatedPoint point) { points.add(point.getCurrentPoint(animationPercent)); }); // Build maps of the position where the follow lines should stop for each // selected data point. final endPointPerValueVertical = {}; final endPointPerValueHorizontal = {}; for (final pointElement in points) { if (pointElement.point.x == null || pointElement.point.y == null) { continue; } final point = pointElement.point.toPoint(); final roundedX = point.x.round(); final roundedY = point.y.round(); // Get the Y value closest to the top of the chart for this X position. if (endPointPerValueVertical[roundedX] == null) { endPointPerValueVertical[roundedX] = roundedY; } else { // In the nearest case, we rely on the selected data always starting // with the nearest point. In this case, we don't care about the rest of // the selected data positions. if (showVerticalFollowLine != LinePointHighlighterFollowLineType.nearest) { endPointPerValueVertical[roundedX] = min(endPointPerValueVertical[roundedX]!, roundedY); } } // Get the X value closest to the "end" side of the chart for this Y // position. if (endPointPerValueHorizontal[roundedY] == null) { endPointPerValueHorizontal[roundedY] = roundedX; } else { // In the nearest case, we rely on the selected data always starting // with the nearest point. In this case, we don't care about the rest of // the selected data positions. if (showHorizontalFollowLine != LinePointHighlighterFollowLineType.nearest) { endPointPerValueHorizontal[roundedY] = max(endPointPerValueHorizontal[roundedY]!, roundedX); } } } var shouldShowHorizontalFollowLine = showHorizontalFollowLine == LinePointHighlighterFollowLineType.all || showHorizontalFollowLine == LinePointHighlighterFollowLineType.nearest; var shouldShowVerticalFollowLine = showVerticalFollowLine == LinePointHighlighterFollowLineType.all || showVerticalFollowLine == LinePointHighlighterFollowLineType.nearest; // Keep track of points for which we've already drawn lines. final paintedHorizontalLinePositions = []; final paintedVerticalLinePositions = []; final drawBounds = chart.drawableLayoutAreaBounds; final rtl = chart.context.isRtl; // Draw the follow lines first, below all of the highlight shapes. for (final pointElement in points) { if (pointElement.point.x == null || pointElement.point.y == null) { continue; } final point = pointElement.point.toPoint(); final roundedX = point.x.round(); final roundedY = point.y.round(); // Draw the horizontal follow line. if (shouldShowHorizontalFollowLine && !paintedHorizontalLinePositions.contains(roundedY)) { int leftBound; int rightBound; if (drawFollowLinesAcrossChart) { // RTL and LTR both go across the whole draw area. leftBound = drawBounds.left; rightBound = drawBounds.left + drawBounds.width; } else { final x = endPointPerValueHorizontal[roundedY]!; // RTL goes from the point to the right edge. LTR goes from the left // edge to the point. leftBound = rtl ? x : drawBounds.left; rightBound = rtl ? drawBounds.left + drawBounds.width : x; } canvas.drawLine( points: [ Point(leftBound, point.y), Point(rightBound, point.y), ], stroke: StyleFactory.style.linePointHighlighterColor, strokeWidthPx: 1.0, dashPattern: dashPattern); if (showHorizontalFollowLine == LinePointHighlighterFollowLineType.nearest) { shouldShowHorizontalFollowLine = false; } paintedHorizontalLinePositions.add(roundedY); } // Draw the vertical follow line. if (shouldShowVerticalFollowLine && !paintedVerticalLinePositions.contains(roundedX)) { final topBound = drawFollowLinesAcrossChart ? drawBounds.top : endPointPerValueVertical[roundedX]!; canvas.drawLine( points: [ Point(point.x, topBound), Point(point.x, drawBounds.top + drawBounds.height), ], stroke: StyleFactory.style.linePointHighlighterColor, strokeWidthPx: 1.0, dashPattern: dashPattern); if (showVerticalFollowLine == LinePointHighlighterFollowLineType.nearest) { shouldShowVerticalFollowLine = false; } paintedVerticalLinePositions.add(roundedX); } if (!shouldShowHorizontalFollowLine && !shouldShowVerticalFollowLine) { break; } } // Draw the highlight shapes on top of all follow lines. for (final pointElement in points) { if (pointElement.point.x == null || pointElement.point.y == null) { continue; } final point = pointElement.point.toPoint(); final bounds = Rectangle( point.x - pointElement.radiusPx, point.y - pointElement.radiusPx, pointElement.radiusPx * 2, pointElement.radiusPx * 2); // Draw the highlight dot. Use the [SymbolRenderer] from the datum if one // is defined. (pointElement.symbolRenderer ?? symbolRenderer).paint(canvas, bounds, fillColor: pointElement.fillColor, strokeColor: pointElement.color, strokeWidthPx: pointElement.strokeWidthPx); } } @override Rectangle get componentBounds => _drawAreaBounds; @override bool get isSeriesRenderer => false; } class _DatumPoint extends NullablePoint { final dynamic datum; final D? domain; final ImmutableSeries? series; _DatumPoint({ this.datum, this.domain, this.series, double? x, double? y, }) : super(x, y); factory _DatumPoint.from(_DatumPoint other, [double? x, double? y]) { return _DatumPoint( datum: other.datum, domain: other.domain, series: other.series, x: x ?? other.x, y: y ?? other.y); } } class _PointRendererElement { _DatumPoint point; Color? color; Color? fillColor; double radiusPx; double? measureAxisPosition; double? strokeWidthPx; SymbolRenderer? symbolRenderer; _PointRendererElement({ required this.point, required this.color, required this.fillColor, required this.radiusPx, required this.measureAxisPosition, required this.strokeWidthPx, required this.symbolRenderer, }); _PointRendererElement clone() { return _PointRendererElement( point: point, color: color, fillColor: fillColor, measureAxisPosition: measureAxisPosition, radiusPx: radiusPx, strokeWidthPx: strokeWidthPx, symbolRenderer: symbolRenderer, ); } void updateAnimationPercent(_PointRendererElement previous, _PointRendererElement target, double animationPercent) { final targetPoint = target.point; final previousPoint = previous.point; final x = _lerpDouble(previousPoint.x, targetPoint.x, animationPercent); final y = _lerpDouble(previousPoint.y, targetPoint.y, animationPercent); point = _DatumPoint.from(targetPoint, x, y); color = getAnimatedColor(previous.color!, target.color!, animationPercent); fillColor = getAnimatedColor( previous.fillColor!, target.fillColor!, animationPercent); radiusPx = _lerpDouble(previous.radiusPx, target.radiusPx, animationPercent)!; final targetStrokeWidthPx = target.strokeWidthPx; final previousStrokeWidthPx = previous.strokeWidthPx; if (targetStrokeWidthPx != null && previousStrokeWidthPx != null) { strokeWidthPx = ((targetStrokeWidthPx - previousStrokeWidthPx) * animationPercent) + previousStrokeWidthPx; } else { strokeWidthPx = null; } } /// Linear interpolation for doubles. /// /// If either [a] or [b] is null, return null. /// This is different than Flutter's lerpDouble method, we want to return null /// instead of assuming it is 0.0. double? _lerpDouble(double? a, double? b, double t) { if (a == null || b == null) return null; return a + (b - a) * t; } } class _AnimatedPoint { final String key; final bool overlaySeries; _PointRendererElement? _previousPoint; late _PointRendererElement _targetPoint; _PointRendererElement? _currentPoint; // Flag indicating whether this point is being animated out of the chart. bool animatingOut = false; _AnimatedPoint({required this.key, required this.overlaySeries}); /// Animates a point that was removed from the series out of the view. /// /// This should be called in place of "setNewTarget" for points that represent /// data that has been removed from the series. /// /// Animates the height of the point down to the measure axis position /// (position of 0). void animateOut() { final newTarget = _currentPoint!.clone(); // Set the target measure value to the axis position for all points. final targetPoint = newTarget.point; final newPoint = _DatumPoint.from(targetPoint, targetPoint.x, newTarget.measureAxisPosition!.roundToDouble()); newTarget.point = newPoint; // Animate the radius to 0 so that we don't get a lingering point after // animation is done. newTarget.radiusPx = 0.0; setNewTarget(newTarget); animatingOut = true; } void setNewTarget(_PointRendererElement newTarget) { animatingOut = false; _currentPoint ??= newTarget.clone(); _previousPoint = _currentPoint!.clone(); _targetPoint = newTarget; } _PointRendererElement getCurrentPoint(double animationPercent) { if (animationPercent == 1.0 || _previousPoint == null) { _currentPoint = _targetPoint; _previousPoint = _targetPoint; return _currentPoint!; } _currentPoint!.updateAnimationPercent( _previousPoint!, _targetPoint, animationPercent); return _currentPoint!; } } /// Type of follow line(s) to draw. enum LinePointHighlighterFollowLineType { /// Draw a follow line for only the nearest point in the selection. nearest, /// Draw no follow lines. none, /// Draw a follow line for every point in the selection. all, } /// Helper class that exposes fewer private internal properties for unit tests. @visibleForTesting class LinePointHighlighterTester { final LinePointHighlighter behavior; LinePointHighlighterTester(this.behavior); int getSelectionLength() => behavior._seriesPointMap.length; bool isDatumSelected(D datum) => behavior._seriesPointMap.values .any((point) => point._currentPoint!.point.datum == datum); } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/common/behavior/range_annotation.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:collection' show LinkedHashMap; import 'dart:math' show pi, Point, Rectangle; import 'package:meta/meta.dart'; import '../../../common/color.dart' show Color; import '../../../common/graphics_factory.dart' show GraphicsFactory; import '../../../common/style/style_factory.dart' show StyleFactory; import '../../../common/text_element.dart' show MaxWidthStrategy, TextDirection, TextElement; import '../../../common/text_style.dart' show TextStyle; import '../../cartesian/axis/axis.dart' show Axis; import '../../cartesian/axis/spec/axis_spec.dart' show TextStyleSpec; import '../../cartesian/cartesian_chart.dart' show CartesianChart; import '../../layout/layout_view.dart' show LayoutPosition, LayoutView, LayoutViewConfig, LayoutViewPaintOrder, LayoutViewPositionOrder, ViewMeasuredSizes; import '../base_chart.dart' show BaseChart, LifecycleListener; import '../chart_canvas.dart' show ChartCanvas, getAnimatedColor; import '../processed_series.dart' show MutableSeries; import 'chart_behavior.dart' show ChartBehavior; const _defaultStrokeWidthPx = 2.0; /// Chart behavior that annotates domain ranges with a solid fill color. /// /// The annotations will be drawn underneath series data and chart axes. /// /// This is typically used for line charts to call out sections of the data /// range. /// /// TODO: Support labels. class RangeAnnotation implements ChartBehavior { static const _defaultLabelAnchor = AnnotationLabelAnchor.end; static const _defaultLabelDirection = AnnotationLabelDirection.auto; static const _defaultLabelPosition = AnnotationLabelPosition.auto; static const _defaultLabelPadding = 5; static final _defaultLabelStyle = TextStyleSpec(fontSize: 12, color: Color.black); /// List of annotations to render on the chart. final List> annotations; /// Default color for annotations. final Color defaultColor; /// Configures where to anchor annotation label text. final AnnotationLabelAnchor defaultLabelAnchor; /// Direction of label text on the annotations. final AnnotationLabelDirection defaultLabelDirection; /// Configures where to place labels relative to the annotation. final AnnotationLabelPosition defaultLabelPosition; /// Configures the style of label text. final TextStyleSpec defaultLabelStyleSpec; /// Configures the stroke width for line annotations. final double defaultStrokeWidthPx; /// Whether or not the range of the axis should be extended to include the /// annotation start and end values. final bool extendAxis; /// Space before and after label text. final int labelPadding; /// Configures the order in which the behavior should be painted. /// This value should be relative to LayoutPaintViewOrder.rangeAnnotation. /// (e.g. LayoutViewPaintOrder.rangeAnnotation + 1) final int layoutPaintOrder; late CartesianChart _chart; late _RangeAnnotationLayoutView _view; late LifecycleListener _lifecycleListener; /// Store a map of data drawn on the chart, mapped by series name. /// /// [LinkedHashMap] is used to render the series on the canvas in the same /// order as the data was given to the chart. // ignore: prefer_collection_literals, https://github.com/dart-lang/linter/issues/1649 final _annotationMap = LinkedHashMap>(); // Store a list of annotations that exist in the current annotation list. // // This list will be used to remove any [_AnimatedAnnotation] that were // rendered in previous draw cycles, but no longer have a corresponding datum // in the new data. final _currentKeys = []; RangeAnnotation(this.annotations, {Color? defaultColor, AnnotationLabelAnchor? defaultLabelAnchor, AnnotationLabelDirection? defaultLabelDirection, AnnotationLabelPosition? defaultLabelPosition, TextStyleSpec? defaultLabelStyleSpec, bool? extendAxis, int? labelPadding, double? defaultStrokeWidthPx, int? layoutPaintOrder}) : defaultColor = StyleFactory.style.rangeAnnotationColor, defaultLabelAnchor = defaultLabelAnchor ?? _defaultLabelAnchor, defaultLabelDirection = defaultLabelDirection ?? _defaultLabelDirection, defaultLabelPosition = defaultLabelPosition ?? _defaultLabelPosition, defaultLabelStyleSpec = defaultLabelStyleSpec ?? _defaultLabelStyle, extendAxis = extendAxis ?? true, labelPadding = labelPadding ?? _defaultLabelPadding, defaultStrokeWidthPx = defaultStrokeWidthPx ?? _defaultStrokeWidthPx, layoutPaintOrder = layoutPaintOrder ?? LayoutViewPaintOrder.rangeAnnotation { _lifecycleListener = LifecycleListener( onPostprocess: _updateAxisRange, onAxisConfigured: _updateViewData); } @override void attachTo(BaseChart chart) { if (chart is! CartesianChart) { throw ArgumentError( 'RangeAnnotation can only be attached to a CartesianChart'); } _chart = chart; _view = _RangeAnnotationLayoutView( defaultColor: defaultColor, labelPadding: labelPadding, chart: _chart, rangeAnnotation: this, layoutPaintOrder: layoutPaintOrder); chart.addView(_view); chart.addLifecycleListener(_lifecycleListener); } @override void removeFrom(BaseChart chart) { chart.removeView(_view); chart.removeLifecycleListener(_lifecycleListener); _view.chart = null; } /// Sub-classes can override this method to control label visibility. @protected bool shouldShowLabels(AnnotationSegment annotation) => true; void _updateAxisRange(List> seriesList) { // Extend the axis range if enabled. if (extendAxis) { for (final annotation in annotations) { // Either an Axis and Axis. Axis axis; switch (annotation.axisType) { case RangeAnnotationAxisType.domain: axis = _chart.domainAxis!; break; case RangeAnnotationAxisType.measure: // We expect an empty axisId to get us the primary measure axis. axis = _chart.getMeasureAxis(axisId: annotation.axisId); break; } if (annotation is RangeAnnotationSegment) { axis.addDomainValue(annotation.startValue); axis.addDomainValue(annotation.endValue); } else if (annotation is LineAnnotationSegment) { axis.addDomainValue(annotation.value); } } } } void _updateViewData() { _currentKeys.clear(); // The values (T) can match the data type of the domain (D) or measure axis // (num). void updateAnnotation( Axis axis, AnnotationSegment annotation, ) { final key = annotation.key; final color = annotation.color ?? defaultColor; final startLabel = annotation.startLabel; final endLabel = annotation.endLabel; final middleLabel = annotation.middleLabel; final labelAnchor = annotation.labelAnchor ?? defaultLabelAnchor; var labelDirection = annotation.labelDirection ?? defaultLabelDirection; if (labelDirection == AnnotationLabelDirection.auto) { switch (annotation.axisType) { case RangeAnnotationAxisType.domain: labelDirection = AnnotationLabelDirection.vertical; break; case RangeAnnotationAxisType.measure: labelDirection = AnnotationLabelDirection.horizontal; break; } } final labelPosition = annotation.labelPosition ?? defaultLabelPosition; final labelStyleSpec = annotation.labelStyleSpec ?? defaultLabelStyleSpec; // Add line annotation settings. final dashPattern = annotation is LineAnnotationSegment ? annotation.dashPattern : null; final strokeWidthPx = annotation is LineAnnotationSegment ? annotation.strokeWidthPx : 0.0; final isRange = annotation is RangeAnnotationSegment; final T startValue; final T endValue; // We unfortunately can't check for `RangeAnnotationSegment` nor // `LineAnnotationSegment` here because the `AnnotationSegment` object // might not have been parameterized on `T` when it was initially // constructed. if (annotation is RangeAnnotationSegment) { startValue = annotation.startValue as T; endValue = annotation.endValue as T; } else if (annotation is LineAnnotationSegment) { startValue = endValue = annotation.value as T; } else { throw UnsupportedError( 'Unrecognized annotation type: ${annotation.runtimeType}'); } final annotationDatum = _getAnnotationDatum(startValue, endValue, axis, annotation.axisType); // If we already have a animatingAnnotation for that index, use it. var animatingAnnotation = _annotationMap[key]; if (animatingAnnotation == null) { // Create a new annotation, positioned at the start and end values. animatingAnnotation = _AnimatedAnnotation(key: key) ..setNewTarget(_AnnotationElement( annotation: annotationDatum, annotationSegment: annotation, color: color, dashPattern: dashPattern, startLabel: startLabel, endLabel: endLabel, middleLabel: middleLabel, isRange: isRange, labelAnchor: labelAnchor, labelDirection: labelDirection, labelPosition: labelPosition, labelStyleSpec: labelStyleSpec, strokeWidthPx: strokeWidthPx, )); _annotationMap[key] = animatingAnnotation; } // Update the set of annotations that still exist in the series data. _currentKeys.add(key); // Get the annotation element we are going to setup. final annotationElement = _AnnotationElement( annotation: annotationDatum, annotationSegment: annotation, color: color, dashPattern: dashPattern, startLabel: startLabel, endLabel: endLabel, middleLabel: middleLabel, isRange: isRange, labelAnchor: labelAnchor, labelDirection: labelDirection, labelPosition: labelPosition, labelStyleSpec: labelStyleSpec, strokeWidthPx: strokeWidthPx, ); animatingAnnotation.setNewTarget(annotationElement); } for (final annotation in annotations) { switch (annotation.axisType) { case RangeAnnotationAxisType.domain: updateAnnotation(_chart.domainAxis!, annotation); break; case RangeAnnotationAxisType.measure: // We expect an empty axisId to get us the primary measure axis. updateAnnotation( _chart.getMeasureAxis(axisId: annotation.axisId), annotation); break; } } // Animate out annotations that don't exist anymore. _annotationMap.forEach((String key, _AnimatedAnnotation annotation) { if (!_currentKeys.contains(annotation.key)) { annotation.animateOut(); } }); _view.annotationMap = _annotationMap; } /// Generates a datum that describes an annotation. /// /// [startValue] and [endValue] are dynamic because they can be different data /// types for domain and measure axes, e.g. DateTime and num for a TimeSeries /// chart. _DatumAnnotation _getAnnotationDatum(T startValue, T endValue, Axis axis, RangeAnnotationAxisType axisType) { // Remove floating point rounding errors by rounding to 2 decimal places of // precision. The difference in the canvas is negligible. final startPosition = (axis.getLocation(startValue)! * 100).round() / 100; final endPosition = (axis.getLocation(endValue)! * 100).round() / 100; return _DatumAnnotation( startPosition: startPosition, endPosition: endPosition, axisType: axisType); } @override String get role => 'RangeAnnotation'; } class _RangeAnnotationLayoutView extends LayoutView { @override final LayoutViewConfig layoutConfig; final Color defaultColor; final int labelPadding; final RangeAnnotation rangeAnnotation; final int layoutPaintOrder; CartesianChart? chart; bool get isRtl => chart!.context.isRtl; late Rectangle _drawAreaBounds; Rectangle get drawBounds => _drawAreaBounds; @override GraphicsFactory? graphicsFactory; /// Store a map of series drawn on the chart, mapped by series name. /// /// [LinkedHashMap] is used to render the series on the canvas in the same /// order as the data was given to the chart. LinkedHashMap>? _annotationMap; _RangeAnnotationLayoutView( {required this.defaultColor, required this.labelPadding, required this.chart, required this.rangeAnnotation, required this.layoutPaintOrder}) : layoutConfig = LayoutViewConfig( paintOrder: layoutPaintOrder, position: LayoutPosition.DrawArea, positionOrder: LayoutViewPositionOrder.drawArea); set annotationMap(LinkedHashMap> value) { _annotationMap = value; } @override ViewMeasuredSizes? measure(int maxWidth, int maxHeight) { return null; } @override void layout(Rectangle componentBounds, Rectangle drawAreaBounds) { _drawAreaBounds = drawAreaBounds; } @override void paint(ChartCanvas canvas, double animationPercent) { final _annotationMap = this._annotationMap; if (_annotationMap == null) { return; } // Clean up the annotations that no longer exist. if (animationPercent == 1.0) { final keysToRemove = []; _annotationMap.forEach((String key, _AnimatedAnnotation annotation) { if (annotation.animatingOut) { keysToRemove.add(key); } }); keysToRemove.forEach(_annotationMap.remove); } _annotationMap.forEach((String key, _AnimatedAnnotation annotation) { final annotationElement = annotation.getCurrentAnnotation(animationPercent); // Calculate the bounds of a range annotation. // // This will still be used for line annotations to compute the position of // labels. We always expect those to end up outside, since the bounds will // have zero width or height. final bounds = _getAnnotationBounds(annotationElement); if (annotationElement.isRange) { // Draw the annotation. canvas.drawRect(bounds, fill: annotationElement.color); } else { // Calculate the points for a line annotation. final points = _getLineAnnotationPoints(annotationElement); // Draw the annotation. canvas.drawLine( dashPattern: annotationElement.dashPattern, points: points, stroke: annotationElement.color, strokeWidthPx: annotationElement.strokeWidthPx); } // Create [TextStyle] from [TextStyleSpec] to be used by all the elements. // The [GraphicsFactory] is needed so it can't be created earlier. final labelStyle = _getTextStyle(graphicsFactory!, annotationElement.labelStyleSpec); final rotation = annotationElement.labelDirection == AnnotationLabelDirection.vertical ? -pi / 2 : 0.0; if (rangeAnnotation .shouldShowLabels(annotationElement.annotationSegment)) { final labels = { if (annotationElement.startLabel != null) _AnnotationLabelType.start: annotationElement.startLabel, if (annotationElement.endLabel != null) _AnnotationLabelType.end: annotationElement.endLabel, if (annotationElement.middleLabel != null) _AnnotationLabelType.middle: annotationElement.middleLabel, }; // Draw labels that have been defined. labels.forEach((labelType, label) { final labelElement = graphicsFactory!.createTextElement(label!) ..maxWidthStrategy = MaxWidthStrategy.ellipsize ..textStyle = labelStyle; // Measure the label max width once if either type of label is defined. labelElement.maxWidth = _getLabelMaxWidth(bounds, annotationElement, labelElement); final labelPoint = _getLabelPosition( labelType, bounds, annotationElement, labelElement); if (labelPoint != null) { canvas.drawText(labelElement, labelPoint.x, labelPoint.y, rotation: rotation); } }); } }); } /// Calculates the bounds of the annotation. Rectangle _getAnnotationBounds(_AnnotationElement annotationElement) { Rectangle bounds; switch (annotationElement.annotation.axisType) { case RangeAnnotationAxisType.domain: bounds = Rectangle( annotationElement.annotation.startPosition, _drawAreaBounds.top, annotationElement.annotation.endPosition - annotationElement.annotation.startPosition, _drawAreaBounds.height); break; case RangeAnnotationAxisType.measure: bounds = Rectangle( _drawAreaBounds.left, annotationElement.annotation.endPosition, _drawAreaBounds.width, annotationElement.annotation.startPosition - annotationElement.annotation.endPosition); break; } return bounds; } /// Calculates the bounds of the annotation. List _getLineAnnotationPoints( _AnnotationElement annotationElement) { final points = []; switch (annotationElement.annotation.axisType) { case RangeAnnotationAxisType.domain: points.add(Point( annotationElement.annotation.startPosition, _drawAreaBounds.top)); points.add(Point( annotationElement.annotation.endPosition, _drawAreaBounds.bottom)); break; case RangeAnnotationAxisType.measure: points.add(Point( _drawAreaBounds.left, annotationElement.annotation.startPosition)); points.add(Point( _drawAreaBounds.right, annotationElement.annotation.endPosition)); break; } return points; } /// Measures the max label width of the annotation. int _getLabelMaxWidth(Rectangle bounds, _AnnotationElement annotationElement, TextElement labelElement) { num maxWidth = 0; final calculatedLabelPosition = _resolveAutoLabelPosition(bounds, annotationElement, labelElement); if (annotationElement.labelPosition == AnnotationLabelPosition.margin && annotationElement.annotation.axisType == RangeAnnotationAxisType.measure) { switch (annotationElement.annotation.axisType) { case RangeAnnotationAxisType.domain: break; case RangeAnnotationAxisType.measure: switch (annotationElement.labelAnchor) { case AnnotationLabelAnchor.start: maxWidth = chart!.marginLeft - labelPadding; break; case AnnotationLabelAnchor.end: maxWidth = chart!.marginRight - labelPadding; break; case AnnotationLabelAnchor.middle: break; } break; } } else { if (calculatedLabelPosition == AnnotationLabelPosition.outside) { maxWidth = annotationElement.labelDirection == AnnotationLabelDirection.horizontal ? drawBounds.width : drawBounds.height; } else { maxWidth = annotationElement.labelDirection == AnnotationLabelDirection.horizontal ? bounds.width : bounds.height; } } return maxWidth.round(); } /// Gets the resolved location for a label element. Point? _getLabelPosition( _AnnotationLabelType labelType, Rectangle bounds, _AnnotationElement annotationElement, TextElement labelElement) { switch (annotationElement.annotation.axisType) { case RangeAnnotationAxisType.domain: return _getDomainLabelPosition( labelType, bounds, annotationElement, labelElement); case RangeAnnotationAxisType.measure: return _getMeasureLabelPosition( labelType, bounds, annotationElement, labelElement); } } /// Gets the resolved location for a domain annotation label element. Point _getDomainLabelPosition( _AnnotationLabelType labelType, Rectangle bounds, _AnnotationElement annotationElement, TextElement labelElement) { if (annotationElement.labelDirection == AnnotationLabelDirection.vertical) { return _getDomainLabelPositionVertical( labelType, bounds, annotationElement, labelElement); } else { return _getDomainLabelPositionHorizontal( labelType, bounds, annotationElement, labelElement); } } /// Gets the resolved location for a horizontal domain annotation label /// element. Point _getDomainLabelPositionHorizontal( _AnnotationLabelType labelType, Rectangle bounds, _AnnotationElement annotationElement, TextElement labelElement) { num labelX = 0; num labelY = 0; final calculatedLabelPosition = _resolveAutoLabelPosition(bounds, annotationElement, labelElement); switch (annotationElement.labelAnchor) { case AnnotationLabelAnchor.middle: labelY = bounds.top + bounds.height / 2 - labelElement.measurement.verticalSliceWidth / 2 - labelPadding; break; case AnnotationLabelAnchor.end: if (annotationElement.labelPosition == AnnotationLabelPosition.margin) { labelY = bounds.top - labelElement.measurement.verticalSliceWidth - labelPadding; } else { labelY = bounds.top + labelPadding; } break; case AnnotationLabelAnchor.start: if (annotationElement.labelPosition == AnnotationLabelPosition.margin) { labelY = bounds.bottom + labelPadding; } else { labelY = bounds.bottom - labelElement.measurement.verticalSliceWidth - labelPadding; } break; } switch (calculatedLabelPosition) { case AnnotationLabelPosition.margin: case AnnotationLabelPosition.auto: throw ArgumentError(_unresolvedAutoMessage); case AnnotationLabelPosition.outside: switch (labelType) { case _AnnotationLabelType.start: labelX = bounds.left - labelElement.measurement.horizontalSliceWidth - labelPadding; break; case _AnnotationLabelType.end: labelX = bounds.right + labelPadding; break; case _AnnotationLabelType.middle: labelX = bounds.left + (bounds.width - labelElement.measurement.horizontalSliceWidth) / 2; break; } labelElement.textDirection = isRtl ? TextDirection.rtl : TextDirection.ltr; break; case AnnotationLabelPosition.inside: switch (labelType) { case _AnnotationLabelType.start: labelX = bounds.left + labelPadding; break; case _AnnotationLabelType.end: labelX = bounds.right - labelElement.measurement.horizontalSliceWidth - labelPadding; break; case _AnnotationLabelType.middle: labelX = bounds.left + (bounds.width - labelElement.measurement.horizontalSliceWidth) / 2; break; } labelElement.textDirection = isRtl ? TextDirection.rtl : TextDirection.ltr; break; } return Point(labelX.round(), labelY.round()); } /// Gets the resolved location for a vertical domain annotation label element. Point _getDomainLabelPositionVertical( _AnnotationLabelType labelType, Rectangle bounds, _AnnotationElement annotationElement, TextElement labelElement) { num labelX = 0; num labelY = 0; final calculatedLabelPosition = _resolveAutoLabelPosition(bounds, annotationElement, labelElement); switch (annotationElement.labelAnchor) { case AnnotationLabelAnchor.middle: labelY = bounds.top + bounds.height / 2 + labelElement.measurement.horizontalSliceWidth / 2 + labelPadding; break; case AnnotationLabelAnchor.end: if (annotationElement.labelPosition == AnnotationLabelPosition.margin) { labelY = bounds.top + labelElement.measurement.horizontalSliceWidth + labelPadding; } else { labelY = bounds.top + labelElement.measurement.horizontalSliceWidth + labelPadding; } break; case AnnotationLabelAnchor.start: if (annotationElement.labelPosition == AnnotationLabelPosition.margin) { labelY = bounds.bottom + labelPadding; } else { labelY = bounds.bottom - labelElement.measurement.horizontalSliceWidth - labelPadding; } break; } switch (calculatedLabelPosition) { case AnnotationLabelPosition.margin: case AnnotationLabelPosition.auto: throw ArgumentError(_unresolvedAutoMessage); case AnnotationLabelPosition.outside: switch (labelType) { case _AnnotationLabelType.start: labelX = bounds.left - labelElement.measurement.verticalSliceWidth - labelPadding; break; case _AnnotationLabelType.end: labelX = bounds.right + labelPadding; break; case _AnnotationLabelType.middle: labelX = bounds.left + (bounds.width - labelElement.measurement.verticalSliceWidth) / 2; break; } labelElement.textDirection = isRtl ? TextDirection.rtl : TextDirection.ltr; break; case AnnotationLabelPosition.inside: switch (labelType) { case _AnnotationLabelType.start: labelX = bounds.left + labelPadding; break; case _AnnotationLabelType.end: labelX = bounds.right - labelElement.measurement.verticalSliceWidth - labelPadding; break; case _AnnotationLabelType.middle: labelX = bounds.left + (bounds.width - labelElement.measurement.verticalSliceWidth) / 2; break; } labelElement.textDirection = isRtl ? TextDirection.rtl : TextDirection.ltr; break; } return Point(labelX.round(), labelY.round()); } /// Gets the resolved location for a measure annotation label element. Point _getMeasureLabelPosition( _AnnotationLabelType labelType, Rectangle bounds, _AnnotationElement annotationElement, TextElement labelElement) { if (annotationElement.labelDirection == AnnotationLabelDirection.vertical) { return _getMeasureLabelPositionVertical( labelType, bounds, annotationElement, labelElement); } else { return _getMeasureLabelPositionHorizontal( labelType, bounds, annotationElement, labelElement); } } /// Gets the resolved location for a horizontal measure annotation label /// element. Point _getMeasureLabelPositionHorizontal( _AnnotationLabelType labelType, Rectangle bounds, _AnnotationElement annotationElement, TextElement labelElement) { num labelX = 0; num labelY = 0; final calculatedLabelPosition = _resolveAutoLabelPosition(bounds, annotationElement, labelElement); switch (annotationElement.labelAnchor) { case AnnotationLabelAnchor.middle: labelX = bounds.left + bounds.width / 2 - labelElement.measurement.horizontalSliceWidth / 2; labelElement.textDirection = isRtl ? TextDirection.rtl : TextDirection.ltr; break; case AnnotationLabelAnchor.end: case AnnotationLabelAnchor.start: if (annotationElement.labelPosition == AnnotationLabelPosition.margin) { final alignLeft = isRtl ? (annotationElement.labelAnchor == AnnotationLabelAnchor.end) : (annotationElement.labelAnchor == AnnotationLabelAnchor.start); if (alignLeft) { labelX = bounds.left - labelPadding; labelElement.textDirection = TextDirection.rtl; } else { labelX = bounds.right + labelPadding; labelElement.textDirection = TextDirection.ltr; } } else { final alignLeft = isRtl ? (annotationElement.labelAnchor == AnnotationLabelAnchor.end) : (annotationElement.labelAnchor == AnnotationLabelAnchor.start); if (alignLeft) { labelX = bounds.left + labelPadding; labelElement.textDirection = TextDirection.ltr; } else { labelX = bounds.right - labelPadding; labelElement.textDirection = TextDirection.rtl; } } break; } switch (calculatedLabelPosition) { case AnnotationLabelPosition.margin: case AnnotationLabelPosition.auto: throw ArgumentError(_unresolvedAutoMessage); case AnnotationLabelPosition.outside: switch (labelType) { case _AnnotationLabelType.start: labelY = bounds.bottom + labelPadding; break; case _AnnotationLabelType.end: labelY = bounds.top - labelElement.measurement.verticalSliceWidth - labelPadding; break; case _AnnotationLabelType.middle: labelY = bounds.top + (bounds.height - labelElement.measurement.verticalSliceWidth) / 2; break; } break; case AnnotationLabelPosition.inside: switch (labelType) { case _AnnotationLabelType.start: labelY = bounds.bottom - labelElement.measurement.verticalSliceWidth - labelPadding; break; case _AnnotationLabelType.end: labelY = bounds.top + labelPadding; break; case _AnnotationLabelType.middle: labelY = bounds.top + (bounds.height - labelElement.measurement.verticalSliceWidth) / 2; break; } break; } return Point(labelX.round(), labelY.round()); } /// Gets the resolved location for a vertical measure annotation label /// element. Point _getMeasureLabelPositionVertical( _AnnotationLabelType labelType, Rectangle bounds, _AnnotationElement annotationElement, TextElement labelElement) { num labelX = 0; num labelY = 0; final calculatedLabelPosition = _resolveAutoLabelPosition(bounds, annotationElement, labelElement); switch (annotationElement.labelAnchor) { case AnnotationLabelAnchor.middle: labelX = bounds.left + bounds.width / 2 - labelElement.measurement.verticalSliceWidth / 2; labelElement.textDirection = isRtl ? TextDirection.rtl : TextDirection.ltr; break; case AnnotationLabelAnchor.end: case AnnotationLabelAnchor.start: if (annotationElement.labelPosition == AnnotationLabelPosition.margin) { final alignLeft = isRtl ? (annotationElement.labelAnchor == AnnotationLabelAnchor.end) : (annotationElement.labelAnchor == AnnotationLabelAnchor.start); if (alignLeft) { labelX = bounds.left - labelElement.measurement.verticalSliceWidth - labelPadding; labelElement.textDirection = TextDirection.ltr; } else { labelX = bounds.right + labelPadding; labelElement.textDirection = TextDirection.ltr; } } else { final alignLeft = isRtl ? (annotationElement.labelAnchor == AnnotationLabelAnchor.end) : (annotationElement.labelAnchor == AnnotationLabelAnchor.start); if (alignLeft) { labelX = bounds.left + labelPadding; labelElement.textDirection = TextDirection.ltr; } else { labelX = bounds.right - labelElement.measurement.verticalSliceWidth - labelPadding; labelElement.textDirection = TextDirection.ltr; } } break; } switch (calculatedLabelPosition) { case AnnotationLabelPosition.margin: case AnnotationLabelPosition.auto: throw ArgumentError(_unresolvedAutoMessage); case AnnotationLabelPosition.outside: switch (labelType) { case _AnnotationLabelType.start: labelY = bounds.bottom + labelElement.measurement.horizontalSliceWidth + labelPadding; break; case _AnnotationLabelType.end: labelY = bounds.top - labelPadding; break; case _AnnotationLabelType.middle: labelY = bounds.bottom + (labelElement.measurement.horizontalSliceWidth - bounds.height) / 2; break; } break; case AnnotationLabelPosition.inside: switch (labelType) { case _AnnotationLabelType.start: labelY = bounds.bottom - labelPadding; break; case _AnnotationLabelType.end: labelY = bounds.top + labelElement.measurement.horizontalSliceWidth + labelPadding; break; case _AnnotationLabelType.middle: labelY = bounds.bottom + (labelElement.measurement.horizontalSliceWidth - bounds.height) / 2; break; } break; } return Point(labelX.round(), labelY.round()); } /// Resolves [AnnotationLabelPosition.auto] configuration for an annotation /// into an inside or outside position, depending on the size of the /// annotation and the chart draw area. AnnotationLabelPosition _resolveAutoLabelPosition(Rectangle bounds, _AnnotationElement annotationElement, TextElement labelElement) { var calculatedLabelPosition = annotationElement.labelPosition; if (calculatedLabelPosition == AnnotationLabelPosition.auto || calculatedLabelPosition == AnnotationLabelPosition.margin) { final isDomain = annotationElement.annotation.axisType == RangeAnnotationAxisType.domain; final annotationBoundsSize = isDomain ? bounds.width : bounds.height; final drawBoundsSize = isDomain ? drawBounds.width : drawBounds.height; final isVertical = annotationElement.labelDirection == AnnotationLabelDirection.vertical; final labelSize = isDomain && isVertical || !isDomain && !isVertical ? labelElement.measurement.verticalSliceWidth : labelElement.measurement.horizontalSliceWidth; // Get space available inside and outside the annotation. final totalPadding = labelPadding * 2; final insideBarWidth = annotationBoundsSize - totalPadding; final outsideBarWidth = drawBoundsSize - annotationBoundsSize - totalPadding; // A label fits if the space inside the annotation is >= outside // annotation or if the length of the text fits and the space. This is // because if the annotation has more space than the outside, it makes // more sense to place the label inside the annotation, even if the // entire label does not fit. calculatedLabelPosition = (insideBarWidth >= outsideBarWidth || labelSize < insideBarWidth) ? AnnotationLabelPosition.inside : AnnotationLabelPosition.outside; } return calculatedLabelPosition; } @override Rectangle get componentBounds => _drawAreaBounds; @override bool get isSeriesRenderer => false; // Helper function that converts [TextStyleSpec] to [TextStyle]. TextStyle _getTextStyle( GraphicsFactory graphicsFactory, TextStyleSpec labelSpec) { return graphicsFactory.createTextPaint() ..color = labelSpec.color ?? Color.black ..fontFamily = labelSpec.fontFamily ..fontSize = labelSpec.fontSize ?? 12 ..lineHeight = labelSpec.lineHeight; } } class _DatumAnnotation { final double startPosition; final double endPosition; final RangeAnnotationAxisType axisType; _DatumAnnotation({ required this.startPosition, required this.endPosition, required this.axisType, }); factory _DatumAnnotation.from(_DatumAnnotation other, [double? startPosition, double? endPosition]) { return _DatumAnnotation( startPosition: startPosition ?? other.startPosition, endPosition: endPosition ?? other.endPosition, axisType: other.axisType); } } class _AnnotationElement { _DatumAnnotation annotation; final AnnotationSegment annotationSegment; Color? color; final String? startLabel; final String? endLabel; final String? middleLabel; final bool isRange; final AnnotationLabelAnchor labelAnchor; final AnnotationLabelDirection labelDirection; final AnnotationLabelPosition labelPosition; final TextStyleSpec labelStyleSpec; final List? dashPattern; double strokeWidthPx; _AnnotationElement({ required this.annotation, required this.annotationSegment, required this.color, required this.startLabel, required this.endLabel, required this.middleLabel, required this.isRange, required this.labelAnchor, required this.labelDirection, required this.labelPosition, required this.labelStyleSpec, required this.dashPattern, required this.strokeWidthPx, }); _AnnotationElement clone() { return _AnnotationElement( annotation: _DatumAnnotation.from(annotation), annotationSegment: annotationSegment, color: color != null ? Color.fromOther(color: color!) : null, startLabel: startLabel, endLabel: endLabel, middleLabel: middleLabel, isRange: isRange, labelAnchor: labelAnchor, labelDirection: labelDirection, labelPosition: labelPosition, labelStyleSpec: labelStyleSpec, dashPattern: dashPattern, strokeWidthPx: strokeWidthPx, ); } void updateAnimationPercent( _AnnotationElement previous, _AnnotationElement target, double animationPercent, ) { final targetAnnotation = target.annotation; final previousAnnotation = previous.annotation; final startPosition = ((targetAnnotation.startPosition - previousAnnotation.startPosition) * animationPercent) + previousAnnotation.startPosition; final endPosition = ((targetAnnotation.endPosition - previousAnnotation.endPosition) * animationPercent) + previousAnnotation.endPosition; annotation = _DatumAnnotation.from(targetAnnotation, startPosition, endPosition); color = getAnimatedColor(previous.color!, target.color!, animationPercent); strokeWidthPx = ((target.strokeWidthPx - previous.strokeWidthPx) * animationPercent) + previous.strokeWidthPx; } } enum _AnnotationLabelType { start, end, middle, } class _AnimatedAnnotation { final String key; _AnnotationElement? _previousAnnotation; late _AnnotationElement _targetAnnotation; _AnnotationElement? _currentAnnotation; // Flag indicating whether this annotation is being animated out of the chart. bool animatingOut = false; _AnimatedAnnotation({required this.key}); /// Animates an annotation that was removed from the list out of the view. /// /// This should be called in place of "setNewTarget" for annotations have been /// removed from the list. /// TODO: Needed? void animateOut() { final newTarget = _currentAnnotation!.clone(); setNewTarget(newTarget); animatingOut = true; } void setNewTarget(_AnnotationElement newTarget) { animatingOut = false; _currentAnnotation ??= newTarget.clone(); _previousAnnotation = _currentAnnotation!.clone(); _targetAnnotation = newTarget; } _AnnotationElement getCurrentAnnotation(double animationPercent) { if (animationPercent == 1.0 || _previousAnnotation == null) { _currentAnnotation = _targetAnnotation; _previousAnnotation = _targetAnnotation; return _currentAnnotation!; } _currentAnnotation!.updateAnimationPercent( _previousAnnotation!, _targetAnnotation, animationPercent); return _currentAnnotation!; } } /// Helper class that exposes fewer private internal properties for unit tests. @visibleForTesting class RangeAnnotationTester { final RangeAnnotation behavior; RangeAnnotationTester(this.behavior); set graphicsFactory(GraphicsFactory value) { behavior._view.graphicsFactory = value; } void mockLayout(Rectangle bounds) { behavior._view.layout(bounds, bounds); } /// Checks if an annotation exists with the given position and color. bool doesAnnotationExist( {num? startPosition, num? endPosition, Color? color, List? dashPattern, String? startLabel, String? endLabel, String? middleLabel, AnnotationLabelAnchor? labelAnchor, AnnotationLabelDirection? labelDirection, AnnotationLabelPosition? labelPosition}) { for (final a in behavior._annotationMap.values) { final currentAnnotation = a._currentAnnotation!; final annotation = currentAnnotation.annotation; if (annotation.startPosition == startPosition && annotation.endPosition == endPosition && currentAnnotation.color == color && currentAnnotation.startLabel == startLabel && currentAnnotation.endLabel == endLabel && currentAnnotation.middleLabel == middleLabel && currentAnnotation.labelAnchor == labelAnchor && currentAnnotation.labelDirection == labelDirection && currentAnnotation.labelPosition == labelPosition && (currentAnnotation is! LineAnnotationSegment || currentAnnotation.dashPattern == dashPattern)) { return true; } } return false; } } /// Base class for chart annotations. abstract class AnnotationSegment { final RangeAnnotationAxisType axisType; final String? axisId; final Color? color; final String? startLabel; final String? endLabel; final String? middleLabel; final AnnotationLabelAnchor? labelAnchor; final AnnotationLabelDirection? labelDirection; final AnnotationLabelPosition? labelPosition; final TextStyleSpec? labelStyleSpec; String get key; AnnotationSegment(this.axisType, {this.axisId, this.color, this.startLabel, this.endLabel, this.middleLabel, this.labelAnchor, this.labelDirection, this.labelPosition, this.labelStyleSpec}); } /// Data for a chart range annotation. class RangeAnnotationSegment extends AnnotationSegment { final D startValue; final D endValue; RangeAnnotationSegment( this.startValue, this.endValue, RangeAnnotationAxisType axisType, {String? axisId, Color? color, String? startLabel, String? endLabel, String? middleLabel, AnnotationLabelAnchor? labelAnchor, AnnotationLabelDirection? labelDirection, AnnotationLabelPosition? labelPosition, TextStyleSpec? labelStyleSpec}) : super(axisType, axisId: axisId, color: color, startLabel: startLabel, endLabel: endLabel, middleLabel: middleLabel, labelAnchor: labelAnchor, labelDirection: labelDirection, labelPosition: labelPosition, labelStyleSpec: labelStyleSpec); @override String get key => 'r::${axisType}::${axisId}::${startValue}::${endValue}'; } /// Data for a chart line annotation. class LineAnnotationSegment extends AnnotationSegment { final D value; final List? dashPattern; final double strokeWidthPx; LineAnnotationSegment(this.value, RangeAnnotationAxisType axisType, {String? axisId, Color? color, String? startLabel, String? endLabel, String? middleLabel, AnnotationLabelAnchor? labelAnchor, AnnotationLabelDirection? labelDirection, AnnotationLabelPosition? labelPosition, TextStyleSpec? labelStyleSpec, this.dashPattern, this.strokeWidthPx = _defaultStrokeWidthPx}) : super(axisType, axisId: axisId, color: color, startLabel: startLabel, endLabel: endLabel, middleLabel: middleLabel, labelAnchor: labelAnchor, labelDirection: labelDirection, labelPosition: labelPosition, labelStyleSpec: labelStyleSpec); @override String get key => 'l::${axisType}::${axisId}::${value}'; } /// Axis type for an annotation. enum RangeAnnotationAxisType { domain, measure, } /// Configures where to anchor the label. enum AnnotationLabelAnchor { /// Anchor to the starting side of the annotation range. start, /// Anchor to the middle of the annotation range. middle, /// Anchor to the ending side of the annotation range. end, } /// Direction of the label text on the chart. enum AnnotationLabelDirection { /// Automatically assign a direction based on the [RangeAnnotationAxisType]. /// /// [horizontal] for measure axes, or [vertical] for domain axes. auto, /// Text flows parallel to the x axis. horizontal, /// Text flows parallel to the y axis. /// TODO[b/112553019]: Implement vertical text rendering of labels. vertical, } /// Configures where to place the label relative to the annotation. enum AnnotationLabelPosition { /// Automatically try to place the label inside the bar first and place it on /// the outside of the space available outside the bar is greater than space /// available inside the bar. auto, /// Always place label on the outside. outside, /// Always place label on the inside. inside, /// Place the label outside of the draw area, in the chart margin. /// /// Labels will be rendered on the opposite side of the chart from the primary /// axis. For measure annotations, this means the "end" side, opposite from /// the "start" side where the primary measure axis is located. /// /// This should not be used for measure annotations if the chart has a /// secondary measure axis. The annotation behaviors do not perform collision /// detection with tick labels. margin, } const String _unresolvedAutoMessage = 'Unresolved AnnotationLabelPosition.auto'; ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/common/behavior/selection/lock_selection.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math'; import '../../../../common/gesture_listener.dart' show GestureListener; import '../../base_chart.dart' show BaseChart; import '../../behavior/chart_behavior.dart' show ChartBehavior; import '../../selection_model/selection_model.dart' show SelectionModelType; import 'selection_trigger.dart' show SelectionTrigger; /// Chart behavior that listens to tap event trigges and locks the specified /// [SelectionModel]. This is used to prevent further updates to the selection /// model, until it is unlocked again. /// /// SelectionModels that can be updated: /// info - To view the details of the selected items (ie: hover for web). /// action - To select an item as an input, drill, or other selection. /// /// You can add one LockSelection for each model type that you are updating. /// Any previous LockSelection behavior for that selection model will be /// removed. class LockSelection implements ChartBehavior { late GestureListener _listener; /// Type of selection model that should be updated by input events. final SelectionModelType selectionModelType; /// Type of input event that should trigger selection. final SelectionTrigger eventTrigger = SelectionTrigger.tap; BaseChart? _chart; LockSelection({this.selectionModelType = SelectionModelType.info}) { // Setup the appropriate gesture listening. switch (eventTrigger) { case SelectionTrigger.tap: _listener = GestureListener(onTapTest: _onTapTest, onTap: _onSelect); break; default: throw ArgumentError('LockSelection does not support the event ' 'trigger "$eventTrigger"'); } } bool _onTapTest(Point chartPoint) { // If the tap is within the drawArea, then claim the event from others. return _chart!.pointWithinRenderer(chartPoint); } bool _onSelect(Point chartPoint, [double? ignored]) { // Skip events that occur outside the drawArea for any series renderer. if (!_chart!.pointWithinRenderer(chartPoint)) { return false; } final selectionModel = _chart!.getSelectionModel(selectionModelType); // Do nothing if the chart has no selection model. if (selectionModel == null) { return false; } // Do not lock the selection model if there is no selection. Locking nothing // would result in a very confusing user interface as the user tries to // interact with content on the chart. if (!selectionModel.locked && !selectionModel.hasAnySelection) { return false; } // Toggle the lock state. selectionModel.locked = !selectionModel.locked; // If the model was just unlocked, clear the selection to dismiss any stale // behavior elements. A new hovercard/etc. will appear after the user // triggers a new gesture. if (!selectionModel.locked) { selectionModel.clearSelection(); } return false; } @override void attachTo(BaseChart chart) { _chart = chart; chart.addGestureListener(_listener); // TODO: Update this dynamically based on tappable location. switch (eventTrigger) { case SelectionTrigger.tap: case SelectionTrigger.tapAndDrag: case SelectionTrigger.pressHold: case SelectionTrigger.longPressHold: chart.registerTappable(this); break; case SelectionTrigger.hover: default: chart.unregisterTappable(this); break; } } @override void removeFrom(BaseChart chart) { chart.removeGestureListener(_listener); chart.unregisterTappable(this); _chart = null; } @override String get role => 'LockSelection-$selectionModelType'; } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/common/behavior/selection/select_nearest.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math'; import '../../../../common/gesture_listener.dart' show GestureListener; import '../../../../common/rate_limit_utils.dart' show throttle; import '../../base_chart.dart' show BaseChart; import '../../behavior/chart_behavior.dart' show ChartBehavior; import '../../datum_details.dart' show DatumDetails; import '../../processed_series.dart' show ImmutableSeries; import '../../selection_model/selection_model.dart' show SelectionModelType; import '../../series_datum.dart' show SeriesDatum; import 'selection_trigger.dart' show SelectionTrigger; /// Chart behavior that listens to the given eventTrigger and updates the /// specified [SelectionModel]. This is used to pair input events to behaviors /// that listen to selection changes. /// /// Input event types: /// hover (default) - Mouse over/near data. /// tap - Mouse/Touch on/near data. /// pressHold - Mouse/Touch and drag across the data instead of panning. /// longPressHold - Mouse/Touch for a while in one place then drag across the /// data. /// /// SelectionModels that can be updated: /// info - To view the details of the selected items (ie: hover for web). /// action - To select an item as an input, drill, or other selection. /// /// Other options available /// [selectionMode] - Optional mode for expanding the selection beyond the /// nearest datum. Defaults to selecting just the nearest datum. /// /// [selectAcrossAllSeriesRendererComponents] - Events in any component that /// draw Series data will propagate to other components that draw Series /// data to get a union of points that match across all series renderer /// components. This is useful when components in the margins draw series /// data and a selection is supposed to bridge the two adjacent /// components. (Default: true) /// [selectClosestSeries] - If true, the closest Series itself will be marked /// as selected in addition to the datum. This is useful for features like /// highlighting the closest Series. (Default: true) /// /// You can add one SelectNearest for each model type that you are updating. /// Any previous SelectNearest behavior for that selection model will be /// removed. class SelectNearest implements ChartBehavior { late GestureListener _listener; /// Type of selection model that should be updated by input events. final SelectionModelType selectionModelType; /// Type of input event that should trigger selection. final SelectionTrigger eventTrigger; /// Optional mode for expanding the selection beyond the nearest datum. /// Defaults to selecting just the nearest datum. final SelectionMode selectionMode; /// Whether or not events in any component that draw Series data will /// propagate to other components that draw Series data to get a union of /// points that match across all series renderer components. /// /// This is useful when components in the margins draw series data and a /// selection is supposed to bridge the two adjacent components. final bool selectAcrossAllSeriesRendererComponents; /// Whether or not the closest Series itself will be marked as selected in /// addition to the datum. final bool selectClosestSeries; /// The farthest away a domain value can be from the mouse position on the /// domain axis before we'll ignore the datum. /// /// This allows sparse data to not get selected until the mouse is some /// reasonable distance. Defaults to no maximum distance. final int? maximumDomainDistancePx; /// Wait time in milliseconds for when the next event can be called. final int? hoverEventDelay; BaseChart? _chart; bool _delaySelect = false; SelectNearest( {this.selectionModelType = SelectionModelType.info, this.selectionMode = SelectionMode.expandToDomain, this.selectAcrossAllSeriesRendererComponents = true, this.selectClosestSeries = true, this.eventTrigger = SelectionTrigger.hover, this.maximumDomainDistancePx, this.hoverEventDelay}) { // Setup the appropriate gesture listening. switch (eventTrigger) { case SelectionTrigger.tap: _listener = GestureListener(onTapTest: _onTapTest, onTap: _onSelect); break; case SelectionTrigger.tapAndDrag: _listener = GestureListener( onTapTest: _onTapTest, onTap: _onSelect, onDragStart: _onSelect, onDragUpdate: _onSelect, ); break; case SelectionTrigger.pressHold: _listener = GestureListener( onTapTest: _onTapTest, onLongPress: _onSelect, onDragStart: _onSelect, onDragUpdate: _onSelect, onDragEnd: _onDeselectAll); break; case SelectionTrigger.longPressHold: _listener = GestureListener( onTapTest: _onTapTest, onLongPress: _onLongPressSelect, onDragStart: _onSelect, onDragUpdate: _onSelect, onDragEnd: _onDeselectAll); break; case SelectionTrigger.hover: default: _listener = GestureListener( onHover: hoverEventDelay == null ? _onSelect : throttle, bool>(_onSelect, delay: Duration(milliseconds: hoverEventDelay!), defaultReturn: false)); break; } } bool _onTapTest(Point chartPoint) { // If the tap is within the drawArea, then claim the event from others. _delaySelect = eventTrigger == SelectionTrigger.longPressHold; return _chart!.pointWithinRenderer(chartPoint); } bool _onLongPressSelect(Point chartPoint) { _delaySelect = false; return _onSelect(chartPoint); } bool _onSelect(Point chartPoint, [double? ignored]) { // If _chart has not yet been attached, then quit. if (_chart == null) return false; // If the selection is delayed (waiting for long press), then quit early. if (_delaySelect) return false; var details = _chart!.getNearestDatumDetailPerSeries( chartPoint, selectAcrossAllSeriesRendererComponents); final seriesList = >[]; var seriesDatumList = >[]; if (details != null && details.isNotEmpty) { details.sort((a, b) => a.domainDistance!.compareTo(b.domainDistance!)); if (maximumDomainDistancePx == null || details[0].domainDistance! <= maximumDomainDistancePx!) { seriesDatumList = _extractSeriesFromNearestSelection(details); // Filter out points from overlay series. seriesDatumList .removeWhere((SeriesDatum datum) => datum.series.overlaySeries); if (selectClosestSeries && seriesList.isEmpty) { if (details.first.series!.overlaySeries) { // If the closest "details" was from an overlay series, grab the // closest remaining series instead. In this case, we need to sort a // copy of the list by domain distance because we do not want to // re-order the actual return values here. final sortedSeriesDatumList = List>.from(seriesDatumList); sortedSeriesDatumList.sort((a, b) { final detailsA = a.datum as DatumDetails; final detailsB = b.datum as DatumDetails; return detailsA.domainDistance! .compareTo(detailsB.domainDistance!); }); seriesList.add(sortedSeriesDatumList.first.series); } else { seriesList.add(details.first.series!); } } } } return _chart! .getSelectionModel(selectionModelType) .updateSelection(seriesDatumList, seriesList); } List> _extractSeriesFromNearestSelection( List> details) { switch (selectionMode) { case SelectionMode.expandToDomain: return _expandToDomain(details.first); case SelectionMode.selectOverlapping: return details .map((datumDetails) => SeriesDatum(datumDetails.series!, datumDetails.datum)) .toList(); case SelectionMode.single: return [SeriesDatum(details.first.series!, details.first.datum)]; } } bool _onDeselectAll(Point _, double __, double ___) { // If the selection is delayed (waiting for long press), then quit early. if (_delaySelect) { return false; } _chart! .getSelectionModel(selectionModelType) .updateSelection(>[], >[]); return false; } List> _expandToDomain(DatumDetails nearestDetails) { // Make sure that the "nearest" datum is at the top of the list. final data = >[ SeriesDatum(nearestDetails.series!, nearestDetails.datum) ]; final nearestDomain = nearestDetails.domain; for (ImmutableSeries series in _chart!.currentSeriesList) { final domainFn = series.domainFn; final domainLowerBoundFn = series.domainLowerBoundFn; final domainUpperBoundFn = series.domainUpperBoundFn; final testBounds = domainLowerBoundFn != null && domainUpperBoundFn != null; for (var i = 0; i < series.data.length; i++) { final Object? datum = series.data[i]; final domain = domainFn(i); // Don't re-add the nearest details. if (nearestDetails.series == series && nearestDetails.datum == datum) { continue; } if (domain == nearestDomain) { data.add(SeriesDatum(series, datum)); } else if (testBounds) { final domainLowerBound = domainLowerBoundFn!(i); final domainUpperBound = domainUpperBoundFn!(i); var addDatum = false; if (domainLowerBound != null && domainUpperBound != null) { if (domain is int) { addDatum = (domainLowerBound as int) <= (nearestDomain as int) && (nearestDomain as int) <= (domainUpperBound as int); } else if (domain is double) { addDatum = (domainLowerBound as double) <= (nearestDomain as double) && (nearestDomain as double) <= (domainUpperBound as double); } else if (domain is DateTime) { addDatum = domainLowerBound == nearestDomain || domainUpperBound == nearestDomain || ((domainLowerBound as DateTime) .isBefore(nearestDomain as DateTime) && (nearestDomain as DateTime) .isBefore(domainUpperBound as DateTime)); } } if (addDatum) { data.add(SeriesDatum(series, datum)); } } } } return data; } @override void attachTo(BaseChart chart) { _chart = chart; chart.addGestureListener(_listener); // TODO: Update this dynamically based on tappable location. switch (eventTrigger) { case SelectionTrigger.tap: case SelectionTrigger.tapAndDrag: case SelectionTrigger.pressHold: case SelectionTrigger.longPressHold: chart.registerTappable(this); break; case SelectionTrigger.hover: default: chart.unregisterTappable(this); break; } } @override void removeFrom(BaseChart chart) { chart.removeGestureListener(_listener); chart.unregisterTappable(this); _chart = null; } @override String get role => 'SelectNearest-$selectionModelType'; } /// Mode for expanding the selection beyond just the nearest datum. enum SelectionMode { /// All data sharing the same domain value as the nearest datum will be /// selected (in charts that have a concept of domain). expandToDomain, /// All data for overlapping points in a series will be selected. selectOverlapping, /// Select only the nearest datum selected by the chart. This is the default /// mode. single, } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/common/behavior/selection/selection_trigger.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. enum SelectionTrigger { hover, tap, tapAndDrag, pressHold, longPressHold, } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/common/behavior/slider/slider.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math'; import 'package:meta/meta.dart'; import '../../../../common/color.dart' show Color; import '../../../../common/gesture_listener.dart' show GestureListener; import '../../../../common/graphics_factory.dart' show GraphicsFactory; import '../../../../common/math.dart' show clamp; import '../../../../common/style/style_factory.dart' show StyleFactory; import '../../../../common/symbol_renderer.dart' show RectSymbolRenderer, SymbolRenderer; import '../../../cartesian/cartesian_chart.dart' show CartesianChart; import '../../../layout/layout_view.dart' show LayoutPosition, LayoutView, LayoutViewConfig, LayoutViewPaintOrder, LayoutViewPositionOrder, ViewMeasuredSizes; import '../../base_chart.dart' show BaseChart, LifecycleListener; import '../../behavior/chart_behavior.dart' show ChartBehavior; import '../../processed_series.dart' show MutableSeries; import '../../chart_canvas.dart' show ChartCanvas, getAnimatedColor; import '../selection/selection_trigger.dart' show SelectionTrigger; /// Chart behavior that adds a slider widget to a chart. When the slider is /// dropped after drag, it will report its domain position and nearest datum /// value. This behavior only supports charts that use continuous scales. /// /// Input event types: /// tapAndDrag - Mouse/Touch on the handle and drag across the chart. /// pressHold - Mouse/Touch on the handle and drag across the chart instead of /// panning. /// longPressHold - Mouse/Touch for a while on the handle, then drag across /// the data. class Slider implements ChartBehavior { late _SliderLayoutView _view; late GestureListener _gestureListener; late LifecycleListener _lifecycleListener; late SliderEventListener _sliderEventListener; /// The order to paint slider on the canvas. /// /// The smaller number is drawn first. This value should be relative to /// LayoutPaintViewOrder.slider (e.g. LayoutViewPaintOrder.slider + 1). int layoutPaintOrder; /// Type of input event for the slider. /// /// Input event types: /// tapAndDrag - Mouse/Touch on the handle and drag across the chart. /// pressHold - Mouse/Touch on the handle and drag across the chart instead /// of panning. /// longPressHold - Mouse/Touch for a while on the handle, then drag across /// the data. final SelectionTrigger eventTrigger; /// Renderer for the handle. Defaults to a rectangle. final SymbolRenderer _handleRenderer; /// Custom role ID for this slider final String _roleId; /// Whether or not the slider will snap onto the nearest datum (by domain /// distance) when dragged. final bool snapToDatum; /// Color and size styles for the slider. final SliderStyle _style; CartesianChart? _chart; /// Rendering data for the slider line and handle. _AnimatedSlider? _sliderHandle; bool _delaySelect = false; bool _handleDrag = false; /// Current location of the slider line. Point? _domainCenterPoint; /// Previous location of the slider line. /// /// This is used to track changes in the position of the slider caused by new /// data being drawn on the chart. Point? _previousDomainCenterPoint; /// Bounding box for the slider drag handle. Rectangle? _handleBounds; /// Domain value of the current slider position. /// /// This is saved in terms of domain instead of chart position so that we can /// adjust the slider automatically when the chart is resized. D? _domainValue; /// Event to fire during the chart's onPostrender event. /// /// This should be set any time the state of the slider has changed. SliderListenerDragState? _dragStateToFireOnPostRender; /// Constructs a [Slider]. /// /// [eventTrigger] sets the type of gesture handled by the slider. /// /// [handleRenderer] draws a handle for the slider. Defaults to a rectangle. /// /// [initialDomainValue] sets the initial position of the slider in domain /// units. The default is the center of the chart. /// /// [onChangeCallback] will be called when the position of the slider /// changes during a drag event. /// /// [roleId] optional custom role ID for the slider. This can be used to allow /// multiple [Slider] behaviors on the same chart. Normally, there can only be /// one slider (per event trigger type) on a chart. This setting allows for /// configuring multiple independent sliders. /// /// [snapToDatum] configures the slider to snap snap onto the nearest datum /// (by domain distance) when dragged. By default, the slider can be /// positioned anywhere along the domain axis. /// /// [style] configures the color and sizing of the slider line and handle. /// /// [layoutPaintOrder] configures the order in which the behavior should be /// painted. This value should be relative to LayoutPaintViewOrder.slider. /// (e.g. LayoutViewPaintOrder.slider + 1). Slider( {this.eventTrigger = SelectionTrigger.tapAndDrag, SymbolRenderer? handleRenderer, D? initialDomainValue, SliderListenerCallback? onChangeCallback, String? roleId, this.snapToDatum = false, SliderStyle? style, this.layoutPaintOrder = LayoutViewPaintOrder.slider}) : _handleRenderer = handleRenderer ?? RectSymbolRenderer(), _roleId = roleId ?? '', _style = style ?? SliderStyle(), _domainValue = initialDomainValue { if (_domainValue != null) { _dragStateToFireOnPostRender = SliderListenerDragState.initial; } // Setup the appropriate gesture listening. switch (eventTrigger) { case SelectionTrigger.tapAndDrag: _gestureListener = GestureListener( onTapTest: _onTapTest, onTap: _onSelect, onDragStart: _onSelect, onDragUpdate: _onSelect, onDragEnd: _onDragEnd); break; case SelectionTrigger.pressHold: _gestureListener = GestureListener( onTapTest: _onTapTest, onLongPress: _onSelect, onDragStart: _onSelect, onDragUpdate: _onSelect, onDragEnd: _onDragEnd); break; case SelectionTrigger.longPressHold: _gestureListener = GestureListener( onTapTest: _onTapTest, onLongPress: _onLongPressSelect, onDragStart: _onSelect, onDragUpdate: _onSelect, onDragEnd: _onDragEnd); break; default: throw ArgumentError('Slider does not support the event trigger ' '"$eventTrigger"'); } // Set up chart draw cycle listeners. _lifecycleListener = LifecycleListener( onData: _setInitialDragState, onAxisConfigured: _updateViewData, onPostrender: _fireChangeEvent, ); // Set up slider event listeners. _sliderEventListener = SliderEventListener(onChange: onChangeCallback); } bool _onTapTest(Point chartPoint) { _delaySelect = eventTrigger == SelectionTrigger.longPressHold; _handleDrag = _sliderContainsPoint(chartPoint); return _handleDrag; } bool _onLongPressSelect(Point chartPoint) { _delaySelect = false; return _onSelect(chartPoint); } bool _onSelect(Point chartPoint, [double? ignored]) { // Skip events that occur outside the drawArea for any series renderer. // If the selection is delayed (waiting for long press), then quit early. if (!_handleDrag || _delaySelect) { return false; } // Move the slider line along the domain axis, without adjusting the measure // position. final positionChanged = _moveSliderToPoint(chartPoint); if (positionChanged) { _dragStateToFireOnPostRender = SliderListenerDragState.drag; _chart!.redraw(skipAnimation: true, skipLayout: true); } return true; } bool _onDragEnd(Point chartPoint, double __, double ___) { // If the selection is delayed (waiting for long press), then quit early. if (_delaySelect) { return false; } _handleDrag = false; // If snapToDatum is enabled, use the x position of the nearest datum // instead of the mouse point. if (snapToDatum) { final details = _chart!.getNearestDatumDetailPerSeries(chartPoint, true); if (details.isNotEmpty && details[0].chartPosition!.x != null) { // Only trigger an animating draw cycle if we need to move the slider. if (_domainValue != details[0].domain) { _moveSliderToDomain(details[0].domain); // Always fire the end event to notify listeners that the gesture is // over. _dragStateToFireOnPostRender = SliderListenerDragState.end; _chart!.redraw(skipAnimation: false, skipLayout: true); } } } else { // Move the slider line along the domain axis, without adjusting the // measure position. _moveSliderToPoint(chartPoint); // Always fire the end event to notify listeners that the gesture is // over. _dragStateToFireOnPostRender = SliderListenerDragState.end; _chart!.redraw(skipAnimation: true, skipLayout: true); } return false; } bool _sliderContainsPoint(Point chartPoint) { return _handleBounds!.containsPoint(chartPoint); } /// Sets the drag state to "initial" when new data is drawn on the chart. void _setInitialDragState(List> _) { _dragStateToFireOnPostRender = SliderListenerDragState.initial; } void _updateViewData() { _sliderHandle ??= _AnimatedSlider(); // If not set in the constructor, initial position for the handle is the // center of the draw area. if (_domainValue == null) { final newDomainValue = _chart!.domainAxis! .getDomain(_view.drawBounds.left + _view.drawBounds.width / 2); _domainValue = (newDomainValue is double) ? (newDomainValue.round().toDouble() as D) : newDomainValue; } // Possibly move the slider, if the axis values have changed since the last // chart draw. _moveSliderToDomain(_domainValue); // Move the handle to the current event position. final _handleBounds = this._handleBounds!; final _domainCenterPoint = this._domainCenterPoint!; final element = _SliderElement( domainCenterPoint: Point(_domainCenterPoint.x, _domainCenterPoint.y), buttonBounds: Rectangle(_handleBounds.left, _handleBounds.top, _handleBounds.width, _handleBounds.height), fill: _style.fillColor, stroke: _style.strokeColor, strokeWidthPx: _style.strokeWidthPx, ); _sliderHandle!.setNewTarget(element); _view.sliderHandle = _sliderHandle!; } /// Fires a [SliderListenerDragState] change event if needed. void _fireChangeEvent(ChartCanvas _) { if (SliderListenerDragState == null || _sliderEventListener.onChange == null) { return; } var dragState = _dragStateToFireOnPostRender; // Initial drag state event should only be fired if the slider has moved // since the last draw. We always set the initial drag state event when new // data was drawn on the chart, since we might need to move the slider if // the axis range changed. if (dragState == SliderListenerDragState.initial && _previousDomainCenterPoint == _domainCenterPoint) { dragState = null; } // Reset state. _dragStateToFireOnPostRender = null; _previousDomainCenterPoint = _domainCenterPoint; // Bail out if the event was cancelled. if (dragState == null) { return; } // Fire the event. _sliderEventListener.onChange!( Point(_domainCenterPoint!.x, _domainCenterPoint!.y), _domainValue, _roleId, dragState); } /// Moves the slider along the domain axis (and primary measure axis if /// [_style.handlePosition] is set to [SliderHandlePosition.manual]) to [point /// ]. /// /// If [point] exists beyond either edge of the draw area, it will be bound to /// the nearest edge. /// /// Updates [_domainValue] with the domain value located at [point]. For /// ordinal axes, this might technically result in a domain value whose center /// point lies slightly outside the draw area. /// /// Updates [_domainCenterPoint] and [_handleBounds] with the new position of /// the slider. /// /// Returns whether or not the position actually changed. This will generally /// be false if the mouse was dragged outside of the domain axis viewport. bool _moveSliderToPoint(Point point) { var positionChanged = false; if (_chart != null) { final viewBounds = _view.componentBounds; // Clamp the position to the edge of the viewport. final positionX = clamp(point.x, viewBounds.left, viewBounds.right); final previousYPosition = _handleBounds == null ? 0.0 : _handleBounds!.top + _style.handleSize.height / 2 - _style.handleOffset.y; var positionY = point.y; if (point.y == 0) { if (_handleBounds == null) { positionY = viewBounds.bottom.toDouble(); } else { positionY = previousYPosition; } } // Clamp the position to the edge of the viewport. positionY = clamp(positionY, viewBounds.top, viewBounds.bottom).toDouble(); final positionXChanged = _previousDomainCenterPoint != null && positionX != _previousDomainCenterPoint!.x; final positionYChanged = _style.handlePosition == SliderHandlePosition.manual && _handleBounds != null && positionY != previousYPosition; positionChanged = positionXChanged || positionYChanged; // Reset the domain value if the position was outside of the chart. _domainValue = _chart!.domainAxis!.getDomain(positionX.toDouble()); if (_domainCenterPoint != null) { _domainCenterPoint = Point(positionX.round(), _domainCenterPoint!.y); } else { _domainCenterPoint = Point(positionX.round(), (viewBounds.top + viewBounds.height / 2).round()); } num handleReferenceY; switch (_style.handlePosition) { case SliderHandlePosition.middle: handleReferenceY = _domainCenterPoint!.y; break; case SliderHandlePosition.top: handleReferenceY = viewBounds.top; break; case SliderHandlePosition.manual: handleReferenceY = positionY; break; default: throw ArgumentError('Slider does not support the handle position ' '"${_style.handlePosition}"'); } // Move the slider handle along the domain axis. _handleBounds = Rectangle( (_domainCenterPoint!.x - _style.handleSize.width / 2 + _style.handleOffset.x) .round(), (handleReferenceY - _style.handleSize.height / 2 + _style.handleOffset.y) .round(), _style.handleSize.width, _style.handleSize.height); } return positionChanged; } /// Moves the slider along the domain axis to the location of [domain] and iff /// [measure] is set moves it also to location of [measure] along the primary /// measure axis. /// /// If [domain] or [measure] exists beyond either edge of the draw area, the position will /// be bound to the nearest edge. /// /// Updates [_domainValue] with the location of [domain]. For ordinal axes, /// this might result in a different domain value if the range band of /// [domain] is completely outside of the viewport. /// /// Updates [_domainCenterPoint] and [_handleBounds] with the new position of /// the slider. /// /// Returns whether or not the position actually changed. This will generally /// be false if the mouse was dragged outside of the domain axis viewport. bool _moveSliderToDomain(D? domain, {num? measure}) { final x = _chart!.domainAxis!.getLocation(domain)!; final y = measure != null ? _chart!.getMeasureAxis().getLocation(measure)! : 0.0; return _moveSliderToPoint(Point(x, y)); } /// Programmatically moves the slider to the location of [domain] on the /// domain axis and iff [measure] is set moves it also to its position along /// the primary measure axis. /// /// If [domain] exists beyond either edge of the draw area, the position will /// be bound to the nearest edge of the chart. The slider's current domain /// value state will reflect the domain value at the edge of the chart. For /// ordinal axes, this might result in a domain value whose range band is /// partially located beyond the edge of the chart. /// /// This does nothing if the domain matches the current domain location. /// /// [SliderEventListener] callbacks will be fired to indicate that the slider /// has moved. /// /// [skipAnimation] controls whether or not the slider will animate. Animation /// is disabled by default. /// /// [measure] controls the vertical position of the handle on the measure /// axis, can only be set if the SliderHandlePosition is set to 'manual'. If /// measure exists beyond the edges of the draw area, the position will be /// bound to the nearest edge of the chart. void moveSliderToDomain(D domain, {num? measure, bool skipAnimation = true}) { // Nothing to do if we are unattached to a chart or asked to move to the // current location. if (_chart == null || domain == _domainValue) { return; } final positionChanged = _moveSliderToDomain(domain, measure: measure); if (positionChanged) { _dragStateToFireOnPostRender = SliderListenerDragState.end; _chart!.redraw(skipAnimation: skipAnimation, skipLayout: true); } } @override void attachTo(BaseChart chart) { if (chart is! CartesianChart) { throw ArgumentError('Slider can only be attached to a cartesian chart.'); } _chart = chart; // Only vertical rendering is supported by this behavior. assert(chart.vertical); _view = _SliderLayoutView( layoutPaintOrder: layoutPaintOrder, handleRenderer: _handleRenderer); chart.addView(_view); chart.addGestureListener(_gestureListener); chart.addLifecycleListener(_lifecycleListener); } @override void removeFrom(BaseChart chart) { chart.removeView(_view); chart.removeGestureListener(_gestureListener); chart.removeLifecycleListener(_lifecycleListener); _chart = null; } @override String get role => 'Slider-$eventTrigger-$_roleId'; } /// Style configuration for a [Slider] behavior. class SliderStyle { /// Fill color of the handle of the slider. Color fillColor; /// Allows users to specify both x-position and y-position offset values that /// determines where the slider handle will be rendered. The offset will be /// calculated relative to its default position at the vertical and horizontal /// center of the slider line. Point handleOffset; /// The vertical position for the slider handle. SliderHandlePosition handlePosition; /// Specifies the size of the slider handle. Rectangle handleSize; /// Stroke width of the slider line and the slider handle. double strokeWidthPx; /// Stroke color of the slider line and hte slider handle Color strokeColor = StyleFactory.style.sliderStrokeColor; SliderStyle( {Color? fillColor, this.handleOffset = const Point(0.0, 0.0), this.handleSize = const Rectangle(0, 0, 10, 20), Color? strokeColor, this.handlePosition = SliderHandlePosition.middle, this.strokeWidthPx = 2.0}) : fillColor = fillColor ?? StyleFactory.style.sliderFillColor, strokeColor = strokeColor ?? StyleFactory.style.sliderStrokeColor; @override bool operator ==(Object other) { return other is SliderStyle && fillColor == other.fillColor && handleOffset == other.handleOffset && handleSize == other.handleSize && strokeWidthPx == other.strokeWidthPx && strokeColor == other.strokeColor; } @override int get hashCode { var hashcode = fillColor.hashCode; hashcode = (hashcode * 37) + handleOffset.hashCode; hashcode = (hashcode * 37) + handleSize.hashCode; hashcode = (hashcode * 37) + strokeWidthPx.hashCode; hashcode = (hashcode * 37) + strokeColor.hashCode; hashcode = (hashcode * 37) + handlePosition.hashCode; return hashcode; } } /// Describes the vertical position of the slider handle on the slider. /// /// [middle] indicates the handle should be half-way between the top and bottom /// of the chart in the middle of the slider line. /// /// [top] indicates the slider should be rendered relative to the top of the /// chart. /// /// [manual] indicates that the slider vertical position can be set every /// time the slider moves by calling moveSliderToDomain. enum SliderHandlePosition { middle, top, manual } /// Layout view component for [Slider]. class _SliderLayoutView extends LayoutView { @override final LayoutViewConfig layoutConfig; late Rectangle _drawAreaBounds; Rectangle get drawBounds => _drawAreaBounds; @override GraphicsFactory? graphicsFactory; /// Renderer for the handle. Defaults to a rectangle. final SymbolRenderer _handleRenderer; /// Rendering data for the slider line and handle. _AnimatedSlider? _sliderHandle; _SliderLayoutView( {required int layoutPaintOrder, required SymbolRenderer handleRenderer}) : layoutConfig = LayoutViewConfig( paintOrder: layoutPaintOrder, position: LayoutPosition.DrawArea, positionOrder: LayoutViewPositionOrder.drawArea), _handleRenderer = handleRenderer; set sliderHandle(_AnimatedSlider value) { _sliderHandle = value; } @override ViewMeasuredSizes? measure(int maxWidth, int maxHeight) { return null; } @override void layout(Rectangle componentBounds, Rectangle drawAreaBounds) { _drawAreaBounds = drawAreaBounds; } @override void paint(ChartCanvas canvas, double animationPercent) { final sliderElement = _sliderHandle!.getCurrentSlider(animationPercent); canvas.drawLine( points: [ Point(sliderElement.domainCenterPoint.x, _drawAreaBounds.top), Point(sliderElement.domainCenterPoint.x, _drawAreaBounds.bottom), ], stroke: sliderElement.stroke, strokeWidthPx: sliderElement.strokeWidthPx); _handleRenderer.paint(canvas, sliderElement.buttonBounds, fillColor: sliderElement.fill, strokeColor: sliderElement.stroke, strokeWidthPx: sliderElement.strokeWidthPx); } @override Rectangle get componentBounds => _drawAreaBounds; @override bool get isSeriesRenderer => false; } /// Rendering information for a slider control element. class _SliderElement { Point domainCenterPoint; Rectangle buttonBounds; Color fill; Color stroke; double strokeWidthPx; _SliderElement({ required this.domainCenterPoint, required this.buttonBounds, required this.fill, required this.stroke, required this.strokeWidthPx, }); _SliderElement clone() { return _SliderElement( domainCenterPoint: domainCenterPoint, buttonBounds: buttonBounds, fill: fill, stroke: stroke, strokeWidthPx: strokeWidthPx, ); } void updateAnimationPercent(_SliderElement previous, _SliderElement target, double animationPercent) { final previousPoint = previous.domainCenterPoint; final targetPoint = target.domainCenterPoint; final x = ((targetPoint.x - previousPoint.x) * animationPercent) + previousPoint.x; final y = ((targetPoint.y - previousPoint.y) * animationPercent) + previousPoint.y; domainCenterPoint = Point(x.round(), y.round()); final previousBounds = previous.buttonBounds; final targetBounds = target.buttonBounds; final top = ((targetBounds.top - previousBounds.top) * animationPercent) + previousBounds.top; final right = ((targetBounds.right - previousBounds.right) * animationPercent) + previousBounds.right; final bottom = ((targetBounds.bottom - previousBounds.bottom) * animationPercent) + previousBounds.bottom; final left = ((targetBounds.left - previousBounds.left) * animationPercent) + previousBounds.left; buttonBounds = Rectangle(left.round(), top.round(), (right - left).round(), (bottom - top).round()); fill = getAnimatedColor(previous.fill, target.fill, animationPercent); stroke = getAnimatedColor(previous.stroke, target.stroke, animationPercent); strokeWidthPx = ((target.strokeWidthPx - previous.strokeWidthPx) * animationPercent) + previous.strokeWidthPx; } } /// Animates the slider control element of the behavior between different /// states. class _AnimatedSlider { _SliderElement? _previousSlider; late _SliderElement _targetSlider; _SliderElement? _currentSlider; // Flag indicating whether this point is being animated out of the chart. bool animatingOut = false; _AnimatedSlider(); /// Animates a point that was removed from the series out of the view. /// /// This should be called in place of "setNewTarget" for points that represent /// data that has been removed from the series. /// /// Animates the width of the slider down to 0. void animateOut() { final newTarget = _currentSlider!.clone(); // Animate the button bounds inwards horizontally towards a 0 width box. final targetBounds = newTarget.buttonBounds; final top = targetBounds.top; final right = targetBounds.left + targetBounds.width / 2; final bottom = targetBounds.bottom; final left = right; newTarget.buttonBounds = Rectangle(left.round(), top.round(), (right - left).round(), (bottom - top).round()); // Animate the stroke width to 0 so that we don't get a lingering line after // animation is done. newTarget.strokeWidthPx = 0.0; setNewTarget(newTarget); animatingOut = true; } void setNewTarget(_SliderElement newTarget) { animatingOut = false; _currentSlider ??= newTarget.clone(); _previousSlider = _currentSlider!.clone(); _targetSlider = newTarget; } _SliderElement getCurrentSlider(double animationPercent) { if (animationPercent == 1.0 || _previousSlider == null) { _currentSlider = _targetSlider; _previousSlider = _targetSlider; return _currentSlider!; } _currentSlider!.updateAnimationPercent( _previousSlider!, _targetSlider, animationPercent); return _currentSlider!; } } /// Event handler for slider events. class SliderEventListener { /// Called when the position of the slider has changed during a drag event. final SliderListenerCallback? onChange; SliderEventListener({this.onChange}); } /// Callback function for [Slider] drag events. /// /// [point] is the current position of the slider line. [point.x] is the domain /// position, and [point.y] is the position of the center of the line on the /// measure axis. /// /// [domain] is the domain value at the slider position. /// /// [dragState] indicates the current state of a drag event. typedef SliderListenerCallback = void Function(Point point, D? domain, String roleId, SliderListenerDragState dragState); /// Describes the current state of a slider change as a result of a drag event. /// /// [initial] indicates that the slider was set to an initial position when new /// data was drawn on a chart. This will be fired if an initialDomainValue is /// passed to [Slider]. It will also be fired if the position of the slider /// changes as a result of new data being drawn on the chart. /// /// [drag] indicates that the slider is being moved as a result of drag events. /// When this is passed, the drag event is still active. Once the drag event is /// completed, an [end] event will be fired. /// /// [end] indicates that a drag event has been completed. This usually occurs /// after one or more [drag] events. An [end] event will also be fired if /// [Slider.moveSliderToDomain] is called, but there will be no preceding [drag] /// events in this case. enum SliderListenerDragState { initial, drag, end } /// Helper class that exposes fewer private internal properties for unit tests. @visibleForTesting class SliderTester { final Slider behavior; SliderTester(this.behavior); Point? get domainCenterPoint => behavior._domainCenterPoint; D? get domainValue => behavior._domainValue; Rectangle? get handleBounds => behavior._handleBounds; void layout(Rectangle componentBounds, Rectangle drawAreaBounds) { behavior._view.layout(componentBounds, drawAreaBounds); } _SliderLayoutView get view => behavior._view; } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/common/behavior/sliding_viewport.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import '../../cartesian/cartesian_chart.dart' show CartesianChart; import '../base_chart.dart' show BaseChart; import '../selection_model/selection_model.dart' show SelectionModel, SelectionModelType; import 'chart_behavior.dart' show ChartBehavior; /// Chart behavior that centers the viewport on the selected domain. /// /// It is used in combination with SelectNearest to update the selection model /// and notify this behavior to update the viewport on selection change. /// /// This behavior can only be used on [CartesianChart]. class SlidingViewport implements ChartBehavior { final SelectionModelType selectionModelType; late CartesianChart _chart; SlidingViewport([this.selectionModelType = SelectionModelType.info]); void _selectionChanged(SelectionModel selectionModel) { if (selectionModel.hasAnySelection == false) { return; } // Calculate current viewport center and determine the translate pixels // needed based on the selected domain value's location and existing amount // of translate pixels. final domainAxis = _chart.domainAxis!; final selectedDatum = selectionModel.selectedDatum.first; final domainLocation = domainAxis .getLocation(selectedDatum.series.domainFn(selectedDatum.index))!; final viewportCenter = domainAxis.range!.start + (domainAxis.range!.width / 2); final translatePx = domainAxis.viewportTranslatePx + (viewportCenter - domainLocation); domainAxis.setViewportSettings( domainAxis.viewportScalingFactor, translatePx); _chart.redraw(); } @override void attachTo(BaseChart chart) { assert(chart is CartesianChart); _chart = chart as CartesianChart; chart .getSelectionModel(selectionModelType) .addSelectionChangedListener(_selectionChanged); } @override void removeFrom(BaseChart chart) { chart .getSelectionModel(selectionModelType) .removeSelectionChangedListener(_selectionChanged); } @override String get role => 'slidingViewport-$selectionModelType'; } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/common/behavior/sunburst_ring_expander.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import '../base_chart.dart' show BaseChart; import '../selection_model/selection_model.dart' show SelectionModel, SelectionModelType; import '../../sunburst/sunburst_chart.dart' show SunburstChart; import 'chart_behavior.dart' show ChartBehavior; /// Expands the initially displayed outer ring to show subset of data in one /// final ring. class SunburstRingExpander implements ChartBehavior { final SelectionModelType selectionModelType; late SunburstChart _chart; SunburstRingExpander([this.selectionModelType = SelectionModelType.action]); void _selectionChanged(SelectionModel selectionModel) { if (selectionModel.selectedDatum.isNotEmpty) { _chart.expandNode(selectionModel.selectedDatum.first.datum); _chart.redraw(skipLayout: true, skipAnimation: true); } } @override void attachTo(BaseChart chart) { if (!(chart is SunburstChart)) { throw ArgumentError( 'SunburstRingExpander can only be attached to a Sunburst chart'); } _chart = chart as SunburstChart; chart .getSelectionModel(selectionModelType) .addSelectionUpdatedListener(_selectionChanged); } @override void removeFrom(BaseChart chart) { chart .getSelectionModel(selectionModelType) .addSelectionUpdatedListener(_selectionChanged); } @override String get role => 'sunburstRingExpander-$selectionModelType'; } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/common/behavior/zoom/initial_hint_behavior.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math' show Point; import 'package:meta/meta.dart' show protected; import '../../../../common/gesture_listener.dart' show GestureListener; import '../../../cartesian/axis/axis.dart' show Axis; import '../../../cartesian/cartesian_chart.dart' show CartesianChart; import '../../base_chart.dart' show BaseChart, LifecycleListener; import '../chart_behavior.dart' show ChartBehavior; /// Adds initial hint behavior for [CartesianChart]. /// /// This behavior animates to the final viewport from an initial translate and /// or scale factor. abstract class InitialHintBehavior implements ChartBehavior { /// Listens for drag gestures. late GestureListener _listener; /// Chart lifecycle listener to setup hint animation. late LifecycleListener _lifecycleListener; @override String get role => 'InitialHint'; /// The chart to which the behavior is attached. CartesianChart? _chart; @protected CartesianChart? get chart => _chart; Duration _hintDuration = Duration(milliseconds: 3000); /// The amount of time to animate to the desired viewport. /// /// If no duration is passed in, the default of 3000 ms is used. @protected Duration get hintDuration => _hintDuration; set hintDuration(Duration duration) { _hintDuration = duration; } double _maxHintTranslate = 0.0; // TODO: Translation animation only works for ordinal axis. /// The maximum amount ordinal values to shift the viewport for the the hint /// animation. /// /// Positive numbers shift the viewport to the right and negative to the left. /// The default is no translation. @protected double get maxHintTranslate => _maxHintTranslate; set maxHintTranslate(double maxHintTranslate) { _maxHintTranslate = maxHintTranslate; } double? _maxHintScaleFactor; /// The amount the domain axis will be scaled for the start of the hint. /// /// A value of 1.0 means the viewport is completely zoomed out (all domains /// are in the viewport). If a value is provided, it cannot be less than 1.0. /// /// By default maxHintScaleFactor is not set. @protected double? get maxHintScaleFactor => _maxHintScaleFactor; set maxHintScaleFactor(double? maxHintScaleFactor) { assert(maxHintScaleFactor != null && maxHintScaleFactor >= 1.0); _maxHintScaleFactor = maxHintScaleFactor; } /// Flag to indicate that hint animation controller has already been set up. /// /// This is to ensure that the hint is only set up on the first draw. bool _hintSetupCompleted = false; /// Flag to indicate that the first call to axis configured is completed. /// /// This is to ensure that the initial and target viewport translate and scale /// factor is only calculated on the first axis configuration. bool _firstAxisConfigured = false; double? _initialViewportTranslatePx; double? _initialViewportScalingFactor; late double _targetViewportTranslatePx; late double _targetViewportScalingFactor; InitialHintBehavior() { _listener = GestureListener(onTapTest: onTapTest); _lifecycleListener = LifecycleListener( onAxisConfigured: _onAxisConfigured, onAnimationComplete: _onAnimationComplete); } @override void attachTo(BaseChart chart) { if (chart is! CartesianChart) { throw ArgumentError( 'InitialHintBehavior can only be attached to a CartesianChart'); } _chart = chart; chart.addGestureListener(_listener); chart.addLifecycleListener(_lifecycleListener); } @override void removeFrom(BaseChart chart) { if (chart is! CartesianChart) { throw ArgumentError( 'InitialHintBehavior can only be removed from a CartesianChart'); } stopHintAnimation(); _chart = chart as CartesianChart; chart.removeGestureListener(_listener); chart.removeLifecycleListener(_lifecycleListener); _chart = null; } @protected bool onTapTest(Point localPosition) { if (_chart == null) { return false; } // If the user taps the chart, stop the hint animation immediately. stopHintAnimation(); return _chart!.withinDrawArea(localPosition); } /// Calculate the animation's initial and target viewport and scale factor /// and shift the viewport to the start. void _onAxisConfigured() { if (!_firstAxisConfigured) { _firstAxisConfigured = true; final domainAxis = chart!.domainAxis!; // TODO: Translation animation only works for axis with a // rangeband type that returns a non zero step size. If two rows have // the same domain value, step size could also equal 0. assert(domainAxis.stepSize != 0.0); // Save the target viewport and scale factor from axis, because the // viewport can be set by the user using AxisSpec. _targetViewportTranslatePx = domainAxis.viewportTranslatePx; _targetViewportScalingFactor = domainAxis.viewportScalingFactor; // Calculate the amount to translate from the target viewport. final translateAmount = domainAxis.stepSize * maxHintTranslate; _initialViewportTranslatePx = _targetViewportTranslatePx - translateAmount; _initialViewportScalingFactor = maxHintScaleFactor ?? _targetViewportScalingFactor; assert(_initialViewportScalingFactor != null); domainAxis.setViewportSettings( _initialViewportScalingFactor!, _initialViewportTranslatePx!); chart!.redraw(skipAnimation: true, skipLayout: false); } } /// Start the hint animation, only start the animation on the very first draw. void _onAnimationComplete() { if (!_hintSetupCompleted) { _hintSetupCompleted = true; startHintAnimation(); } } /// Setup and start the hint animation. /// /// Animation controller to be handled by the native platform. @protected void startHintAnimation() { // When panning starts, measure tick provider should not update ticks. // This is still needed because axis internally updates the tick location // after the tick provider generates the ticks. If we do not tell the axis // not to update the location of the measure axes, the measure axis will // change during the hint animation and make values jump back and forth. _chart!.getMeasureAxis().lockAxis = true; _chart!.getMeasureAxis(axisId: Axis.secondaryMeasureAxisId).lockAxis = true; } /// Stop hint animation @protected void stopHintAnimation() { // When panning is completed, unlock the measure axis. _chart!.getMeasureAxis().lockAxis = false; _chart!.getMeasureAxis(axisId: Axis.secondaryMeasureAxisId).lockAxis = false; } /// Animation hint percent, to be returned by the native platform. @protected double get hintAnimationPercent; /// Shift domain viewport on hint animation ticks. @protected void onHintTick() { final percent = hintAnimationPercent; final scaleFactor = _lerpDouble( _initialViewportScalingFactor, _targetViewportScalingFactor, percent); var translatePx = _lerpDouble( _initialViewportTranslatePx, _targetViewportTranslatePx, percent); // If there is a scale factor animation, need to scale the translatePx so // the animation appears to be zooming in on the viewport when there is no // [maxHintTranslate] provided. // // If there is a translate hint, the animation will still first zoom in // and then translate the [maxHintTranslate] amount. if (_initialViewportScalingFactor != _targetViewportScalingFactor) { translatePx = translatePx * percent; } final chart = this.chart!; final domainAxis = chart.domainAxis!; domainAxis.setViewportSettings(scaleFactor, translatePx, drawAreaWidth: chart.drawAreaBounds.width); if (percent >= 1.0) { stopHintAnimation(); chart.redraw(); } else { chart.redraw(skipAnimation: true, skipLayout: true); } } /// Linear interpolation for doubles. double _lerpDouble(double? a, double? b, double t) { a ??= 0.0; b ??= 0.0; return a + (b - a) * t; } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/common/behavior/zoom/pan_and_zoom_behavior.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math' show min, max, Point; import 'package:meta/meta.dart' show protected; import 'pan_behavior.dart'; import 'panning_tick_provider.dart' show PanningTickProviderMode; /// Adds domain axis panning and zooming support to the chart. /// /// Zooming is supported for the web by mouse wheel events. Scrolling up zooms /// the chart in, and scrolling down zooms the chart out. The chart can never be /// zoomed out past the domain axis range. /// /// Zooming is supported by pinch gestures for mobile devices. /// /// Panning is supported by clicking and dragging the mouse for web, or tapping /// and dragging on the chart for mobile devices. class PanAndZoomBehavior extends PanBehavior { @override String get role => 'PanAndZoom'; /// Flag which is enabled to indicate that the user is "zooming" the chart. bool _isZooming = false; @protected bool get isZooming => _isZooming; /// Current zoom scaling factor for the behavior. double _scalingFactor = 1.0; /// Minimum scalingFactor to prevent zooming out beyond the data range. final _minScalingFactor = 1.0; /// Maximum scalingFactor to prevent zooming in so far that no data is /// visible. /// /// TODO: Dynamic max based on data range? final _maxScalingFactor = 5.0; @override bool onDragStart(Point localPosition) { if (chart == null) { return false; } super.onDragStart(localPosition); // Save the current scaling factor to make zoom events relative. _scalingFactor = chart!.domainAxis!.viewportScalingFactor; _isZooming = true; return true; } @override bool onDragUpdate(Point localPosition, double scale) { // Swipe gestures should be handled by the [PanBehavior]. if (scale == 1.0) { _isZooming = false; return super.onDragUpdate(localPosition, scale); } // No further events in this chain should be handled by [PanBehavior]. cancelPanning(); final chart = this.chart; if (!_isZooming || lastPosition == null || chart == null) { return false; } // Update the domain axis's viewport scale factor to zoom the chart. final domainAxis = chart.domainAxis; if (domainAxis == null) { return false; } // This is set during onDragUpdate and NOT onDragStart because we don't yet // know during onDragStart whether pan/zoom behavior is panning or zooming. // During zoom in / zoom out, domain tick provider set to return existing // cached ticks. domainAxisTickProvider.mode = PanningTickProviderMode.useCachedTicks; // Clamp the scale to prevent zooming out beyond the range of the data, or // zooming in so far that we show nothing useful. final newScalingFactor = min(max(_scalingFactor * scale, _minScalingFactor), _maxScalingFactor); domainAxis.setViewportSettings( newScalingFactor, domainAxis.viewportTranslatePx, drawAreaWidth: chart.drawAreaBounds.width, drawAreaHeight: chart.drawAreaBounds.height); chart.redraw(skipAnimation: true, skipLayout: true); return true; } @override bool onDragEnd( Point localPosition, double scale, double pixelsPerSec) { _isZooming = false; return super.onDragEnd(localPosition, scale, pixelsPerSec); } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/common/behavior/zoom/pan_behavior.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math' show Point; import 'package:meta/meta.dart' show protected; import '../../../../common/gesture_listener.dart' show GestureListener; import '../../../cartesian/axis/axis.dart' show Axis; import '../../../cartesian/cartesian_chart.dart' show CartesianChart; import '../../base_chart.dart' show BaseChart; import '../chart_behavior.dart' show ChartBehavior; import 'panning_tick_provider.dart'; /// Adds domain axis panning support to a chart. /// /// Panning is supported by clicking and dragging the mouse for web, or tapping /// and dragging on the chart for mobile devices. class PanBehavior implements ChartBehavior { /// Listens for drag gestures. late GestureListener _listener; /// Wrapped domain tick provider for pan and zoom behavior. late PanningTickProvider _domainAxisTickProvider; @protected PanningTickProvider get domainAxisTickProvider => _domainAxisTickProvider; @override String get role => 'Pan'; /// The chart to which the behavior is attached. CartesianChart? _chart; @protected CartesianChart? get chart => _chart; /// Flag which is enabled to indicate that the user is "panning" the chart. bool _isPanning = false; @protected bool get isPanning => _isPanning; /// Last position of the mouse/tap that was used to adjust the scale translate /// factor. Point? _lastPosition; @protected Point? get lastPosition => _lastPosition; /// Optional callback that is invoked at the end of panning ([onPanEnd]). PanningCompletedCallback? _panningCompletedCallback; set panningCompletedCallback(PanningCompletedCallback? callback) { _panningCompletedCallback = callback; } PanBehavior() { _listener = GestureListener( onTapTest: onTapTest, onDragStart: onDragStart, onDragUpdate: onDragUpdate, onDragEnd: onDragEnd); } /// Injects the behavior into a chart. @override void attachTo(BaseChart chart) { if (chart is! CartesianChart) { throw ArgumentError( 'PanBehavior can only be attached to a CartesianChart'); } _chart = chart; chart.addGestureListener(_listener); // Disable the autoViewport feature to enable panning. chart.domainAxis!.autoViewport = false; // Wrap domain axis tick provider with the panning behavior one. _domainAxisTickProvider = PanningTickProvider(chart.domainAxis!.tickProvider!); chart.domainAxis!.tickProvider = _domainAxisTickProvider; } /// Removes the behavior from a chart. @override void removeFrom(BaseChart chart) { if (chart is! CartesianChart) { throw ArgumentError( 'PanBehavior can only be attached to a CartesianChart'); } _chart = chart; chart.removeGestureListener(_listener); // Restore the default autoViewport state. chart.domainAxis!.autoViewport = true; // Restore the original tick providers chart.domainAxis!.tickProvider = _domainAxisTickProvider.tickProvider; _chart = null; } @protected bool onTapTest(Point localPosition) { if (_chart == null) { return false; } return _chart!.withinDrawArea(localPosition); } @protected bool onDragStart(Point localPosition) { if (_chart == null) { return false; } onPanStart(); _lastPosition = localPosition; _isPanning = true; return true; } @protected bool onDragUpdate(Point localPosition, double scale) { if (!_isPanning || _lastPosition == null || _chart == null) { return false; } // Pinch gestures should be handled by the [PanAndZoomBehavior]. if (scale != 1.0) { _isPanning = false; return false; } // Update the domain axis's viewport translate to pan the chart. final domainAxis = _chart!.domainAxis; if (domainAxis == null) { return false; } // This is set during onDragUpdate and NOT onDragStart because we don't yet // know during onDragStart whether pan/zoom behavior is panning or zooming. // During panning, domain tick provider set to generate ticks with locked // steps. _domainAxisTickProvider.mode = PanningTickProviderMode.stepSizeLocked; final domainScalingFactor = domainAxis.viewportScalingFactor; var domainChange = 0.0; if (domainAxis.isVertical) { domainChange = domainAxis.viewportTranslatePx + localPosition.y - _lastPosition!.y; } else { domainChange = domainAxis.viewportTranslatePx + localPosition.x - _lastPosition!.x; } final chart = this.chart!; domainAxis.setViewportSettings(domainScalingFactor, domainChange, drawAreaWidth: chart.drawAreaBounds.width, drawAreaHeight: chart.drawAreaBounds.height); _lastPosition = localPosition; chart.redraw(skipAnimation: true, skipLayout: true); return true; } @protected bool onDragEnd( Point localPosition, double scale, double pixelsPerSec) { onPanEnd(); return true; } @protected void onPanStart() { // When panning starts, measure tick provider should not update ticks. // This is still needed because axis internally updates the tick location // after the tick provider generates the ticks. If we do not tell the axis // not to update the location of the measure axes, we get a jittery effect // as the measure axes location changes ever so slightly during pan/zoom. _chart!.getMeasureAxis().lockAxis = true; _chart!.getMeasureAxis(axisId: Axis.secondaryMeasureAxisId).lockAxis = true; } @protected void onPanEnd() { cancelPanning(); // When panning stops, allow tick provider to update ticks, and then // request redraw. _domainAxisTickProvider.mode = PanningTickProviderMode.passThrough; final _chart = this._chart!; _chart.getMeasureAxis().lockAxis = false; _chart.getMeasureAxis(axisId: Axis.secondaryMeasureAxisId).lockAxis = false; _chart.redraw(); _panningCompletedCallback?.call(); } /// Cancels the handling of any current panning event. void cancelPanning() { _isPanning = false; } } /// Callback for when panning is completed. typedef PanningCompletedCallback = void Function(); ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/common/behavior/zoom/panning_tick_provider.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import '../../../../common/graphics_factory.dart' show GraphicsFactory; import '../../../cartesian/axis/axis.dart' show AxisOrientation; import '../../../cartesian/axis/draw_strategy/tick_draw_strategy.dart' show TickDrawStrategy; import '../../../cartesian/axis/scale.dart' show MutableScale; import '../../../cartesian/axis/tick.dart' show Tick; import '../../../cartesian/axis/tick_formatter.dart' show TickFormatter; import '../../../cartesian/axis/tick_provider.dart' show TickProvider, TickHint; import '../../../common/chart_context.dart' show ChartContext; enum PanningTickProviderMode { /// Return cached ticks. useCachedTicks, /// Request ticks with [TickHint] calculated from cached ticks. stepSizeLocked, /// Request ticks directly from tick provider. passThrough, } /// Wraps an existing tick provider to be able to return cached ticks during /// zoom in/out, return ticks calculated with locked step size during panning, /// or just pass through to the existing tick provider. class PanningTickProvider implements TickProvider { final TickProvider tickProvider; PanningTickProviderMode _mode = PanningTickProviderMode.passThrough; late List> _ticks; PanningTickProvider(this.tickProvider); set mode(PanningTickProviderMode mode) { _mode = mode; } @override List> getTicks({ required ChartContext? context, required GraphicsFactory graphicsFactory, required MutableScale scale, required TickFormatter formatter, required Map formatterValueCache, required TickDrawStrategy tickDrawStrategy, required AxisOrientation? orientation, bool viewportExtensionEnabled = false, TickHint? tickHint, }) { if (_mode == PanningTickProviderMode.stepSizeLocked) { tickHint = TickHint( _ticks.first.value, _ticks.last.value, tickCount: _ticks.length, ); } if (_mode != PanningTickProviderMode.useCachedTicks) { _ticks = tickProvider.getTicks( context: context, graphicsFactory: graphicsFactory, scale: scale, formatter: formatter, formatterValueCache: formatterValueCache, tickDrawStrategy: tickDrawStrategy, orientation: orientation, viewportExtensionEnabled: viewportExtensionEnabled, tickHint: tickHint, ); } return _ticks; } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/common/canvas_shapes.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math' show Rectangle, min, max, Point; import '../../common/color.dart' show Color; import 'chart_canvas.dart' show FillPatternType; /// A rectangle to be painted by [ChartCanvas]. class CanvasRect { final Rectangle bounds; final List? dashPattern; final Color? fill; final FillPatternType? pattern; final Color? stroke; final double? strokeWidthPx; CanvasRect(this.bounds, {this.dashPattern, this.fill, this.pattern, this.stroke, this.strokeWidthPx}); } /// A stack of [CanvasRect] to be painted by [ChartCanvas]. class CanvasBarStack { final List segments; final int? radius; final int stackedBarPadding; final bool roundTopLeft; final bool roundTopRight; final bool roundBottomLeft; final bool roundBottomRight; final Rectangle fullStackRect; factory CanvasBarStack( List segments, { int? radius, int stackedBarPadding = 1, bool roundTopLeft = false, bool roundTopRight = false, bool roundBottomLeft = false, bool roundBottomRight = false, }) { final firstBarBounds = segments.first.bounds; // Find the rectangle that would represent the full stack of bars. var left = firstBarBounds.left; var top = firstBarBounds.top; var right = firstBarBounds.right; var bottom = firstBarBounds.bottom; for (var barIndex = 1; barIndex < segments.length; barIndex++) { final bounds = segments[barIndex].bounds; left = min(left, bounds.left); top = min(top, bounds.top); right = max(right, bounds.right); bottom = max(bottom, bounds.bottom); } final width = right - left; final height = bottom - top; final fullStackRect = Rectangle(left, top, width, height); return CanvasBarStack._internal( segments, radius: radius, stackedBarPadding: stackedBarPadding, roundTopLeft: roundTopLeft, roundTopRight: roundTopRight, roundBottomLeft: roundBottomLeft, roundBottomRight: roundBottomRight, fullStackRect: fullStackRect, ); } CanvasBarStack._internal( this.segments, { required this.radius, required this.stackedBarPadding, required this.roundTopLeft, required this.roundTopRight, required this.roundBottomLeft, required this.roundBottomRight, required this.fullStackRect, }); } /// A list of [CanvasPieSlice]s to be painted by [ChartCanvas]. class CanvasPie { final List slices; Point center; double radius; double innerRadius; /// Color of separator lines between arcs. final Color? stroke; /// Stroke width of separator lines between arcs. double strokeWidthPx; CanvasPie(this.slices, this.center, this.radius, this.innerRadius, {this.stroke, this.strokeWidthPx = 0.0}); } /// A circle sector to be painted by [ChartCanvas]. class CanvasPieSlice { double startAngle; double endAngle; Color? fill; CanvasPieSlice(this.startAngle, this.endAngle, {this.fill}); } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/common/chart_canvas.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math' show Point, Rectangle; import '../../common/color.dart' show Color; import '../../common/graphics_factory.dart' show GraphicsFactory; import '../../common/text_element.dart' show TextElement; import 'canvas_shapes.dart' show CanvasBarStack, CanvasPie; abstract class ChartCanvas { /// Get [GraphicsFactory] for creating native graphics elements. GraphicsFactory get graphicsFactory; /// Set the name of the view doing the rendering for debugging purposes, /// or null when we believe rendering is complete. set drawingView(String? viewName); /// Renders a sector of a circle, with an optional hole in the center. /// /// [center] The x, y coordinates of the circle's center. /// [radius] The radius of the circle. /// [innerRadius] Optional radius of a hole in the center of the circle that /// should not be filled in as part of the sector. /// [startAngle] The angle at which the arc starts, measured clockwise from /// the positive x axis and expressed in radians /// [endAngle] The angle at which the arc ends, measured clockwise from the /// positive x axis and expressed in radians. /// [fill] Fill color for the sector. /// [stroke] Stroke color of the arc and radius lines. /// [strokeWidthPx] Stroke width of the arc and radius lines. void drawCircleSector(Point center, double radius, double innerRadius, double startAngle, double endAngle, {Color? fill, Color? stroke, double? strokeWidthPx}); /// Renders a simple line. /// /// [dashPattern] controls the pattern of dashes and gaps in a line. It is a /// list of lengths of alternating dashes and gaps. The rendering is similar /// to stroke-dasharray in SVG path elements. An odd number of values in the /// pattern will be repeated to derive an even number of values. "1,2,3" is /// equivalent to "1,2,3,1,2,3." void drawLine( {required List points, Rectangle? clipBounds, Color? fill, Color? stroke, bool? roundEndCaps, double? strokeWidthPx, List? dashPattern}); /// Renders a pie, with an optional hole in the center. void drawPie(CanvasPie canvasPie); /// Renders a simple point. /// /// [point] The x, y coordinates of the point. /// /// [radius] The radius of the point. /// /// [fill] Fill color for the point. /// /// [stroke] and [strokeWidthPx] configure the color and thickness of the /// outer edge of the point. Both must be provided together for a line to /// appear. /// /// [blendMode] Blend mode to be used when drawing this point on canvas. void drawPoint( {required Point point, required double radius, Color? fill, Color? stroke, double? strokeWidthPx, BlendMode? blendMode}); /// Renders a polygon shape described by a set of points. /// /// [points] describes the vertices of the polygon. The last point will always /// be connected to the first point to close the shape. /// /// [fill] configures the color inside the polygon. The shape will be /// transparent if this is not provided. /// /// [stroke] and [strokeWidthPx] configure the color and thickness of the /// edges of the polygon. Both must be provided together for a line to appear. void drawPolygon( {required List points, Rectangle? clipBounds, Color? fill, Color? stroke, double? strokeWidthPx}); /// Renders a simple rectangle. /// /// [drawAreaBounds] if specified and if the bounds of the rectangle exceed /// the draw area bounds on the top, the first x pixels (decided by the native /// platform) exceeding the draw area will apply a gradient to transparent /// with anything exceeding the x pixels to be transparent. void drawRect(Rectangle bounds, {Color? fill, Color? stroke, double? strokeWidthPx, Rectangle? drawAreaBounds}); /// Renders a rounded rectangle. void drawRRect(Rectangle bounds, {Color? fill, Color? stroke, Color? patternColor, FillPatternType? fillPattern, double? patternStrokeWidthPx, double? strokeWidthPx, num? radius, bool roundTopLeft = false, bool roundTopRight = false, bool roundBottomLeft = false, bool roundBottomRight = false}); /// Renders a stack of bars, rounding the last bar in the stack. /// /// The first bar of the stack is expected to be the "base" bar. This would /// be the bottom most bar for a vertically rendered bar. /// /// [drawAreaBounds] if specified and if the bounds of the rectangle exceed /// the draw area bounds on the top, the first x pixels (decided by the native /// platform) exceeding the draw area will apply a gradient to transparent /// with anything exceeding the x pixels to be transparent. void drawBarStack(CanvasBarStack canvasBarStack, {Rectangle? drawAreaBounds}); void drawText(TextElement textElement, int offsetX, int offsetY, {double rotation = 0.0}); /// Request the canvas to clip to [clipBounds]. /// /// Applies to all operations until [restClipBounds] is called. void setClipBounds(Rectangle clipBounds); /// Restore void resetClipBounds(); } Color getAnimatedColor(Color previous, Color target, double animationPercent) { var r = (((target.r - previous.r) * animationPercent) + previous.r).round(); var g = (((target.g - previous.g) * animationPercent) + previous.g).round(); var b = (((target.b - previous.b) * animationPercent) + previous.b).round(); var a = (((target.a - previous.a) * animationPercent) + previous.a).round(); return Color(a: a, r: r, g: g, b: b); } /// Defines the pattern for a color fill. /// /// * [forwardHatch] defines a pattern of white lines angled up and to the right /// on top of a bar filled with the fill color. /// * [solid] defines a simple bar filled with the fill color. This is the /// default pattern for bars. enum FillPatternType { forwardHatch, solid } /// Defines the blend modes to use for drawing on canvas. enum BlendMode { color, colorBurn, colorDodge, darken, defaultMode, difference, exclusion, hardLight, hue, lighten, luminosity, multiply, overlay, saturation, screen, softLight, copy, destinationAtop, destinationIn, destinationOut, destinationOver, lighter, sourceAtop, sourceIn, sourceOut, sourceOver, xor } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/common/chart_context.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import '../../common/date_time_factory.dart'; import '../../common/rtl_spec.dart' show RTLSpec; import '../common/behavior/a11y/a11y_node.dart' show A11yNode; abstract class ChartContext { /// Flag indicating whether or not the chart's container was configured in /// right to left mode. /// /// This should be set when the chart is created (or if its container ever /// gets configured to the other direction setting). /// /// Any chart component that needs to know whether the chart axes should be /// rendered right to left should read [isRtl]. bool get chartContainerIsRtl; /// Configures the behavior of the chart when [chartContainerIsRtl] is true. RTLSpec? get rtlSpec; /// Gets whether or not the chart axes should be rendered in right to left /// mode. /// /// This will only be true if the container for the chart component was /// configured with the rtl direction setting ([chartContainerIsRtl] == true), and the chart's /// [RTLSpec] is set to reverse the axis direction in rtl mode. bool get isRtl; /// Whether or not the chart will respond to tap events. /// /// This will generally be true if there is a behavior attached to the chart /// that does something with tap events, such as "click to select data." bool get isTappable; double get pixelsPerDp; DateTimeFactory get dateTimeFactory; void requestRedraw(); void requestAnimation(Duration transition); void requestPaint(); void enableA11yExploreMode(List nodes, {String? announcement}); void disableA11yExploreMode({String? announcement}); } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/common/datum_details.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math'; import '../../common/color.dart' show Color; import '../../common/math.dart' show NullablePoint; import '../../common/symbol_renderer.dart' show SymbolRenderer; import 'chart_canvas.dart' show FillPatternType; import 'processed_series.dart' show ImmutableSeries; typedef DomainFormatter = String Function(D domain); typedef MeasureFormatter = String Function(num? measure); /// Represents processed rendering details for a data point from a series. class DatumDetails { final dynamic datum; /// The index of the datum in the series. final int? index; /// Domain value of [datum]. final D? domain; /// Domain lower bound value of [datum]. This may represent an error bound, or /// a previous domain value. final D? domainLowerBound; /// Domain upper bound value of [datum]. This may represent an error bound, or /// a target domain value. final D? domainUpperBound; /// Measure value of [datum]. final num? measure; /// Measure lower bound value of [datum]. This may represent an error bound, /// or a previous value. final num? measureLowerBound; /// Measure upper bound value of [datum]. This may represent an error bound, /// or a target measure value. final num? measureUpperBound; /// Measure offset value of [datum]. final num? measureOffset; /// Original measure value of [datum]. This may differ from [measure] if a /// behavior attached to a chart automatically adjusts measure values. final num? rawMeasure; /// Original measure lower bound value of [datum]. This may differ from /// [measureLowerBound] if a behavior attached to a chart automatically /// adjusts measure values. final num? rawMeasureLowerBound; /// Original measure upper bound value of [datum]. This may differ from /// [measureUpperBound] if a behavior attached to a chart automatically /// adjusts measure values. final num? rawMeasureUpperBound; /// The series the [datum] is from. final ImmutableSeries? series; /// The color of this [datum]. final Color? color; /// Optional fill color of this [datum]. /// /// If this is defined, then [color] will be used as a stroke color. /// Otherwise, [color] will be used for the fill color. final Color? fillColor; /// Optional fill pattern of this [datum]. final FillPatternType? fillPattern; /// Optional area color of this [datum]. /// /// This color is used for supplemental information on the series, such as /// confidence intervals or area skirts. If not provided, then some variation /// of the main [color] will be used (e.g. 10% opacity). final Color? areaColor; /// Optional dash pattern of this [datum]. final List? dashPattern; /// The chart position of the (domain, measure) for the [datum] from a /// renderer. final NullablePoint? chartPosition; /// The chart position of the (domainLowerBound, measureLowerBound) for the /// [datum] from a renderer. final NullablePoint? chartPositionLower; /// The chart position of the (domainUpperBound, measureUpperBound) for the /// [datum] from a renderer. final NullablePoint? chartPositionUpper; /// The bounding box for the chart space occupied by this datum. /// /// This is currently only populated by the bar series renderer. /// /// TODO: Fill this in for other series renderers. final Rectangle? bounds; /// Distance of [domain] from a given (x, y) coordinate. final double? domainDistance; /// Distance of [measure] from a given (x, y) coordinate. final double? measureDistance; /// Relative Cartesian distance of ([domain], [measure]) from a given (x, y) /// coordinate. final double? relativeDistance; /// The radius of this [datum]. final double? radiusPx; /// Renderer used to draw the shape of this datum. /// /// This is primarily used for point shapes on line and scatter plot charts. final SymbolRenderer? symbolRenderer; /// The stroke width of this [datum]. final double? strokeWidthPx; /// Optional formatter for [domain]. DomainFormatter? domainFormatter; /// Optional formatter for [measure]. MeasureFormatter? measureFormatter; DatumDetails( {this.datum, this.index, this.domain, this.domainFormatter, this.domainLowerBound, this.domainUpperBound, this.measure, this.measureFormatter, this.measureLowerBound, this.measureUpperBound, this.measureOffset, this.rawMeasure, this.rawMeasureLowerBound, this.rawMeasureUpperBound, this.series, this.color, this.fillColor, this.fillPattern, this.areaColor, this.dashPattern, this.chartPosition, this.chartPositionLower, this.chartPositionUpper, this.bounds, this.domainDistance, this.measureDistance, this.relativeDistance, this.radiusPx, this.symbolRenderer, this.strokeWidthPx}); factory DatumDetails.from(DatumDetails other, {D? datum, int? index, D? domain, D? domainLowerBound, D? domainUpperBound, num? measure, MeasureFormatter? measureFormatter, num? measureLowerBound, num? measureUpperBound, num? measureOffset, num? rawMeasure, num? rawMeasureLowerBound, num? rawMeasureUpperBound, ImmutableSeries? series, Color? color, Color? fillColor, FillPatternType? fillPattern, Color? areaColor, List? dashPattern, NullablePoint? chartPosition, NullablePoint? chartPositionLower, NullablePoint? chartPositionUpper, Rectangle? bounds, DomainFormatter? domainFormatter, double? domainDistance, double? measureDistance, double? radiusPx, SymbolRenderer? symbolRenderer, double? strokeWidthPx}) { return DatumDetails( datum: datum ?? other.datum, index: index ?? other.index, domain: domain ?? other.domain, domainFormatter: domainFormatter ?? other.domainFormatter, domainLowerBound: domainLowerBound ?? other.domainLowerBound, domainUpperBound: domainUpperBound ?? other.domainUpperBound, measure: measure ?? other.measure, measureFormatter: measureFormatter ?? other.measureFormatter, measureLowerBound: measureLowerBound ?? other.measureLowerBound, measureUpperBound: measureUpperBound ?? other.measureUpperBound, measureOffset: measureOffset ?? other.measureOffset, rawMeasure: rawMeasure ?? other.rawMeasure, rawMeasureLowerBound: rawMeasureLowerBound ?? other.rawMeasureLowerBound, rawMeasureUpperBound: rawMeasureUpperBound ?? other.rawMeasureUpperBound, series: series ?? other.series, color: color ?? other.color, fillColor: fillColor ?? other.fillColor, fillPattern: fillPattern ?? other.fillPattern, areaColor: areaColor ?? other.areaColor, dashPattern: dashPattern ?? other.dashPattern, chartPosition: chartPosition ?? other.chartPosition, chartPositionLower: chartPositionLower ?? other.chartPositionLower, chartPositionUpper: chartPositionUpper ?? other.chartPositionUpper, bounds: bounds ?? other.bounds, domainDistance: domainDistance ?? other.domainDistance, measureDistance: measureDistance ?? other.measureDistance, radiusPx: radiusPx ?? other.radiusPx, symbolRenderer: symbolRenderer ?? other.symbolRenderer, strokeWidthPx: radiusPx ?? other.strokeWidthPx); } String get formattedDomain => (domainFormatter != null) ? domainFormatter!(domain!) : domain.toString(); String get formattedMeasure => (measureFormatter != null) ? measureFormatter!(measure) : measure.toString(); } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/common/processed_series.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import '../../common/color.dart' show Color; import 'datum_details.dart' show DomainFormatter, MeasureFormatter; import '../../data/series.dart' show AccessorFn, Series, SeriesAttributes, AttributeKey; import '../cartesian/axis/axis.dart' show Axis; import '../cartesian/axis/spec/axis_spec.dart' show TextStyleSpec; import '../common/chart_canvas.dart' show FillPatternType; class MutableSeries extends ImmutableSeries { @override final String id; @override String? displayName; @override bool overlaySeries; @override String? seriesCategory; @override Color? seriesColor; @override late int seriesIndex; /// Sum of the measure values for the series. @override late num seriesMeasureTotal; @override List data; @override AccessorFn? keyFn; @override AccessorFn domainFn; @override AccessorFn>? domainFormatterFn; @override AccessorFn? domainLowerBoundFn; @override AccessorFn? domainUpperBoundFn; @override AccessorFn measureFn; @override AccessorFn? measureFormatterFn; @override AccessorFn? measureLowerBoundFn; @override AccessorFn? measureUpperBoundFn; @override AccessorFn? measureOffsetFn; @override AccessorFn rawMeasureFn; @override AccessorFn? rawMeasureLowerBoundFn; @override AccessorFn? rawMeasureUpperBoundFn; @override AccessorFn? areaColorFn; @override AccessorFn? colorFn; @override AccessorFn?>? dashPatternFn; @override AccessorFn? fillColorFn; @override AccessorFn? fillPatternFn; @override AccessorFn? patternColorFn; @override AccessorFn? radiusPxFn; @override AccessorFn? strokeWidthPxFn; @override AccessorFn? labelAccessorFn; @override AccessorFn? insideLabelStyleAccessorFn; @override AccessorFn? outsideLabelStyleAccessorFn; final _attrs = SeriesAttributes(); Axis? measureAxis; Axis? domainAxis; MutableSeries(Series series) : id = series.id, displayName = series.displayName ?? series.id, overlaySeries = series.overlaySeries, seriesCategory = series.seriesCategory, seriesColor = series.seriesColor, data = series.data, keyFn = series.keyFn, domainFn = series.domainFn, domainFormatterFn = series.domainFormatterFn, domainLowerBoundFn = series.domainLowerBoundFn, domainUpperBoundFn = series.domainUpperBoundFn, measureFn = series.measureFn, measureFormatterFn = series.measureFormatterFn, measureLowerBoundFn = series.measureLowerBoundFn, measureUpperBoundFn = series.measureUpperBoundFn, measureOffsetFn = series.measureOffsetFn, // Save the original measure functions in case they get replaced later. rawMeasureFn = series.measureFn, rawMeasureLowerBoundFn = series.measureLowerBoundFn, rawMeasureUpperBoundFn = series.measureUpperBoundFn, areaColorFn = series.areaColorFn, colorFn = series.colorFn, dashPatternFn = series.dashPatternFn, fillColorFn = series.fillColorFn, fillPatternFn = series.fillPatternFn, patternColorFn = series.patternColorFn, insideLabelStyleAccessorFn = series.insideLabelStyleAccessorFn, outsideLabelStyleAccessorFn = series.outsideLabelStyleAccessorFn, radiusPxFn = series.radiusPxFn, strokeWidthPxFn = series.strokeWidthPxFn { // Pre-compute the sum of the measure values to make it available on demand. seriesMeasureTotal = 0; for (var i = 0; i < data.length; i++) { final measure = measureFn(i); if (measure != null) { seriesMeasureTotal += measure; } } labelAccessorFn = series.labelAccessorFn ?? (i) => domainFn(i).toString(); _attrs.mergeFrom(series.attributes); } MutableSeries.clone(MutableSeries other) : id = other.id, displayName = other.displayName, overlaySeries = other.overlaySeries, seriesCategory = other.seriesCategory, seriesColor = other.seriesColor, seriesIndex = other.seriesIndex, data = other.data, keyFn = other.keyFn, domainFn = other.domainFn, domainFormatterFn = other.domainFormatterFn, domainLowerBoundFn = other.domainLowerBoundFn, domainUpperBoundFn = other.domainUpperBoundFn, measureFn = other.measureFn, measureFormatterFn = other.measureFormatterFn, measureLowerBoundFn = other.measureLowerBoundFn, measureUpperBoundFn = other.measureUpperBoundFn, measureOffsetFn = other.measureOffsetFn, rawMeasureFn = other.rawMeasureFn, rawMeasureLowerBoundFn = other.rawMeasureLowerBoundFn, rawMeasureUpperBoundFn = other.rawMeasureUpperBoundFn, seriesMeasureTotal = other.seriesMeasureTotal, areaColorFn = other.areaColorFn, colorFn = other.colorFn, dashPatternFn = other.dashPatternFn, fillColorFn = other.fillColorFn, fillPatternFn = other.fillPatternFn, patternColorFn = other.patternColorFn, labelAccessorFn = other.labelAccessorFn, insideLabelStyleAccessorFn = other.insideLabelStyleAccessorFn, outsideLabelStyleAccessorFn = other.outsideLabelStyleAccessorFn, radiusPxFn = other.radiusPxFn, strokeWidthPxFn = other.strokeWidthPxFn, measureAxis = other.measureAxis, domainAxis = other.domainAxis { _attrs.mergeFrom(other._attrs); } @override void setAttr(AttributeKey key, R value) => _attrs.setAttr(key, value); @override R? getAttr(AttributeKey key) => _attrs.getAttr(key); @override bool operator ==(Object other) => other is MutableSeries && data == other.data && id == other.id; @override int get hashCode => data.hashCode * 31 + id.hashCode; } abstract class ImmutableSeries { String get id; String? get displayName; /// Overlay series provided supplemental information on a chart, but are not /// considered to be primary data. They should not be selectable by user /// interaction. bool get overlaySeries; String? get seriesCategory; /// Color which represents the entire series in legends. /// /// If this is not provided in the original series object, it will be inferred /// from the color of the first datum in the series. /// /// If this is provided, but no [colorFn] is provided, then it will be treated /// as the color for each datum in the series. /// /// If neither are provided, then the chart will insert colors for each series /// on the chart using a mapping function. Color? get seriesColor; int get seriesIndex; /// Sum of the measure values for the series. num get seriesMeasureTotal; // Uses `dynamic` for convenience to callers. List get data; /// [keyFn] defines a globally unique identifier for each datum. /// /// The key for each datum is used during chart animation to smoothly /// transition data still in the series to its new state. /// /// Note: This is currently an optional function that is not fully used by all /// series renderers yet. AccessorFn? keyFn; AccessorFn get domainFn; AccessorFn>? get domainFormatterFn; AccessorFn? get domainLowerBoundFn; AccessorFn? get domainUpperBoundFn; AccessorFn get measureFn; AccessorFn? get measureFormatterFn; AccessorFn? get measureLowerBoundFn; AccessorFn? get measureUpperBoundFn; AccessorFn? get measureOffsetFn; AccessorFn get rawMeasureFn; AccessorFn? get rawMeasureLowerBoundFn; AccessorFn? get rawMeasureUpperBoundFn; AccessorFn? get areaColorFn; AccessorFn? get colorFn; AccessorFn?>? get dashPatternFn; AccessorFn? get fillColorFn; AccessorFn? get patternColorFn; AccessorFn? get fillPatternFn; AccessorFn? get labelAccessorFn; AccessorFn? insideLabelStyleAccessorFn; AccessorFn? outsideLabelStyleAccessorFn; AccessorFn? get radiusPxFn; AccessorFn? get strokeWidthPxFn; void setAttr(AttributeKey key, R value); R? getAttr(AttributeKey key); } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/common/selection_model/selection_model.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'package:collection/collection.dart' show ListEquality; import '../processed_series.dart' show ImmutableSeries; import '../series_datum.dart' show SeriesDatum, SeriesDatumConfig; /// Holds the state of interaction or selection for the chart to coordinate /// between various event sources and things that wish to act upon the selection /// state (highlight, drill, etc). /// /// There is one instance per interaction type (ex: info, action) with each /// maintaining their own state. Info is typically used to update a hover/touch /// card while action is used in case of a secondary selection/action. /// /// The series selection state is kept separate from datum selection state to /// allow more complex highlighting. For example: a Hovercard that shows entries /// for each datum for a given domain/time, but highlights the closest entry to /// match up with highlighting/bolding of the line and legend. class SelectionModel { var _selectedDatum = >[]; var _selectedSeries = >[]; /// Create selection model with the desired selection. SelectionModel( {List>? selectedData, List>? selectedSeries}) { if (selectedData != null) { _selectedDatum = selectedData; } if (selectedSeries != null) { _selectedSeries = selectedSeries; } } /// Create a deep copy of the selection model. SelectionModel.fromOther(SelectionModel other) { _selectedDatum = List.of(other._selectedDatum); _selectedSeries = List.of(other._selectedSeries); } /// Create selection model from configuration. SelectionModel.fromConfig(List>? selectedDataConfig, List? selectedSeriesConfig, List> seriesList) { final selectedDataMap = >{}; if (selectedDataConfig != null) { for (final config in selectedDataConfig) { selectedDataMap[config.seriesId] ??= []; selectedDataMap[config.seriesId]!.add(config.domainValue as D); } // Add to list of selected series. _selectedSeries.addAll(seriesList.where((ImmutableSeries series) => selectedDataMap.keys.contains(series.id))); // Add to list of selected data. for (final series in seriesList) { if (selectedDataMap.containsKey(series.id)) { final domainFn = series.domainFn; for (var i = 0; i < series.data.length; i++) { final Object? datum = series.data[i]; if (selectedDataMap[series.id]!.contains(domainFn(i))) { _selectedDatum.add(SeriesDatum(series, datum)); } } } } } // Add to list of selected series, if it does not already exist. if (selectedSeriesConfig != null) { final existingSeriesIds = { for (final series in _selectedSeries) series.id, }; final remainingSeriesToAdd = selectedSeriesConfig .where((String seriesId) => !existingSeriesIds.contains(seriesId)) .toSet(); _selectedSeries.addAll(seriesList.where((ImmutableSeries series) => remainingSeriesToAdd.contains(series.id))); } } /// Returns true if this [SelectionModel] has a selected datum. bool get hasDatumSelection => _selectedDatum.isNotEmpty; bool isDatumSelected(ImmutableSeries series, int? index) { final Object? datum = index == null ? null : series.data[index]; return _selectedDatum.contains(SeriesDatum(series, datum)); } /// Returns the selected [SeriesDatum] for this [SelectionModel]. /// /// This is empty by default. List> get selectedDatum => List.unmodifiable(_selectedDatum); /// Returns true if this [SelectionModel] has a selected series. bool get hasSeriesSelection => _selectedSeries.isNotEmpty; /// Returns the selected [ImmutableSeries] for this [SelectionModel]. /// /// This is empty by default. List> get selectedSeries => List.unmodifiable(_selectedSeries); /// Returns true if this [SelectionModel] has a selected datum or series. bool get hasAnySelection => _selectedDatum.isNotEmpty || selectedSeries.isNotEmpty; @override bool operator ==(Object other) { return other is SelectionModel && ListEquality>() .equals(_selectedDatum, other.selectedDatum) && ListEquality>() .equals(_selectedSeries, other.selectedSeries); } @override int get hashCode { var hashcode = ListEquality>().hash(_selectedDatum); hashcode = hashcode * 37 + ListEquality>().hash(_selectedSeries); return hashcode; } } /// A [SelectionModel] that can be updated. /// /// This model will notify listeners subscribed to this model when the selection /// is modified. class MutableSelectionModel extends SelectionModel { final _changedListeners = >[]; final _updatedListeners = >[]; final _lockChangedListeners = >[]; bool _locked = false; /// When set to true, prevents the model from being updated. set locked(bool locked) { _locked = locked; _lockChangedListeners .forEach((listener) => listener(SelectionModel.fromOther(this))); } bool get locked => _locked; /// Clears the selection state. bool clearSelection({bool notifyListeners = true}) { return updateSelection([], [], notifyListeners: notifyListeners); } /// Updates the selection state. If mouse driven, [datumSelection] should be /// ordered by distance from mouse, closest first. bool updateSelection( List> datumSelection, List> seriesList, {bool notifyListeners = true}) { if (_locked) return false; final origSelectedDatum = _selectedDatum; final origSelectedSeries = _selectedSeries; _selectedDatum = datumSelection; _selectedSeries = seriesList; // Provide a copy, so listeners get an immutable model. final copyOfSelectionModel = SelectionModel.fromOther(this); _updatedListeners.forEach((listener) => listener(copyOfSelectionModel)); final changed = !ListEquality>() .equals(origSelectedDatum, _selectedDatum) || !ListEquality>() .equals(origSelectedSeries, _selectedSeries); if (notifyListeners && changed) { _changedListeners.forEach((listener) => listener(copyOfSelectionModel)); } return changed; } /// Add a listener to be notified when this [SelectionModel] changes. /// /// Note: the listener will not be triggered if [updateSelection] is called /// resulting in the same selection state. void addSelectionChangedListener(SelectionModelListener listener) { _changedListeners.add(listener); } /// Remove listener from being notified when this [SelectionModel] changes. void removeSelectionChangedListener(SelectionModelListener listener) { _changedListeners.remove(listener); } /// Add a listener to be notified when [updateSelection] is called, even if /// the selection state is the same. /// /// This is necessary in order to support programmatic selections in Flutter. /// Due to the way widgets are constructed in Flutter, there currently isn't /// a way for users to programmatically specify the selection. In order to /// provide this support, the users who subscribe to the selection updated /// event can keep a copy of the selection model and also decide if it should /// be overwritten. void addSelectionUpdatedListener(SelectionModelListener listener) { _updatedListeners.add(listener); } /// Remove listener from being notified when [updateSelection] is called. void removeSelectionUpdatedListener(SelectionModelListener listener) { _updatedListeners.remove(listener); } /// Add a listener to be notified when this [SelectionModel] is locked. void addSelectionLockChangedListener(SelectionModelListener listener) { _lockChangedListeners.add(listener); } /// Remove listener from being notified when this [SelectionModel] is locked. void removeSelectionLockChangedListener(SelectionModelListener listener) { _lockChangedListeners.remove(listener); } /// Remove all listeners. void clearAllListeners() { _changedListeners.clear(); _updatedListeners.clear(); _lockChangedListeners.clear(); } } /// Callback for SelectionModel. It is triggered when the selection state /// changes. typedef SelectionModelListener = void Function(SelectionModel model); enum SelectionModelType { /// Typical Hover or Details event for viewing the details of the selected /// items. info, /// Typical Selection, Drill or Input event likely updating some external /// content. action, } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/common/series_datum.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'processed_series.dart' show ImmutableSeries; /// Stores datum and the series the datum originated. class SeriesDatum { final ImmutableSeries series; final dynamic datum; /// This is set after [index] getter is called. So accessing this directly is /// considered unsafe. Always uses [index] getter instead. int? _index; SeriesDatum(this.series, this.datum); /// Returns null if-and-only if [datum] is null. int? get index { if (datum == null) return null; _index ??= series.data.indexOf(datum); return _index; } @override bool operator ==(Object other) => other is SeriesDatum && other.series == series && other.datum == datum; @override int get hashCode => series.hashCode * 31 + datum.hashCode; } /// Represents a series datum based on series id and datum index. class SeriesDatumConfig { final String seriesId; final D domainValue; SeriesDatumConfig(this.seriesId, this.domainValue); @override bool operator ==(Object other) { return other is SeriesDatumConfig && seriesId == other.seriesId && domainValue == other.domainValue; } @override int get hashCode { var hashcode = seriesId.hashCode; hashcode = hashcode * 37 + domainValue.hashCode; return hashcode; } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/common/series_renderer.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math' show Point, Rectangle, max; import 'package:meta/meta.dart'; import '../../common/color.dart' show Color; import '../../common/graphics_factory.dart' show GraphicsFactory; import '../../common/style/style_factory.dart' show StyleFactory; import '../../common/symbol_renderer.dart' show SymbolRenderer; import '../../data/series.dart' show AttributeKey; import '../layout/layout_view.dart' show LayoutPosition, LayoutView, LayoutViewConfig, LayoutViewPositionOrder, ViewMeasuredSizes; import 'base_chart.dart' show BaseChart; import 'chart_canvas.dart' show ChartCanvas; import 'datum_details.dart' show DatumDetails; import 'processed_series.dart' show ImmutableSeries, MutableSeries; import 'series_datum.dart' show SeriesDatum; /// Unique identifier used to associate custom series renderers on a chart with /// one or more series of data. /// /// [rendererIdKey] can be added as an attribute to user-defined [Series] /// objects. const rendererIdKey = AttributeKey('SeriesRenderer.rendererId'); const rendererKey = AttributeKey>('SeriesRenderer.renderer'); /// A series renderer draws one or more series of data onto a chart canvas. abstract class SeriesRenderer extends LayoutView { static const defaultRendererId = 'default'; /// Symbol renderer for this renderer. /// /// The default is set natively by the platform. This is because in Flutter, /// the [SymbolRenderer] has to be a Flutter wrapped version to support /// building widget based symbols. SymbolRenderer? get symbolRenderer; set symbolRenderer(SymbolRenderer? symbolRenderer); /// Unique identifier for this renderer. Any [Series] on a chart with a /// matching [rendererIdKey] will be drawn by this renderer. String get rendererId; set rendererId(String rendererId); /// Handles any setup of the renderer that needs to be deferred until it is /// attached to a chart. void onAttach(BaseChart chart); /// Handles any clean-up of the renderer that needs to be performed when it is /// detached from a chart. void onDetach(BaseChart chart); /// Performs basic configuration for the series, before it is pre-processed. /// /// Typically, a series renderer should assign color mapping functions to /// series that do not have them. void configureSeries(List> seriesList); /// Pre-calculates some details for the series that will be needed later /// during the drawing phase. void preprocessSeries(List> seriesList); /// Adds the domain values for the given series to the chart's domain axis. void configureDomainAxes(List> seriesList); /// Adds the measure values for the given series to the chart's measure axes. void configureMeasureAxes(List> seriesList); /// Generates rendering data needed to paint the data on the chart. /// /// This is called during the post layout phase of the chart draw cycle. void update(List> seriesList, bool isAnimating); /// Renders the series data on the canvas, using the data generated during the /// [update] call. @override void paint(ChartCanvas canvas, double animationPercent); /// Gets a list of the data from each series that is closest to a given point. /// /// [chartPoint] represents a point in the chart, such as a point that was /// clicked/tapped on by a user. /// /// [selectOverlappingPoints] specifies whether to include all points that /// overlap the tapped position in the result. If specified, the method will /// return either the closest point or all the overlapping points with the /// tapped position. /// /// [byDomain] specifies whether the nearest data should be defined by domain /// distance, or relative Cartesian distance. /// /// [boundsOverride] optionally specifies a bounding box for the selection /// event. If specified, then no data should be returned if [chartPoint] lies /// outside the box. If not specified, then each series renderer on the chart /// will use its own component bounds for filtering out selection events /// (usually the chart draw area). List> getNearestDatumDetailPerSeries( Point chartPoint, bool byDomain, Rectangle? boundsOverride, { bool selectOverlappingPoints = false, bool selectExactEventLocation = false, }); /// Get an expanded set of processed [DatumDetails] for a given [SeriesDatum]. /// /// This is typically called by chart behaviors that need to get full details /// on selected data. DatumDetails getDetailsForSeriesDatum(SeriesDatum seriesDatum); /// Adds chart position data to [details]. /// /// This is a helper function intended to be called from /// [getDetailsForSeriesDatum]. Every concrete [SeriesRenderer] needs to /// implement custom logic for setting location data. DatumDetails addPositionToDetailsForSeriesDatum( DatumDetails details, SeriesDatum seriesDatum); } /// Concrete base class for [SeriesRenderer]s that implements common /// functionality. abstract class BaseSeriesRenderer implements SeriesRenderer { @override final LayoutViewConfig layoutConfig; @override String rendererId; @override SymbolRenderer? symbolRenderer; Rectangle? _drawAreaBounds; Rectangle? get drawBounds => _drawAreaBounds; @override GraphicsFactory? graphicsFactory; BaseSeriesRenderer({ required this.rendererId, required int layoutPaintOrder, this.symbolRenderer, }) : layoutConfig = LayoutViewConfig( paintOrder: layoutPaintOrder, position: LayoutPosition.DrawArea, positionOrder: LayoutViewPositionOrder.drawArea); @override void onAttach(BaseChart chart) {} @override void onDetach(BaseChart chart) {} /// Assigns colors to series that are missing their colorFn. /// /// [emptyCategoryUsesSinglePalette] Flag indicating whether having all /// series with no categories will use the same or separate palettes. /// Setting it to true uses various Blues for each series. /// Setting it to false used different palettes (ie: s1 uses Blue500, /// s2 uses Red500), @protected void assignMissingColors(Iterable> seriesList, {required bool emptyCategoryUsesSinglePalette}) { const defaultCategory = '__default__'; // Count up the number of missing series per category, keeping a max across // categories. final missingColorCountPerCategory = {}; var maxMissing = 0; var hasSpecifiedCategory = false; seriesList.forEach((MutableSeries series) { // Assign the seriesColor as the color of every datum if no colorFn was // provided. if (series.colorFn == null && series.seriesColor != null) { series.colorFn = (_) => series.seriesColor!; } // This series was missing both seriesColor and a colorFn. Add it to the // "missing" set. if (series.colorFn == null) { // If there is no category, give it a default category to match logic. var category = series.seriesCategory; if (category == null) { category = defaultCategory; } else { hasSpecifiedCategory = true; } // Increment the missing counts for the category. final missingCnt = (missingColorCountPerCategory[category] ?? 0) + 1; missingColorCountPerCategory[category] = missingCnt; maxMissing = max(maxMissing, missingCnt); } }); if (maxMissing > 0) { // Special handling of only series with empty categories when we want // to use different palettes. if (!emptyCategoryUsesSinglePalette && !hasSpecifiedCategory) { final palettes = StyleFactory.style.getOrderedPalettes(maxMissing); var index = 0; seriesList.forEach((series) { if (series.colorFn == null) { final color = palettes[index % palettes.length].shadeDefault; index++; series.colorFn = (_) => color; series.seriesColor ??= color; } else { // Fill in missing seriesColor values with the color of the first // datum in the series. Note that [Series.colorFn] should always // return a color. if (series.seriesColor == null) { try { series.seriesColor = series.colorFn!(0); } catch (exception) { series.seriesColor = StyleFactory.style.defaultSeriesColor; } } } }); return; } // Get a list of palettes to use given the number of categories we've // seen. One palette per category (but might need to repeat). final colorPalettes = StyleFactory.style .getOrderedPalettes(missingColorCountPerCategory.length); // Create a map of Color palettes for each category. Each Palette uses // the max for any category to ensure that the gradients look appropriate. final colorsByCategory = >{}; var index = 0; missingColorCountPerCategory.keys.forEach((String category) { colorsByCategory[category] = colorPalettes[index % colorPalettes.length].makeShades(maxMissing); index++; // Reset the count so we can use it to count as we set the colorFn. missingColorCountPerCategory[category] = 0; }); seriesList.forEach((series) { if (series.colorFn == null) { final category = series.seriesCategory ?? defaultCategory; // Get the current index into the color list. final colorIndex = missingColorCountPerCategory[category]!; missingColorCountPerCategory[category] = colorIndex + 1; final color = colorsByCategory[category]![colorIndex]; series.colorFn = (_) => color; } // Fill color defaults to the series color if no accessor is provided. series.fillColorFn ??= (int? index) => series.colorFn!(index); }); } else { seriesList.forEach((series) { // Fill color defaults to the series color if no accessor is provided. series.fillColorFn ??= (int? index) => series.colorFn!(index); }); } // Fill in any missing seriesColor values with the color of the first datum // in the series. Note that [Series.colorFn] should always return a color. seriesList.forEach((series) { if (series.seriesColor == null) { try { series.seriesColor = series.colorFn!(0); } catch (exception) { series.seriesColor = StyleFactory.style.defaultSeriesColor; } } }); } @override ViewMeasuredSizes? measure(int maxWidth, int maxHeight) { return null; } @override void layout(Rectangle componentBounds, Rectangle drawAreaBounds) { _drawAreaBounds = drawAreaBounds; } @override Rectangle? get componentBounds => _drawAreaBounds; @override bool get isSeriesRenderer => true; @override void configureSeries(List> seriesList) {} @override void preprocessSeries(List> seriesList) {} @override void configureDomainAxes(List> seriesList) {} @override void configureMeasureAxes(List> seriesList) {} @override DatumDetails getDetailsForSeriesDatum(SeriesDatum seriesDatum) { // Generate details relevant to every type of series renderer. Position // details are left as an exercise for every renderer that extends this // class. final series = seriesDatum.series; final index = seriesDatum.index; final domainFn = series.domainFn; final domainLowerBoundFn = series.domainLowerBoundFn; final domainUpperBoundFn = series.domainUpperBoundFn; final measureFn = series.measureFn; final measureLowerBoundFn = series.measureLowerBoundFn; final measureUpperBoundFn = series.measureUpperBoundFn; final measureOffsetFn = series.measureOffsetFn; final rawMeasureFn = series.rawMeasureFn; final rawMeasureLowerBoundFn = series.rawMeasureLowerBoundFn; final rawMeasureUpperBoundFn = series.rawMeasureUpperBoundFn; final colorFn = series.colorFn; final areaColorFn = series.areaColorFn ?? colorFn; final fillColorFn = series.fillColorFn ?? colorFn; final radiusPxFn = series.radiusPxFn; final strokeWidthPxFn = series.strokeWidthPxFn; final domainValue = domainFn(index); final domainLowerBoundValue = domainLowerBoundFn?.call(index); final domainUpperBoundValue = domainUpperBoundFn?.call(index); final measureValue = measureFn(index); final measureLowerBoundValue = measureLowerBoundFn?.call(index); final measureUpperBoundValue = measureUpperBoundFn?.call(index); final measureOffsetValue = measureOffsetFn?.call(index); final rawMeasureValue = rawMeasureFn(index); final rawMeasureLowerBoundValue = rawMeasureLowerBoundFn?.call(index); final rawMeasureUpperBoundValue = rawMeasureUpperBoundFn?.call(index); final color = colorFn!(index); // Fill color is an optional override for color. Make sure we get a value if // the series doesn't define anything specific. var fillColor = fillColorFn!(index); fillColor ??= color; // Area color is entirely optional. final areaColor = areaColorFn!(index); var radiusPx = radiusPxFn?.call(index)?.toDouble(); radiusPx = radiusPx?.toDouble(); var strokeWidthPx = strokeWidthPxFn?.call(index)?.toDouble(); strokeWidthPx = strokeWidthPx?.toDouble(); final details = DatumDetails( datum: seriesDatum.datum, index: seriesDatum.index, domain: domainValue, domainLowerBound: domainLowerBoundValue, domainUpperBound: domainUpperBoundValue, measure: measureValue, measureLowerBound: measureLowerBoundValue, measureUpperBound: measureUpperBoundValue, measureOffset: measureOffsetValue, rawMeasure: rawMeasureValue, rawMeasureLowerBound: rawMeasureLowerBoundValue, rawMeasureUpperBound: rawMeasureUpperBoundValue, series: series, color: color, fillColor: fillColor, areaColor: areaColor, radiusPx: radiusPx, strokeWidthPx: strokeWidthPx); // chartPosition depends on the shape of the rendered elements, and must be // added by concrete [SeriesRenderer] classes. return addPositionToDetailsForSeriesDatum(details, seriesDatum); } /// Returns true of [chartPoint] is within the component bounds for this /// renderer. /// /// [chartPoint] a point to test. /// /// [bounds] optional override for component bounds. If this is passed, then /// we will check whether the point is within these bounds instead of the /// component bounds. bool isPointWithinBounds(Point chartPoint, Rectangle? bounds) { // Was it even in the drawArea? if (bounds != null) { if (!bounds.containsPoint(chartPoint)) { return false; } } else if (componentBounds == null || !componentBounds!.containsPoint(chartPoint)) { return false; } return true; } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/common/series_renderer_config.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import '../../common/symbol_renderer.dart'; import '../../common/typed_registry.dart'; import 'series_renderer.dart' show SeriesRenderer; /// Interface for series renderer configuration. abstract class SeriesRendererConfig { /// Stores typed renderer attributes /// /// This is useful for storing attributes that is used on the native platform. /// Such as the SymbolRenderer that is associated with each renderer but is /// a native builder since legend is built natively. RendererAttributes get rendererAttributes; String? get customRendererId; SymbolRenderer? get symbolRenderer; SeriesRenderer build(); } class RendererAttributeKey extends TypedKey { const RendererAttributeKey(String uniqueKey) : super(uniqueKey); } class RendererAttributes extends TypedRegistry {} ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/common/unitconverter/identity_converter.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'unit_converter.dart' show UnitConverter; /// A No op unit converter. class IdentityConverter implements UnitConverter { const IdentityConverter(); @override U convert(U value) => value; @override U invert(U value) => value; } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/common/unitconverter/unit_converter.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /// Converts a num value in the 'from' unit to a num value in the 'to' unit. /// /// [F] Type of the value in the 'from' units. /// [T] Type of the value in 'to' units. abstract class UnitConverter { /// Converts 'from' unit value to the 'to' unit value. T convert(F value); /// Converts 'to' unit value back to the 'from' unit value. F invert(T value); } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/layout/layout_config.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /// Collection of configurations that apply to the [LayoutManager]. class LayoutConfig { final MarginSpec leftSpec; final MarginSpec rightSpec; final MarginSpec topSpec; final MarginSpec bottomSpec; /// Create a new [LayoutConfig] used by [DynamicLayoutManager]. LayoutConfig({ MarginSpec? leftSpec, MarginSpec? rightSpec, MarginSpec? topSpec, MarginSpec? bottomSpec, }) : leftSpec = leftSpec ?? MarginSpec.defaultSpec, rightSpec = rightSpec ?? MarginSpec.defaultSpec, topSpec = topSpec ?? MarginSpec.defaultSpec, bottomSpec = bottomSpec ?? MarginSpec.defaultSpec; } /// Specs that applies to one margin. class MarginSpec { /// [MarginSpec] that has max of 50 percent. static const defaultSpec = MarginSpec._internal(null, null, null, 50); final int? _minPixel; final int? _maxPixel; final int? _minPercent; final int? _maxPercent; const MarginSpec._internal( int? minPixel, int? maxPixel, int? minPercent, int? maxPercent) : _minPixel = minPixel, _maxPixel = maxPixel, _minPercent = minPercent, _maxPercent = maxPercent; /// Create [MarginSpec] that specifies min/max pixels. /// /// [minPixel] if set must be greater than or equal to 0 and less than max if /// it is also set. /// [maxPixel] if set must be greater than or equal to 0. factory MarginSpec.fromPixel({int? minPixel, int? maxPixel}) { // Require zero or higher settings if set assert(minPixel == null || minPixel >= 0); assert(maxPixel == null || maxPixel >= 0); // Min must be less than or equal to max. // Can be equal to enforce strict pixel size. if (minPixel != null && maxPixel != null) { assert(minPixel <= maxPixel); } return MarginSpec._internal(minPixel, maxPixel, null, null); } /// Create [MarginSpec] with a fixed pixel size [pixels]. /// /// [pixels] if set must be greater than or equal to 0. factory MarginSpec.fixedPixel(int? pixels) { // Require require or higher setting if set assert(pixels == null || pixels >= 0); return MarginSpec._internal(pixels, pixels, null, null); } /// Create [MarginSpec] that specifies min/max percentage. /// /// [minPercent] if set must be between 0 and 100 inclusive. If [maxPercent] /// is also set, then must be less than [maxPercent]. /// [maxPercent] if set must be between 0 and 100 inclusive. factory MarginSpec.fromPercent({int? minPercent, int? maxPercent}) { // Percent must be within 0 to 100 assert(minPercent == null || (minPercent >= 0 && minPercent <= 100)); assert(maxPercent == null || (maxPercent >= 0 && maxPercent <= 100)); // Min must be less than or equal to max. // Can be equal to enforce strict percentage. if (minPercent != null && maxPercent != null) { assert(minPercent <= maxPercent); } return MarginSpec._internal(null, null, minPercent, maxPercent); } /// Get the min pixels, given the [totalPixels]. int getMinPixels(int totalPixels) { final _minPixel = this._minPixel; final _minPercent = this._minPercent; if (_minPixel != null) { assert(_minPixel < totalPixels); return _minPixel; } else if (_minPercent != null) { return (totalPixels * (_minPercent / 100)).round(); } else { return 0; } } /// Get the max pixels, given the [totalPixels]. int getMaxPixels(int totalPixels) { final _maxPixel = this._maxPixel; final _maxPercent = this._maxPercent; if (_maxPixel != null) { assert(_maxPixel < totalPixels); return _maxPixel; } else if (_maxPercent != null) { return (totalPixels * (_maxPercent / 100)).round(); } else { return totalPixels; } } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/layout/layout_manager.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math' show Point, Rectangle; import '../layout/layout_config.dart' show LayoutConfig; import 'layout_view.dart' show LayoutView; abstract class LayoutManager { /// Adds a view to be managed by the LayoutManager. void addView(LayoutView view); /// Removes a view previously added to the LayoutManager. /// No-op if it wasn't there to begin with. void removeView(LayoutView view); /// Returns true if view is already attached. bool isAttached(LayoutView view); /// Walk through the child views and determine their desired sizes storing /// off the information for layout. void measure(int width, int height); /// Walk through the child views and set their bounds from the perspective /// of the canvas origin. void layout(int width, int height); /// Updates the layout configuration. void updateConfig(LayoutConfig layoutConfig); /// Returns the bounds of the drawArea. Must be called after layout(). Rectangle get drawAreaBounds; /// Returns the combined bounds of the drawArea, and all components that /// function as series draw areas. Must be called after layout(). Rectangle get drawableLayoutAreaBounds; /// Gets the measured size of the bottom margin, available after layout. int get marginBottom; /// Gets the measured size of the left margin, available after layout. int get marginLeft; /// Gets the measured size of the right margin, available after layout. int get marginRight; /// Gets the measured size of the top margin, available after layout. int get marginTop; /// Returns whether or not [point] is within the draw area bounds. bool withinDrawArea(Point point); /// Walk through the child views and apply the function passed in. void applyToViews(void Function(LayoutView view) apply); /// Return the child views in the order that they should be drawn. List get paintOrderedViews; /// Return the child views in the order that they should be positioned within /// chart margins. List get positionOrderedViews; } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/layout/layout_manager_impl.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math' show Point, Rectangle, max; import 'layout_config.dart' show LayoutConfig; import 'layout_manager.dart'; import 'layout_margin_strategy.dart'; import 'layout_view.dart' show LayoutView, LayoutPosition; /// Default Layout manager for [LayoutView]s. class LayoutManagerImpl implements LayoutManager { static const _minDrawWidth = 20; static const _minDrawHeight = 20; // Allow [Layoutconfig] to be mutable so it can be modified without requiring // a new copy of [DefaultLayoutManager] to be created. LayoutConfig config; /// Unordered list of views in the layout. final _views = []; /// List of views in the order they should be drawn on the canvas. /// /// First element is painted first. late List _paintOrderedViews; /// List of vies in the order they should be positioned in a chart margin. /// /// First element is closest to the draw area. late List _positionOrderedViews; late _MeasuredSizes _measurements; late Rectangle _drawAreaBounds; bool _drawAreaBoundsOutdated = true; bool _viewsNeedPaintSort = true; bool _viewsNeedPositionSort = true; /// Create a new [LayoutManager]. LayoutManagerImpl({LayoutConfig? config}) : config = config ?? LayoutConfig(); /// Add one [LayoutView]. @override void addView(LayoutView view) { _views.add(view); _drawAreaBoundsOutdated = true; _viewsNeedPositionSort = true; _viewsNeedPaintSort = true; } /// Remove one [LayoutView]. @override void removeView(LayoutView view) { if (_views.remove(view)) { _drawAreaBoundsOutdated = true; _viewsNeedPositionSort = true; _viewsNeedPaintSort = true; } } /// Returns true if [view] is already attached. @override bool isAttached(LayoutView view) => _views.contains(view); @override void updateConfig(LayoutConfig layoutConfig) { config = layoutConfig; } /// Get all layout components in the order to be drawn. @override List get paintOrderedViews { if (_viewsNeedPaintSort) { _paintOrderedViews = List.of(_views); _paintOrderedViews.sort((LayoutView v1, LayoutView v2) => v1.layoutConfig.paintOrder!.compareTo(v2.layoutConfig.paintOrder!)); _viewsNeedPaintSort = false; } return _paintOrderedViews; } /// Get all layout components in the order to be visited. @override List get positionOrderedViews { if (_viewsNeedPositionSort) { _positionOrderedViews = List.of(_views); _positionOrderedViews.sort((LayoutView v1, LayoutView v2) => v1 .layoutConfig.positionOrder! .compareTo(v2.layoutConfig.positionOrder!)); _viewsNeedPositionSort = false; } return _positionOrderedViews; } @override Rectangle get drawAreaBounds { assert(_drawAreaBoundsOutdated == false); return _drawAreaBounds; } @override Rectangle get drawableLayoutAreaBounds { assert(_drawAreaBoundsOutdated == false); final drawableViews = _views.where((LayoutView view) => view.isSeriesRenderer); var componentBounds = drawableViews.first.componentBounds; if (componentBounds != null) { for (final view in drawableViews.skip(1)) { if (view.componentBounds != null) { // See https://github.com/dart-lang/language/issues/1308 for why // `componentBounds` isn't promoted to be non-nullable. componentBounds = componentBounds!.boundingBox(view.componentBounds!); } } } else { componentBounds = Rectangle(0, 0, 0, 0); } return componentBounds!; } @override int get marginBottom { assert(_drawAreaBoundsOutdated == false); return _measurements.bottomHeight; } @override int get marginLeft { assert(_drawAreaBoundsOutdated == false); return _measurements.leftWidth; } @override int get marginRight { assert(_drawAreaBoundsOutdated == false); return _measurements.rightWidth; } @override int get marginTop { assert(_drawAreaBoundsOutdated == false); return _measurements.topHeight; } @override bool withinDrawArea(Point point) { return _drawAreaBounds.containsPoint(point); } /// Measure and layout with given [width] and [height]. @override void measure(int width, int height) { var topViews = _viewsForPositions(LayoutPosition.Top, LayoutPosition.FullTop); var rightViews = _viewsForPositions(LayoutPosition.Right, LayoutPosition.FullRight); var bottomViews = _viewsForPositions(LayoutPosition.Bottom, LayoutPosition.FullBottom); var leftViews = _viewsForPositions(LayoutPosition.Left, LayoutPosition.FullLeft); // Assume the full width and height of the chart is available when measuring // for the first time but adjust the maximum if margin spec is set. var measurements = _measure(width, height, topViews: topViews, rightViews: rightViews, bottomViews: bottomViews, leftViews: leftViews, useMax: true); // Measure a second time but pass in the preferred width and height from // the first measure cycle. // Allow views to report a different size than the previously measured max. final secondMeasurements = _measure(width, height, topViews: topViews, rightViews: rightViews, bottomViews: bottomViews, leftViews: leftViews, previousMeasurements: measurements, useMax: true); // If views need more space with the 2nd pass, perform a third pass. if (measurements.leftWidth != secondMeasurements.leftWidth || measurements.rightWidth != secondMeasurements.rightWidth || measurements.topHeight != secondMeasurements.topHeight || measurements.bottomHeight != secondMeasurements.bottomHeight) { final thirdMeasurements = _measure(width, height, topViews: topViews, rightViews: rightViews, bottomViews: bottomViews, leftViews: leftViews, previousMeasurements: secondMeasurements, useMax: false); measurements = thirdMeasurements; } else { measurements = secondMeasurements; } _measurements = measurements; // Draw area size. // Set to a minimum size if there is not enough space for the draw area. // Prevents the app from crashing by rendering overlapping content instead. final drawAreaWidth = max( _minDrawWidth, width - measurements.leftWidth - measurements.rightWidth, ); final drawAreaHeight = max( _minDrawHeight, height - measurements.bottomHeight - measurements.topHeight, ); // Bounds for the draw area. _drawAreaBounds = Rectangle(measurements.leftWidth, measurements.topHeight, drawAreaWidth, drawAreaHeight); _drawAreaBoundsOutdated = false; } @override void layout(int width, int height) { var topViews = _viewsForPositions(LayoutPosition.Top, LayoutPosition.FullTop); var rightViews = _viewsForPositions(LayoutPosition.Right, LayoutPosition.FullRight); var bottomViews = _viewsForPositions(LayoutPosition.Bottom, LayoutPosition.FullBottom); var leftViews = _viewsForPositions(LayoutPosition.Left, LayoutPosition.FullLeft); var drawAreaViews = _viewsForPositions(LayoutPosition.DrawArea); final fullBounds = Rectangle(0, 0, width, height); // Layout the margins. LeftMarginLayoutStrategy() .layout(leftViews, _measurements.leftSizes, fullBounds, drawAreaBounds); RightMarginLayoutStrategy().layout( rightViews, _measurements.rightSizes, fullBounds, drawAreaBounds); BottomMarginLayoutStrategy().layout( bottomViews, _measurements.bottomSizes, fullBounds, drawAreaBounds); TopMarginLayoutStrategy() .layout(topViews, _measurements.topSizes, fullBounds, drawAreaBounds); // Layout the drawArea. drawAreaViews.forEach( (LayoutView view) => view.layout(_drawAreaBounds, _drawAreaBounds)); } Iterable _viewsForPositions(LayoutPosition p1, [LayoutPosition? p2]) { return positionOrderedViews.where((LayoutView view) => view.layoutConfig.position == p1 || (p2 != null && view.layoutConfig.position == p2)); } /// Measure and return size measurements. /// [width] full width of chart /// [height] full height of chart _MeasuredSizes _measure( int width, int height, { required Iterable topViews, required Iterable rightViews, required Iterable bottomViews, required Iterable leftViews, _MeasuredSizes? previousMeasurements, required bool useMax, }) { final maxLeftWidth = config.leftSpec.getMaxPixels(width); final maxRightWidth = config.rightSpec.getMaxPixels(width); final maxBottomHeight = config.bottomSpec.getMaxPixels(height); final maxTopHeight = config.topSpec.getMaxPixels(height); // Assume the full width and height of the chart is available when measuring // for the first time but adjust the maximum if margin spec is set. var leftWidth = previousMeasurements?.leftWidth ?? maxLeftWidth; var rightWidth = previousMeasurements?.rightWidth ?? maxRightWidth; var bottomHeight = previousMeasurements?.bottomHeight ?? maxBottomHeight; var topHeight = previousMeasurements?.topHeight ?? maxTopHeight; // Only adjust the height if we have previous measurements. final adjustedHeight = (previousMeasurements != null) ? height - bottomHeight - topHeight : height; var leftSizes = LeftMarginLayoutStrategy().measure(leftViews, maxWidth: useMax ? maxLeftWidth : leftWidth, height: adjustedHeight, fullHeight: height); leftWidth = max(leftSizes.total, config.leftSpec.getMinPixels(width)); var rightSizes = RightMarginLayoutStrategy().measure(rightViews, maxWidth: useMax ? maxRightWidth : rightWidth, height: adjustedHeight, fullHeight: height); rightWidth = max(rightSizes.total, config.rightSpec.getMinPixels(width)); final adjustedWidth = width - leftWidth - rightWidth; var bottomSizes = BottomMarginLayoutStrategy().measure(bottomViews, maxHeight: useMax ? maxBottomHeight : bottomHeight, width: adjustedWidth, fullWidth: width); bottomHeight = max(bottomSizes.total, config.bottomSpec.getMinPixels(height)); var topSizes = TopMarginLayoutStrategy().measure(topViews, maxHeight: useMax ? maxTopHeight : topHeight, width: adjustedWidth, fullWidth: width); topHeight = max(topSizes.total, config.topSpec.getMinPixels(height)); return _MeasuredSizes( leftWidth: leftWidth, leftSizes: leftSizes, rightWidth: rightWidth, rightSizes: rightSizes, topHeight: topHeight, topSizes: topSizes, bottomHeight: bottomHeight, bottomSizes: bottomSizes); } @override void applyToViews(void Function(LayoutView view) apply) { _views.forEach(apply); } } /// Helper class that stores measured width and height during measure cycles. class _MeasuredSizes { final int leftWidth; final SizeList leftSizes; final int rightWidth; final SizeList rightSizes; final int topHeight; final SizeList topSizes; final int bottomHeight; final SizeList bottomSizes; _MeasuredSizes({ required this.leftWidth, required this.leftSizes, required this.rightWidth, required this.rightSizes, required this.topHeight, required this.topSizes, required this.bottomHeight, required this.bottomSizes, }); } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/layout/layout_margin_strategy.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math' show Rectangle; import 'layout_view.dart'; class SizeList { final _sizes = []; int _total = 0; int operator [](int i) => _sizes[i]; int get total => _total; int get length => _sizes.length; void add(int size) { _sizes.add(size); _total += size; } void adjust(int index, int amount) { _sizes[index] += amount; _total += amount; } } class _DesiredViewSizes { final preferredSizes = SizeList(); final minimumSizes = SizeList(); void add(int preferred, int minimum) { preferredSizes.add(preferred); minimumSizes.add(minimum); } void adjustedTo(int maxSize) { if (maxSize < preferredSizes.total) { var delta = preferredSizes.total - maxSize; for (var i = preferredSizes.length - 1; i >= 0; i--) { final viewAvailablePx = preferredSizes[i] - minimumSizes[i]; if (viewAvailablePx < delta) { // We need even more than this one view can give up, so assign the // minimum to the view and adjust totals. preferredSizes.adjust(i, -viewAvailablePx); delta -= viewAvailablePx; } else { // We can adjust this view to account for the delta. preferredSizes.adjust(i, -delta); return; } } } } } /// A strategy for calculating size of vertical margins (RIGHT & LEFT). abstract class VerticalMarginStrategy { SizeList measure(Iterable views, {required int maxWidth, required int height, required int fullHeight}) { final measuredWidths = _DesiredViewSizes(); var remainingWidth = maxWidth; views.forEach((LayoutView view) { final params = view.layoutConfig; final viewMargin = params.viewMargin; final availableHeight = (params.isFullPosition ? fullHeight : height) - viewMargin.height; // Measure with all available space, minus the buffer. remainingWidth = remainingWidth - viewMargin.width; maxWidth -= viewMargin.width; var size = ViewMeasuredSizes.zero; // Don't ask component to measure if both measurements are 0. // // Measure still needs to be called even when one dimension has a size of // zero because if the component is an axis, the axis needs to still // recalculate ticks even if it is not to be shown. if (remainingWidth > 0 || availableHeight > 0) { size = view.measure(remainingWidth, availableHeight)!; remainingWidth -= size.preferredWidth; } measuredWidths.add(size.preferredWidth, size.minWidth); }); measuredWidths.adjustedTo(maxWidth); return measuredWidths.preferredSizes; } void layout(List views, SizeList measuredSizes, Rectangle fullBounds, Rectangle drawAreaBounds); } /// A strategy for calculating size and bounds of left margins. class LeftMarginLayoutStrategy extends VerticalMarginStrategy { @override void layout(Iterable views, SizeList measuredSizes, Rectangle fullBounds, Rectangle drawAreaBounds) { var prevBoundsRight = drawAreaBounds.left; var i = 0; views.forEach((LayoutView view) { final params = view.layoutConfig; final width = measuredSizes[i]; final left = prevBoundsRight - params.viewMargin.rightPx - width; final height = (params.isFullPosition ? fullBounds.height : drawAreaBounds.height) - params.viewMargin.height; final top = params.viewMargin.topPx + (params.isFullPosition ? fullBounds.top : drawAreaBounds.top); // Update the remaining bounds. prevBoundsRight = left - params.viewMargin.leftPx; // Layout this component. view.layout(Rectangle(left, top, width, height), drawAreaBounds); i++; }); } } /// A strategy for calculating size and bounds of right margins. class RightMarginLayoutStrategy extends VerticalMarginStrategy { @override void layout(Iterable views, SizeList measuredSizes, Rectangle fullBounds, Rectangle drawAreaBounds) { var prevBoundsLeft = drawAreaBounds.right; var i = 0; views.forEach((view) { final params = view.layoutConfig; final width = measuredSizes[i]; final left = prevBoundsLeft + params.viewMargin.leftPx; final height = (params.isFullPosition ? fullBounds.height : drawAreaBounds.height) - params.viewMargin.height; final top = params.viewMargin.topPx + (params.isFullPosition ? fullBounds.top : drawAreaBounds.top); // Update the remaining bounds. prevBoundsLeft = left + width + params.viewMargin.rightPx; // Layout this component. view.layout(Rectangle(left, top, width, height), drawAreaBounds); i++; }); } } /// A strategy for calculating size of horizontal margins (TOP & BOTTOM). abstract class HorizontalMarginStrategy { SizeList measure(Iterable views, {required int maxHeight, required int width, required int fullWidth}) { final measuredHeights = _DesiredViewSizes(); var remainingHeight = maxHeight; views.forEach((LayoutView view) { final params = view.layoutConfig; final viewMargin = params.viewMargin; final availableWidth = (params.isFullPosition ? fullWidth : width) - viewMargin.width; // Measure with all available space, minus the buffer. remainingHeight = remainingHeight - viewMargin.height; maxHeight -= viewMargin.height; var size = ViewMeasuredSizes.zero; // Don't ask component to measure if both measurements are 0. // // Measure still needs to be called even when one dimension has a size of // zero because if the component is an axis, the axis needs to still // recalculate ticks even if it is not to be shown. if (remainingHeight > 0 || availableWidth > 0) { size = view.measure(availableWidth, remainingHeight)!; remainingHeight -= size.preferredHeight; } measuredHeights.add(size.preferredHeight, size.minHeight); }); measuredHeights.adjustedTo(maxHeight); return measuredHeights.preferredSizes; } void layout(Iterable views, SizeList measuredSizes, Rectangle fullBounds, Rectangle drawAreaBounds); } /// A strategy for calculating size and bounds of top margins. class TopMarginLayoutStrategy extends HorizontalMarginStrategy { @override void layout(Iterable views, SizeList measuredSizes, Rectangle fullBounds, Rectangle drawAreaBounds) { var prevBoundsBottom = drawAreaBounds.top; var i = 0; views.forEach((LayoutView view) { final params = view.layoutConfig; final height = measuredSizes[i]; final top = prevBoundsBottom - height - params.viewMargin.bottomPx; final width = (params.isFullPosition ? fullBounds.width : drawAreaBounds.width) - params.viewMargin.width; final left = params.viewMargin.leftPx + (params.isFullPosition ? fullBounds.left : drawAreaBounds.left); // Update the remaining bounds. prevBoundsBottom = top - params.viewMargin.topPx; // Layout this component. view.layout(Rectangle(left, top, width, height), drawAreaBounds); i++; }); } } /// A strategy for calculating size and bounds of bottom margins. class BottomMarginLayoutStrategy extends HorizontalMarginStrategy { @override void layout(Iterable views, SizeList measuredSizes, Rectangle fullBounds, Rectangle drawAreaBounds) { var prevBoundsTop = drawAreaBounds.bottom; var i = 0; views.forEach((view) { final params = view.layoutConfig; final height = measuredSizes[i]; final top = prevBoundsTop + params.viewMargin.topPx; final width = (params.isFullPosition ? fullBounds.width : drawAreaBounds.width) - params.viewMargin.width; final left = params.viewMargin.leftPx + (params.isFullPosition ? fullBounds.left : drawAreaBounds.left); // Update the remaining bounds. prevBoundsTop = top + height + params.viewMargin.bottomPx; // Layout this component. view.layout(Rectangle(left, top, width, height), drawAreaBounds); i++; }); } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/layout/layout_view.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math' show Rectangle; import '../common/behavior/chart_behavior.dart' show BehaviorPosition, OutsideJustification; import '../../common/graphics_factory.dart' show GraphicsFactory; import '../common/chart_canvas.dart' show ChartCanvas; /// Position of a [LayoutView]. enum LayoutPosition { Bottom, FullBottom, Top, FullTop, Left, FullLeft, Right, FullRight, DrawArea, } /// Standard layout paint orders for all internal components. /// /// Custom component layers should define their paintOrder by taking the nearest /// layer from this list, and adding or subtracting 1. This will help reduce the /// chance of custom behaviors, renderers, etc. from breaking if we need to /// re-order these components internally. class LayoutViewPaintOrder { // Draw range annotations beneath axis grid lines. static const rangeAnnotation = -10; // Axis elements form the "base layer" of all components on the chart. Domain // axes are drawn on top of measure axes to ensure that the domain axis line // appears on top of any measure axis grid lines. static const measureAxis = 0; static const domainAxis = 5; // Draw series data on top of axis elements. static const arc = 10; static const bar = 10; static const treeMap = 10; static const barTargetLine = 15; static const line = 20; static const point = 25; // Draw most behaviors on top of series data. static const legend = 100; static const linePointHighlighter = 110; static const slider = 150; static const chartTitle = 160; } /// Standard layout position orders for all internal components. /// /// Custom component layers should define their positionOrder by taking the /// nearest component from this list, and adding or subtracting 1. This will /// help reduce the chance of custom behaviors, renderers, etc. from breaking if /// we need to re-order these components internally. class LayoutViewPositionOrder { static const drawArea = 0; static const symbolAnnotation = 10; static const axis = 20; static const legend = 30; static const chartTitle = 40; } /// A configuration for margin (empty space) around a layout child view. class ViewMargin { /// A [ViewMargin] with all zero px. static const empty = ViewMargin(topPx: 0, bottomPx: 0, rightPx: 0, leftPx: 0); final int topPx; final int bottomPx; final int rightPx; final int leftPx; const ViewMargin({int? topPx, int? bottomPx, int? rightPx, int? leftPx}) : topPx = topPx ?? 0, bottomPx = bottomPx ?? 0, rightPx = rightPx ?? 0, leftPx = leftPx ?? 0; /// Total width. int get width => leftPx + rightPx; /// Total height. int get height => topPx + bottomPx; } /// Configuration of a [LayoutView]. class LayoutViewConfig { /// Unique identifier for the [LayoutView]. String? id; /// The order to paint a [LayoutView] on the canvas. /// /// The smaller number is drawn first. int? paintOrder; /// The position of a [LayoutView] defining where to place the view. LayoutPosition? position; /// The order to place the [LayoutView] within a chart margin. /// /// The smaller number is closer to the draw area. Elements positioned closer /// to the draw area will be given extra layout space first, before those /// further away. /// /// Note that all views positioned in the draw area are given the entire draw /// area bounds as their component bounds. int? positionOrder; /// Defines the space around a layout component. ViewMargin viewMargin; /// Creates new [LayoutParams]. /// /// [paintOrder] the order that this component will be drawn. /// [position] the [ComponentPosition] of this component. /// [positionOrder] the order of this component in a chart margin. LayoutViewConfig({ this.paintOrder, this.position, this.positionOrder, ViewMargin? viewMargin, }) : viewMargin = viewMargin ?? ViewMargin.empty; /// Returns true if it is a full position. bool get isFullPosition => position == LayoutPosition.FullBottom || position == LayoutPosition.FullTop || position == LayoutPosition.FullRight || position == LayoutPosition.FullLeft; } /// Size measurements of one component. /// /// The measurement is tight to the component, without adding [ComponentBuffer]. class ViewMeasuredSizes { /// All zeroes component size. static const zero = ViewMeasuredSizes( preferredWidth: 0, preferredHeight: 0, minWidth: 0, minHeight: 0); final int preferredWidth; final int preferredHeight; final int minWidth; final int minHeight; /// Create a new [ViewSizes]. /// /// [preferredWidth] the component's preferred width. /// [preferredHeight] the component's preferred width. /// [minWidth] the component's minimum width. If not set, default to 0. /// [minHeight] the component's minimum height. If not set, default to 0. const ViewMeasuredSizes( {required this.preferredWidth, required this.preferredHeight, int? minWidth, int? minHeight}) : minWidth = minWidth ?? 0, minHeight = minHeight ?? 0; } /// A component that measures its size and accepts bounds to complete layout. abstract class LayoutView { GraphicsFactory? get graphicsFactory; set graphicsFactory(GraphicsFactory? value); /// Layout params for this component. LayoutViewConfig get layoutConfig; /// Measure and return the size of this component. /// /// This measurement is without the [ComponentBuffer], which is added by the /// layout manager. ViewMeasuredSizes? measure(int maxWidth, int maxHeight); /// Layout this component. void layout(Rectangle componentBounds, Rectangle drawAreaBounds); /// Draw this component on the canvas. void paint(ChartCanvas canvas, double animationPercent); /// Bounding box for drawing this component. Rectangle? get componentBounds; /// Whether or not this component is a series renderer that draws series /// data. /// /// This component may either render into the chart's draw area, or into a /// separate area bounded by the component bounds. bool get isSeriesRenderer; } /// Translates a component's [BehaviorPosition] and [OutsideJustification] into /// a [LayoutPosition] that a [LayoutManager] can use to place components on the /// chart. LayoutPosition layoutPosition(BehaviorPosition behaviorPosition, OutsideJustification outsideJustification, bool isRtl) { LayoutPosition position; switch (behaviorPosition) { case BehaviorPosition.bottom: position = LayoutPosition.Bottom; break; case BehaviorPosition.end: position = isRtl ? LayoutPosition.Left : LayoutPosition.Right; break; case BehaviorPosition.inside: position = LayoutPosition.DrawArea; break; case BehaviorPosition.start: position = isRtl ? LayoutPosition.Right : LayoutPosition.Left; break; case BehaviorPosition.top: position = LayoutPosition.Top; break; } // If we have a "full" [OutsideJustification], convert the layout position // to the "full" form. if (outsideJustification == OutsideJustification.start || outsideJustification == OutsideJustification.middle || outsideJustification == OutsideJustification.end) { switch (position) { case LayoutPosition.Bottom: position = LayoutPosition.FullBottom; break; case LayoutPosition.Left: position = LayoutPosition.FullLeft; break; case LayoutPosition.Top: position = LayoutPosition.FullTop; break; case LayoutPosition.Right: position = LayoutPosition.FullRight; break; // Ignore other positions, like DrawArea. default: break; } } return position; } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/line/line_chart.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:collection' show LinkedHashMap; import '../cartesian/axis/axis.dart' show NumericAxis; import '../cartesian/cartesian_chart.dart' show NumericCartesianChart; import '../common/series_renderer.dart' show SeriesRenderer; import '../layout/layout_config.dart' show LayoutConfig; import '../line/line_renderer.dart' show LineRenderer; class LineChart extends NumericCartesianChart { LineChart( {bool? vertical, LayoutConfig? layoutConfig, NumericAxis? primaryMeasureAxis, NumericAxis? secondaryMeasureAxis, LinkedHashMap? disjointMeasureAxes}) : super( vertical: vertical, layoutConfig: layoutConfig, primaryMeasureAxis: primaryMeasureAxis, secondaryMeasureAxis: secondaryMeasureAxis, disjointMeasureAxes: disjointMeasureAxes); @override SeriesRenderer makeDefaultRenderer() { return LineRenderer()..rendererId = SeriesRenderer.defaultRendererId; } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/line/line_renderer.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:collection' show LinkedHashMap; import 'dart:math' show Rectangle, Point; import 'package:collection/collection.dart' show IterableExtension; import 'package:meta/meta.dart' show visibleForTesting; import '../../common/color.dart' show Color; import '../../common/math.dart'; import '../../data/series.dart' show AttributeKey, AccessorFn; import '../cartesian/axis/axis.dart' show ImmutableAxis, OrdinalAxis, domainAxisKey, measureAxisKey; import '../cartesian/cartesian_renderer.dart' show BaseCartesianRenderer; import '../common/base_chart.dart' show BaseChart; import '../common/chart_canvas.dart' show ChartCanvas, getAnimatedColor; import '../common/datum_details.dart' show DatumDetails; import '../common/processed_series.dart' show ImmutableSeries, MutableSeries; import '../common/series_datum.dart' show SeriesDatum; import '../scatter_plot/point_renderer.dart' show PointRenderer; import '../scatter_plot/point_renderer_config.dart' show PointRendererConfig; import 'line_renderer_config.dart' show LineRendererConfig; const styleSegmentsKey = AttributeKey>>( 'LineRenderer.styleSegments'); const lineStackIndexKey = AttributeKey('LineRenderer.lineStackIndex'); class LineRenderer extends BaseCartesianRenderer { // Configuration used to extend the clipping area to extend the draw bounds. static const drawBoundTopExtensionPx = 5; static const drawBoundBottomExtensionPx = 5; final LineRendererConfig config; late PointRenderer _pointRenderer; BaseChart? _chart; /// True if any series has a measureUpperBoundFn and measureLowerBoundFn. /// /// Used to enable drawing confidence interval areas segments. late bool _hasMeasureBounds; /// Store a map of series drawn on the chart, mapped by series name. /// /// [LinkedHashMap] is used to render the series on the canvas in the same /// order as the data was given to the chart. final _seriesLineMap = >>{}; // Store a list of lines that exist in the series data. // // This list will be used to remove any [_AnimatedLine] that were rendered in // previous draw cycles, but no longer have a corresponding datum in the new // data. final _currentKeys = []; factory LineRenderer({String? rendererId, LineRendererConfig? config}) { return LineRenderer._internal( rendererId: rendererId ?? 'line', config: config ?? LineRendererConfig()); } LineRenderer._internal({required String rendererId, required this.config}) : _pointRenderer = PointRenderer( config: PointRendererConfig(radiusPx: config.radiusPx)), super( rendererId: rendererId, layoutPaintOrder: config.layoutPaintOrder, symbolRenderer: config.symbolRenderer); @override void layout(Rectangle componentBounds, Rectangle drawAreaBounds) { super.layout(componentBounds, drawAreaBounds); if (config.includePoints) { _pointRenderer.layout(componentBounds, drawAreaBounds); } } @override void configureSeries(List> seriesList) { assignMissingColors(seriesList, emptyCategoryUsesSinglePalette: false); seriesList.forEach((series) { // Add a default area color function which applies the configured // areaOpacity value to the datum's current color. series.areaColorFn ??= (int? index) { final color = series.colorFn?.call(index); if (color == null) { return null; } return Color( r: color.r, g: color.g, b: color.b, a: (color.a * config.areaOpacity).round()); }; }); if (config.includePoints) { _pointRenderer.configureSeries(seriesList); } } @override void preprocessSeries(List> seriesList) { var stackIndex = 0; _hasMeasureBounds = seriesList.any((series) => series.measureUpperBoundFn != null && series.measureLowerBoundFn != null); seriesList.forEach((MutableSeries series) { final colorFn = series.colorFn; final areaColorFn = series.areaColorFn; final domainFn = series.domainFn; final measureFn = series.measureFn; final strokeWidthPxFn = series.strokeWidthPxFn; series.dashPatternFn ??= (_) => config.dashPattern; final dashPatternFn = series.dashPatternFn!; final styleSegments = <_LineRendererElement>[]; var styleSegmentsIndex = 0; final usedKeys = {}; // Configure style segments for each series. String? previousSegmentKey; _LineRendererElement? currentDetails; for (var index = 0; index < series.data.length; index++) { final domain = domainFn(index); final measure = measureFn(index); if (domain == null || measure == null) { continue; } final color = colorFn!(index); final areaColor = areaColorFn!(index); final dashPattern = dashPatternFn(index); final strokeWidthPx = strokeWidthPxFn?.call(index)?.toDouble() ?? config.strokeWidthPx; // Create a style key for this datum, and then compare it to the // previous datum. // // Compare strokeWidthPx to 2 decimals of precision. Any less and you // can't see any difference in the canvas anyways. final strokeWidthPxRounded = (strokeWidthPx * 100).round() / 100; var styleKey = '${series.id}__${styleSegmentsIndex}__${color}' '__${dashPattern}__${strokeWidthPxRounded}'; if (styleKey != previousSegmentKey) { // If we have a repeated style segment, update the repeat index and // create a new key. // TODO: Paint repeated styles with multiple clip regions. if (usedKeys.isNotEmpty && usedKeys.contains(styleKey)) { styleSegmentsIndex++; styleKey = '${series.id}__${styleSegmentsIndex}__${color}' '__${dashPattern}__${strokeWidthPxRounded}'; } // Make sure that the previous style segment extends to the current // domain value. This will ensure that the style of the line changes // right at the point of the datum that changes the style. if (currentDetails != null) { currentDetails.domainExtent.includePoint(domain); } // Create a new style segment. currentDetails = _LineRendererElement( color: color, areaColor: areaColor, dashPattern: dashPattern, domainExtent: _Range(domain, domain), strokeWidthPx: strokeWidthPx, styleKey: styleKey, roundEndCaps: config.roundEndCaps, ); styleSegments.add(currentDetails); usedKeys.add(styleKey); previousSegmentKey = styleKey; } else { // Extend the range of the current segment to include the current // domain value. currentDetails!.domainExtent.includePoint(domain); } } series.setAttr(styleSegmentsKey, styleSegments); series.setAttr(lineStackIndexKey, stackIndex); if (config.stacked) { stackIndex++; } }); if (config.includePoints) { _pointRenderer.preprocessSeries(seriesList); } // If we are stacking, generate new stacking measure offset functions for // each series. Each datum should have a measure offset consisting of the // sum of the measure and measure offsets of each datum with the same domain // value in series below it in the stack. The first series will be treated // as the bottom of the stack. if (config.stacked && seriesList.isNotEmpty) { var curOffsets = _createInitialOffsetMap(seriesList[0]); var nextOffsets = {}; for (var i = 0; i < seriesList.length; i++) { final series = seriesList[i]; final measureOffsetFn = _createStackedMeasureOffsetFunction( series, curOffsets, nextOffsets); if (i > 0) { series.measureOffsetFn = measureOffsetFn; } curOffsets = nextOffsets; nextOffsets = {}; } } } /// Creates the initial offsets for the series given the measureOffset values. Map _createInitialOffsetMap(MutableSeries series) { final domainFn = series.domainFn; final measureOffsetFn = series.measureOffsetFn!; final initialOffsets = {}; for (var index = 0; index < series.data.length; index++) { initialOffsets[domainFn(index)] = measureOffsetFn(index); } return initialOffsets; } /// Function needed to create a closure preserving the previous series /// information. y0 for this series is just y + y0 for previous series as long /// as both y and y0 are not null. If they are null propagate up the /// missing/null data. AccessorFn _createStackedMeasureOffsetFunction(MutableSeries series, Map curOffsets, Map nextOffsets) { final domainFn = series.domainFn; final measureFn = series.measureFn; for (var index = 0; index < series.data.length; index++) { final domainValue = domainFn(index); final measure = measureFn(index); final prevOffset = curOffsets[domainValue]; if (measure != null && prevOffset != null) { nextOffsets[domainValue] = measure + prevOffset; } } return (int? i) => curOffsets[domainFn(i)]; } /// Merge the line map and the new series so that the new elements are mixed /// with the previous ones. /// /// This is to deal with the issue that every new series added after the fact /// would be be rendered on top of the old ones, no matter the order of the /// new series list. void _mergeIntoSeriesMap(List> seriesList) { final newLineMap = >>>[]; seriesList.forEach((ImmutableSeries series) { final key = series.id; // First, add all the series from the old map that have been removed from // the new seriesList in the same order they appear, stopping at the first // series that is still in the list. We need to maintain them in the same // order animate them out smoothly. var checkNext = true; while (checkNext && _seriesLineMap.isNotEmpty) { final firstKey = _seriesLineMap.keys.first; if (!seriesList.any((s) => s.id == firstKey)) { newLineMap.add(MapEntry(firstKey, _seriesLineMap.remove(firstKey)!)); checkNext = true; } else { checkNext = false; } } // If it's a new key, we add it and move to the next one. If not, we // remove it from the current list and add it to the new one. if (!_seriesLineMap.containsKey(key)) { newLineMap.add(MapEntry(key, [])); } else { newLineMap.add(MapEntry(key, _seriesLineMap.remove(key)!)); } }); // Now whatever is left is stuff that has been removed. We still add it to // the end and removed them as the map is modified in place. newLineMap.addAll(_seriesLineMap.entries); _seriesLineMap.clear(); _seriesLineMap.addEntries(newLineMap); } @override void update(List> seriesList, bool isAnimatingThisDraw) { _currentKeys.clear(); // List of final points for the previous line in a stack. final previousPointList = >>[]; // List of initial points for the previous line in a stack, animated in from // the measure axis. final previousInitialPointList = >>[]; _mergeIntoSeriesMap(seriesList); seriesList.forEach((ImmutableSeries series) { final domainAxis = series.getAttr(domainAxisKey) as ImmutableAxis; final lineKey = series.id; final stackIndex = series.getAttr(lineStackIndexKey)!; previousPointList.add([]); previousInitialPointList.add([]); final elementsList = _seriesLineMap[lineKey]!; final styleSegments = series.getAttr(styleSegmentsKey)!; // Include the end points of the domain axis range in the first and last // style segments to avoid clipping everything when the domain range of // the data is very small. Doing this after [preProcess] handles invalid // data (e.g. null measure) at the ends of the series data. // // TODO: Handle ordinal axes by looking at the next domains. if (styleSegments.isNotEmpty && !(domainAxis is OrdinalAxis)) { final drawBounds = this.drawBounds!; final startPx = (isRtl ? drawBounds.right : drawBounds.left).toDouble(); final endPx = (isRtl ? drawBounds.left : drawBounds.right).toDouble(); final startDomain = domainAxis.getDomain(startPx); final endDomain = domainAxis.getDomain(endPx); styleSegments.first.domainExtent.includePoint(startDomain); styleSegments.last.domainExtent.includePoint(endDomain); } // Create a set of animated line and area elements for each style segment. // // If the series contains null measure values, then multiple animated line // and area objects will be created to represent the isolated sections of // the series. // // The full set of line and area elements will be rendered on the canvas // for each style segment, with a clip region added in the [paint] process // later to display only the relevant parts of data. This ensures that // styles that visually depend on the start location, such as dash // patterns, are not disrupted by other changes in style. styleSegments.forEach((styleSegment) { final styleKey = styleSegment.styleKey; // If we already have an AnimatingPoint for that index, use it. var animatingElements = elementsList .firstWhereOrNull((elements) => elements.styleKey == styleKey); if (animatingElements != null) { previousInitialPointList[stackIndex] = animatingElements.allPoints; } else { // Create a new line and have it animate in from axis. final lineAndArea = _createLineAndAreaElements( series, styleSegment as _LineRendererElement, stackIndex > 0 ? previousInitialPointList[stackIndex - 1] : null, true); final lineElementList = lineAndArea[0] as List<_LineRendererElement>; final areaElementList = lineAndArea[1] as List<_AreaRendererElement>; final allPointList = lineAndArea[2] as List<_DatumPoint>; final boundsElementList = lineAndArea[3] as List<_AreaRendererElement>; // Create the line elements. final animatingLines = <_AnimatedLine>[]; for (var index = 0; index < lineElementList.length; index++) { animatingLines.add(_AnimatedLine( key: lineElementList[index].styleKey, overlaySeries: series.overlaySeries) ..setNewTarget(lineElementList[index])); } // Create the area elements. List<_AnimatedArea>? animatingAreas; if (config.includeArea) { animatingAreas = <_AnimatedArea>[]; for (var index = 0; index < areaElementList.length; index++) { animatingAreas.add(_AnimatedArea( key: areaElementList[index].styleKey, overlaySeries: series.overlaySeries) ..setNewTarget(areaElementList[index])); } } // Create the bound elements separately from area elements, because // it needs to be rendered on top of the area elements. List<_AnimatedArea>? animatingBounds; if (_hasMeasureBounds) { animatingBounds ??= <_AnimatedArea>[]; for (var index = 0; index < boundsElementList.length; index++) { animatingBounds.add(_AnimatedArea( key: boundsElementList[index].styleKey, overlaySeries: series.overlaySeries) ..setNewTarget(boundsElementList[index])); } } animatingElements = _AnimatedElements( styleKey: styleSegment.styleKey, allPoints: allPointList, lines: animatingLines, areas: animatingAreas, bounds: animatingBounds, ); elementsList.add(animatingElements); previousInitialPointList[stackIndex] = allPointList; } // Create a new line using the final point locations. final lineAndArea = _createLineAndAreaElements( series, styleSegment as _LineRendererElement, stackIndex > 0 ? previousPointList[stackIndex - 1] : null, false); final lineElementList = lineAndArea[0] as List<_LineRendererElement>; final areaElementList = lineAndArea[1] as List<_AreaRendererElement>; final allPointList = lineAndArea[2] as List<_DatumPoint>; final boundsElementList = lineAndArea[3] as List<_AreaRendererElement>; for (var index = 0; index < lineElementList.length; index++) { final lineElement = lineElementList[index]; // Add a new animated line if we have more segments in this draw cycle // than we did in the previous chart draw cycle. // TODO: Nicer animations for incoming segments. if (index >= animatingElements.lines.length) { animatingElements.lines.add(_AnimatedLine( key: lineElement.styleKey, overlaySeries: series.overlaySeries)); } animatingElements.lines[index].setNewTarget(lineElement); } if (config.includeArea) { for (var index = 0; index < areaElementList.length; index++) { final areaElement = areaElementList[index]; // Add a new animated area if we have more segments in this draw // cycle than we did in the previous chart draw cycle. // TODO: Nicer animations for incoming segments. if (index >= animatingElements.areas!.length) { animatingElements.areas!.add(_AnimatedArea( key: areaElement.styleKey, overlaySeries: series.overlaySeries)); } animatingElements.areas![index].setNewTarget(areaElement); } } if (_hasMeasureBounds) { for (var index = 0; index < boundsElementList.length; index++) { final boundElement = boundsElementList[index]; // Add a new animated bound if we have more segments in this draw // cycle than we did in the previous chart draw cycle. // TODO: Nicer animations for incoming segments. if (index >= animatingElements.bounds!.length) { animatingElements.bounds!.add(_AnimatedArea( key: boundElement.styleKey, overlaySeries: series.overlaySeries)); } animatingElements.bounds![index].setNewTarget(boundElement); } } animatingElements.allPoints = allPointList; // Save the line points for the current series so that we can use them // in the area skirt for the next stacked series. previousPointList[stackIndex] = allPointList; }); }); // Animate out lines that don't exist anymore. _seriesLineMap.forEach((String key, List<_AnimatedElements> elements) { for (var element in elements) { if (element.lines != null) { for (var line in element.lines) { if (!_currentKeys.contains(line.key)) { line.animateOut(); } } } if (element.areas != null) { for (var area in element.areas!) { if (!_currentKeys.contains(area.key)) { area.animateOut(); } } } if (element.bounds != null) { for (var bound in element.bounds!) { if (!_currentKeys.contains(bound.key)) { bound.animateOut(); } } } } }); if (config.includePoints) { _pointRenderer.update(seriesList, isAnimatingThisDraw); } } /// Creates a tuple of lists of [_LineRendererElement]s, /// [_AreaRendererElement]s, [_DatumPoint]s for a given style segment of a /// series. /// /// The first element in the returned array is a list of line elements, broken /// apart by null data. /// /// The second element in the returned array is a list of area elements, /// broken apart by null data. /// /// The third element in the returned array is a list of all of the points for /// the entire series. This is intended to be used as the [previousPointList] /// for the next series. /// /// [series] the series that this line represents. /// /// [styleSegment] represents the rendering style for a subset of the series /// data, bounded by its domainExtent. /// /// [previousPointList] contains the points for the line below this series in /// the stack, if stacking is enabled. It forms the bottom edges for the area /// skirt. /// /// [initializeFromZero] controls whether we generate elements with measure /// values of 0, or using series data. This should be true when calculating /// point positions to animate in from the measure axis. List _createLineAndAreaElements( ImmutableSeries series, _LineRendererElement styleSegment, List<_DatumPoint>? previousPointList, bool initializeFromZero) { final measureAxis = series.getAttr(measureAxisKey) as ImmutableAxis; final color = styleSegment.color; final areaColor = styleSegment.areaColor; final dashPattern = styleSegment.dashPattern; final domainExtent = styleSegment.domainExtent; final strokeWidthPx = styleSegment.strokeWidthPx; final styleKey = styleSegment.styleKey; final roundEndCaps = styleSegment.roundEndCaps; // Get a list of all positioned points for this series. final pointList = _createPointListForSeries(series, initializeFromZero); // Break pointList up into sets of line and area segments, divided by null // measure values in the series data. final segmentsList = _createLineAndAreaSegmentsForSeries( pointList, previousPointList, series, initializeFromZero); final lineSegments = segmentsList[0]; final areaSegments = segmentsList[1]; final boundsSegment = segmentsList[2]; _currentKeys.add(styleKey); final positionExtent = _createPositionExtent(series, styleSegment); // Get the line elements we are going to to set up. final lineElements = <_LineRendererElement>[]; for (var index = 0; index < lineSegments.length; index++) { final linePointList = lineSegments[index]; // Update the set of areas that still exist in the series data. final lineStyleKey = '${styleKey}__line__${index}'; _currentKeys.add(lineStyleKey); lineElements.add(_LineRendererElement( points: linePointList, color: color, areaColor: areaColor, dashPattern: dashPattern, domainExtent: domainExtent, measureAxisPosition: measureAxis.getLocation(0.0), positionExtent: positionExtent, strokeWidthPx: strokeWidthPx, styleKey: lineStyleKey, roundEndCaps: roundEndCaps, )); } // Get the area elements we are going to set up. final areaElements = <_AreaRendererElement>[]; if (config.includeArea) { for (var index = 0; index < areaSegments.length; index++) { final areaPointList = areaSegments[index]; // Update the set of areas that still exist in the series data. final areaStyleKey = '${styleKey}__area_${index}'; _currentKeys.add(areaStyleKey); areaElements.add(_AreaRendererElement( points: areaPointList, color: color, areaColor: areaColor, domainExtent: domainExtent, measureAxisPosition: measureAxis.getLocation(0.0)!, positionExtent: positionExtent, styleKey: areaStyleKey, )); } } // Create the bounds element final boundsElements = <_AreaRendererElement>[]; if (_hasMeasureBounds) { // Update the set of bounds that still exist in the series data. for (var index = 0; index < boundsSegment.length; index++) { final boundsPointList = boundsSegment[index]; final boundsStyleKey = '${styleKey}__bounds_${index}'; _currentKeys.add(boundsStyleKey); boundsElements.add(_AreaRendererElement( points: boundsPointList, color: color, areaColor: areaColor, domainExtent: domainExtent, measureAxisPosition: measureAxis.getLocation(0.0)!, positionExtent: positionExtent, styleKey: boundsStyleKey, )); } } return [lineElements, areaElements, pointList, boundsElements]; } /// Builds a list of data points for the entire series. /// /// [series] the series that this line represents. /// /// [initializeFromZero] controls whether we generate elements with measure /// values of 0, or using series data. This should be true when calculating /// point positions to animate in from the measure axis. List<_DatumPoint> _createPointListForSeries( ImmutableSeries series, bool initializeFromZero) { final domainAxis = series.getAttr(domainAxisKey) as ImmutableAxis; final domainFn = series.domainFn; final measureAxis = series.getAttr(measureAxisKey) as ImmutableAxis; final measureFn = series.measureFn; final measureOffsetFn = series.measureOffsetFn!; final pointList = <_DatumPoint>[]; // Generate [_DatumPoints]s for the series data. for (var index = 0; index < series.data.length; index++) { final Object? datum = series.data[index]; // TODO: Animate from the nearest lines in the stack. var measure = measureFn(index); if (measure != null && initializeFromZero) { measure = 0.0; } var measureOffset = measureOffsetFn(index); if (measureOffset != null && initializeFromZero) { measureOffset = 0.0; } pointList.add(_getPoint(datum, domainFn(index), series, domainAxis, measure, measureOffset, measureAxis, index: index)); } return pointList; } /// Builds a list of line and area segments for a series. /// /// This method returns a list of two elements. The first is a list of line /// segments, and the second is a list of area segments. Both sets of segments /// are broken up by null measure values in the series data. /// /// [pointList] list of all points in the line. /// /// [previousPointList] list of all points in the line below this one in the /// stack. /// /// [series] the series that this line represents. List>>> _createLineAndAreaSegmentsForSeries( List<_DatumPoint> pointList, List<_DatumPoint>? previousPointList, ImmutableSeries series, bool initializeFromZero) { final lineSegments = >>[]; final areaSegments = >>[]; final boundsSegments = >>[]; int? startPointIndex; int? endPointIndex; // Only build bound segments for this series if it has bounds functions. final seriesHasMeasureBounds = series.measureUpperBoundFn != null && series.measureLowerBoundFn != null; for (var index = 0; index < pointList.length; index++) { final point = pointList[index]; if (point.y == null) { if (startPointIndex == null) { continue; } assert(endPointIndex != null); lineSegments.add( _createLineSegment(startPointIndex, endPointIndex!, pointList)); // Isolated data points are handled by the line painter. Do not add an // area segment for them. if (startPointIndex != endPointIndex) { if (config.includeArea) { areaSegments.add(_createAreaSegment(startPointIndex, endPointIndex, pointList, previousPointList, series, initializeFromZero)); } if (seriesHasMeasureBounds) { boundsSegments.add(_createBoundsSegment( pointList.sublist(startPointIndex, endPointIndex + 1), series, initializeFromZero)); } } startPointIndex = null; endPointIndex = null; continue; } startPointIndex ??= index; endPointIndex = index; } // Create an area point list for the final segment. This will be the only // segment if no null measure values were found in the series. if (startPointIndex != null && endPointIndex != null) { lineSegments .add(_createLineSegment(startPointIndex, endPointIndex, pointList)); // Isolated data points are handled by the line painter. Do not add an // area segment for them. if (startPointIndex != endPointIndex) { if (config.includeArea) { areaSegments.add(_createAreaSegment(startPointIndex, endPointIndex, pointList, previousPointList, series, initializeFromZero)); } if (seriesHasMeasureBounds) { boundsSegments.add(_createBoundsSegment( pointList.sublist(startPointIndex, endPointIndex + 1), series, initializeFromZero)); } } } return [lineSegments, areaSegments, boundsSegments]; } /// Builds a list of data points for a line segment. /// /// For a line, this is effectively just a sub list of [pointList]. /// /// [start] index of the first point in the segment. /// /// [end] index of the last point in the segment. /// /// [pointList] list of all points in the line. List<_DatumPoint> _createLineSegment( int start, int end, List<_DatumPoint> pointList) => pointList.sublist(start, end + 1); /// Builds a list of data points for an area segment. /// /// The list of points will include a baseline at the domain axis if there was /// no previous line in the stack. Otherwise, the bottom of the shape will /// consist of the points from the previous series that line up with the /// current series. /// /// [start] index of the first point in the segment. /// /// [end] index of the last point in the segment. /// /// [pointList] list of all points in the line. /// /// [previousPointList] list of all points in the line below this one in the /// stack. /// /// [series] the series that this line represents. List<_DatumPoint> _createAreaSegment( int start, int end, List<_DatumPoint> pointList, List<_DatumPoint>? previousPointList, ImmutableSeries series, bool initializeFromZero) { final domainAxis = series.getAttr(domainAxisKey) as ImmutableAxis; final domainFn = series.domainFn; final measureAxis = series.getAttr(measureAxisKey) as ImmutableAxis; final areaPointList = <_DatumPoint>[]; if (!config.stacked || previousPointList == null) { // Start area segments at the bottom of a stack by adding a bottom line // segment along the measure axis. areaPointList.add(_getPoint( null, domainFn(end), series, domainAxis, 0.0, 0.0, measureAxis)); areaPointList.add(_getPoint( null, domainFn(start), series, domainAxis, 0.0, 0.0, measureAxis)); } else { // Start subsequent area segments in a stack by adding the previous // points in reverse order, so that we can get a properly closed // polygon. areaPointList.addAll(previousPointList.sublist(start, end + 1).reversed); } areaPointList.addAll(pointList.sublist(start, end + 1)); return areaPointList; } List<_DatumPoint> _createBoundsSegment(List<_DatumPoint> pointList, ImmutableSeries series, bool initializeFromZero) { final measureAxis = series.getAttr(measureAxisKey) as ImmutableAxis; final areaPointList = <_DatumPoint>[]; // Add all points for upper bounds. areaPointList.addAll(pointList.map((datumPoint) => _DatumPoint.from( datumPoint, datumPoint.x, initializeFromZero ? datumPoint.y : measureAxis.getLocation( (series.measureUpperBoundFn!(datumPoint.index) ?? 0) + series.measureOffsetFn!(datumPoint.index)!)))); // Add all points for lower bounds, in reverse order. areaPointList.addAll(pointList.reversed.map((datumPoint) => _DatumPoint.from( datumPoint, datumPoint.x, initializeFromZero ? datumPoint.y : measureAxis.getLocation( (series.measureLowerBoundFn!(datumPoint.index) ?? 0) + series.measureOffsetFn!(datumPoint.index)!)))); return areaPointList; } /// Converts the domain value extent for the series into axis positions, /// clamped to the edges of the draw area. /// /// [series] the series that this line represents. /// /// [details] represents the element details for a line segment. _Range _createPositionExtent( ImmutableSeries series, _LineRendererElement details) { final domainAxis = series.getAttr(domainAxisKey) as ImmutableAxis; // Convert the domain extent into axis positions. // Clamp start position to the beginning of the draw area if it is outside // the domain viewport range. final startPosition = domainAxis.getLocation(details.domainExtent.start) ?? drawBounds!.left.toDouble(); // Clamp end position to the end of the draw area if it is outside the // domain viewport range. final endPosition = domainAxis.getLocation(details.domainExtent.end) ?? drawBounds!.right.toDouble(); return _Range(startPosition, endPosition); } @override void onAttach(BaseChart chart) { super.onAttach(chart); // We only need the chart.context.isRtl setting, but context is not yet // available when the default renderer is attached to the chart on chart // creation time, since chart onInit is called after the chart is created. _chart = chart; } @override void paint(ChartCanvas canvas, double animationPercent) { // Clean up the lines that no longer exist. if (animationPercent == 1.0) { final keysToRemove = []; _seriesLineMap.forEach((String key, List<_AnimatedElements> elements) { elements.removeWhere( (_AnimatedElements element) => element.animatingOut); if (elements.isEmpty) { keysToRemove.add(key); } }); keysToRemove.forEach(_seriesLineMap.remove); } _seriesLineMap.forEach((String key, List<_AnimatedElements> elements) { if (config.includeArea) { elements .map>>( (_AnimatedElements animatingElement) => animatingElement.areas!) .expand<_AnimatedArea>((List<_AnimatedArea> areas) => areas) .map<_AreaRendererElement>((_AnimatedArea animatingArea) => animatingArea.getCurrentArea(animationPercent)) .forEach((area) { if (area != null) { canvas.drawPolygon( clipBounds: _getClipBoundsForExtent(area.positionExtent), fill: area.areaColor ?? area.color, points: area.points.toPoints()); } }); } if (_hasMeasureBounds) { elements .map>>( (_AnimatedElements animatingElement) => animatingElement.bounds!) .expand<_AnimatedArea>((List<_AnimatedArea> bounds) => bounds) .map<_AreaRendererElement>((_AnimatedArea animatingBounds) => animatingBounds.getCurrentArea(animationPercent)) .forEach((bound) { if (bound != null) { canvas.drawPolygon( clipBounds: _getClipBoundsForExtent(bound.positionExtent), fill: bound.areaColor ?? bound.color, points: bound.points.toPoints()); } }); } if (config.includeLine) { elements .map>>( (_AnimatedElements animatingElement) => animatingElement.lines) .expand<_AnimatedLine>((List<_AnimatedLine> lines) => lines) .map<_LineRendererElement>((_AnimatedLine animatingLine) => animatingLine.getCurrentLine(animationPercent)) .forEach((line) { if (line != null) { canvas.drawLine( clipBounds: _getClipBoundsForExtent(line.positionExtent!), dashPattern: line.dashPattern, points: line.points!.toPoints(), stroke: line.color, strokeWidthPx: line.strokeWidthPx, roundEndCaps: line.roundEndCaps); } }); } }); if (config.includePoints) { _pointRenderer.paint(canvas, animationPercent); } } /// Builds a clip region bounding box within the component [drawBounds] for a /// given domain range [extent]. Rectangle _getClipBoundsForExtent(_Range extent) { // In RTL mode, the domain range extent has start on the right side of the // chart. Adjust the calculated positions to define a regular left-anchored // [Rectangle]. Clamp both ends to be within the draw area. final drawBounds = this.drawBounds!; final left = isRtl ? clamp(extent.end, drawBounds.left, drawBounds.right) : clamp(extent.start, drawBounds.left, drawBounds.right); final right = isRtl ? clamp(extent.start, drawBounds.left, drawBounds.right) : clamp(extent.end, drawBounds.left, drawBounds.right); return Rectangle( left, drawBounds.top - drawBoundTopExtensionPx, right - left, drawBounds.height + drawBoundTopExtensionPx + drawBoundBottomExtensionPx); } bool get isRtl => _chart?.context.isRtl ?? false; _DatumPoint _getPoint( dynamic datum, D? domainValue, ImmutableSeries series, ImmutableAxis domainAxis, num? measureValue, num? measureOffsetValue, ImmutableAxis measureAxis, {int? index}) { final domainPosition = domainAxis.getLocation(domainValue); final measurePosition = measureValue != null && measureOffsetValue != null ? measureAxis.getLocation(measureValue + measureOffsetValue) : null; return _DatumPoint( datum: datum, domain: domainValue, series: series, x: domainPosition, y: measurePosition, index: index); } @override List> getNearestDatumDetailPerSeries( Point chartPoint, bool byDomain, Rectangle? boundsOverride, { bool selectOverlappingPoints = false, bool selectExactEventLocation = false, }) { final nearest = >[]; // Was it even in the component bounds? if (!isPointWithinBounds(chartPoint, boundsOverride)) { return nearest; } for (final seriesSegments in _seriesLineMap.values) { _DatumPoint? nearestPoint; var nearestDomainDistance = 10000.0; var nearestMeasureDistance = 10000.0; var nearestRelativeDistance = 10000.0; for (final segment in seriesSegments) { if (segment.overlaySeries) { continue; } for (final p in segment.allPoints) { // Don't look at points not in the drawArea. if (p.x! < componentBounds!.left || p.x! > componentBounds!.right) { continue; } double measureDistance; double relativeDistance; double domainDistance; if (p.y != null) { measureDistance = (p.y! - chartPoint.y).abs(); domainDistance = (p.x! - chartPoint.x).abs(); relativeDistance = chartPoint.distanceTo(p.toPoint()); } else { // Null measures have no real position, so make them the farthest // away by real distance. measureDistance = double.infinity; domainDistance = double.infinity; relativeDistance = byDomain ? domainDistance : double.infinity; } if (byDomain) { if ((domainDistance < nearestDomainDistance) || ((domainDistance == nearestDomainDistance) && (measureDistance < nearestMeasureDistance))) { nearestPoint = p; nearestDomainDistance = domainDistance; nearestMeasureDistance = measureDistance; nearestRelativeDistance = relativeDistance; } } else { if (relativeDistance < nearestRelativeDistance) { nearestPoint = p; nearestDomainDistance = domainDistance; nearestMeasureDistance = measureDistance; nearestRelativeDistance = relativeDistance; } } } } // Found a point, add it to the list. if (nearestPoint != null) { nearest.add(DatumDetails( chartPosition: NullablePoint(nearestPoint.x, nearestPoint.y), datum: nearestPoint.datum, domain: nearestPoint.domain, series: nearestPoint.series, domainDistance: nearestDomainDistance, measureDistance: nearestMeasureDistance, relativeDistance: nearestRelativeDistance)); } } // Note: the details are already sorted by domain & measure distance in // base chart. return nearest; } @override DatumDetails addPositionToDetailsForSeriesDatum( DatumDetails details, SeriesDatum seriesDatum) { final series = details.series!; final domainAxis = series.getAttr(domainAxisKey) as ImmutableAxis; final measureAxis = series.getAttr(measureAxisKey) as ImmutableAxis; final point = _getPoint(seriesDatum.datum, details.domain, series, domainAxis, details.measure, details.measureOffset, measureAxis); final chartPosition = NullablePoint(point.x, point.y); return DatumDetails.from(details, chartPosition: chartPosition); } } class _DatumPoint extends NullablePoint { final dynamic datum; final D? domain; final ImmutableSeries? series; final int? index; _DatumPoint({ this.datum, this.domain, this.series, this.index, double? x, double? y, }) : super(x, y); factory _DatumPoint.from(_DatumPoint other, [double? x, double? y]) { return _DatumPoint( datum: other.datum, domain: other.domain, series: other.series, index: other.index, x: x ?? other.x, y: y ?? other.y); } } /// Rendering information for the line portion of a series. class _LineRendererElement { List<_DatumPoint>? points; Color? color; Color? areaColor; List? dashPattern; _Range domainExtent; double? measureAxisPosition; _Range? positionExtent; double strokeWidthPx; String styleKey; bool roundEndCaps; _LineRendererElement({ this.points, required this.color, required this.areaColor, required this.dashPattern, required this.domainExtent, this.measureAxisPosition, this.positionExtent, required this.strokeWidthPx, required this.styleKey, required this.roundEndCaps, }); _LineRendererElement clone() { return _LineRendererElement( points: points != null ? List.of(points!) : null, color: color != null ? Color.fromOther(color: color!) : null, areaColor: areaColor != null ? Color.fromOther(color: areaColor!) : null, dashPattern: dashPattern != null ? List.of(dashPattern!) : null, domainExtent: domainExtent, measureAxisPosition: measureAxisPosition, positionExtent: positionExtent, strokeWidthPx: strokeWidthPx, styleKey: styleKey, roundEndCaps: roundEndCaps, ); } void updateAnimationPercent(_LineRendererElement previous, _LineRendererElement target, double animationPercent) { final points = this.points!; late _DatumPoint lastPoint; int pointIndex; for (pointIndex = 0; pointIndex < target.points!.length; pointIndex++) { final targetPoint = target.points![pointIndex]; // If we have more points than the previous line, animate in the new point // by starting its measure position at the last known official point. // TODO: Can this be done in setNewTarget instead? _DatumPoint previousPoint; if (previous.points!.length - 1 >= pointIndex) { previousPoint = previous.points![pointIndex]; lastPoint = previousPoint; } else { previousPoint = _DatumPoint.from(targetPoint, targetPoint.x, lastPoint.y); } final x = ((targetPoint.x! - previousPoint.x!) * animationPercent) + previousPoint.x!; double? y; if (targetPoint.y != null && previousPoint.y != null) { y = ((targetPoint.y! - previousPoint.y!) * animationPercent) + previousPoint.y!; } else if (targetPoint.y != null) { y = targetPoint.y; } else { y = null; } if (points.length - 1 >= pointIndex) { points[pointIndex] = _DatumPoint.from(targetPoint, x, y); } else { points.add(_DatumPoint.from(targetPoint, x, y)); } } // Removing extra points that don't exist anymore. if (pointIndex < points.length) { points.removeRange(pointIndex, points.length); } color = getAnimatedColor(previous.color!, target.color!, animationPercent); if (areaColor != null) { areaColor = getAnimatedColor( previous.areaColor!, target.areaColor!, animationPercent); } strokeWidthPx = ((target.strokeWidthPx - previous.strokeWidthPx) * animationPercent) + previous.strokeWidthPx; } } /// Animates the line element of a series between different states. class _AnimatedLine { final String key; final bool overlaySeries; _LineRendererElement? _previousLine; late _LineRendererElement _targetLine; _LineRendererElement? _currentLine; // Flag indicating whether this line is being animated out of the chart. bool animatingOut = false; _AnimatedLine({required this.key, required this.overlaySeries}); /// Animates a line that was removed from the series out of the view. /// /// This should be called in place of "setNewTarget" for lines that represent /// data that has been removed from the series. /// /// Animates the height of the line down to the measure axis position /// (position of 0). void animateOut() { var newTarget = _currentLine!.clone(); // Set the target measure value to the axis position for all points. // TODO: Animate to the nearest lines in the stack. var newPoints = <_DatumPoint>[]; for (var index = 0; index < newTarget.points!.length; index++) { var targetPoint = newTarget.points![index]; newPoints.add(_DatumPoint.from(targetPoint, targetPoint.x, newTarget.measureAxisPosition!.roundToDouble())); } newTarget.points = newPoints; // Animate the stroke width to 0 so that we don't get a lingering line after // animation is done. newTarget.strokeWidthPx = 0.0; setNewTarget(newTarget); animatingOut = true; } void setNewTarget(_LineRendererElement newTarget) { animatingOut = false; _currentLine ??= newTarget.clone(); _previousLine = _currentLine!.clone(); _targetLine = newTarget; } _LineRendererElement getCurrentLine(double animationPercent) { if (animationPercent == 1.0 || _previousLine == null) { _currentLine = _targetLine; _previousLine = _targetLine; return _currentLine!; } _currentLine! .updateAnimationPercent(_previousLine!, _targetLine, animationPercent); return _currentLine!; } /// Returns the [points] of the current target element, without updating /// animation state. List<_DatumPoint>? get currentPoints => _currentLine?.points; } /// Rendering information for the area skirt portion of a series. class _AreaRendererElement { List<_DatumPoint> points; Color? color; Color? areaColor; _Range domainExtent; double measureAxisPosition; _Range positionExtent; String styleKey; _AreaRendererElement({ required this.points, required this.color, required this.areaColor, required this.domainExtent, required this.measureAxisPosition, required this.positionExtent, required this.styleKey, }); _AreaRendererElement clone() { return _AreaRendererElement( points: List.of(points), color: color != null ? Color.fromOther(color: color!) : null, areaColor: areaColor != null ? Color.fromOther(color: areaColor!) : null, domainExtent: domainExtent, measureAxisPosition: measureAxisPosition, positionExtent: positionExtent, styleKey: styleKey, ); } void updateAnimationPercent(_AreaRendererElement previous, _AreaRendererElement target, double animationPercent) { late _DatumPoint lastPoint; int pointIndex; for (pointIndex = 0; pointIndex < target.points.length; pointIndex++) { var targetPoint = target.points[pointIndex]; // If we have more points than the previous line, animate in the new point // by starting its measure position at the last known official point. // TODO: Can this be done in setNewTarget instead? _DatumPoint previousPoint; if (previous.points.length - 1 >= pointIndex) { previousPoint = previous.points[pointIndex]; lastPoint = previousPoint; } else { previousPoint = _DatumPoint.from(targetPoint, targetPoint.x, lastPoint.y); } final x = ((targetPoint.x! - previousPoint.x!) * animationPercent) + previousPoint.x!; double? y; if (targetPoint.y != null && previousPoint.y != null) { y = ((targetPoint.y! - previousPoint.y!) * animationPercent) + previousPoint.y!; } else if (targetPoint.y != null) { y = targetPoint.y; } else { y = null; } if (points.length - 1 >= pointIndex) { points[pointIndex] = _DatumPoint.from(targetPoint, x, y); } else { points.add(_DatumPoint.from(targetPoint, x, y)); } } // Removing extra points that don't exist anymore. if (pointIndex < points.length) { points.removeRange(pointIndex, points.length); } color = getAnimatedColor(previous.color!, target.color!, animationPercent); if (areaColor != null) { areaColor = getAnimatedColor( previous.areaColor!, target.areaColor!, animationPercent); } } } /// Animates the area element of a series between different states. class _AnimatedArea { final String key; final bool overlaySeries; _AreaRendererElement? _previousArea; late _AreaRendererElement _targetArea; _AreaRendererElement? _currentArea; // Flag indicating whether this line is being animated out of the chart. bool animatingOut = false; _AnimatedArea({required this.key, required this.overlaySeries}); /// Animates a line that was removed from the series out of the view. /// /// This should be called in place of "setNewTarget" for lines that represent /// data that has been removed from the series. /// /// Animates the height of the line down to the measure axis position /// (position of 0). void animateOut() { var newTarget = _currentArea!.clone(); // Set the target measure value to the axis position for all points. // TODO: Animate to the nearest areas in the stack. var newPoints = <_DatumPoint>[]; for (var index = 0; index < newTarget.points.length; index++) { var targetPoint = newTarget.points[index]; newPoints.add(_DatumPoint.from(targetPoint, targetPoint.x, newTarget.measureAxisPosition.roundToDouble())); } newTarget.points = newPoints; setNewTarget(newTarget); animatingOut = true; } void setNewTarget(_AreaRendererElement newTarget) { animatingOut = false; _currentArea ??= newTarget.clone(); _previousArea = _currentArea!.clone(); _targetArea = newTarget; } _AreaRendererElement getCurrentArea(double animationPercent) { if (animationPercent == 1.0 || _previousArea == null) { _currentArea = _targetArea; _previousArea = _targetArea; return _currentArea!; } _currentArea! .updateAnimationPercent(_previousArea!, _targetArea, animationPercent); return _currentArea!; } } class _AnimatedElements { List<_DatumPoint> allPoints; List<_AnimatedArea>? areas; List<_AnimatedLine> lines; List<_AnimatedArea>? bounds; String styleKey; _AnimatedElements({ required this.allPoints, required this.areas, required this.lines, required this.bounds, required this.styleKey, }); bool get animatingOut { var areasAnimatingOut = true; if (areas != null) { for (final area in areas!) { areasAnimatingOut = areasAnimatingOut && area.animatingOut; } } var linesAnimatingOut = true; if (lines != null) { for (final line in lines) { linesAnimatingOut = linesAnimatingOut && line.animatingOut; } } var boundsAnimatingOut = true; if (bounds != null) { for (final bound in bounds!) { boundsAnimatingOut = boundsAnimatingOut && bound.animatingOut; } } return areasAnimatingOut && linesAnimatingOut && boundsAnimatingOut; } bool get overlaySeries { var areasOverlaySeries = true; if (areas != null) { for (final area in areas!) { areasOverlaySeries = areasOverlaySeries && area.overlaySeries; } } var linesOverlaySeries = true; if (lines != null) { for (final line in lines) { linesOverlaySeries = linesOverlaySeries && line.overlaySeries; } } var boundsOverlaySeries = true; if (bounds != null) { for (final bound in bounds!) { boundsOverlaySeries = boundsOverlaySeries && bound.overlaySeries; } } return areasOverlaySeries && linesOverlaySeries && boundsOverlaySeries; } } /// Describes a numeric range with a start and end value. /// /// [start] must always be less than [end]. class _Range { D _start; D _end; _Range(D start, D end) : _start = start, _end = end; /// Gets the start of the range. D get start => _start; /// Gets the end of the range. D get end => _end; /// Extends the range to include [value]. void includePoint(D? value) { if (value == null) { return; } else if (value is num) { _includePointAsNum(value); } else if (value is DateTime) { _includePointAsDateTime(value); } else if (value is String) { _includePointAsString(value); } else { throw ArgumentError( 'Unsupported object type for LineRenderer domain value: ' '${value.runtimeType}'); } } /// Extends the range to include value by casting as numbers. void _includePointAsNum(D value) { value as num; if (value < (_start as num)) { _start = value; } else if (value > (_end as num)) { _end = value; } } /// Extends the range to include value by casting as DateTime objects. void _includePointAsDateTime(D value) { value as DateTime; if (value.isBefore(_start as DateTime)) { _start = value; } else if (value.isAfter(_end as DateTime)) { _end = value; } } /// Extends the range to include value by casting as String objects. /// /// In this case, we assume that the data is ordered in the same order as the /// axis. void _includePointAsString(D value) { _end = value; } } @visibleForTesting class LineRendererTester { final LineRenderer renderer; LineRendererTester(this.renderer); Iterable get seriesKeys => renderer._seriesLineMap.keys; void setSeriesKeys(List keys) { renderer._seriesLineMap.addEntries(keys.map((key) => MapEntry(key, []))); } void merge(List> series) { renderer._mergeIntoSeriesMap(series); } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/line/line_renderer_config.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import '../../common/symbol_renderer.dart'; import '../common/series_renderer_config.dart' show RendererAttributes, SeriesRendererConfig; import '../layout/layout_view.dart' show LayoutViewConfig, LayoutViewPaintOrder; import 'line_renderer.dart' show LineRenderer; /// Configuration for a line renderer. class LineRendererConfig extends LayoutViewConfig implements SeriesRendererConfig { @override final String? customRendererId; @override final SymbolRenderer symbolRenderer; @override final rendererAttributes = RendererAttributes(); /// Radius of points on the line, if [includePoints] is enabled. final double radiusPx; /// Whether or not series should be rendered in a stack. /// /// This is typically enabled when including area skirts. final bool stacked; /// Stroke width of the line. final double strokeWidthPx; /// Dash pattern for the line. final List? dashPattern; /// Configures whether a line representing the data will be drawn. final bool includeLine; /// Configures whether points representing the data will be drawn. final bool includePoints; /// Configures whether an area skirt representing the data will be drawn. /// /// An area skirt will be drawn from the line for each series, down to the /// domain axis. It will be layered underneath the primary line on the chart. /// /// The area skirt color will be a semi-transparent version of the series /// color, using [areaOpacity] as the opacity. /// /// When stacking is enabled, the bottom of each area skirt will instead be /// the previous line in the stack. The bottom area will be drawn down to the /// domain axis. final bool includeArea; /// The order to paint this renderer on the canvas. final int layoutPaintOrder; /// Configures the opacity of the area skirt on the chart. final double areaOpacity; /// Whether lines should have round end caps, or square if false. final bool roundEndCaps; LineRendererConfig( {this.customRendererId, this.radiusPx = 3.5, this.stacked = false, this.strokeWidthPx = 2.0, this.dashPattern, this.includeLine = true, this.includePoints = false, this.includeArea = false, this.layoutPaintOrder = LayoutViewPaintOrder.line, this.areaOpacity = 0.1, this.roundEndCaps = false, SymbolRenderer? symbolRenderer}) : symbolRenderer = symbolRenderer ?? LineSymbolRenderer(); @override LineRenderer build() { return LineRenderer(config: this, rendererId: customRendererId); } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/pie/arc_label_decorator.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math' show cos, min, sin, pi, Point, Rectangle; import 'package:meta/meta.dart' show immutable, protected; import '../../common/color.dart' show Color; import '../../common/graphics_factory.dart' show GraphicsFactory; import '../../common/style/style_factory.dart' show StyleFactory; import '../../common/text_element.dart' show MaxWidthStrategy, TextDirection, TextElement; import '../../common/text_style.dart' show TextStyle; import '../../data/series.dart' show AccessorFn; import '../cartesian/axis/spec/axis_spec.dart' show TextStyleSpec; import '../common/chart_canvas.dart' show ChartCanvas; import 'arc_renderer_element.dart' show ArcRendererElement, ArcRendererElementList; import 'arc_renderer_decorator.dart' show ArcRendererDecorator; /// Renders labels for arc renderers. /// /// This decorator performs very basic label collision detection. If the y /// position of a label positioned outside collides with the previously drawn /// label (on the same side of the chart), then that label will be skipped. class ArcLabelDecorator extends ArcRendererDecorator { // Default configuration static const _defaultLabelPosition = ArcLabelPosition.auto; static const _defaultLabelPadding = 5; static final _defaultInsideLabelStyle = TextStyleSpec(fontSize: 12, color: Color.white); static final _defaultOutsideLabelStyle = TextStyleSpec(fontSize: 12, color: Color.black); static final _defaultLeaderLineStyle = ArcLabelLeaderLineStyleSpec( length: 20.0, thickness: 1.0, color: StyleFactory.style.arcLabelOutsideLeaderLine); static const _defaultShowLeaderLines = true; /// Configures [TextStyleSpec] for labels placed inside the arcs. final TextStyleSpec insideLabelStyleSpec; /// Configures [TextStyleSpec] for labels placed outside the arcs. final TextStyleSpec outsideLabelStyleSpec; /// Configures [ArcLabelLeaderLineStyleSpec] for leader lines for labels /// placed outside the arcs. final ArcLabelLeaderLineStyleSpec leaderLineStyleSpec; /// Configures where to place the label relative to the arcs. final ArcLabelPosition labelPosition; /// Space before and after the label text. final int labelPadding; /// Whether or not to draw leader lines for labels placed outside the arcs. final bool showLeaderLines; /// Render the labels on top of series data. @override final bool renderAbove = true; ArcLabelDecorator( {TextStyleSpec? insideLabelStyleSpec, TextStyleSpec? outsideLabelStyleSpec, ArcLabelLeaderLineStyleSpec? leaderLineStyleSpec, this.labelPosition = _defaultLabelPosition, this.labelPadding = _defaultLabelPadding, this.showLeaderLines = _defaultShowLeaderLines, Color? leaderLineColor}) : insideLabelStyleSpec = insideLabelStyleSpec ?? _defaultInsideLabelStyle, outsideLabelStyleSpec = outsideLabelStyleSpec ?? _defaultOutsideLabelStyle, leaderLineStyleSpec = leaderLineStyleSpec ?? _defaultLeaderLineStyle; @override void decorate(ArcRendererElementList arcElements, ChartCanvas canvas, GraphicsFactory graphicsFactory, {required Rectangle drawBounds, required double animationPercent, bool rtl = false}) { // Only decorate the arcs when animation is at 100%. if (animationPercent != 1.0) { return; } // Create [TextStyle] from [TextStyleSpec] to be used by all the elements. // The [GraphicsFactory] is needed so it can't be created earlier. final insideLabelStyle = _getTextStyle(graphicsFactory, insideLabelStyleSpec); final outsideLabelStyle = _getTextStyle(graphicsFactory, outsideLabelStyleSpec); // Track the Y position of the previous outside label for collision // detection purposes. num? previousOutsideLabelY; bool? previousLabelLeftOfChart; for (var element in arcElements.arcs) { final labelFn = element.series.labelAccessorFn; final datumIndex = element.index; final label = (labelFn != null) ? labelFn(datumIndex) : null; // If there are custom styles, use that instead of the default or the // style defined for the entire decorator. final datumInsideLabelStyle = _getDatumStyle( element.series.insideLabelStyleAccessorFn, datumIndex, graphicsFactory, defaultStyle: insideLabelStyle); final datumOutsideLabelStyle = _getDatumStyle( element.series.outsideLabelStyleAccessorFn, datumIndex, graphicsFactory, defaultStyle: outsideLabelStyle); // Skip calculation and drawing for this element if no label. if (label == null || label.isEmpty) { continue; } final arcAngle = element.endAngle - element.startAngle; final centerAngle = element.startAngle + (arcAngle / 2); final centerRadius = arcElements.innerRadius + ((arcElements.radius - arcElements.innerRadius) / 2); final innerPoint = Point( arcElements.center.x + arcElements.innerRadius * cos(centerAngle), arcElements.center.y + arcElements.innerRadius * sin(centerAngle)); final outerPoint = Point( arcElements.center.x + arcElements.radius * cos(centerAngle), arcElements.center.y + arcElements.radius * sin(centerAngle)); //final bounds = element.bounds; final bounds = Rectangle.fromPoints(innerPoint, outerPoint); // Get space available inside and outside the arc. final totalPadding = labelPadding * 2; final insideArcWidth = min( (((arcAngle * 180 / pi) / 360) * (2 * pi * centerRadius)).round(), (arcElements.radius - arcElements.innerRadius) - labelPadding) .round(); final leaderLineLength = showLeaderLines ? leaderLineStyleSpec.length : 0; final outsideArcWidth = ((drawBounds.width / 2) - bounds.width - totalPadding - leaderLineLength) .round(); final labelElement = graphicsFactory.createTextElement(label) ..maxWidthStrategy = MaxWidthStrategy.ellipsize; var calculatedLabelPosition = calculateLabelPosition( labelElement, datumInsideLabelStyle, insideArcWidth, outsideArcWidth, element, labelPosition); // Set the max width and text style. if (calculatedLabelPosition == ArcLabelPosition.inside) { labelElement.textStyle = datumInsideLabelStyle; labelElement.maxWidth = insideArcWidth; } else { // calculatedLabelPosition == LabelPosition.outside labelElement.textStyle = datumOutsideLabelStyle; labelElement.maxWidth = outsideArcWidth; } // Only calculate and draw label if there's actually space for the label. if (labelElement.maxWidth! > 0) { // Calculate the start position of label based on [labelAnchor]. if (calculatedLabelPosition == ArcLabelPosition.inside) { _drawInsideLabel(canvas, arcElements, labelElement, centerAngle); } else { final l = _drawOutsideLabel( canvas, drawBounds, arcElements, labelElement, centerAngle, previousOutsideLabelY, previousLabelLeftOfChart); // List destructuring. if (l != null) { previousLabelLeftOfChart = l[0] as bool; previousOutsideLabelY = l[1] as int; } } } } } @protected ArcLabelPosition calculateLabelPosition( TextElement labelElement, TextStyle labelStyle, int insideArcWidth, int outsideArcWidth, ArcRendererElement arcRendererelement, ArcLabelPosition labelPosition) { if (labelPosition == ArcLabelPosition.auto) { // For auto, first try to fit the text inside the arc. labelElement.textStyle = labelStyle; // A label fits if the space inside the arc is >= outside arc or if the // length of the text fits and the space. This is because if the arc has // more space than the outside, it makes more sense to place the label // inside the arc, even if the entire label does not fit. return (insideArcWidth >= outsideArcWidth || labelElement.measurement.horizontalSliceWidth < insideArcWidth) ? ArcLabelPosition.inside : ArcLabelPosition.outside; } else { return labelPosition; } } /// Helper function that converts [TextStyleSpec] to [TextStyle]. TextStyle _getTextStyle( GraphicsFactory graphicsFactory, TextStyleSpec labelSpec) { return graphicsFactory.createTextPaint() ..color = labelSpec.color ?? Color.black ..fontFamily = labelSpec.fontFamily ..fontSize = labelSpec.fontSize ?? 12 ..lineHeight = labelSpec.lineHeight; } /// Helper function to get datum specific style TextStyle _getDatumStyle(AccessorFn? labelFn, int? datumIndex, GraphicsFactory graphicsFactory, {required TextStyle defaultStyle}) { final styleSpec = (labelFn != null) ? labelFn(datumIndex) : null; return (styleSpec != null) ? _getTextStyle(graphicsFactory, styleSpec) : defaultStyle; } /// Draws a label inside of an arc. void _drawInsideLabel( ChartCanvas canvas, ArcRendererElementList arcElements, TextElement labelElement, double centerAngle) { // Center the label inside the arc. final labelRadius = arcElements.innerRadius + (arcElements.radius - arcElements.innerRadius) / 2; final labelX = (arcElements.center.x + labelRadius * cos(centerAngle)).round(); final labelY = (arcElements.center.y + labelRadius * sin(centerAngle) - insideLabelStyleSpec.fontSize! / 2) .round(); labelElement.textDirection = TextDirection.center; canvas.drawText(labelElement, labelX, labelY); } /// Draws a label outside of an arc. List? _drawOutsideLabel( ChartCanvas canvas, Rectangle drawBounds, ArcRendererElementList arcElements, TextElement labelElement, double centerAngle, num? previousOutsideLabelY, bool? previousLabelLeftOfChart) { final labelRadius = arcElements.radius + leaderLineStyleSpec.length / 2; final labelPoint = Point( arcElements.center.x + labelRadius * cos(centerAngle), arcElements.center.y + labelRadius * sin(centerAngle)); // Use the label's chart quandrant to determine whether it's rendered to the // right or left. final centerAbs = centerAngle.abs() % (2 * pi); final labelLeftOfChart = pi / 2 < centerAbs && centerAbs < pi * 3 / 2; // Shift the label horizontally away from the center of the chart. var labelX = labelLeftOfChart ? (labelPoint.x - labelPadding).round() : (labelPoint.x + labelPadding).round(); // Shift the label up by the size of the font. final labelY = (labelPoint.y - outsideLabelStyleSpec.fontSize! / 2).round(); // Outside labels should flow away from the center of the chart labelElement.textDirection = labelLeftOfChart ? TextDirection.rtl : TextDirection.ltr; // Skip this label if it collides with the previously drawn label. if (_detectOutsideLabelCollision(labelY, labelLeftOfChart, previousOutsideLabelY, previousLabelLeftOfChart)) { return null; } if (showLeaderLines) { final tailX = _drawLeaderLine(canvas, labelLeftOfChart, labelPoint, arcElements.radius, arcElements.center, centerAngle); // Shift the label horizontally by the length of the leader line. labelX = (labelX + tailX).round(); labelElement.maxWidth = (labelElement.maxWidth! - tailX).round(); } canvas.drawText(labelElement, labelX, labelY); // Return a structured list of values. return [labelLeftOfChart, labelY]; } /// Detects whether the current outside label collides with the previous label. bool _detectOutsideLabelCollision(num labelY, bool labelLeftOfChart, num? previousOutsideLabelY, bool? previousLabelLeftOfChart) { var collides = false; // Given that labels are vertically centered, we can assume they will // collide if the current label's Y coordinate +/- the font size // crosses past the Y coordinate of the previous label drawn on the // same side of the chart. if (previousOutsideLabelY != null && labelLeftOfChart == previousLabelLeftOfChart) { if (labelY > previousOutsideLabelY) { if (labelY - outsideLabelStyleSpec.fontSize! <= previousOutsideLabelY) { collides = true; } } else { if (labelY + outsideLabelStyleSpec.fontSize! >= previousOutsideLabelY) { collides = true; } } } return collides; } /// Draws a leader line for the current arc. double _drawLeaderLine( ChartCanvas canvas, bool labelLeftOfChart, Point labelPoint, double radius, Point arcCenterPoint, double centerAngle) { final tailX = (labelLeftOfChart ? -1 : 1) * leaderLineStyleSpec.length; final leaderLineTailPoint = Point(labelPoint.x + tailX, labelPoint.y); final centerRadius = radius - leaderLineStyleSpec.length / 2; final leaderLineStartPoint = Point( arcCenterPoint.x + centerRadius * cos(centerAngle), arcCenterPoint.y + centerRadius * sin(centerAngle)); canvas.drawLine( points: [ leaderLineStartPoint, labelPoint, leaderLineTailPoint, ], stroke: leaderLineStyleSpec.color, strokeWidthPx: leaderLineStyleSpec.thickness); return tailX; } } /// Configures where to place the label relative to the arcs. enum ArcLabelPosition { /// Automatically try to place the label inside the arc first and place it on /// the outside of the space available outside the arc is greater than space /// available inside the arc. auto, /// Always place label on the outside. outside, /// Always place label on the inside. inside } /// Style configuration for leader lines. @immutable class ArcLabelLeaderLineStyleSpec { final Color color; final double length; final double thickness; ArcLabelLeaderLineStyleSpec({ required this.color, required this.length, required this.thickness, }); @override bool operator ==(Object other) { return other is ArcLabelLeaderLineStyleSpec && color == other.color && thickness == other.thickness && length == other.length; } @override int get hashCode { var hashcode = color.hashCode; hashcode = (hashcode * 37) + thickness.hashCode; hashcode = (hashcode * 37) + length.hashCode; return hashcode; } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/pie/arc_renderer.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:collection' show LinkedHashMap; import 'dart:math' show max, pi, Point; import 'package:collection/collection.dart' show IterableExtension; import '../../common/style/style_factory.dart' show StyleFactory; import '../../data/series.dart' show AttributeKey; import '../common/chart_canvas.dart' show ChartCanvas; import '../common/processed_series.dart' show ImmutableSeries, MutableSeries; import 'arc_renderer_config.dart' show ArcRendererConfig; import 'arc_renderer_decorator.dart' show ArcRendererDecorator; import 'arc_renderer_element.dart' show ArcRendererElement, AnimatedArcList, AnimatedArc; import 'base_arc_renderer.dart'; const arcElementsKey = AttributeKey>>('ArcRenderer.elements'); class ArcRenderer extends BaseArcRenderer { final ArcRendererConfig config; final List> arcRendererDecorators; /// Store a map of series drawn on the chart, mapped by series name. /// /// [LinkedHashMap] is used to render the series on the canvas in the same /// order as the data was given to the chart. // ignore: prefer_collection_literals, https://github.com/dart-lang/linter/issues/1649 final _seriesArcMap = LinkedHashMap>(); // Store a list of arcs that exist in the series data. // // This list will be used to remove any [AnimatedArc] that were rendered in // previous draw cycles, but no longer have a corresponding datum in the new // data. final _currentKeys = []; factory ArcRenderer({String? rendererId, ArcRendererConfig? config}) { return ArcRenderer._internal( rendererId: rendererId ?? 'line', config: config ?? ArcRendererConfig()); } ArcRenderer._internal({required String rendererId, required this.config}) : arcRendererDecorators = config.arcRendererDecorators, super(config: config, rendererId: rendererId); @override void preprocessSeries(List> seriesList) { seriesList.forEach((MutableSeries series) { var elements = >[]; var domainFn = series.domainFn; var measureFn = series.measureFn; final seriesMeasureTotal = series.seriesMeasureTotal; // On the canvas, arc measurements are defined as angles from the positive // x axis. Start our first slice at the positive y axis instead. var startAngle = config.startAngle; var arcLength = config.arcLength; var totalAngle = 0.0; var measures = []; if (series.data.isEmpty) { // If the series has no data, generate an empty arc element that // occupies the entire chart. // // Use a tiny epsilon difference to ensure that the canvas renders a // "full" circle, in the correct direction. var angle = arcLength == 2 * pi ? arcLength * .999999 : arcLength; var endAngle = startAngle + angle; var details = ArcRendererElement( startAngle: startAngle, endAngle: endAngle, index: 0, key: 0, series: series, ); elements.add(details); } else { // Otherwise, generate an arc element per datum. for (var arcIndex = 0; arcIndex < series.data.length; arcIndex++) { var domain = domainFn(arcIndex); var measure = measureFn(arcIndex); measures.add(measure); if (measure == null) { continue; } final percentOfSeries = measure / seriesMeasureTotal; var angle = arcLength * percentOfSeries; var endAngle = startAngle + angle; var details = ArcRendererElement( startAngle: startAngle, endAngle: endAngle, index: arcIndex, key: arcIndex, domain: domain, series: series, ); elements.add(details); // Update the starting angle for the next datum in the series. startAngle = endAngle; totalAngle = totalAngle + angle; } } series.setAttr(arcElementsKey, elements); }); } @override void update(List> seriesList, bool isAnimatingThisDraw) { _currentKeys.clear(); final bounds = chart!.drawAreaBounds; final center = Point((bounds.left + bounds.width / 2).toDouble(), (bounds.top + bounds.height / 2).toDouble()); final radius = bounds.height < bounds.width ? (bounds.height / 2).toDouble() : (bounds.width / 2).toDouble(); if (config.arcRatio != null && (config.arcRatio! < 0 || config.arcRatio! > 1)) { throw ArgumentError('arcRatio must be between 0 and 1'); } final innerRadius = _calculateInnerRadius(radius); seriesList.forEach((ImmutableSeries series) { var colorFn = series.colorFn; var arcListKey = series.id; var arcList = _seriesArcMap.putIfAbsent(arcListKey, () => AnimatedArcList()); var elementsList = series.getAttr(arcElementsKey) as List>; if (series.data.isEmpty) { // If the series is empty, set up the "no data" arc element. This should // occupy the entire chart, and use the chart style's no data color. final details = elementsList[0]; var arcKey = '__no_data__'; // If we already have an AnimatingArc for that index, use it. var animatingArc = arcList.arcs.firstWhereOrNull((arc) => arc.key == arcKey); arcList.center = center; arcList.radius = radius; arcList.innerRadius = innerRadius; arcList.series = series; arcList.stroke = config.noDataColor; arcList.strokeWidthPx = 0.0; // If we don't have any existing arc element, create a new arc. Unlike // real arcs, we should not animate the no data state in from 0. if (animatingArc == null) { animatingArc = AnimatedArc(arcKey, null, null); arcList.arcs.add(animatingArc); } else { animatingArc.datum = null; animatingArc.domain = null; } // Update the set of arcs that still exist in the series data. _currentKeys.add(arcKey); // Get the arcElement we are going to setup. // Optimization to prevent allocation in non-animating case. final arcElement = ArcRendererElement( color: config.noDataColor, startAngle: details.startAngle, endAngle: details.endAngle, series: series, ); animatingArc.setNewTarget(arcElement); } else { var previousEndAngle = config.startAngle; for (var arcIndex = 0; arcIndex < series.data.length; arcIndex++) { final Object? datum = series.data[arcIndex]; final details = elementsList[arcIndex]; final domainValue = details.domain; var arcKey = '${series.id}__$domainValue'; // If we already have an AnimatingArc for that index, use it. var animatingArc = arcList.arcs.firstWhereOrNull((arc) => arc.key == arcKey); arcList.center = center; arcList.radius = radius; arcList.innerRadius = innerRadius; arcList.series = series; arcList.stroke = config.stroke; arcList.strokeWidthPx = config.strokeWidthPx; // If we don't have any existing arc element, create a new arc and // have it animate in from the position of the previous arc's end // angle. If there were no previous arcs, then animate everything in // from 0. if (animatingArc == null) { animatingArc = AnimatedArc(arcKey, datum, domainValue) ..setNewTarget(ArcRendererElement( color: colorFn!(arcIndex), startAngle: previousEndAngle, endAngle: previousEndAngle, index: arcIndex, series: series, )); arcList.arcs.add(animatingArc); } else { animatingArc.datum = datum; previousEndAngle = animatingArc.previousArcEndAngle ?? 0.0; } animatingArc.domain = domainValue; // Update the set of arcs that still exist in the series data. _currentKeys.add(arcKey); // Get the arcElement we are going to setup. // Optimization to prevent allocation in non-animating case. final arcElement = ArcRendererElement( color: colorFn!(arcIndex), startAngle: details.startAngle, endAngle: details.endAngle, index: arcIndex, series: series, ); animatingArc.setNewTarget(arcElement); } } }); // Animate out arcs that don't exist anymore. _seriesArcMap.forEach((String key, AnimatedArcList arcList) { for (var arcIndex = 0; arcIndex < arcList.arcs.length; arcIndex++) { final arc = arcList.arcs[arcIndex]; final arcStartAngle = arc.previousArcStartAngle; if (_currentKeys.contains(arc.key) != true) { // Default to animating out to the top of the chart, clockwise, if // there are no arcs that start past this arc. var targetArcAngle = (2 * pi) + config.startAngle; // Find the nearest start angle of the next arc that still exists in // the data. for (final nextArc in arcList.arcs.where((arc) => _currentKeys.contains(arc.key))) { final nextArcStartAngle = nextArc.newTargetArcStartAngle; if (arcStartAngle! < nextArcStartAngle! && nextArcStartAngle < targetArcAngle) { targetArcAngle = nextArcStartAngle; } } arc.animateOut(targetArcAngle); } } }); } @override void paint(ChartCanvas canvas, double animationPercent) { // Clean up the arcs that no longer exist. if (animationPercent == 1.0) { final keysToRemove = []; _seriesArcMap.forEach((String key, AnimatedArcList arcList) { arcList.arcs.removeWhere((AnimatedArc arc) => arc.animatingOut); if (arcList.arcs.isEmpty) { keysToRemove.add(key); } }); keysToRemove.forEach(_seriesArcMap.remove); } super.paint(canvas, animationPercent); } /// Assigns colors to series that are missing their colorFn. @override void assignMissingColors(Iterable> seriesList, {required bool emptyCategoryUsesSinglePalette}) { var maxMissing = 0; seriesList.forEach((series) { if (series.colorFn == null) { maxMissing = max(maxMissing, series.data.length); } }); if (maxMissing > 0) { final colorPalettes = StyleFactory.style.getOrderedPalettes(1); final colorPalette = colorPalettes[0].makeShades(maxMissing); seriesList.forEach((series) { series.colorFn ??= (index) => colorPalette[index!]; }); } } /// Calculates the size of the inner pie radius given the outer radius. double _calculateInnerRadius(double radius) { // arcRatio trumps arcWidth. If neither is defined, then inner radius is 0. if (config.arcRatio != null) { return max(radius - radius * config.arcRatio!, 0.0).toDouble(); } else if (config.arcWidth != null) { return max(radius - config.arcWidth!, 0.0).toDouble(); } else { return 0.0; } } @override List> getArcLists({String? seriesId}) { if (seriesId == null) { return _seriesArcMap.values.toList(); } final arcList = _seriesArcMap[seriesId]; if (arcList == null) return >[]; return [arcList]; } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/pie/arc_renderer_config.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math' show pi; import '../../common/symbol_renderer.dart'; import '../layout/layout_view.dart' show LayoutViewPaintOrder; import 'arc_renderer.dart' show ArcRenderer; import 'arc_renderer_decorator.dart' show ArcRendererDecorator; import 'base_arc_renderer_config.dart' show BaseArcRendererConfig; /// Configuration for an [ArcRenderer]. class ArcRendererConfig extends BaseArcRendererConfig { ArcRendererConfig( {String? customRendererId, double arcLength = 2 * pi, List> arcRendererDecorators = const [], double? arcRatio, int? arcWidth, int layoutPaintOrder = LayoutViewPaintOrder.arc, int minHoleWidthForCenterContent = 30, double startAngle = -pi / 2, double strokeWidthPx = 2.0, SymbolRenderer? symbolRenderer}) : super( customRendererId: customRendererId, arcLength: arcLength, arcRatio: arcRatio, arcWidth: arcWidth, layoutPaintOrder: layoutPaintOrder, minHoleWidthForCenterContent: minHoleWidthForCenterContent, startAngle: startAngle, strokeWidthPx: strokeWidthPx, arcRendererDecorators: arcRendererDecorators); @override ArcRenderer build() { return ArcRenderer(config: this, rendererId: customRendererId); } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/pie/arc_renderer_decorator.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math' show Rectangle; import '../../common/graphics_factory.dart' show GraphicsFactory; import '../common/chart_canvas.dart' show ChartCanvas; import 'arc_renderer_element.dart' show ArcRendererElementList; /// Decorates arcs after the arcs have already been painted. abstract class ArcRendererDecorator { const ArcRendererDecorator(); /// Configures whether the decorator should be rendered on top of or below /// series data elements. bool get renderAbove; void decorate(ArcRendererElementList arcElements, ChartCanvas canvas, GraphicsFactory graphicsFactory, {required Rectangle drawBounds, required double animationPercent, bool rtl = false}); } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/pie/arc_renderer_element.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'package:meta/meta.dart' show protected; import 'dart:math' show Point; import '../../common/color.dart' show Color; import '../common/processed_series.dart' show ImmutableSeries; import '../common/chart_canvas.dart' show getAnimatedColor; class ArcRendererElementList { final List> arcs; final Point center; final double innerRadius; final double radius; final double startAngle; /// Color of separator lines between arcs. final Color? stroke; /// Stroke width of separator lines between arcs. final double? strokeWidthPx; ArcRendererElementList({ required this.arcs, required this.center, required this.innerRadius, required this.radius, required this.startAngle, this.stroke, this.strokeWidthPx, }); } class ArcRendererElement { double startAngle; double endAngle; Color? color; int? index; num? key; D? domain; ImmutableSeries series; ArcRendererElement({ required this.startAngle, required this.endAngle, this.color, this.index, this.key, this.domain, required this.series, }); ArcRendererElement clone() { return ArcRendererElement( startAngle: startAngle, endAngle: endAngle, color: color == null ? null : Color.fromOther(color: color!), index: index, key: key, series: series, ); } void updateAnimationPercent(ArcRendererElement previous, ArcRendererElement target, double animationPercent) { startAngle = ((target.startAngle - previous.startAngle) * animationPercent) + previous.startAngle; endAngle = ((target.endAngle - previous.endAngle) * animationPercent) + previous.endAngle; color = getAnimatedColor(previous.color!, target.color!, animationPercent); } } @protected class AnimatedArcList { final arcs = >[]; Point? center; double? innerRadius; double? radius; ImmutableSeries? series; /// Color of separator lines between arcs. Color? stroke; /// Stroke width of separator lines between arcs. double? strokeWidthPx; } @protected class AnimatedArc { final String key; Object? datum; D? domain; ArcRendererElement? _previousArc; late ArcRendererElement _targetArc; ArcRendererElement? _currentArc; // Flag indicating whether this arc is being animated out of the chart. bool animatingOut = false; AnimatedArc(this.key, this.datum, this.domain); /// Animates a arc that was removed from the series out of the view. /// /// This should be called in place of "setNewTarget" for arcs that represent /// data that has been removed from the series. /// /// Animates the angle of the arc to [endAngle], in radians. void animateOut(double endAngle) { var newTarget = _currentArc!.clone(); // Animate the arc out by setting the angles to 0. newTarget.startAngle = endAngle; newTarget.endAngle = endAngle; setNewTarget(newTarget); animatingOut = true; } void setNewTarget(ArcRendererElement newTarget) { animatingOut = false; _currentArc ??= newTarget.clone(); _previousArc = _currentArc!.clone(); _targetArc = newTarget; } ArcRendererElement getCurrentArc(double animationPercent) { if (animationPercent == 1.0 || _previousArc == null) { _currentArc = _targetArc; _previousArc = _targetArc; return _currentArc!; } _currentArc! .updateAnimationPercent(_previousArc!, _targetArc, animationPercent); return _currentArc!; } /// Returns the [startAngle] of the new target element, without updating /// animation state. double? get newTargetArcStartAngle => _targetArc.startAngle; /// Returns the [endAngle] of the new target element, without updating /// animation state. double? get currentArcEndAngle => _currentArc?.endAngle; /// Returns the [startAngle] of the currently rendered element, without /// updating animation state. double? get currentArcStartAngle => _currentArc?.startAngle; /// Returns the [endAngle] of the new target element, without updating /// animation state. double? get previousArcEndAngle => _previousArc?.endAngle; /// Returns the [startAngle] of the previously rendered element, without /// updating animation state. double? get previousArcStartAngle => _previousArc?.startAngle; } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/pie/base_arc_renderer.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math' show atan2, cos, sin, pi, Point, Rectangle; import 'package:meta/meta.dart' show protected; import '../../common/math.dart' show NullablePoint; import '../../data/series.dart' show AttributeKey; import '../common/base_chart.dart' show BaseChart; import '../common/canvas_shapes.dart' show CanvasPieSlice, CanvasPie; import '../common/chart_canvas.dart' show ChartCanvas; import '../common/datum_details.dart' show DatumDetails; import '../common/processed_series.dart' show MutableSeries; import '../common/series_datum.dart' show SeriesDatum; import '../common/series_renderer.dart' show BaseSeriesRenderer; import 'arc_renderer_config.dart' show ArcRendererConfig; import 'arc_renderer_decorator.dart' show ArcRendererDecorator; import 'arc_renderer_element.dart' show ArcRendererElement, ArcRendererElementList, AnimatedArcList, AnimatedArc; import 'base_arc_renderer_config.dart' show BaseArcRendererConfig; const arcElementsKey = AttributeKey>>('ArcRenderer.elements'); abstract class BaseArcRenderer extends BaseSeriesRenderer { // Constant used in the calculation of [centerContentBounds], calculated // once to save runtime cost. static final _cosPIOver4 = cos(pi / 4); final BaseArcRendererConfig config; final List> arcRendererDecorators; @protected BaseChart? chart; BaseArcRenderer({required this.config, required String rendererId}) : arcRendererDecorators = config.arcRendererDecorators, super( rendererId: rendererId, layoutPaintOrder: config.layoutPaintOrder, symbolRenderer: config.symbolRenderer); @override void onAttach(BaseChart chart) { super.onAttach(chart); this.chart = chart; } @override void configureSeries(List> seriesList) { assignMissingColors(seriesList, emptyCategoryUsesSinglePalette: false); } bool get isRtl => chart?.context.isRtl ?? false; /// Gets a bounding box for the largest center content card that can fit /// inside the hole of the chart. /// /// If the inner radius of the arcs is smaller than /// [ArcRendererConfig.minHoleWidthForCenterContent], this will return a /// rectangle of 0 width and height to indicate that no card can fit inside /// the chart. Rectangle get centerContentBounds { // Grab the first arcList from the animated set. var arcLists = getArcLists(); var arcList = arcLists.isNotEmpty ? arcLists.first : null; // No card should be visible if the hole in the chart is too small. if (arcList == null || arcList.innerRadius! < config.minHoleWidthForCenterContent) { // Return default bounds of 0 size. final bounds = chart!.drawAreaBounds; return Rectangle((bounds.left + bounds.width / 2).round(), (bounds.top + bounds.height / 2).round(), 0, 0); } // Fix the height and width of the center content div to the maximum box // size that will fit within the pie's inner radius. final width = (_cosPIOver4 * arcList.innerRadius!).floor(); return Rectangle((arcList.center!.x - width).round(), (arcList.center!.y - width).round(), width * 2, width * 2); } /// Returns an expanded [DatumDetails] object that contains location data. DatumDetails getExpandedDatumDetails(SeriesDatum seriesDatum) { final series = seriesDatum.series; final Object? datum = seriesDatum.datum; final datumIndex = seriesDatum.index; final domain = series.domainFn(datumIndex); final measure = series.measureFn(datumIndex); final color = series.colorFn!(datumIndex); final chartPosition = _getChartPosition(series.id, '${series.id}__$domain'); return DatumDetails( datum: datum, domain: domain, measure: measure, series: series, color: color, chartPosition: NullablePoint.from(chartPosition)); } /// Returns the List of AnimatedArcList associated with the renderer. The Pie /// Chart has one AnimatedArcList and the Sunburst chart usually has multiple /// elements. @protected List> getArcLists({String? seriesId}); /// Returns the chart position for a given datum by series ID and domain /// value. /// /// [seriesId] the series ID. /// /// [key] the key in the current animated arc list. Point? _getChartPosition(String seriesId, String key) { Point? chartPosition; final arcLists = getArcLists(seriesId: seriesId); if (arcLists.isEmpty) { return chartPosition; } for (var arcList in arcLists) { for (final arc in arcList.arcs) { if (arc.key == key) { // Now that we have found the matching arc, calculate the center // point halfway between the inner and outer radius, and the start // and end angles. final centerAngle = arc.currentArcStartAngle! + (arc.currentArcEndAngle! - arc.currentArcStartAngle!) / 2; final centerPointRadius = arcList.innerRadius! + (arcList.radius! - arcList.innerRadius!) / 2; chartPosition = Point( centerPointRadius * cos(centerAngle) + arcList.center!.x, centerPointRadius * sin(centerAngle) + arcList.center!.y); break; } } } return chartPosition; } @override void paint(ChartCanvas canvas, double animationPercent) { final arcLists = getArcLists(); var arcListToElementsList = {}; for (var arcList in arcLists) { final elementsList = ArcRendererElementList( arcs: >[], center: arcList.center!, innerRadius: arcList.innerRadius!, radius: arcList.radius!, startAngle: config.startAngle, stroke: arcList.stroke, strokeWidthPx: arcList.strokeWidthPx, ); // Decorate the arcs with decorators that should appear below the main // series data. arcRendererDecorators .where((decorator) => !decorator.renderAbove) .forEach((decorator) { decorator.decorate(elementsList, canvas, graphicsFactory!, drawBounds: drawBounds!, animationPercent: animationPercent, rtl: isRtl); }); arcListToElementsList[arcList] = elementsList; } for (var arcList in arcLists) { final circleSectors = []; arcList.arcs .map>((AnimatedArc animatingArc) => animatingArc.getCurrentArc(animationPercent)) .forEach((arc) { circleSectors .add(CanvasPieSlice(arc.startAngle, arc.endAngle, fill: arc.color)); arcListToElementsList[arcList].arcs.add(arc); }); // Draw the arcs. canvas.drawPie(CanvasPie( circleSectors, arcList.center!, arcList.radius!, arcList.innerRadius!, stroke: arcList.stroke, strokeWidthPx: arcList.strokeWidthPx ?? 0)); } // Decorate the arcs with decorators that should appear above the main // series data. This is the typical place for labels. for (var arcList in arcLists) { arcRendererDecorators .where((decorator) => decorator.renderAbove) .forEach((decorator) { decorator.decorate( arcListToElementsList[arcList], canvas, graphicsFactory!, drawBounds: drawBounds!, animationPercent: animationPercent, rtl: isRtl); }); } } @override List> getNearestDatumDetailPerSeries( Point chartPoint, bool byDomain, Rectangle? boundsOverride, { bool selectOverlappingPoints = false, bool selectExactEventLocation = false, }) { final nearest = >[]; // Was it even in the component bounds? if (!isPointWithinBounds(chartPoint, boundsOverride)) { return nearest; } final arcLists = getArcLists(); for (var arcList in arcLists) { if (arcList.series!.overlaySeries) { return nearest; } final center = arcList.center!; final innerRadius = arcList.innerRadius!; final radius = arcList.radius!; final distance = center.distanceTo(chartPoint); // Calculate the angle of [chartPoint] from the center of the arcs. var chartPointAngle = atan2(chartPoint.y - center.y, chartPoint.x - center.x); // atan2 returns NaN if we are at the exact center of the circle. if (chartPointAngle.isNaN) { chartPointAngle = config.startAngle; } // atan2 returns an angle in the range -PI..PI, from the positive x-axis. // Our arcs start at the positive y-axis, in the range -PI/2..3PI/2. Thus, // if angle is in the -x, +y section of the circle, we need to adjust the // angle into our range. if (chartPointAngle < config.startAngle && chartPointAngle < 0) { chartPointAngle = 2 * pi + chartPointAngle; } arcList.arcs.forEach((AnimatedArc arc) { if (innerRadius <= distance && distance <= radius && arc.currentArcStartAngle! <= chartPointAngle && chartPointAngle <= arc.currentArcEndAngle!) { nearest.add(DatumDetails( series: arcList.series, datum: arc.datum, domain: arc.domain, domainDistance: 0.0, measureDistance: 0.0, )); } }); } return nearest; } @override DatumDetails addPositionToDetailsForSeriesDatum( DatumDetails details, SeriesDatum seriesDatum) { final chartPosition = _getChartPosition(details.series!.id, details.domain.toString()); return DatumDetails.from(details, chartPosition: NullablePoint.from(chartPosition)); } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/pie/base_arc_renderer_config.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math' show pi; import '../../common/color.dart' show Color; import '../../common/style/style_factory.dart' show StyleFactory; import '../../common/symbol_renderer.dart'; import '../common/series_renderer_config.dart' show RendererAttributes, SeriesRendererConfig; import '../layout/layout_view.dart' show LayoutViewConfig, LayoutViewPaintOrder; import 'arc_renderer_decorator.dart' show ArcRendererDecorator; /// The base renderer config for arc renderer and sunburst arc renderer. abstract class BaseArcRendererConfig extends LayoutViewConfig implements SeriesRendererConfig { @override final String? customRendererId; /// List of decorators applied to rendered arcs. final List> arcRendererDecorators; @override final SymbolRenderer symbolRenderer; @override final rendererAttributes = RendererAttributes(); /// Total arc length, in radians. /// /// The default arcLength is 2π. final double arcLength; /// If set, configures the arcWidth to be a percentage of the radius. final double? arcRatio; /// Fixed width of the arc within the radius. /// /// If arcRatio is set, this value will be ignored. final int? arcWidth; /// The order to paint this renderer on the canvas. final int layoutPaintOrder; /// Minimum radius in pixels of the hole in a donut chart for center content /// to appear. final int minHoleWidthForCenterContent; /// Start angle for pie slices, in radians. /// /// Angles are defined from the positive x axis in Cartesian space. The /// default startAngle is -π/2. final double startAngle; /// Stroke width of the border of the arcs. final double strokeWidthPx; /// Stroke color of the border of the arcs. final Color stroke; /// Color of the "no data" state for the chart, used when an empty series is /// drawn. final Color noDataColor; BaseArcRendererConfig( {this.customRendererId, this.arcLength = 2 * pi, this.arcRendererDecorators = const [], this.arcRatio, this.arcWidth, this.layoutPaintOrder = LayoutViewPaintOrder.arc, this.minHoleWidthForCenterContent = 30, this.startAngle = -pi / 2, this.strokeWidthPx = 2.0, SymbolRenderer? symbolRenderer}) : noDataColor = StyleFactory.style.noDataColor, stroke = StyleFactory.style.arcStrokeColor, symbolRenderer = symbolRenderer ?? CircleSymbolRenderer(); } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/pie/pie_chart.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math' show Rectangle; import '../common/base_chart.dart' show BaseChart; import '../common/datum_details.dart' show DatumDetails; import '../common/processed_series.dart' show MutableSeries; import '../common/selection_model/selection_model.dart' show SelectionModelType; import '../common/series_renderer.dart' show rendererIdKey, SeriesRenderer; import '../layout/layout_config.dart' show LayoutConfig, MarginSpec; import 'arc_renderer.dart' show ArcRenderer; class PieChart extends BaseChart { static final _defaultLayoutConfig = LayoutConfig( topSpec: MarginSpec.fromPixel(minPixel: 20), bottomSpec: MarginSpec.fromPixel(minPixel: 20), leftSpec: MarginSpec.fromPixel(minPixel: 20), rightSpec: MarginSpec.fromPixel(minPixel: 20), ); PieChart({LayoutConfig? layoutConfig}) : super(layoutConfig: layoutConfig ?? _defaultLayoutConfig); @override void drawInternal(List> seriesList, {bool? skipAnimation, bool? skipLayout}) { if (seriesList.length > 1) { throw ArgumentError('PieChart can only render a single series'); } super.drawInternal(seriesList, skipAnimation: skipAnimation, skipLayout: skipLayout); } @override void updateConfig(LayoutConfig? layoutConfig) { super.updateConfig(layoutConfig ?? _defaultLayoutConfig); } @override SeriesRenderer makeDefaultRenderer() { return ArcRenderer()..rendererId = SeriesRenderer.defaultRendererId; } /// Returns a list of datum details from selection model of [type]. @override List> getDatumDetails(SelectionModelType type) { final entries = >[]; for (final seriesDatum in getSelectionModel(type).selectedDatum) { final rendererId = seriesDatum.series.getAttr(rendererIdKey); final renderer = getSeriesRenderer(rendererId); // This should never happen. if (renderer is! ArcRenderer) { continue; } final details = renderer.getExpandedDatumDetails(seriesDatum); if (details != null) { entries.add(details); } } return entries; } Rectangle? get centerContentBounds { final defaultRenderer = this.defaultRenderer; if (defaultRenderer is ArcRenderer) { return defaultRenderer.centerContentBounds; } else { return null; } } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/scatter_plot/comparison_points_decorator.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math' show Point, Rectangle; import 'package:meta/meta.dart' show protected; import '../../common/graphics_factory.dart' show GraphicsFactory; import '../../common/symbol_renderer.dart'; import '../common/chart_canvas.dart' show ChartCanvas; import 'point_renderer.dart' show PointRendererElement; import 'point_renderer_decorator.dart' show PointRendererDecorator; /// Decorates a point chart by drawing a shape connecting the domain and measure /// data bounds. /// /// The line will connect the point (domainLowerBound, measureLowerBound) to the /// point (domainUpperBound, measureUpperBound). class ComparisonPointsDecorator extends PointRendererDecorator { /// Renderer used to draw the points. Defaults to a line with circular end /// caps. final PointSymbolRenderer symbolRenderer; /// Render the bounds shape underneath series data. @override final bool renderAbove = false; ComparisonPointsDecorator({PointSymbolRenderer? symbolRenderer}) : symbolRenderer = symbolRenderer ?? CylinderSymbolRenderer(); @override void decorate(PointRendererElement pointElement, ChartCanvas canvas, GraphicsFactory graphicsFactory, {required Rectangle drawBounds, required double animationPercent, bool rtl = false}) { final points = computeBoundedPointsForElement(pointElement, drawBounds); if (points == null) { return; } final color = pointElement.color!.lighter; symbolRenderer.paint(canvas, points[0], pointElement.boundsLineRadiusPx, fillColor: color, strokeColor: color, p2: points[1]); } /// Computes end points for the [pointElement]'s lower and upper data bounds. /// /// This will compute two points representing the end points of the symbol, /// from (xLower, yLower) to (xUpper, yUpper). The end points will be clamped /// along the line so that it is fully contained within [drawBounds]. /// /// Returns null if [pointElement] is missing any of the data bounds, or if /// the line connecting them is located entirely outside of [drawBounds]. @protected List>? computeBoundedPointsForElement( PointRendererElement pointElement, Rectangle drawBounds) { // All bounds points must be defined for a valid comparison point to be // drawn. final point = pointElement.point!; if (point.xLower == null || point.xUpper == null || point.yLower == null || point.yUpper == null) { return null; } // Construct the points that describe our line p1p2. var p1 = Point(point.xLower!, point.yLower!); var p2 = Point(point.xUpper!, point.yUpper!); // First check to see if there is no intersection at all between the line // p1p2 and [drawBounds]. final dataBoundsRect = Rectangle.fromPoints(p1, p2); if (!drawBounds.intersects(dataBoundsRect)) { return null; } // Line with end points [p1] and [p2]. final p1p2 = _Line.fromPoints(p1, p2); // Next, slide p1 along the line p1p2 towards the edge of the draw area if // the point is located outside of it. if (!drawBounds.containsPoint(p1)) { final p = _clampPointAlongLineToBoundingBox(p1, p1p2, drawBounds); if (p != null) { p1 = p; } } // Next, slide p2 along the line p1p2 towards the edge of the draw area if // the point is located outside of it. if (!drawBounds.containsPoint(p2)) { final p = _clampPointAlongLineToBoundingBox(p2, p1p2, drawBounds); if (p != null) { p2 = p; } } return [p1, p2]; } /// Slide the given point [p1] along the line [line], such that it intersects /// the nearest edge of [bounds]. /// /// This method assumes that we have already verified that the [line] /// intercepts the [bounds] somewhere. Point? _clampPointAlongLineToBoundingBox( Point p1, _Line line, Rectangle bounds) { // The top and bottom edges of the bounds box describe two horizontal lines, // with equations y = bounds.top and y = bounds.bottom. We can pass these // into a standard line interception method to find our point. if (p1.y < bounds.top) { final p = line.intersection(_Line(0.0, bounds.top.toDouble())); if (p != null && bounds.containsPoint(p)) { return p; } } if (p1.y > bounds.bottom) { final p = line.intersection(_Line(0.0, bounds.bottom.toDouble())); if (p != null && bounds.containsPoint(p)) { return p; } } // The left and right edges of the bounds box describe two vertical lines, // with equations x = bounds.right and x = bounds.left. To find the // intersection, we just need to solve for y in our line described by // [slope] and [yIntercept]: // // y = slope * x + yIntercept if (p1.x < bounds.left) { final p = line.intersection(_Line.fromVertical(bounds.left.toDouble())); if (p != null && bounds.containsPoint(p)) { return p; } } if (p1.x > bounds.right) { final p = line.intersection(_Line.fromVertical(bounds.right.toDouble())); if (p != null && bounds.containsPoint(p)) { return p; } } return null; } } /// Describes a simple line with the equation y = slope * x + yIntercept. class _Line { /// Slope of the line. double? slope; /// y-intercept of the line (i.e. the y value of the point where the line /// intercepts the y axis). double? yIntercept; /// x-intercept of the line (i.e. the x value of the point where the line /// intercepts the x axis). This is normally only needed for vertical lines, /// which have no slope. double? xIntercept; /// True if this line is a vertical line, of the form x = [xIntercept]. bool get vertical => slope == null && xIntercept != null; _Line(this.slope, this.yIntercept, [this.xIntercept]); /// Creates a line with end points [p1] and [p2]. factory _Line.fromPoints(Point p1, Point p2) { // Handle vertical lines. if (p1.x == p2.x) { return _Line.fromVertical(p1.x); } // Slope of the line p1p2. final m = ((p2.y - p1.y) / (p2.x - p1.x)).toDouble(); // y-intercept of the line p1p2. final b = (p1.y - (m * p1.x)).toDouble(); return _Line(m, b); } /// Creates a vertical line, with the question x = [xIntercept]. factory _Line.fromVertical(num xIntercept) { return _Line(null, null, xIntercept.toDouble()); } /// Computes the intersection of `this` and [other]. /// /// Returns the intersection of this and `other`, or `null` if they don't /// intersect. Point? intersection(_Line other) { // Parallel lines have no intersection. if (slope == other.slope || (vertical && other.vertical)) { return null; } // If the other line is a vertical line (has undefined slope), then we can // just plug its xIntercept value into the line equation as x and solve for // y. if (other.vertical) { return Point( other.xIntercept!, slope! * other.xIntercept! + yIntercept!); } // If this line is a vertical line (has undefined slope), then we can just // plug its xIntercept value into the line equation as x and solve for y. if (vertical) { return Point( xIntercept!, other.slope! * xIntercept! + other.yIntercept!); } // Now that we know that we have intersecting, non-vertical lines, compute // the intersection. final x = (other.yIntercept! - yIntercept!) / (slope! - other.slope!); final y = slope! * (other.yIntercept! - yIntercept!) / (slope! - other.slope!) + yIntercept!; return Point(x, y); } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/scatter_plot/point_renderer.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:collection' show LinkedHashMap; import 'dart:math' show min, Point, Rectangle; import 'package:collection/collection.dart' show IterableExtension; import 'package:meta/meta.dart' show protected; import 'package:vector_math/vector_math.dart' show Vector2; import '../../common/color.dart' show Color; import '../../common/math.dart' show distanceBetweenPointAndLineSegment, NullablePoint; import '../../common/symbol_renderer.dart' show CircleSymbolRenderer, SymbolRenderer; import '../../data/series.dart' show AccessorFn, AttributeKey, TypedAccessorFn; import '../cartesian/axis/axis.dart' show ImmutableAxis, domainAxisKey, measureAxisKey; import '../cartesian/cartesian_renderer.dart' show BaseCartesianRenderer; import '../common/base_chart.dart' show BaseChart; import '../common/chart_canvas.dart' show ChartCanvas, getAnimatedColor; import '../common/datum_details.dart' show DatumDetails; import '../common/processed_series.dart' show ImmutableSeries, MutableSeries; import '../common/series_datum.dart' show SeriesDatum; import '../layout/layout_view.dart' show LayoutViewPaintOrder; import 'comparison_points_decorator.dart' show ComparisonPointsDecorator; import 'point_renderer_config.dart' show PointRendererConfig; import 'point_renderer_decorator.dart' show PointRendererDecorator; const pointElementsKey = AttributeKey>>('PointRenderer.elements'); const pointSymbolRendererFnKey = AttributeKey>('PointRenderer.symbolRendererFn'); const pointSymbolRendererIdKey = AttributeKey('PointRenderer.symbolRendererId'); /// Defines a fixed radius for data bounds lines (typically drawn by attaching a /// [ComparisonPointsDecorator] to the renderer. const boundsLineRadiusPxKey = AttributeKey('SymbolAnnotationRenderer.boundsLineRadiusPx'); /// Defines an [AccessorFn] for the radius for data bounds lines (typically /// drawn by attaching a [ComparisonPointsDecorator] to the renderer. const boundsLineRadiusPxFnKey = AttributeKey>( 'SymbolAnnotationRenderer.boundsLineRadiusPxFn'); const defaultSymbolRendererId = '__default__'; /// Large number used as a starting sentinel for data distance comparisons. /// /// This is generally larger than the distance from any datum to the mouse. const _maxInitialDistance = 10000.0; class PointRenderer extends BaseCartesianRenderer { final PointRendererConfig config; final List> pointRendererDecorators; BaseChart? _chart; /// Store a map of series drawn on the chart, mapped by series name. /// /// [LinkedHashMap] is used to render the series on the canvas in the same /// order as the data was given to the chart. @protected // ignore: prefer_collection_literals, https://github.com/dart-lang/linter/issues/1649 var seriesPointMap = LinkedHashMap>>(); // Store a list of lines that exist in the series data. // // This list will be used to remove any [_AnimatedPoint] that were rendered in // previous draw cycles, but no longer have a corresponding datum in the new // data. final _currentKeys = []; PointRenderer({String? rendererId, PointRendererConfig? config}) : config = config ?? PointRendererConfig(), pointRendererDecorators = config?.pointRendererDecorators ?? [], super( rendererId: rendererId ?? 'point', layoutPaintOrder: config?.layoutPaintOrder ?? LayoutViewPaintOrder.point, symbolRenderer: config?.symbolRenderer ?? CircleSymbolRenderer()); @override void configureSeries(List> seriesList) { assignMissingColors(seriesList, emptyCategoryUsesSinglePalette: false); } @override void preprocessSeries(List> seriesList) { seriesList.forEach((MutableSeries series) { final elements = >[]; // Default to the configured radius if none was defined by the series. series.radiusPxFn ??= (_) => config.radiusPx; // Create an accessor function for the bounds line radius, if needed. If // the series doesn't define an accessor function, then each datum's // boundsLineRadiusPx value will be filled in by using the following // values, in order of what is defined: // // 1) boundsLineRadiusPx defined on the series. // 2) boundsLineRadiusPx defined on the renderer config. // 3) Final fallback is to use the point radiusPx for this datum. var boundsLineRadiusPxFn = series.getAttr(boundsLineRadiusPxFnKey); if (boundsLineRadiusPxFn == null) { var boundsLineRadiusPx = series.getAttr(boundsLineRadiusPxKey); boundsLineRadiusPx ??= config.boundsLineRadiusPx; if (boundsLineRadiusPx != null) { boundsLineRadiusPxFn = (_) => boundsLineRadiusPx!.toDouble(); series.setAttr(boundsLineRadiusPxFnKey, boundsLineRadiusPxFn); } } final symbolRendererFn = series.getAttr(pointSymbolRendererFnKey); // Add a key function to help animate points moved in position in the // series data between chart draw cycles. Ideally we should require the // user to provide a key function, but this at least provides some // smoothing when adding/removing data. series.keyFn ??= (int? index) => '${series.id}__${series.domainFn(index)}__' '${series.measureFn(index)}'; for (var index = 0; index < series.data.length; index++) { // Default to the configured radius if none was returned by the // accessor function. var radiusPx = series.radiusPxFn!(index); radiusPx ??= config.radiusPx; num? boundsLineRadiusPx; if (boundsLineRadiusPxFn != null) { boundsLineRadiusPx = (boundsLineRadiusPxFn is TypedAccessorFn) ? (boundsLineRadiusPxFn as TypedAccessorFn)( series.data[index], index) : boundsLineRadiusPxFn(index); } boundsLineRadiusPx ??= config.boundsLineRadiusPx; boundsLineRadiusPx ??= radiusPx; // Default to the configured stroke width if none was returned by the // accessor function. var strokeWidthPx = series.strokeWidthPxFn != null ? series.strokeWidthPxFn!(index) : null; strokeWidthPx ??= config.strokeWidthPx; // Get the ID of the [SymbolRenderer] for this point. An ID may be // specified on the datum, or on the series. If neither is specified, // fall back to the default. String? symbolRendererId; if (symbolRendererFn != null) { symbolRendererId = symbolRendererFn(index); } symbolRendererId ??= series.getAttr(pointSymbolRendererIdKey); symbolRendererId ??= defaultSymbolRendererId; // Get the colors. If no fill color is provided, default it to the // primary data color. final colorFn = series.colorFn; final fillColorFn = series.fillColorFn ?? colorFn; final color = colorFn!(index); // Fill color is an optional override for color. Make sure we get a // value if the series doesn't define anything specific. var fillColor = fillColorFn!(index); fillColor ??= color; final details = PointRendererElement( index: index, color: color, fillColor: fillColor, radiusPx: radiusPx.toDouble(), boundsLineRadiusPx: boundsLineRadiusPx.toDouble(), strokeWidthPx: strokeWidthPx.toDouble(), symbolRendererId: symbolRendererId, ); elements.add(details); } series.setAttr(pointElementsKey, elements); }); } @override void update(List> seriesList, bool isAnimatingThisDraw) { _currentKeys.clear(); // Build a list of sorted series IDs as we iterate through the list, used // later for sorting. final sortedSeriesIds = []; seriesList.forEach((ImmutableSeries series) { sortedSeriesIds.add(series.id); final domainAxis = series.getAttr(domainAxisKey) as ImmutableAxis; final domainFn = series.domainFn; final domainLowerBoundFn = series.domainLowerBoundFn; final domainUpperBoundFn = series.domainUpperBoundFn; final measureAxis = series.getAttr(measureAxisKey) as ImmutableAxis; final measureFn = series.measureFn; final measureLowerBoundFn = series.measureLowerBoundFn; final measureUpperBoundFn = series.measureUpperBoundFn; final measureOffsetFn = series.measureOffsetFn; final seriesKey = series.id; final keyFn = series.keyFn!; var pointList = seriesPointMap.putIfAbsent(seriesKey, () => []); var elementsList = series.getAttr(pointElementsKey); for (var index = 0; index < series.data.length; index++) { final Object? datum = series.data[index]; final details = elementsList![index]; final domainValue = domainFn(index); final domainLowerBoundValue = domainLowerBoundFn?.call(index); final domainUpperBoundValue = domainUpperBoundFn?.call(index); final measureValue = measureFn(index); final measureLowerBoundValue = measureLowerBoundFn?.call(index); final measureUpperBoundValue = measureUpperBoundFn?.call(index); final measureOffsetValue = measureOffsetFn!(index); // Create a new point using the final location. final point = getPoint( datum, domainValue, domainLowerBoundValue, domainUpperBoundValue, series, domainAxis, measureValue, measureLowerBoundValue, measureUpperBoundValue, measureOffsetValue, measureAxis); final pointKey = keyFn(index); // If we already have an AnimatingPoint for that index, use it. var animatingPoint = pointList.firstWhereOrNull((point) => point.key == pointKey); // If we don't have any existing arc element, create a new arc and // have it animate in from the position of the previous arc's end // angle. If there were no previous arcs, then animate everything in // from 0. if (animatingPoint == null) { // Create a new point and have it animate in from axis. final point = getPoint( datum, domainValue, domainLowerBoundValue, domainUpperBoundValue, series, domainAxis, 0.0, 0.0, 0.0, 0.0, measureAxis); animatingPoint = AnimatedPoint( key: pointKey, overlaySeries: series.overlaySeries) ..setNewTarget(PointRendererElement( index: details.index, color: details.color, fillColor: details.fillColor, measureAxisPosition: measureAxis.getLocation(0.0), point: point, radiusPx: details.radiusPx, boundsLineRadiusPx: details.boundsLineRadiusPx, strokeWidthPx: details.strokeWidthPx, symbolRendererId: details.symbolRendererId, )); pointList.add(animatingPoint); } // Update the set of arcs that still exist in the series data. _currentKeys.add(pointKey); // Get the pointElement we are going to setup. final pointElement = PointRendererElement( index: index, color: details.color, fillColor: details.fillColor, measureAxisPosition: measureAxis.getLocation(0.0), point: point, radiusPx: details.radiusPx, boundsLineRadiusPx: details.boundsLineRadiusPx, strokeWidthPx: details.strokeWidthPx, symbolRendererId: details.symbolRendererId, ); animatingPoint.setNewTarget(pointElement); } }); // Sort the renderer elements to be in the same order as the series list. // They may get disordered between chart draw cycles if a behavior adds or // removes series from the list (e.g. click to hide on legends). seriesPointMap = LinkedHashMap>>.fromIterable( sortedSeriesIds, key: (dynamic k) => k as String, value: (dynamic k) => seriesPointMap[k]!); // Animate out points that don't exist anymore. seriesPointMap.forEach((String key, List> points) { for (var point in points) { if (_currentKeys.contains(point.key) != true) { point.animateOut(); } } }); } @override void onAttach(BaseChart chart) { super.onAttach(chart); // We only need the chart.context.isRtl setting, but context is not yet // available when the default renderer is attached to the chart on chart // creation time, since chart onInit is called after the chart is created. _chart = chart; } @override void paint(ChartCanvas canvas, double animationPercent) { // Clean up the points that no longer exist. if (animationPercent == 1.0) { final keysToRemove = []; seriesPointMap.forEach((String key, List> points) { points.removeWhere((AnimatedPoint point) => point.animatingOut); if (points.isEmpty) { keysToRemove.add(key); } }); keysToRemove.forEach(seriesPointMap.remove); } seriesPointMap.forEach((String key, List> points) { points .map>((AnimatedPoint animatingPoint) => animatingPoint.getCurrentPoint(animationPercent)) .forEach((point) { // Decorate the points with decorators that should appear below the main // series data. pointRendererDecorators .where((decorator) => !decorator.renderAbove) .forEach((decorator) { decorator.decorate(point, canvas, graphicsFactory!, drawBounds: componentBounds!, animationPercent: animationPercent, rtl: isRtl); }); // Skip points whose center lies outside the draw bounds. Those that lie // near the edge will be allowed to render partially outside. This // prevents harshly clipping off half of the shape. if (point.point!.y != null && componentBounds!.containsPoint(point.point!.toPoint())) { final bounds = Rectangle( point.point!.x! - point.radiusPx, point.point!.y! - point.radiusPx, point.radiusPx * 2, point.radiusPx * 2); if (point.symbolRendererId == defaultSymbolRendererId) { symbolRenderer!.paint(canvas, bounds, fillColor: point.fillColor, strokeColor: point.color, strokeWidthPx: point.strokeWidthPx); } else { final id = point.symbolRendererId; if (!config.customSymbolRenderers!.containsKey(id)) { throw ArgumentError('Invalid custom symbol renderer id "${id}"'); } final customRenderer = config.customSymbolRenderers![id]!; customRenderer.paint(canvas, bounds, fillColor: point.fillColor, strokeColor: point.color, strokeWidthPx: point.strokeWidthPx); } } // Decorate the points with decorators that should appear above the main // series data. This is the typical place for labels. pointRendererDecorators .where((decorator) => decorator.renderAbove) .forEach((decorator) { decorator.decorate(point, canvas, graphicsFactory!, drawBounds: componentBounds!, animationPercent: animationPercent, rtl: isRtl); }); }); }); } bool get isRtl => _chart?.context.isRtl ?? false; @protected DatumPoint getPoint( Object? datum, D? domainValue, D? domainLowerBoundValue, D? domainUpperBoundValue, ImmutableSeries series, ImmutableAxis domainAxis, num? measureValue, num? measureLowerBoundValue, num? measureUpperBoundValue, num? measureOffsetValue, ImmutableAxis measureAxis) { final domainPosition = domainAxis.getLocation(domainValue); final domainLowerBoundPosition = domainLowerBoundValue != null ? domainAxis.getLocation(domainLowerBoundValue) : null; final domainUpperBoundPosition = domainUpperBoundValue != null ? domainAxis.getLocation(domainUpperBoundValue) : null; final measurePosition = measureValue != null && measureOffsetValue != null ? measureAxis.getLocation(measureValue + measureOffsetValue) : null; final measureLowerBoundPosition = measureLowerBoundValue != null ? measureAxis.getLocation(measureLowerBoundValue + measureOffsetValue!) : null; final measureUpperBoundPosition = measureUpperBoundValue != null ? measureAxis.getLocation(measureUpperBoundValue + measureOffsetValue!) : null; return DatumPoint( datum: datum, domain: domainValue, series: series, x: domainPosition, xLower: domainLowerBoundPosition, xUpper: domainUpperBoundPosition, y: measurePosition, yLower: measureLowerBoundPosition, yUpper: measureUpperBoundPosition); } @override List> getNearestDatumDetailPerSeries( Point chartPoint, bool byDomain, Rectangle? boundsOverride, { bool selectOverlappingPoints = false, bool selectExactEventLocation = false, }) { final nearest = >[]; final inside = >[]; // Was it even in the component bounds? if (!isPointWithinBounds(chartPoint, boundsOverride)) { return nearest; } seriesPointMap.values.forEach((List> points) { PointRendererElement? nearestPoint; var nearestDistances = _Distances( domainDistance: _maxInitialDistance, measureDistance: _maxInitialDistance, relativeDistance: _maxInitialDistance); points.forEach((point) { if (point.overlaySeries) { return; } final p = point._currentPoint!.point!; // Don't look at points not in the drawArea. if (p.x! < componentBounds!.left || p.x! > componentBounds!.right) { return; } final distances = _getDatumDistance(point, chartPoint); if (selectOverlappingPoints) { if (distances.insidePoint!) { inside.add(_createDatumDetails(point._currentPoint!, distances)); } } // If any point was added to the inside list on previous iterations, // we don't need to go through calculating nearest points because we // only return inside list as a result in that case. if (inside.isEmpty) { // Do not consider the points outside event location when // selectExactEventLocation flag is set. if (!selectExactEventLocation || distances.insidePoint!) { if (byDomain) { if ((distances.domainDistance < nearestDistances.domainDistance) || (distances.domainDistance == nearestDistances.domainDistance && distances.measureDistance < nearestDistances.measureDistance)) { nearestPoint = point._currentPoint; nearestDistances = distances; } } else { if (distances.relativeDistance < nearestDistances.relativeDistance) { nearestPoint = point._currentPoint; nearestDistances = distances; } } } } }); // Found a point, add it to the list. if (nearestPoint != null) { nearest.add(_createDatumDetails(nearestPoint!, nearestDistances)); } }); // Note: the details are already sorted by domain & measure distance in // base chart. If asking for all overlapping points, return the list of // inside points - only if there was overlap. return (selectOverlappingPoints && inside.isNotEmpty) ? inside : nearest; } DatumDetails _createDatumDetails( PointRendererElement point, _Distances distances) { SymbolRenderer? pointSymbolRenderer; if (point.symbolRendererId == defaultSymbolRendererId) { pointSymbolRenderer = symbolRenderer; } else { final id = point.symbolRendererId; if (!config.customSymbolRenderers!.containsKey(id)) { throw ArgumentError('Invalid custom symbol renderer id "${id}"'); } pointSymbolRenderer = config.customSymbolRenderers![id]; } return DatumDetails( datum: point.point!.datum, domain: point.point!.domain, series: point.point!.series, domainDistance: distances.domainDistance, measureDistance: distances.measureDistance, relativeDistance: distances.relativeDistance, symbolRenderer: pointSymbolRenderer); } /// Returns a struct containing domain, measure, and relative distance between /// a datum and a point within the chart. _Distances _getDatumDistance( AnimatedPoint point, Point chartPoint) { final datumPoint = point._currentPoint!.point!; final radiusPx = point._currentPoint!.radiusPx; final boundsLineRadiusPx = point._currentPoint!.boundsLineRadiusPx; // Compute distances from [chartPoint] to the primary point of the datum. final domainDistance = (chartPoint.x - datumPoint.x!).abs(); final measureDistance = datumPoint.y != null ? (chartPoint.y - datumPoint.y!).abs() : _maxInitialDistance; var relativeDistance = datumPoint.y != null ? chartPoint.distanceTo(datumPoint.toPoint()) : _maxInitialDistance; var insidePoint = false; if (datumPoint.xLower != null && datumPoint.xUpper != null && datumPoint.yLower != null && datumPoint.yUpper != null) { // If we have data bounds, compute the relative distance between // [chartPoint] and the nearest point of the data bounds element. We will // use the smaller of this distance and the distance from the primary // point as the relativeDistance from this datum. final relativeDistanceBounds = distanceBetweenPointAndLineSegment( Vector2(chartPoint.x, chartPoint.y), Vector2(datumPoint.xLower!, datumPoint.yLower!), Vector2(datumPoint.xUpper!, datumPoint.yUpper!)); insidePoint = (relativeDistance < radiusPx) || (boundsLineRadiusPx != null && // This may be inaccurate if the symbol is drawn without end caps. relativeDistanceBounds < boundsLineRadiusPx); // Keep the smaller relative distance after we have determined whether // [chartPoint] is located inside the datum. relativeDistance = min(relativeDistance, relativeDistanceBounds); } else { insidePoint = relativeDistance < radiusPx; } return _Distances( domainDistance: domainDistance, measureDistance: measureDistance, relativeDistance: relativeDistance, insidePoint: insidePoint, ); } @override DatumDetails addPositionToDetailsForSeriesDatum( DatumDetails details, SeriesDatum seriesDatum) { final series = details.series!; final domainAxis = series.getAttr(domainAxisKey) as ImmutableAxis; final measureAxis = series.getAttr(measureAxisKey) as ImmutableAxis; final point = getPoint( seriesDatum.datum, details.domain, details.domainLowerBound, details.domainUpperBound, series, domainAxis, details.measure, details.measureLowerBound, details.measureUpperBound, details.measureOffset, measureAxis); final symbolRendererFn = series.getAttr(pointSymbolRendererFnKey); // Get the ID of the [SymbolRenderer] for this point. An ID may be // specified on the datum, or on the series. If neither is specified, // fall back to the default. String? symbolRendererId; if (symbolRendererFn != null) { symbolRendererId = symbolRendererFn(details.index); } symbolRendererId ??= series.getAttr(pointSymbolRendererIdKey); symbolRendererId ??= defaultSymbolRendererId; // Now that we have the ID, get the configured [SymbolRenderer]. SymbolRenderer? nearestSymbolRenderer; if (symbolRendererId == defaultSymbolRendererId) { nearestSymbolRenderer = symbolRenderer; } else { final id = symbolRendererId; if (!config.customSymbolRenderers!.containsKey(id)) { throw ArgumentError('Invalid custom symbol renderer id "${id}"'); } nearestSymbolRenderer = config.customSymbolRenderers![id]; } return DatumDetails.from(details, chartPosition: NullablePoint(point.x, point.y), chartPositionLower: NullablePoint(point.xLower, point.yLower), chartPositionUpper: NullablePoint(point.xUpper, point.yUpper), symbolRenderer: nearestSymbolRenderer); } } class DatumPoint extends NullablePoint { final Object? datum; final D? domain; final ImmutableSeries? series; // Coordinates for domain bounds. final double? xLower; final double? xUpper; // Coordinates for measure bounds. final double? yLower; final double? yUpper; DatumPoint({ this.datum, this.domain, this.series, required double? x, required this.xLower, required this.xUpper, required double? y, required this.yLower, required this.yUpper, }) : super(x, y); factory DatumPoint.from(DatumPoint other, {double? x, double? xLower, double? xUpper, double? y, double? yLower, double? yUpper}) { return DatumPoint( datum: other.datum, domain: other.domain, series: other.series, x: x ?? other.x, xLower: xLower ?? other.xLower, xUpper: xUpper ?? other.xUpper, y: y ?? other.y, yLower: yLower ?? other.yLower, yUpper: yUpper ?? other.yUpper); } } class PointRendererElement { DatumPoint? point; int? index; Color? color; Color? fillColor; double? measureAxisPosition; double radiusPx; double boundsLineRadiusPx; double strokeWidthPx; String? symbolRendererId; PointRendererElement({ this.point, this.index, this.color, this.fillColor, this.measureAxisPosition, required this.radiusPx, required this.boundsLineRadiusPx, required this.strokeWidthPx, this.symbolRendererId, }); PointRendererElement clone() { return PointRendererElement( point: point != null ? DatumPoint.from(point!) : null, index: index, color: color != null ? Color.fromOther(color: color!) : null, fillColor: fillColor != null ? Color.fromOther(color: fillColor!) : null, measureAxisPosition: measureAxisPosition, radiusPx: radiusPx, boundsLineRadiusPx: boundsLineRadiusPx, strokeWidthPx: strokeWidthPx, symbolRendererId: symbolRendererId, ); } void updateAnimationPercent(PointRendererElement previous, PointRendererElement target, double animationPercent) { final targetPoint = target.point!; final previousPoint = previous.point!; final x = ((targetPoint.x! - previousPoint.x!) * animationPercent) + previousPoint.x!; final xLower = targetPoint.xLower != null && previousPoint.xLower != null ? ((targetPoint.xLower! - previousPoint.xLower!) * animationPercent) + previousPoint.xLower! : null; final xUpper = targetPoint.xUpper != null && previousPoint.xUpper != null ? ((targetPoint.xUpper! - previousPoint.xUpper!) * animationPercent) + previousPoint.xUpper! : null; double? y; if (targetPoint.y != null && previousPoint.y != null) { y = ((targetPoint.y! - previousPoint.y!) * animationPercent) + previousPoint.y!; } else if (targetPoint.y != null) { y = targetPoint.y; } else { y = null; } final yLower = targetPoint.yLower != null && previousPoint.yLower != null ? ((targetPoint.yLower! - previousPoint.yLower!) * animationPercent) + previousPoint.yLower! : null; final yUpper = targetPoint.yUpper != null && previousPoint.yUpper != null ? ((targetPoint.yUpper! - previousPoint.yUpper!) * animationPercent) + previousPoint.yUpper! : null; point = DatumPoint.from(targetPoint, x: x, xLower: xLower, xUpper: xUpper, y: y, yLower: yLower, yUpper: yUpper); color = getAnimatedColor(previous.color!, target.color!, animationPercent); fillColor = getAnimatedColor( previous.fillColor!, target.fillColor!, animationPercent); radiusPx = (target.radiusPx - previous.radiusPx) * animationPercent + previous.radiusPx; boundsLineRadiusPx = ((target.boundsLineRadiusPx - previous.boundsLineRadiusPx) * animationPercent) + previous.boundsLineRadiusPx; strokeWidthPx = ((target.strokeWidthPx - previous.strokeWidthPx) * animationPercent) + previous.strokeWidthPx; } } class AnimatedPoint { final String key; final bool overlaySeries; PointRendererElement? _previousPoint; late PointRendererElement _targetPoint; PointRendererElement? _currentPoint; // Flag indicating whether this point is being animated out of the chart. bool animatingOut = false; AnimatedPoint({required this.key, required this.overlaySeries}); /// Animates a point that was removed from the series out of the view. /// /// This should be called in place of "setNewTarget" for points that represent /// data that has been removed from the series. /// /// Animates the height of the point down to the measure axis position /// (position of 0). void animateOut() { var newTarget = _currentPoint!.clone(); // Set the target measure value to the axis position. var targetPoint = newTarget.point!; var y = newTarget.measureAxisPosition!.roundToDouble(); newTarget.point = DatumPoint.from( targetPoint, x: targetPoint.x, y: y, yLower: y, yUpper: y, ); // Animate the radius and stroke width to 0 so that we don't get a lingering // point after animation is done. newTarget.radiusPx = 0.0; newTarget.strokeWidthPx = 0.0; setNewTarget(newTarget); animatingOut = true; } void setNewTarget(PointRendererElement newTarget) { animatingOut = false; _currentPoint ??= newTarget.clone(); _previousPoint = _currentPoint!.clone(); _targetPoint = newTarget; } PointRendererElement getCurrentPoint(double animationPercent) { if (animationPercent == 1.0 || _previousPoint == null) { _currentPoint = _targetPoint; _previousPoint = _targetPoint; return _currentPoint!; } _currentPoint!.updateAnimationPercent( _previousPoint!, _targetPoint, animationPercent); return _currentPoint!; } } /// Struct of distances between a datum and a point in the chart. class _Distances { /// Distance between two points along the domain axis. final double domainDistance; /// Distance between two points along the measure axis. final double measureDistance; /// Cartesian distance between the two points. final double relativeDistance; /// Whether or not the point was located inside the datum. final bool? insidePoint; _Distances({ required this.domainDistance, required this.measureDistance, required this.relativeDistance, this.insidePoint, }); } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/scatter_plot/point_renderer_config.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import '../../common/symbol_renderer.dart'; import '../common/series_renderer_config.dart' show RendererAttributes, SeriesRendererConfig; import '../layout/layout_view.dart' show LayoutViewConfig, LayoutViewPaintOrder; import 'point_renderer.dart' show PointRenderer, pointSymbolRendererIdKey; import 'point_renderer_decorator.dart' show PointRendererDecorator; /// Configuration for a line renderer. class PointRendererConfig extends LayoutViewConfig implements SeriesRendererConfig { @override final String? customRendererId; /// The order to paint this renderer on the canvas. final int layoutPaintOrder; /// List of decorators applied to rendered points. final List> pointRendererDecorators; /// Renderer used to draw the points. Defaults to a circle. @override final SymbolRenderer? symbolRenderer; /// Map of custom symbol renderers used to draw points. /// /// Each series or point can be associated with a custom renderer by /// specifying a [pointSymbolRendererIdKey] matching a key in the map. Any /// point that doesn't define one will fall back to the default /// [symbolRenderer]. final Map? customSymbolRenderers; @override final rendererAttributes = RendererAttributes(); /// Default radius of the points, used if a series does not define a radiusPx /// accessor function. final double radiusPx; /// Stroke width of the target line. final double strokeWidthPx; /// Optional default radius of data bounds lines, used if a series does not /// define a boundsLineRadiusPx accessor function. /// /// If the series does not define a boundsLineRadiusPx accessor function, then /// each datum's boundsLineRadiusPx value will be filled in by using the /// following values, in order of what is defined: /// /// 1) boundsLineRadiusPx property defined on the series. /// 2) boundsLineRadiusPx property defined on this renderer config. /// 3) Final fallback is to use the point radiusPx for the datum. final double? boundsLineRadiusPx; PointRendererConfig( {this.customRendererId, this.layoutPaintOrder = LayoutViewPaintOrder.point, this.pointRendererDecorators = const [], this.radiusPx = 3.5, this.boundsLineRadiusPx, this.strokeWidthPx = 0.0, this.symbolRenderer, this.customSymbolRenderers}); @override PointRenderer build() { return PointRenderer(config: this, rendererId: customRendererId); } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/scatter_plot/point_renderer_decorator.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math' show Rectangle; import '../../common/graphics_factory.dart' show GraphicsFactory; import '../common/chart_canvas.dart' show ChartCanvas; import 'point_renderer.dart' show PointRendererElement; /// Decorates points after the points have already been painted. abstract class PointRendererDecorator { const PointRendererDecorator(); /// Configures whether the decorator should be rendered on top of or below /// series data elements. bool get renderAbove; void decorate(PointRendererElement pointElement, ChartCanvas canvas, GraphicsFactory graphicsFactory, {required Rectangle drawBounds, required double animationPercent, bool rtl = false}); } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/scatter_plot/scatter_plot_chart.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:collection' show LinkedHashMap; import '../cartesian/axis/axis.dart' show NumericAxis; import '../cartesian/axis/draw_strategy/gridline_draw_strategy.dart' show GridlineRendererSpec; import '../cartesian/cartesian_chart.dart' show NumericCartesianChart; import '../common/series_renderer.dart' show SeriesRenderer; import '../layout/layout_config.dart' show LayoutConfig; import 'point_renderer.dart' show PointRenderer; /// A scatter plot draws series data as a collection of points in a two /// dimensional Cartesian space, plotting two variables from each datum at a /// point represented by (domain, measure). /// /// A third and fourth metric can be represented by configuring the color and /// radius of each datum. /// /// Scatter plots render grid lines along both the domain and measure axes by /// default. class ScatterPlotChart extends NumericCartesianChart { /// Select data by relative Cartesian distance. Scatter plots draw potentially /// overlapping data in an arbitrary (x, y) space, and do not consider the /// domain axis to be more or less important for data selection than the /// measure axis. @override bool get selectNearestByDomain => false; /// On scatter plots, overlapping points that contain the click/tap location /// are all added to the selection. @override bool get selectOverlappingPoints => true; ScatterPlotChart( {bool? vertical, LayoutConfig? layoutConfig, NumericAxis? primaryMeasureAxis, NumericAxis? secondaryMeasureAxis, LinkedHashMap? disjointMeasureAxes}) : super( vertical: vertical, layoutConfig: layoutConfig, primaryMeasureAxis: primaryMeasureAxis, secondaryMeasureAxis: secondaryMeasureAxis, disjointMeasureAxes: disjointMeasureAxes); @override SeriesRenderer makeDefaultRenderer() { return PointRenderer()..rendererId = SeriesRenderer.defaultRendererId; } @override void initDomainAxis() { domainAxis!.tickDrawStrategy = GridlineRendererSpec() .createDrawStrategy(context, graphicsFactory!); } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/scatter_plot/symbol_annotation_renderer.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:collection' show LinkedHashMap; import 'dart:math' show max, Rectangle; import '../../common/graphics_factory.dart' show GraphicsFactory; import '../cartesian/axis/axis.dart' show ImmutableAxis; import '../cartesian/cartesian_chart.dart' show CartesianChart; import '../common/base_chart.dart' show BaseChart; import '../common/chart_canvas.dart' show ChartCanvas; import '../common/processed_series.dart' show ImmutableSeries, MutableSeries; import '../layout/layout_view.dart' show LayoutPosition, LayoutView, LayoutViewConfig, LayoutViewPaintOrder, LayoutViewPositionOrder, ViewMeasuredSizes; import 'point_renderer.dart' show AnimatedPoint, DatumPoint, PointRenderer; import 'symbol_annotation_renderer_config.dart' show SymbolAnnotationRendererConfig; /// Series renderer which draws a row of symbols for each series below the /// drawArea but above the bottom axis. /// /// This renderer can draw point annotations and range annotations. Point /// annotations are drawn at the location of the domain along the chart's domain /// axis, in the row for its series. Range annotations are drawn as a range /// shape between the domainLowerBound and domainUpperBound positions along the /// chart's domain axis. Point annotations are drawn on top of range /// annotations. /// /// Limitations: /// Does not handle horizontal bars. class SymbolAnnotationRenderer extends PointRenderer implements LayoutView { late Rectangle _componentBounds; @override GraphicsFactory? graphicsFactory; late CartesianChart _chart; var _currentHeight = 0; // ignore: prefer_collection_literals, https://github.com/dart-lang/linter/issues/1649 final _seriesInfo = LinkedHashMap>(); SymbolAnnotationRenderer( {String? rendererId, SymbolAnnotationRendererConfig? config}) : super(rendererId: rendererId ?? 'symbolAnnotation', config: config); // // Renderer methods // /// Symbol annotations do not use any measure axes, or draw anything in the /// main draw area associated with them. @override void configureMeasureAxes(List> seriesList) {} @override void preprocessSeries(List> seriesList) { var localConfig = config as SymbolAnnotationRendererConfig; _seriesInfo.clear(); var offset = 0.0; seriesList.forEach((series) { final seriesKey = series.id; // Default to the configured radius if none was defined by the series. series.radiusPxFn ??= (_) => config.radiusPx; var maxRadius = 0.0; for (var index = 0; index < series.data.length; index++) { // Default to the configured radius if none was returned by the // accessor function. var radiusPx = series.radiusPxFn?.call(index)?.toDouble(); radiusPx ??= config.radiusPx; maxRadius = max(maxRadius, radiusPx); } final rowInnerHeight = maxRadius * 2; final rowHeight = localConfig.verticalSymbolBottomPaddingPx + localConfig.verticalSymbolTopPaddingPx + rowInnerHeight; final symbolCenter = offset + localConfig.verticalSymbolTopPaddingPx + (rowInnerHeight / 2); series.measureFn = (index) => 0; series.measureOffsetFn = (index) => 0; // Override the key function to allow for range annotations that start at // the same point. This is a necessary hack because every annotation has a // measure value of 0, so the key generated in [PointRenderer] is not // unique enough. series.keyFn ??= (index) => '${series.id}__${series.domainFn(index)}__' '${series.domainLowerBoundFn!(index)}__' '${series.domainUpperBoundFn!(index)}'; _seriesInfo[seriesKey] = _SeriesInfo( rowHeight: rowHeight, rowStart: offset, symbolCenter: symbolCenter, ); offset += rowHeight; }); _currentHeight = offset.ceil(); super.preprocessSeries(seriesList); } @override DatumPoint getPoint( Object? datum, D? domainValue, D? domainLowerBoundValue, D? domainUpperBoundValue, ImmutableSeries series, ImmutableAxis domainAxis, num? measureValue, num? measureLowerBoundValue, num? measureUpperBoundValue, num? measureOffsetValue, ImmutableAxis measureAxis) { final domainPosition = domainAxis.getLocation(domainValue); final domainLowerBoundPosition = domainLowerBoundValue != null ? domainAxis.getLocation(domainLowerBoundValue) : null; final domainUpperBoundPosition = domainUpperBoundValue != null ? domainAxis.getLocation(domainUpperBoundValue) : null; final seriesKey = series.id; final seriesInfo = _seriesInfo[seriesKey]!; final measurePosition = _componentBounds.top + seriesInfo.symbolCenter; final measureLowerBoundPosition = domainLowerBoundPosition != null ? measurePosition : null; final measureUpperBoundPosition = domainUpperBoundPosition != null ? measurePosition : null; return DatumPoint( datum: datum, domain: domainValue, series: series, x: domainPosition, xLower: domainLowerBoundPosition, xUpper: domainUpperBoundPosition, y: measurePosition, yLower: measureLowerBoundPosition, yUpper: measureUpperBoundPosition); } @override void onAttach(BaseChart chart) { if (chart is! CartesianChart) { throw ArgumentError( 'SymbolAnnotationRenderer can only be attached to a CartesianChart'); } _chart = chart; // Only vertical rendering is supported by this behavior. assert(_chart.vertical); super.onAttach(chart); _chart.addView(this); } @override void onDetach(BaseChart chart) { chart.removeView(this); } @override void paint(ChartCanvas canvas, double animationPercent) { super.paint(canvas, animationPercent); // Use the domain axis of the attached chart to render the separator lines // to keep the same overall style. if ((config as SymbolAnnotationRendererConfig).showSeparatorLines) { seriesPointMap.forEach((String key, List> points) { final seriesInfo = _seriesInfo[key]!; final y = componentBounds.top + seriesInfo.rowStart; final domainAxis = _chart.domainAxis!; final bounds = Rectangle( componentBounds.left, y.round(), componentBounds.width, 0); domainAxis.tickDrawStrategy! .drawAxisLine(canvas, domainAxis.axisOrientation!, bounds); }); } } // // Layout methods // @override LayoutViewConfig get layoutConfig { return LayoutViewConfig( paintOrder: LayoutViewPaintOrder.point, position: LayoutPosition.Bottom, positionOrder: LayoutViewPositionOrder.symbolAnnotation); } @override ViewMeasuredSizes measure(int maxWidth, int maxHeight) { // The sizing of component is not flexible. It's height is always a multiple // of the number of series rendered, even if that ends up taking all of the // available margin space. return ViewMeasuredSizes( preferredWidth: maxWidth, preferredHeight: _currentHeight); } @override void layout(Rectangle componentBounds, Rectangle drawAreaBounds) { _componentBounds = componentBounds; super.layout(componentBounds, drawAreaBounds); } @override Rectangle get componentBounds => _componentBounds; } class _SeriesInfo { double rowHeight; double rowStart; double symbolCenter; _SeriesInfo( {required this.rowHeight, required this.rowStart, required this.symbolCenter}); } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/scatter_plot/symbol_annotation_renderer_config.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import '../../common/symbol_renderer.dart'; import 'comparison_points_decorator.dart' show ComparisonPointsDecorator; import 'point_renderer_config.dart' show PointRendererConfig; import 'point_renderer_decorator.dart' show PointRendererDecorator; import 'symbol_annotation_renderer.dart' show SymbolAnnotationRenderer; /// Configuration for [SymbolAnnotationRenderer]. /// /// This renderer is configured with a [ComparisonPointsDecorator] by default, /// used to draw domain ranges. This decorator will draw a rectangular shape /// between the points (domainLowerBound, measureLowerBound) and /// (domainUpperBound, measureUpperBound), beneath the primary point for each /// series. class SymbolAnnotationRendererConfig extends PointRendererConfig { /// Whether a separator line should be drawn between the bottom row of /// rendered symbols and the axis ticks/labels. final bool showBottomSeparatorLine; /// Whether or not separator lines will be rendered between rows of rendered /// symbols. final bool showSeparatorLines; /// Space reserved at the bottom of each row where the symbol should not /// render into. final double verticalSymbolBottomPaddingPx; /// Space reserved at the top of each row where the symbol should not render /// into. final double verticalSymbolTopPaddingPx; SymbolAnnotationRendererConfig( {String? customRendererId, List>? pointRendererDecorators, double radiusPx = 5.0, SymbolRenderer? symbolRenderer, Map? customSymbolRenderers, this.showBottomSeparatorLine = false, this.showSeparatorLines = true, this.verticalSymbolBottomPaddingPx = 5.0, this.verticalSymbolTopPaddingPx = 5.0}) : super( customRendererId: customRendererId, pointRendererDecorators: pointRendererDecorators ?? [ ComparisonPointsDecorator( symbolRenderer: RectangleRangeSymbolRenderer()) ], radiusPx: radiusPx, symbolRenderer: symbolRenderer, customSymbolRenderers: customSymbolRenderers); @override SymbolAnnotationRenderer build() { return SymbolAnnotationRenderer( config: this, rendererId: customRendererId); } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/sunburst/sunburst_arc_label_decorator.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math' show Rectangle; import '../../common/color.dart' show Color; import '../../common/graphics_factory.dart' show GraphicsFactory; import '../../common/text_element.dart' show TextElement; import '../../common/text_style.dart' show TextStyle; import '../cartesian/axis/spec/axis_spec.dart' show TextStyleSpec; import '../common/chart_canvas.dart' show ChartCanvas; import '../pie/arc_renderer_element.dart' show ArcRendererElement, ArcRendererElementList; import '../pie/arc_label_decorator.dart'; import 'sunburst_arc_renderer.dart' show SunburstArcRendererElement; /// Renders labels for sunburst arc renderers. Configures label based on arc's /// position via innerRingArcLabelPosition, innerRingLeafArcLabelPosition, and /// outerRingArcLabelPosition. Currently label for non-leaf arcs in the inner /// ring may only be drawn inside until there's better collision detection for /// inner arcs' label versus the outer arcs. /// /// TODO: Improve label handling for sunburst chart. class SunburstArcLabelDecorator extends ArcLabelDecorator { /// Configures the [ArcLabelPosition] for the non-leaf arcs in the inner ring. /// Label can only be rendered inside, If set to ArcLabelPosition.outside, /// label will not be rendered. final ArcLabelPosition innerRingArcLabelPosition; /// Configures the [ArcLabelPosition] for the leaf arcs in the inner ring. final ArcLabelPosition innerRingLeafArcLabelPosition; /// Configures the [ArcLabelPosition] for the arcs in the outer most ring. final ArcLabelPosition outerRingArcLabelPosition; SunburstArcLabelDecorator( {TextStyleSpec? insideLabelStyleSpec, TextStyleSpec? outsideLabelStyleSpec, ArcLabelLeaderLineStyleSpec? leaderLineStyleSpec, int labelPadding = 5, bool showLeaderLines = true, Color? leaderLineColor, // TODO: Change to auto when we can detect collision of inner // arcs' label with outer arcs. this.innerRingArcLabelPosition = ArcLabelPosition.inside, // TODO: Change to auto when we can detect collision of inner // arcs' label with outer arcs. this.innerRingLeafArcLabelPosition = ArcLabelPosition.inside, this.outerRingArcLabelPosition = ArcLabelPosition.auto}) : super( insideLabelStyleSpec: insideLabelStyleSpec, outsideLabelStyleSpec: outsideLabelStyleSpec, leaderLineStyleSpec: leaderLineStyleSpec, labelPosition: ArcLabelPosition.auto, labelPadding: labelPadding, showLeaderLines: showLeaderLines, leaderLineColor: leaderLineColor); @override void decorate(ArcRendererElementList arcElements, ChartCanvas canvas, GraphicsFactory graphicsFactory, {required Rectangle drawBounds, required double animationPercent, bool rtl = false}) { /// TODO: Improve label handling for sunburst chart. When a /// more sophisticated collision detection is in place, we can draw the /// label for inner arc outside when it doesn't collide with outer arcs. // Do not draw label for arcs on the inner ring if positioned outside. if (innerRingArcLabelPosition == ArcLabelPosition.outside) { arcElements.arcs .retainWhere((e) => (e as SunburstArcRendererElement).isLeaf == true); } super.decorate(arcElements, canvas, graphicsFactory, drawBounds: drawBounds, animationPercent: animationPercent, rtl: rtl); } @override ArcLabelPosition calculateLabelPosition( TextElement labelElement, TextStyle labelStyle, int insideArcWidth, int outsideArcWidth, ArcRendererElement arcRendererElement, ArcLabelPosition labelPosition) { assert(arcRendererElement is SunburstArcRendererElement); if ((arcRendererElement as SunburstArcRendererElement).isOuterMostRing == true) { return super.calculateLabelPosition( labelElement, labelStyle, insideArcWidth, outsideArcWidth, arcRendererElement, outerRingArcLabelPosition); } else if ((arcRendererElement as SunburstArcRendererElement).isLeaf == true) { return super.calculateLabelPosition( labelElement, labelStyle, insideArcWidth, outsideArcWidth, arcRendererElement, innerRingLeafArcLabelPosition); } else { /// TODO: Improve label handling for sunburst chart. When a /// more sophisticated collision detection is in place, we can draw the /// label for inner arc outside when it doesn't collide with outer arcs. // Force label for arc on the inner ring inside. return ArcLabelPosition.inside; } } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/sunburst/sunburst_arc_renderer.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:collection' show LinkedHashMap, HashSet; import 'dart:math' show max, min, pi, Point; import 'package:collection/collection.dart' show IterableExtension; import '../../common/color.dart' show Color; import '../../common/style/style_factory.dart' show StyleFactory; import '../../data/series.dart' show AttributeKey; import '../../data/tree.dart' show TreeNode; import '../common/chart_canvas.dart' show ChartCanvas; import '../common/processed_series.dart' show ImmutableSeries, MutableSeries; import '../pie/arc_renderer_decorator.dart' show ArcRendererDecorator; import '../pie/arc_renderer_element.dart' show ArcRendererElement, AnimatedArcList, AnimatedArc; import '../pie/base_arc_renderer.dart'; import 'sunburst_arc_renderer_config.dart' show SunburstArcRendererConfig, SunburstColorStrategy; const arcElementsKey = AttributeKey>>( 'SunburstArcRenderer.elements'); /// ArcRenderer for the Sunburst chart using Tree based data. class SunburstArcRenderer extends BaseArcRenderer { final SunburstArcRendererConfig config; final List> arcRendererDecorators; /// Store a map of series drawn on the chart, mapped by series name. /// /// [LinkedHashMap] is used to render the series on the canvas in the same /// order as the data was given to the chart. // ignore: prefer_collection_literals, https://github.com/dart-lang/linter/issues/1649 final _seriesArcMap = LinkedHashMap>>(); final _nodeToArcRenderElementMap = LinkedHashMap, SunburstArcRendererElement>(); // Store a list of arcs that exist in the series data. // // This list will be used to remove any [AnimatedArc] that were rendered in // previous draw cycles, but no longer have a corresponding datum in the new // data. final _currentKeys = []; final _nodeToExpand = HashSet>(); factory SunburstArcRenderer( {String? rendererId, SunburstArcRendererConfig? config}) { return SunburstArcRenderer._internal( rendererId: rendererId ?? 'sunburst', config: config ?? SunburstArcRendererConfig()); } SunburstArcRenderer._internal( {required String rendererId, required this.config}) : arcRendererDecorators = config.arcRendererDecorators, super(rendererId: rendererId, config: config); @override void preprocessSeries(List> seriesList) { _nodeToArcRenderElementMap.clear(); seriesList.forEach((MutableSeries series) { var elements = >[]; var domainFn = series.domainFn; var measureFn = series.measureFn; // The seriesMeasureTotal needs to be computed from currently displayed // top level. var seriesMeasureTotal = 0.0; for (var i = 0; i < series.data.length; i++) { final node = series.data[i] as TreeNode; final measure = measureFn(i); if (node.depth == 1 && measure != null) { seriesMeasureTotal += measure; } } // On the canvas, arc measurements are defined as angles from the positive // x axis. Start our first slice at the positive y axis instead. var startAngle = config.startAngle; var arcLength = config.arcLength; var totalAngle = 0.0; var measures = []; // No data processing is same as the regular arc renderer. if (series.data.isEmpty) { // If the series has no data, generate an empty arc element that // occupies the entire chart. // // Use a tiny epsilon difference to ensure that the canvas renders a // "full" circle, in the correct direction. var angle = arcLength == 2 * pi ? arcLength * .999999 : arcLength; var endAngle = startAngle + angle; var details = SunburstArcRendererElement( startAngle: startAngle, endAngle: endAngle, index: 0, key: 0, series: series); elements.add(details); } else { // Create SunburstArcRendererElement for each item in the tree, // excluding the root node. var root = series.data.first as TreeNode; root.visit((node) { elements.addAll(_createArcRenderElementForNode(series, node)); }); } series.setAttr(arcElementsKey, elements); }); } // Create SunburstArcRendererElement for children of the node. List> _createArcRenderElementForNode( MutableSeries series, TreeNode node) { var elements = >[]; final children = node.children; if (children.isNotEmpty) { var childrenMeasureTotal = 0.0; // Compute the measure total for the node's children. for (var i = 0; i < children.length; i++) { final child = children.elementAt(i); final measure = series.measureFn(series.data.indexOf(child)); if (measure != null) { childrenMeasureTotal += measure; } } // Create ArcRenderElement for the node's children. Computing arc angles // based on parent arc’s arcLength and the nodes measure versus the // sibling nodes. var startAngle = _getParentStartAngle(node); for (var i = 0; i < children.length; i++) { final child = children.elementAt(i); final arcIndex = series.data.indexOf(child); final measure = series.measureFn(arcIndex); final domain = series.domainFn(arcIndex); if (measure == null) { continue; } final percentOfLevel = measure / childrenMeasureTotal; var angle = _getParentArcLength(node) * percentOfLevel; var endAngle = startAngle + angle; var details = SunburstArcRendererElement( arcLength: angle, startAngle: startAngle, endAngle: endAngle, index: arcIndex, key: arcIndex, domain: domain, series: series); _nodeToArcRenderElementMap[child] = details; elements.add(details); // Update the starting angle for the next datum in the series. startAngle = endAngle; } } return elements; } double _getParentArcLength(TreeNode parent) => _nodeToArcRenderElementMap[parent]?.arcLength != null ? _nodeToArcRenderElementMap[parent]!.arcLength! : config.arcLength; double _getParentStartAngle(TreeNode parent) => _nodeToArcRenderElementMap[parent] != null ? _nodeToArcRenderElementMap[parent]!.startAngle : config.startAngle; @override void update(List> seriesList, bool isAnimatingThisDraw) { _currentKeys.clear(); final bounds = chart!.drawAreaBounds; final center = Point((bounds.left + bounds.width / 2).toDouble(), (bounds.top + bounds.height / 2).toDouble()); final radius = bounds.height < bounds.width ? (bounds.height / 2).toDouble() : (bounds.width / 2).toDouble(); if (config.arcRatio != null) { if (config.arcRatio! < 0 || config.arcRatio! > 1) { throw ArgumentError('arcRatio must be between 0 and 1'); } } seriesList.forEach((ImmutableSeries series) { var colorFn = series.colorFn; var arcListKey = series.id; var elementsList = series.getAttr(arcElementsKey) as List>; var arcLists = _seriesArcMap.putIfAbsent(arcListKey, () => >[]); if (series.data.isEmpty) { var arcList = AnimatedArcList(); _seriesArcMap.putIfAbsent(arcListKey, () => [arcList]); final innerRadius = _calculateRadii(radius).first; // If the series is empty, set up the "no data" arc element. This should // occupy the entire chart, and use the chart style's no data color. final details = elementsList[0]; var arcKey = '__no_data__'; // If we already have an AnimatingArc for that index, use it. var animatingArc = arcList.arcs.firstWhereOrNull((arc) => arc.key == arcKey); arcList.center = center; arcList.radius = radius; arcList.innerRadius = innerRadius; arcList.series = series; arcList.stroke = config.noDataColor; arcList.strokeWidthPx = 0.0; // If we don't have any existing arc element, create a new arc. Unlike // real arcs, we should not animate the no data state in from 0. if (animatingArc == null) { animatingArc = AnimatedArc(arcKey, null, null); arcList.arcs.add(animatingArc); } else { animatingArc.datum = null; animatingArc.domain = null; } // Update the set of arcs that still exist in the series data. _currentKeys.add(arcKey); // Get the arcElement we are going to setup. // Optimization to prevent allocation in non-animating case. final arcElement = SunburstArcRendererElement( startAngle: details.startAngle, endAngle: details.endAngle, color: config.noDataColor, series: series); animatingArc.setNewTarget(arcElement); arcLists.add(arcList); } else { var previousEndAngle = config.startAngle; // Create Arc and add to arcList for each of the node with depth // within config.maxDisplayLevel var root = series.data.first as TreeNode; var maxDepth = 0; root.visit((node) { maxDepth = max(maxDepth, node.depth); }); // Create arcLists up to min(maxDepth, config.maxDisplayLevel). final maxDisplayLevel = min(maxDepth, config.maxDisplayLevel); final displayLevel = min(maxDepth, config.initialDisplayLevel); for (var i = 0; i < maxDisplayLevel; i++) { var arcList = arcLists.length > i ? arcLists[i] : AnimatedArcList(); // Create arc for node that’s within the initial display level or // selected nodes and its children up to the maxDisplayLevel. for (var node in _nodeToArcRenderElementMap.keys.where((e) => e.depth == i + 1 && (e.depth <= displayLevel || _nodeToExpand.contains(e)))) { final radii = _calculateRadii(radius, maxDisplayLevel, i + 1); final innerRadius = radii.first; final outerRadius = radii.last; final arcIndex = series.data.indexOf(node); final Object datum = series.data[arcIndex]; final details = _nodeToArcRenderElementMap[node]; final domainValue = details!.domain; final isLeaf = !node.hasChildren || ((node.depth == displayLevel || _nodeToExpand.contains(node)) && !_nodeToExpand.any((e) => node.children.contains(e))); final isOuterMostRing = node.depth == maxDisplayLevel; var arcKey = '${series.id}__${domainValue.toString()}'; // If we already have an AnimatingArc for that index, use it. var animatingArc = arcList.arcs.firstWhereOrNull((arc) => arc.key == arcKey); arcList.center = center; arcList.radius = outerRadius; arcList.innerRadius = innerRadius; arcList.series = series; arcList.stroke = config.stroke; arcList.strokeWidthPx = config.strokeWidthPx; // If we don't have any existing arc element, create a new arc and // have it animate in from the position of the previous arc's end // angle. If there were no previous arcs, then animate everything in // from 0. if (animatingArc == null) { animatingArc = AnimatedArc(arcKey, datum, domainValue) ..setNewTarget(SunburstArcRendererElement( color: colorFn!(arcIndex), startAngle: previousEndAngle, endAngle: previousEndAngle, index: arcIndex, series: series, isLeaf: isLeaf, isOuterMostRing: isOuterMostRing)); arcList.arcs.add(animatingArc); } else { animatingArc.datum = datum; previousEndAngle = animatingArc.previousArcEndAngle ?? 0.0; } animatingArc.domain = domainValue; // Update the set of arcs that still exist in the series data. _currentKeys.add(arcKey); // Get the arcElement we are going to setup. // Optimization to prevent allocation in non-animating case. final arcElement = SunburstArcRendererElement( color: colorFn!(arcIndex), startAngle: details.startAngle, endAngle: details.endAngle, index: arcIndex, series: series, isLeaf: isLeaf, isOuterMostRing: isOuterMostRing); animatingArc.setNewTarget(arcElement); } if (arcLists.length <= i && arcList.arcs.isNotEmpty) { arcLists.add(arcList); } } } }); // Animate out arcs that don't exist anymore. _seriesArcMap.forEach((String key, List> arcLists) { for (var arcList in arcLists) { for (var arcIndex = 0; arcIndex < arcList.arcs.length; arcIndex++) { final arc = arcList.arcs[arcIndex]; final arcStartAngle = arc.previousArcStartAngle; if (_currentKeys.contains(arc.key) != true) { // Default to animating out to the top of the chart, clockwise, if // there are no arcs that start past this arc. var targetArcAngle = (2 * pi) + config.startAngle; // Find the nearest start angle of the next arc that still exists in // the data. for (final nextArc in arcList.arcs .where((arc) => _currentKeys.contains(arc.key))) { final nextArcStartAngle = nextArc.newTargetArcStartAngle; if (arcStartAngle! < nextArcStartAngle! && nextArcStartAngle < targetArcAngle) { targetArcAngle = nextArcStartAngle; } } arc.animateOut(targetArcAngle); } } } }); } @override void paint(ChartCanvas canvas, double animationPercent) { // Clean up the arcs that no longer exist. if (animationPercent == 1.0) { final keysToRemove = []; _seriesArcMap.forEach((String key, List> arcLists) { final arcListToRemove = >[]; for (var arcList in arcLists) { arcList.arcs.removeWhere((AnimatedArc arc) => arc.animatingOut); if (arcList.arcs.isEmpty) { arcListToRemove.add(arcList); } } arcListToRemove.forEach(arcLists.remove); if (arcLists.isEmpty) { keysToRemove.add(key); } }); keysToRemove.forEach(_seriesArcMap.remove); } super.paint(canvas, animationPercent); } bool _isNodeDisplayed(TreeNode? node) { return node != null && (node.depth <= config.initialDisplayLevel || _nodeToExpand.contains(node)); } // Records the nodes to expand beyond initial display level. void expandNode(TreeNode node) { if (node == null) { _nodeToExpand.clear(); } else if (node.hasChildren) { // Collapse rings up to the clicked expanded node. if (node.children.any((e) => _nodeToExpand.contains(e))) { node.visit((e) { if (node != e) { _nodeToExpand.remove(e); } }); } else { // Expand clicked node by one level. _nodeToExpand.add(node); _nodeToExpand.addAll(node.children); } } } /// Assigns one color pallet for each subtree from the children of the root /// node, and one shade for each node of the subtree to series that are /// missing their colorFn. @override void assignMissingColors(Iterable> seriesList, {required bool emptyCategoryUsesSinglePalette}) { seriesList.forEach((series) { if (series.colorFn == null) { final root = series.data.first as TreeNode; final firstLevelChildren = (series.data.first as TreeNode).children; // Create number of palettes based on the first level children of root. final colorPalettes = StyleFactory.style.getOrderedPalettes(root.children.length); final nodeToColorMap = {}; // Create shades base on number of Nodes in the subtree if (config.colorAssignmentStrategy == SunburstColorStrategy.newShadePerArc) { for (var i = 0; i < firstLevelChildren.length; i++) { var numOfNodeInSubTree = 0; firstLevelChildren.elementAt(i).visit((node) { numOfNodeInSubTree++; }); final colorList = colorPalettes[i].makeShades(numOfNodeInSubTree); // Fill in node to color map to be used in the colorFn numOfNodeInSubTree = 0; firstLevelChildren.elementAt(i).visit((node) { nodeToColorMap[node] = colorList[numOfNodeInSubTree]; numOfNodeInSubTree++; }); } } else { // Create number of shades based on the full depth of the tree instead // of each subtree, so the shades of each branch looks more aligned // at each level. var depthOfTree = 0; root.visit((node) { depthOfTree = max(depthOfTree, node.depth); }); for (var i = 0; i < firstLevelChildren.length; i++) { final colorList = colorPalettes[i].makeShades(depthOfTree); // Fill in node to color map to be used in the colorFn firstLevelChildren.elementAt(i).visit((node) { nodeToColorMap[node] = colorList[node.depth - 1]; }); } } series.colorFn ??= (index) => nodeToColorMap[series.data[index!]] ?? Color.black; } }); } /// Calculate the inner and outer radius of the current level based on config. List _calculateRadii(double radius, [int maxDisplayLevel = 1, int currentLevel = 1]) { // arcRatio trumps arcWidth for determining the inner radius. If neither is // defined, then inner radius is 0. final baseInnerRadius; if (config.arcRatio != null) { baseInnerRadius = max(radius - radius * config.arcRatio!, 0.0).toDouble(); } else if (config.arcWidth != null) { baseInnerRadius = max(radius - config.arcWidth!, 0.0).toDouble(); } else { baseInnerRadius = 0.0; } if (config.arcWidths != null && config.arcWidths!.isNotEmpty) { // Check if arcWidths provided covers maxDisplayLevel, if not, copy the // last value for each level not provided. List arcWidths = _ensureConfigLengthCoversMaxDisplayLevel( config.arcWidths!, maxDisplayLevel); final sumOfPreviousLevelRadii = currentLevel > 1 ? arcWidths.take(currentLevel - 1).reduce((a, b) => a + b) : 0; final innerRadius = baseInnerRadius + sumOfPreviousLevelRadii; return [ innerRadius, innerRadius + arcWidths[currentLevel - 1] - config.strokeWidthPx ]; } else { final totalRadius = radius - baseInnerRadius; final radiusDenom; final sumOfPreviousLevelRadiiFactor; final currentLevelRadiusFactor; // If arcRatios is defined, calculate inner and outer radius based on it. if (config.arcRatios != null && config.arcRatios!.isNotEmpty) { List arcRatios = _ensureConfigLengthCoversMaxDisplayLevel( config.arcRatios!, maxDisplayLevel); radiusDenom = arcRatios.reduce((a, b) => a + b); sumOfPreviousLevelRadiiFactor = currentLevel > 1 ? arcRatios.take(currentLevel - 1).reduce((a, b) => a + b) : 0; currentLevelRadiusFactor = arcRatios[currentLevel - 1]; } else { // Else distribute the chart area to rings evenly. radiusDenom = maxDisplayLevel; sumOfPreviousLevelRadiiFactor = (currentLevel - 1); currentLevelRadiusFactor = 1; } // InnerRadius is baseInnerRadius + sum of radii of previous levels. final innerRadius = baseInnerRadius + totalRadius * sumOfPreviousLevelRadiiFactor / radiusDenom; // OuterRadius is baseInnerRadius + sum of radii of previous levels + // radius of currentLevel. Subtract config.strokeWidth from outerRadius to // create the separation of slice between levels. final outerRadius = baseInnerRadius + totalRadius * (sumOfPreviousLevelRadiiFactor + currentLevelRadiusFactor) / radiusDenom - config.strokeWidthPx; return [innerRadius, outerRadius]; } } @override List> getArcLists({String? seriesId}) { if (seriesId == null) { return _seriesArcMap.values.first; } final arcList = _seriesArcMap[seriesId]; if (arcList == null) return >[]; return arcList; } List _ensureConfigLengthCoversMaxDisplayLevel( List configParam, int maxDisplayLevel) { // Check if config param provided covers maxDisplayLevel, if not, copy the // last value for each level not provided. List arcWidths; if (configParam.length < maxDisplayLevel) { // Repeat last value in the config param to match length of // maxDisplayLevel. arcWidths = List.generate(maxDisplayLevel, (i) => (configParam.length > i) ? configParam[i] : configParam.last); } else { arcWidths = List.from(configParam); } return arcWidths; } } class SunburstArcRendererElement extends ArcRendererElement { /// Records the arcLength of a particular node, so its children can use it /// to compute the start and end angles. double? arcLength; /// Whether the SunburstArcRendererElement is currently displayed as the outer /// most arc of the branch. bool? isLeaf; /// Whether the SunburstArcRendererElement is on the outer most ring of the /// sunburst. bool? isOuterMostRing; SunburstArcRendererElement( {required double startAngle, required double endAngle, required ImmutableSeries series, Color? color, int? index, num? key, D? domain, this.arcLength, this.isLeaf, this.isOuterMostRing}) : super( startAngle: startAngle, endAngle: endAngle, series: series, color: color, index: index, key: key, domain: domain, ); SunburstArcRendererElement clone() { return SunburstArcRendererElement( arcLength: arcLength, startAngle: startAngle, endAngle: endAngle, color: color == null ? null : Color.fromOther(color: color!), index: index, key: key, series: series, isLeaf: isLeaf, isOuterMostRing: isOuterMostRing); } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/sunburst/sunburst_arc_renderer_config.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math' show pi; import '../../common/symbol_renderer.dart' show SymbolRenderer; import '../layout/layout_view.dart' show LayoutViewPaintOrder; import '../pie/arc_renderer.dart' show ArcRenderer; import '../pie/arc_renderer_decorator.dart' show ArcRendererDecorator; import 'sunburst_arc_renderer.dart' show SunburstArcRenderer; import '../pie/base_arc_renderer_config.dart' show BaseArcRendererConfig; import '../../data/tree.dart' show TreeNode; /// Given the selected node and a list of currently expanded node, returns the /// new set of node to be expanded (shown beyond the initialDisplayLevel). typedef List> ExpandNodeCallback( TreeNode node, List> expandedNode); /// Configuration for an [ArcRenderer]. class SunburstArcRendererConfig extends BaseArcRendererConfig { static const _maxInt32Value = 1 << 31; /// Ratio of the arc widths for each of the ring drawn in the sunburst. The /// arc ratio of each ring will be normalized based on the actual render area /// of the chart. If the maxDisplayLevel to be rendered is greater than the /// arcRatios provided, the last value of the arcRatios will be used to fill /// the rest of the levels. If neither arcRatios nor arcWidths is provided, /// space will be distributed evenly between levels. final List? arcRatios; /// Fixed width of the arcs for each of the ring drawn in the sunburst. The /// arcs will be drawn exactly as the defined width, any part exceeding the /// chart area will not be drawn. If the maxDisplayLevel to be rendered is /// greater than the arcWidths provided, the last value of the arcWidths will /// be used to fill the rest of the levels. arcWidths has more precedence than /// arcRatios. If neither arcRatios nor arcWidths is provided, space will be /// distributed evenly between levels. final List? arcWidths; /// Configures how missing colors are assigned for the Sunburst. final SunburstColorStrategy colorAssignmentStrategy; /// The initial display level of rings to render in the sunburst. Children /// of hovered/selected node may expand up to the maxDisplayLevel. If unset, /// defaults to maxDisplayLevel. final int initialDisplayLevel; /// The max level of rings to render in the sunburst. If unset, display all /// data. final int maxDisplayLevel; SunburstArcRendererConfig( {String? customRendererId, double arcLength = 2 * pi, List> arcRendererDecorators = const [], double? arcRatio, this.arcRatios, int? arcWidth, this.arcWidths, this.colorAssignmentStrategy = SunburstColorStrategy.newShadePerLevel, int layoutPaintOrder = LayoutViewPaintOrder.arc, int? maxDisplayLevel, int? initialDisplayLevel, int minHoleWidthForCenterContent = 30, double startAngle = -pi / 2, double strokeWidthPx = 2.0, SymbolRenderer? symbolRenderer}) : this.maxDisplayLevel = maxDisplayLevel ?? _maxInt32Value, this.initialDisplayLevel = initialDisplayLevel ?? maxDisplayLevel ?? _maxInt32Value, super( customRendererId: customRendererId, arcLength: arcLength, arcRatio: arcRatio, arcWidth: arcWidth, layoutPaintOrder: layoutPaintOrder, minHoleWidthForCenterContent: minHoleWidthForCenterContent, startAngle: startAngle, strokeWidthPx: strokeWidthPx, arcRendererDecorators: arcRendererDecorators); @override SunburstArcRenderer build() { return SunburstArcRenderer(config: this, rendererId: customRendererId); } } /// Strategies for assinging color to the arcs if colorFn is not provided for /// Series. enum SunburstColorStrategy { /// Assign a new shade to each of the arcs. newShadePerArc, /// Assign a new shade to each ring of the sunburst. newShadePerLevel, } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/sunburst/sunburst_chart.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math' show Rectangle; import '../common/base_chart.dart' show BaseChart; import '../common/datum_details.dart' show DatumDetails; import '../common/selection_model/selection_model.dart' show SelectionModelType; import '../common/series_renderer.dart' show rendererIdKey, SeriesRenderer; import '../layout/layout_config.dart' show LayoutConfig; import '../../data/tree.dart' show TreeNode; import 'sunburst_arc_renderer.dart' show SunburstArcRenderer; class SunburstChart extends BaseChart { SunburstChart({LayoutConfig? layoutConfig}) : super(layoutConfig: layoutConfig); @override SeriesRenderer makeDefaultRenderer() { return SunburstArcRenderer() ..rendererId = SeriesRenderer.defaultRendererId; } /// Returns a list of datum details from selection model of [type]. @override List> getDatumDetails(SelectionModelType type) { final entries = >[]; for (final seriesDatum in getSelectionModel(type).selectedDatum) { final rendererId = seriesDatum.series.getAttr(rendererIdKey); final renderer = getSeriesRenderer(rendererId); assert(renderer is SunburstArcRenderer); final details = (renderer as SunburstArcRenderer) .getExpandedDatumDetails(seriesDatum); if (details != null) { entries.add(details); } } return entries; } Rectangle? get centerContentBounds { assert(defaultRenderer is SunburstArcRenderer); return (defaultRenderer as SunburstArcRenderer).centerContentBounds; } void expandNode(TreeNode node) { assert(defaultRenderer is SunburstArcRenderer); (defaultRenderer as SunburstArcRenderer).expandNode(node); } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/time_series/time_series_chart.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:collection' show LinkedHashMap; import '../../common/date_time_factory.dart' show DateTimeFactory, LocalDateTimeFactory; import '../cartesian/axis/axis.dart' show Axis, NumericAxis; import '../cartesian/axis/draw_strategy/small_tick_draw_strategy.dart' show SmallTickRendererSpec; import '../cartesian/axis/spec/axis_spec.dart' show AxisSpec; import '../cartesian/axis/spec/date_time_axis_spec.dart' show DateTimeAxisSpec; import '../cartesian/axis/time/date_time_axis.dart' show DateTimeAxis; import '../cartesian/cartesian_chart.dart' show CartesianChart; import '../common/series_renderer.dart' show SeriesRenderer; import '../layout/layout_config.dart' show LayoutConfig; import '../line/line_renderer.dart' show LineRenderer; class TimeSeriesChart extends CartesianChart { final DateTimeFactory dateTimeFactory; TimeSeriesChart( {bool? vertical, LayoutConfig? layoutConfig, NumericAxis? primaryMeasureAxis, NumericAxis? secondaryMeasureAxis, LinkedHashMap? disjointMeasureAxes, this.dateTimeFactory = const LocalDateTimeFactory()}) : super( vertical: vertical, layoutConfig: layoutConfig, domainAxis: DateTimeAxis(dateTimeFactory), primaryMeasureAxis: primaryMeasureAxis, secondaryMeasureAxis: secondaryMeasureAxis, disjointMeasureAxes: disjointMeasureAxes); @override void initDomainAxis() { domainAxis!.tickDrawStrategy = SmallTickRendererSpec() .createDrawStrategy(context, graphicsFactory!); } @override SeriesRenderer makeDefaultRenderer() { return LineRenderer() ..rendererId = SeriesRenderer.defaultRendererId; } @override Axis createDomainAxisFromSpec(AxisSpec axisSpec) { return (axisSpec as DateTimeAxisSpec).createDateTimeAxis(dateTimeFactory); } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/treemap/base_treemap_renderer.dart ================================================ // Copyright 2019 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:collection' show Queue; import 'dart:math' show MutableRectangle, Point, Rectangle, min; import 'package:charts_common/src/chart/common/base_chart.dart'; import 'package:charts_common/src/chart/common/chart_canvas.dart'; import 'package:charts_common/src/chart/common/datum_details.dart'; import 'package:charts_common/src/chart/common/processed_series.dart'; import 'package:charts_common/src/chart/common/series_datum.dart'; import 'package:charts_common/src/chart/common/series_renderer.dart'; import 'package:charts_common/src/common/math.dart' show NullablePoint; import 'package:charts_common/src/common/style/style_factory.dart'; import 'package:charts_common/src/data/series.dart'; import 'package:charts_common/src/data/tree.dart'; import 'package:meta/meta.dart'; import 'treemap_label_decorator.dart'; import 'treemap_renderer_config.dart'; import 'treemap_renderer_element.dart'; /// Key for storing a list of treemap renderer elements. /// /// Each element contains a bounding rectangle for rendering. const treeMapElementsKey = AttributeKey>>( 'TreeMapRenderer.elements'); abstract class BaseTreeMapRenderer extends BaseSeriesRenderer { /// Default renderer ID for treemap. static const defaultRendererId = 'treemap'; /// A hash map that allows accessing the renderer element drawn on the chart /// from a treemap node. final _treeNodeToRendererElement = , TreeMapRendererElement>{}; /// An ordered map of [_AnimatedTreeMapRect] that will get drawn on the /// canvas. final _animatedTreeMapRects = >{}; /// Renderer configuration. final TreeMapRendererConfig config; /// Decorator for rendering treemap node label. final TreeMapLabelDecorator? labelDecorator; BaseChart? _chart; BaseTreeMapRenderer({required this.config, String? rendererId}) : labelDecorator = config.labelDecorator, super( rendererId: rendererId ?? defaultRendererId, layoutPaintOrder: config.layoutPaintOrder, symbolRenderer: config.symbolRenderer, ); @override void onAttach(BaseChart chart) { super.onAttach(chart); _chart = chart; } /// Rtl direction setting from chart context. bool get isRtl => _chart?.context.isRtl ?? false; @override void configureSeries(List> seriesList) { assignMissingColors(seriesList, emptyCategoryUsesSinglePalette: true); assignMissingStrokeWidths(seriesList); } @override void preprocessSeries(List> seriesList) { _ensureSingleTree(seriesList); // Clears [_treeNodeToRendererElement] map when a new seriesList is passed // in for preprocessing. The order in this map matters because the first // entry is expected to be the root. _treeNodeToRendererElement.clear(); for (final series in seriesList) { final count = series.data.length; // Populates [treeNodeToRendererElement] map entries. for (var i = 0; i < count; i++) { final node = series.data[i] as TreeNode; _treeNodeToRendererElement[node] = _createRendererElement(series, i, isLeaf: !node.hasChildren); } series.setAttr(treeMapElementsKey, _treeNodeToRendererElement.values); } } /// Tiling algorithm for dividing a region into subregions of specified areas. void tile(TreeNode node); @override void update(List> seriesList, bool isAnimating) { // _visibleTreeMapRectKeys is used to remove any [_AnimatedTreeMapRect]s // that were rendered in the previous draw cycles, but no longer have a // corresponding datum in the new series data. final _visibleTreeMapRectKeys = {}; for (final series in seriesList) { if (series.data.isNotEmpty) { final root = series.data.first as TreeNode; // Configures the renderer element for root node. _configureRootRendererElement(root); // Applies tiling algorithm to each node. for (final datum in series.data) { final node = datum as TreeNode; tile(node); final element = _getRendererElement(node)..refreshPaintProperties(); final rect = _createAnimatedTreeMapRect(element); _visibleTreeMapRectKeys.add(rect.key); } } } _animatedTreeMapRects.forEach((_, rect) { if (!_visibleTreeMapRectKeys.contains(rect.key)) { rect.animateOut(); } }); } @override void paint(ChartCanvas canvas, double animationPercent) { if (animationPercent == 1.0) { _animatedTreeMapRects.removeWhere((_, rect) => rect.animatingOut); } _animatedTreeMapRects.forEach((_, animatedRect) { final element = animatedRect.getCurrentRect(animationPercent); final rect = element.boundingRect; // canvas.drawRRect is used instead of canvas.drawRect because drawRRect // supports FillPatternType.forwardHatch. canvas.drawRRect( rect, fill: element.fillColor, fillPattern: element.fillPattern, patternStrokeWidthPx: config.patternStrokeWidthPx, patternColor: element.patternColor, stroke: element.strokeColor, strokeWidthPx: element.strokeWidthPx!.toDouble(), radius: 0, roundTopLeft: false, roundTopRight: false, roundBottomLeft: false, roundBottomRight: false, ); // Paint label. labelDecorator?.decorate(element, canvas, graphicsFactory!, drawBounds: drawBounds!, animationPercent: animationPercent, rtl: isRtl, // only leaf node could possibly render label vertically. renderVertically: element.isLeaf && rect.width < rect.height, renderMultiline: element.isLeaf); }); } /// Datum details of nearest rectangles in the treemap. @override List> getNearestDatumDetailPerSeries( Point chartPoint, bool byDomain, Rectangle? boundsOverride, { bool selectOverlappingPoints = false, bool selectExactEventLocation = false, }) { final nearest = >[]; // Checks if the [chartPoint] is within bounds. if (!isPointWithinBounds(chartPoint, boundsOverride)) return nearest; final root = _treeNodeToRendererElement.entries.first.key; final queue = Queue>()..add(root); while (queue.isNotEmpty) { final node = queue.removeFirst(); final element = _getRendererElement(node); if (element.boundingRect.containsPoint(chartPoint)) { nearest.add(DatumDetails( index: element.index, series: element.series, datum: node, domain: element.domain, measure: element.measure, domainDistance: 0.0, measureDistance: 0.0, )); // No need to verify remaining siblings. queue.clear(); // Only processes nodes whose parents contain the [chartPoint]. // This reduces the number of nodes to verify. queue.addAll(node.children); } } // Prioritizes nodes with larger depth; nearest.sort((a, b) { final nodeA = a.datum as TreeNode; final nodeB = b.datum as TreeNode; return nodeB.depth.compareTo(nodeA.depth); }); return nearest; } @override DatumDetails addPositionToDetailsForSeriesDatum( DatumDetails details, SeriesDatum seriesDatum) { final bounds = _getRendererElement(seriesDatum.datum as TreeNode).boundingRect; final chartPosition = Point( (isRtl ? bounds.left : bounds.right).toDouble(), (bounds.top + (bounds.height / 2)).toDouble()); return DatumDetails.from(details, chartPosition: NullablePoint.from(chartPosition)); } /// Assigns missing colors in case when color accessor functions are not set. /// /// Assigned color is based on the depth of each node. @override void assignMissingColors(Iterable> seriesList, {required bool emptyCategoryUsesSinglePalette}) { for (final series in seriesList) { final colorPalettes = StyleFactory.style.getOrderedPalettes(series.data.length); final count = colorPalettes.length; series.fillColorFn ??= (int? index) { var node = series.data[index!] as TreeNode; return colorPalettes[node.depth % count].shadeDefault; }; // Pattern color and stroke color defaults to the default config stroke // color if no accessor is provided. series.colorFn ??= (index) => config.strokeColor; series.patternColorFn ??= (index) => config.strokeColor; } } /// Assigns missing stroke widths in case when strokeWidthPx accessor /// functions are not set. @protected void assignMissingStrokeWidths(Iterable> seriesList) { for (final series in seriesList) { series.strokeWidthPxFn ??= (_) => config.strokeWidthPx; } } /// Available bounding rectangle that can be used to lay out the child /// renderer elements. /// /// Available bounding rectangle is computed after padding is applied. @protected MutableRectangle availableLayoutBoundingRect(TreeNode node) { final element = _getRendererElement(node); final rect = element.boundingRect; final padding = config.rectPaddingPx; var top = rect.top + padding.topPx; var left = rect.left + padding.leftPx; var width = rect.width - padding.leftPx - padding.rightPx; var height = rect.height - padding.topPx - padding.bottomPx; // Handles an edge case when width or height is negative. if (width < 0) { left += width / 2; width = 0; } if (height < 0) { top += height / 2; height = 0; } return MutableRectangle(left, top, width, height); } /// Scales the area of each renderer element in [children] by a [scaleFactor]. /// /// [scaleFactor] should be calculated based on the available layout area and /// the measure which the available layout area represents. @protected void scaleArea(Iterable> children, num scaleFactor) { for (final child in children) { final element = _getRendererElement(child); final area = element.measure * (scaleFactor < 0 ? 0 : scaleFactor); element.area = area <= 0 ? 0 : area; } } /// Gets the measure for a tree [node]. @protected num measureForTreeNode(TreeNode node) => _getRendererElement(node).measure; /// Gets the area of a [Rectangle]. @protected num areaForRectangle(Rectangle rect) => rect.height * rect.width; /// Gets the area for a tree [node]. @protected num areaForTreeNode(TreeNode node) => _getRendererElement(node).area; /// Positions each renderer element in [nodes] within the [boundingRect]. /// /// [side] is defined as the smallest side of the [layoutArea]. /// /// Consider the following boundingRect: /// ``` /// boundingRect: /// ------------------ /// |************| | /// (side) |*layoutArea*| | height /// |************| | /// ------------------ /// width /// ``` @protected void position(Iterable> nodes, MutableRectangle boundingRect, num side, num layoutArea) { var top = boundingRect.top; var left = boundingRect.left; var length = side > 0 ? (layoutArea / side) : 0; // [side] is equal to the height of the boundingRect, so stacks rectangles // vertically. [length] is the width of the stacking rectangles. if (side == boundingRect.height) { // Truncates the length since it is out of bounds. if (length > boundingRect.width) length = boundingRect.width.toInt(); for (final node in nodes) { final element = _getRendererElement(node); final height = min(boundingRect.top + boundingRect.height - top, length > 0 ? (element.area / length) : 0); element.boundingRect = Rectangle(left, top, length, height); top += height; } boundingRect.left += length; boundingRect.width -= length; } else { // Positions rectangles horizontally. if (length > boundingRect.height) length = boundingRect.height.toInt(); for (final node in nodes) { final element = _getRendererElement(node); final width = min(boundingRect.left + boundingRect.width - left, length > 0 ? (element.area / length) : 0); element.boundingRect = Rectangle(left, top, width, length); left += width; } boundingRect.top += length; boundingRect.height -= length; } } void _configureRootRendererElement(TreeNode root) { // Root should take up the entire [drawBounds] area. final drawBounds = this.drawBounds!; _getRendererElement(root) ..boundingRect = drawBounds ..area = areaForRectangle(drawBounds); } /// Creates an [_AnimatedTreeMapRect]. /// /// This object contains previous, current, and target animation state of /// treemap renderer [element]. _AnimatedTreeMapRect _createAnimatedTreeMapRect( TreeMapRendererElement element) { final key = element.domain; // Creates a new _AnimatedTreeMapRect if not exists. Otherwise, moves the // existing one to the end of the list so that the iteration order of // _AnimatedTreeMapRects is preserved. This is important because the order // of rects in _animatedTreeMapRects determines the painting order. final rect = _animatedTreeMapRects.containsKey(key) ? _animatedTreeMapRects.remove(key)! : _AnimatedTreeMapRect(key: key); _animatedTreeMapRects[key] = rect; return rect..setNewTarget(element); } /// Creates a basic [TreeMapRendererElement]. /// /// `boundingRect` and `area` are set after tile function is applied. TreeMapRendererElement _createRendererElement( MutableSeries series, int index, { required bool isLeaf, }) => TreeMapRendererElement( domain: series.domainFn(index), measure: series.measureFn(index)!, isLeaf: isLeaf, index: index, series: series, ); TreeMapRendererElement _getRendererElement(TreeNode node) { final element = _treeNodeToRendererElement[node]; assert( element != null, 'There is no associated renderer element for $node.'); return element!; } void _ensureSingleTree(List> seriesList) { assert(seriesList.length <= 1, 'TreeMapRenderer only supports a single series at most.'); } } /// A representation of the animation state of [TreeMapRendererElement]. class _AnimatedTreeMapRect { final D key; /// A previous [TreeMapRendererElement] before animation. TreeMapRendererElement? _previousRect; /// A target [TreeMapRendererElement] after animation is performed. late TreeMapRendererElement _targetRect; /// Current [TreeMapRendererElement] at a given animation percent time. TreeMapRendererElement? _currentRect; // Flag indicating whether this rect is being animated out of the chart. bool animatingOut = false; _AnimatedTreeMapRect({required this.key}); /// Animates a rect that was removed from the tree out of the view. /// /// Animates the height and width of the rect down to zero, centered in the /// middle of the original rect. void animateOut() { final newTarget = _currentRect!.clone(); final rect = newTarget.boundingRect; newTarget.boundingRect = Rectangle( rect.left + (rect.width / 2), rect.top + (rect.height / 2), 0, 0); newTarget.strokeWidthPx = 0.0; setNewTarget(newTarget); animatingOut = true; } void setNewTarget(TreeMapRendererElement newTarget) { animatingOut = false; // Only when [currentRect] is null, [currentRect] should be [newTarget]. _currentRect ??= newTarget.clone(); _previousRect = _currentRect!.clone(); _targetRect = newTarget; } /// Current [TreeMapRendererElement] at a given animation percent time. TreeMapRendererElement getCurrentRect(double animationPercent) { if (animationPercent == 1.0 || _previousRect == null) { _currentRect = _targetRect; _previousRect = _targetRect; return _currentRect!; } _currentRect! .updateAnimationPercent(_previousRect!, _targetRect, animationPercent); return _currentRect!; } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/treemap/dice_treemap_renderer.dart ================================================ // Copyright 2019 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'package:charts_common/src/data/tree.dart'; import 'base_treemap_renderer.dart'; import 'treemap_renderer_config.dart'; /// A treemap renderer that renders a treemap with dice layout. class DiceTreeMapRenderer extends BaseTreeMapRenderer { DiceTreeMapRenderer({String? rendererId, TreeMapRendererConfig? config}) : super( config: config ?? TreeMapRendererConfig(tileType: TreeMapTileType.dice), rendererId: rendererId ?? BaseTreeMapRenderer.defaultRendererId); /// Uses dicing as the tiling algorithm for this tree map. @override void tile(TreeNode node) { final children = node.children; if (children.isNotEmpty) { final rect = availableLayoutBoundingRect(node); final measure = measureForTreeNode(node); final scaleFactor = measure == 0 ? 0 : areaForRectangle(rect) / measure; scaleArea(children, scaleFactor); position(children, rect, rect.height, areaForRectangle(rect)); } } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/treemap/slice_dice_treemap_renderer.dart ================================================ // Copyright 2019 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'package:charts_common/src/data/tree.dart'; import 'base_treemap_renderer.dart'; import 'treemap_renderer_config.dart'; /// A treemap renderer that renders a treemap with slice-and-dice layout. class SliceDiceTreeMapRenderer extends BaseTreeMapRenderer { SliceDiceTreeMapRenderer( {String? rendererId, TreeMapRendererConfig? config}) : super( config: config ?? TreeMapRendererConfig(tileType: TreeMapTileType.sliceDice), rendererId: rendererId ?? BaseTreeMapRenderer.defaultRendererId); /// Uses slice-and-dice as the tiling algorithm for this tree map. @override void tile(TreeNode node) { final children = node.children; if (children.isNotEmpty) { final rect = availableLayoutBoundingRect(node); final measure = measureForTreeNode(node); final scaleFactor = measure == 0 ? 0 : areaForRectangle(rect) / measure; scaleArea(children, scaleFactor); position(children, rect, node.depth & 1 == 1 ? rect.height : rect.width, areaForRectangle(rect)); } } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/treemap/slice_treemap_renderer.dart ================================================ // Copyright 2019 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'package:charts_common/src/data/tree.dart'; import 'base_treemap_renderer.dart'; import 'treemap_renderer_config.dart'; /// A treemap renderer that renders a treemap with slice layout. class SliceTreeMapRenderer extends BaseTreeMapRenderer { SliceTreeMapRenderer({String? rendererId, TreeMapRendererConfig? config}) : super( config: config ?? TreeMapRendererConfig(tileType: TreeMapTileType.slice), rendererId: rendererId ?? BaseTreeMapRenderer.defaultRendererId); /// Uses slicing as the tiling algorithm for this tree map. @override void tile(TreeNode node) { final children = node.children; if (children.isNotEmpty) { final rect = availableLayoutBoundingRect(node); final measure = measureForTreeNode(node); final scaleFactor = measure == 0 ? 0 : areaForRectangle(rect) / measure; scaleArea(children, scaleFactor); position(children, rect, rect.width, areaForRectangle(rect)); } } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/treemap/squarified_treemap_renderer.dart ================================================ // Copyright 2019 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:collection' show Queue; import 'dart:math' as math; import 'package:charts_common/src/data/tree.dart'; import 'base_treemap_renderer.dart'; import 'treemap_renderer_config.dart'; /// A treemap renderer that renders a squarified treemap. class SquarifiedTreeMapRenderer extends BaseTreeMapRenderer { /// Golden ratio. final _ratio = .5 * (1 + math.sqrt(5)); SquarifiedTreeMapRenderer( {String? rendererId, TreeMapRendererConfig? config}) : super( config: config ?? TreeMapRendererConfig(tileType: TreeMapTileType.squarified), rendererId: rendererId ?? BaseTreeMapRenderer.defaultRendererId); /// Uses squarification as the tiling algorithm for this tree map. /// /// The idea is to present treemap layouts in which the rectangles approximate /// squares. /// /// For more information about squarify technique, see: /// Squarified Treemaps: /// https://www.win.tue.nl/~vanwijk/stm.pdf /// Squarify algorithm from Charted: /// https://cs.corp.google.com/piper///depot/google3/third_party/dart/charted/lib/layout/src/treemap_layout.dart?l=158 @override void tile(TreeNode node) { final children = node.children; if (children.isNotEmpty) { final remainingNodes = Queue.of(children); final rect = availableLayoutBoundingRect(node); final analyzer = _SquarifyRatioAnalyzer(_ratio, areaForTreeNode); var bestScore = double.infinity; var width = math.min(rect.width, rect.height); final measure = measureForTreeNode(node); final scaleFactor = measure == 0 ? 0 : areaForRectangle(rect) / measure; scaleArea(children, scaleFactor); while (remainingNodes.isNotEmpty) { final child = remainingNodes.first; analyzer.addNode(child); final score = analyzer.worst(width).toDouble(); // Adding a new child rectangle improves score for the aspect ratio . if (score <= bestScore) { remainingNodes.removeFirst(); bestScore = score; } else { analyzer.removeLast(); position(analyzer.nodes, rect, width, analyzer.layoutArea); width = math.min(rect.width, rect.height); analyzer.reset(); bestScore = double.infinity; } } if (analyzer.nodes.isNotEmpty) { position(analyzer.nodes, rect, width, analyzer.layoutArea); analyzer.reset(); } } } } /// An analyzer that computes whether adding a node to a layout can improve the /// aspect ratio of the layout. class _SquarifyRatioAnalyzer { /// A accessor function that returns area of a [TreeNode]. final AreaFn _areaFn; /// Target aspect ratio. final num _ratio; /// List of processing nodes. final nodes = >[]; var _layoutArea = 0.0; _SquarifyRatioAnalyzer(this._ratio, this._areaFn); /// Adds a node for processing. void addNode(TreeNode node) { nodes.add(node); _layoutArea += _areaFn(node); } /// Removes the last node added for processing. void removeLast() { _layoutArea -= _areaFn(nodes.removeLast()); } /// Allocated area for laying out processing [nodes]. num get layoutArea => _layoutArea; /// Aspect-ratio score for the list of processing [nodes] in a given [width]. /// /// [width] is defined as the smallest side of a rectangle. /// /// Underlying equation: /// max(w^2 * r_max * ratio / (r_layout^2), /// r_layout^2 / (w^2 * r_min * ratio)). num worst(num width) { var rMin = double.infinity; var rMax = 0.0; // Finds rMin (i.e minimum area) and rMax (i.e maximum area) in [nodes]. for (final node in nodes) { final area = _areaFn(node).toDouble(); if (area <= 0) continue; if (area < rMin) rMin = area; if (area > rMax) rMax = area; } final sqWidth = _square(width); final sqArea = _square(_layoutArea); return sqArea > 0 ? math.max(sqWidth * rMax * _ratio / sqArea, sqArea / (sqWidth * rMin * _ratio)) : double.infinity; } void reset() { nodes.clear(); _layoutArea = 0.0; } /// Calculates the square of a number [n]. num _square(num n) => n * n; } /// A function type that returns area for a tree [node]. typedef AreaFn = num Function(TreeNode node); ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/treemap/treemap_chart.dart ================================================ // Copyright 2019 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'package:charts_common/src/chart/common/base_chart.dart'; import 'package:charts_common/src/chart/common/datum_details.dart'; import 'package:charts_common/src/chart/common/processed_series.dart'; import 'package:charts_common/src/chart/common/selection_model/selection_model.dart'; import 'package:charts_common/src/chart/common/series_renderer.dart'; import 'package:charts_common/src/chart/layout/layout_config.dart'; import 'squarified_treemap_renderer.dart'; class TreeMapChart extends BaseChart { TreeMapChart({LayoutConfig? layoutConfig}) : super(layoutConfig: layoutConfig ?? LayoutConfig()); @override void drawInternal(List> seriesList, {bool? skipAnimation, bool? skipLayout}) { if (seriesList.length > 1) { throw ArgumentError('TreeMapChart can only render a single tree.'); } super.drawInternal(seriesList, skipAnimation: skipAnimation, skipLayout: skipLayout); } /// Squarified treemap is used as default renderer. @override SeriesRenderer makeDefaultRenderer() { return SquarifiedTreeMapRenderer() ..rendererId = SeriesRenderer.defaultRendererId; } /// Returns a list of datum details from the selection model of [type]. @override List> getDatumDetails(SelectionModelType type) { final details = >[]; final treeMapSelection = getSelectionModel(type); for (final seriesDatum in treeMapSelection.selectedDatum) { final series = seriesDatum.series; final datumIndex = seriesDatum.index; final renderer = getSeriesRenderer(series.getAttr(rendererIdKey)); final datumDetails = renderer.addPositionToDetailsForSeriesDatum( DatumDetails( datum: seriesDatum.datum, domain: series.domainFn(datumIndex), measure: series.measureFn(datumIndex), series: seriesDatum.series, color: series.colorFn!(datumIndex)), seriesDatum); details.add(datumDetails); } return details; } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/treemap/treemap_label_decorator.dart ================================================ // Copyright 2019 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math' show Rectangle, pi; import 'package:charts_common/src/chart/cartesian/axis/spec/axis_spec.dart'; import 'package:charts_common/src/chart/common/chart_canvas.dart'; import 'package:charts_common/src/common/color.dart'; import 'package:charts_common/src/common/graphics_factory.dart'; import 'package:charts_common/src/common/text_element.dart'; import 'package:charts_common/src/common/text_style.dart'; import 'package:charts_common/src/common/text_utils.dart'; import 'package:charts_common/src/data/series.dart'; import 'treemap_renderer_decorator.dart'; import 'treemap_renderer_element.dart'; /// Decorator that renders label for treemap renderer element. class TreeMapLabelDecorator extends TreeMapRendererDecorator { // Default configuration static const _defaultLabelPadding = 4; static const _defaultFontSize = 12; static final _defaultLabelStyle = TextStyleSpec(fontSize: _defaultFontSize, color: Color.black); /// Rotation value of 90 degrees clockwise. static const _90DegreeClockwise = pi / 2; /// Text style spec for labels. final TextStyleSpec labelStyleSpec; /// Padding of the label text. final int labelPadding; /// Whether or not to allow labels to draw outside of their bounding box. final bool allowLabelOverflow; /// Whether or not drawing a label in multiple lines if there is enough /// space. final bool enableMultiline; TreeMapLabelDecorator( {TextStyleSpec? labelStyleSpec, this.labelPadding = _defaultLabelPadding, this.allowLabelOverflow = true, this.enableMultiline = false}) : labelStyleSpec = labelStyleSpec ?? _defaultLabelStyle; @override void decorate(TreeMapRendererElement rendererElement, ChartCanvas canvas, GraphicsFactory graphicsFactory, {required Rectangle drawBounds, required double animationPercent, bool rtl = false, bool renderVertically = false, bool renderMultiline = false}) { // Decorates the renderer elements when animation is completed. if (animationPercent != 1.0) return; // Creates [TextStyle] from [TextStyleSpec] to be used by all the elements. // The [GraphicsFactory] is needed since it cannot be created earlier. final labelStyle = _asTextStyle(graphicsFactory, labelStyleSpec); final labelFn = rendererElement.series.labelAccessorFn; final datumIndex = rendererElement.index; final label = labelFn != null ? labelFn(datumIndex) : null; // Skips if this element has no label. if (label == null || label.isEmpty) return; // Uses datum specific label style if provided. final datumLabelStyle = _datumStyle( rendererElement.series.insideLabelStyleAccessorFn, datumIndex, graphicsFactory, defaultStyle: labelStyle); final rect = rendererElement.boundingRect; final labelElement = graphicsFactory.createTextElement(label) ..textStyle = datumLabelStyle ..textDirection = rtl ? TextDirection.rtl : TextDirection.ltr; final labelHeight = labelElement.measurement.verticalSliceWidth; final maxLabelHeight = (renderVertically ? rect.width : rect.height) - (labelPadding * 2); final maxLabelWidth = (renderVertically ? rect.height : rect.width) - (labelPadding * 2); final multiline = enableMultiline && renderMultiline; final parts = wrapLabelLines( labelElement, graphicsFactory, maxLabelWidth, maxLabelHeight, allowLabelOverflow: allowLabelOverflow, multiline: multiline); for (var index = 0; index < parts.length; index++) { final segment = _createLabelSegment( rect, labelHeight, parts[index], index, rtl: rtl, rotate: renderVertically); // Draws a label inside of a treemap renderer element. canvas.drawText(segment.text, segment.xOffet, segment.yOffset, rotation: segment.rotationAngle); } } /// Converts [TextStyleSpec] to [TextStyle]. TextStyle _asTextStyle( GraphicsFactory graphicsFactory, TextStyleSpec labelSpec) => graphicsFactory.createTextPaint() ..color = labelSpec.color ?? Color.black ..fontFamily = labelSpec.fontFamily ..fontSize = labelSpec.fontSize ?? _defaultFontSize ..lineHeight = labelSpec.lineHeight; /// Gets datum specific style. TextStyle _datumStyle(AccessorFn? labelStyleFn, int datumIndex, GraphicsFactory graphicsFactory, {required TextStyle defaultStyle}) { final styleSpec = labelStyleFn?.call(datumIndex); return (styleSpec != null) ? _asTextStyle(graphicsFactory, styleSpec) : defaultStyle; } _TreeMapLabelSegment _createLabelSegment(Rectangle elementBoundingRect, num labelHeight, TextElement labelElement, int position, {bool rtl = false, bool rotate = false}) { num xOffset; num yOffset; // Set x offset for each line. if (rotate) { xOffset = elementBoundingRect.right - labelPadding - 2 * labelElement.textStyle!.fontSize! - labelHeight * position; } else if (rtl) { xOffset = elementBoundingRect.right - labelPadding; } else { xOffset = elementBoundingRect.left + labelPadding; } // Set y offset for each line. if (!rotate) { yOffset = elementBoundingRect.top + labelPadding + (labelHeight * position); } else if (rtl) { yOffset = elementBoundingRect.bottom - labelPadding; } else { yOffset = elementBoundingRect.top + labelPadding; } return _TreeMapLabelSegment(labelElement, xOffset.toInt(), yOffset.toInt(), rotate ? _90DegreeClockwise : 0.0); } } /// Represents a segment of a label that will be drawn in a single line. class _TreeMapLabelSegment { /// Text to be drawn on the canvas. final TextElement text; /// x-coordinate offset for [text]. final int xOffet; /// y-coordinate offset for [text]. final int yOffset; /// Rotation angle for drawing [text]. final double rotationAngle; _TreeMapLabelSegment( this.text, this.xOffet, this.yOffset, this.rotationAngle); } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/treemap/treemap_renderer_config.dart ================================================ // Copyright 2019 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'package:charts_common/src/chart/common/series_renderer_config.dart'; import 'package:charts_common/src/chart/layout/layout_view.dart'; import 'package:charts_common/src/common/color.dart'; import 'package:charts_common/src/common/style/style_factory.dart'; import 'package:charts_common/src/common/symbol_renderer.dart'; import 'base_treemap_renderer.dart'; import 'dice_treemap_renderer.dart'; import 'slice_dice_treemap_renderer.dart'; import 'slice_treemap_renderer.dart'; import 'squarified_treemap_renderer.dart'; import 'treemap_label_decorator.dart'; /// Configuration for a [BaseTreeMapRenderer]. class TreeMapRendererConfig extends LayoutViewConfig implements SeriesRendererConfig { /// Default padding of a treemap rectangle. static const _defaultRectPadding = ViewMargin(topPx: 26, leftPx: 4, rightPx: 4, bottomPx: 4); @override final String? customRendererId; @override final SymbolRenderer symbolRenderer; @override final rendererAttributes = RendererAttributes(); /// Tiling algorithm, which is the way to divide a region into sub-regions of /// specified areas, in the treemap. final TreeMapTileType tileType; /// The order to paint this renderer on the canvas. final int layoutPaintOrder; /// Padding of the treemap rectangle. final ViewMargin rectPaddingPx; /// Stroke width of the border of the treemap rectangle. final double strokeWidthPx; /// Stroke color of the border of the treemap rectangle. final Color strokeColor; /// Pattern stroke width of the treemap rectangle. final double patternStrokeWidthPx; /// Decorator for optionally decorating treemap rectangle label. final TreeMapLabelDecorator? labelDecorator; TreeMapRendererConfig( {this.customRendererId, this.patternStrokeWidthPx = 1.0, this.strokeWidthPx = 1.0, this.layoutPaintOrder = LayoutViewPaintOrder.treeMap, this.rectPaddingPx = _defaultRectPadding, this.tileType = TreeMapTileType.squarified, this.labelDecorator, Color? strokeColor, SymbolRenderer? symbolRenderer}) : strokeColor = strokeColor ?? StyleFactory.style.black, symbolRenderer = symbolRenderer ?? RectSymbolRenderer(); @override BaseTreeMapRenderer build() { switch (tileType) { case TreeMapTileType.dice: return DiceTreeMapRenderer( config: this, rendererId: customRendererId); case TreeMapTileType.slice: return SliceTreeMapRenderer( config: this, rendererId: customRendererId); case TreeMapTileType.sliceDice: return SliceDiceTreeMapRenderer( config: this, rendererId: customRendererId); default: return SquarifiedTreeMapRenderer( config: this, rendererId: customRendererId); } } } /// Tiling algorithm, which is the way to divide a region into subregions of /// specified areas, in a treemap. /// /// * [dice] - Renders rectangles in dice layout. /// * [slice] - Renders rectangles in slice layout. /// * [sliceDice] - Renders rectangles in slice-and-dice layout. /// * [squarified] - Renders rectangles such that their aspect-ratios approach /// one as close as possible. enum TreeMapTileType { dice, slice, sliceDice, squarified } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/treemap/treemap_renderer_decorator.dart ================================================ // Copyright 2019 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math' show Rectangle; import 'package:charts_common/src/chart/common/chart_canvas.dart'; import 'package:charts_common/src/common/graphics_factory.dart'; import 'treemap_renderer_element.dart'; /// Decorator that gets rendered after [TreeMapRendererElement]s are rendered. abstract class TreeMapRendererDecorator { const TreeMapRendererDecorator(); /// Paints decorator on top of [rendererElement]. void decorate(TreeMapRendererElement rendererElement, ChartCanvas canvas, GraphicsFactory graphicsFactory, {required Rectangle drawBounds, required double animationPercent, bool rtl = false, bool renderVertically = false, bool renderMultiline = false}); } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/chart/treemap/treemap_renderer_element.dart ================================================ // Copyright 2019 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math' show Rectangle; import 'package:charts_common/src/chart/common/chart_canvas.dart'; import 'package:charts_common/src/chart/common/processed_series.dart'; import 'package:charts_common/src/common/color.dart'; /// A renderer element that represents a TreeNode. class TreeMapRendererElement { /// Bounding rectangle of this element. Rectangle get boundingRect => _boundingRect!; set boundingRect(Rectangle value) => _boundingRect = value; Rectangle? _boundingRect; /// Occupied area of this element in pixel. num get area => _area!; set area(num value) => _area = value; num? _area; /// Fill color of this element. Color? fillColor; /// Fill pattern of the background of the treemap rectangle. FillPatternType? fillPattern; /// Pattern color of this element. Color? patternColor; /// Stroke color of this element. Color? strokeColor; /// Whether this element is a leaf in the treemap. bool isLeaf; /// Stroke width of this element. num? strokeWidthPx; /// Associated index in the [series]. int index; /// Original series. ImmutableSeries series; /// Domain of this element. D domain; /// Measure of this element. num measure; TreeMapRendererElement({ Rectangle? boundingRect, num? area, this.fillColor, this.fillPattern, this.patternColor, this.strokeColor, required this.isLeaf, this.strokeWidthPx, required this.index, required this.series, required this.domain, required this.measure, }) : _boundingRect = boundingRect, _area = area; /// Clones a new renderer element with the same properties. TreeMapRendererElement clone() => TreeMapRendererElement( boundingRect: _boundingRect == null ? null : Rectangle.fromPoints( boundingRect.topLeft, boundingRect.bottomRight), area: area, fillPattern: fillPattern, fillColor: fillColor == null ? null : Color.fromOther(color: fillColor!), patternColor: patternColor == null ? null : Color.fromOther(color: patternColor!), strokeColor: strokeColor == null ? null : Color.fromOther(color: strokeColor!), strokeWidthPx: strokeWidthPx, isLeaf: isLeaf, index: index, series: series, domain: domain, measure: measure, ); /// Refreshes paint properties by invoking series accessor functions again. /// /// This is useful when series accessor functions are updated by behaviors /// and redraw of this element is triggered. void refreshPaintProperties() { strokeColor = series.colorFn!(index); strokeWidthPx = series.strokeWidthPxFn!(index); fillColor = series.fillColorFn!(index); fillPattern = series.fillPatternFn == null ? FillPatternType.solid : series.fillPatternFn!(index); patternColor = series.patternColorFn!(index); } /// Updates properties of this element based on [animationPercent]. /// /// Used when animation is in progress. void updateAnimationPercent(TreeMapRendererElement previous, TreeMapRendererElement target, double animationPercent) { // TODO: Implements animation based on animationPercent. boundingRect = target.boundingRect; area = target.area; } @override String toString() => '$runtimeType' + { 'boundingRect': boundingRect, 'area': area, 'strokeColor': strokeColor, 'strokeWidthPx': strokeWidthPx, 'fillColor': fillColor, 'fillPattern': fillPattern, 'patternColor': patternColor, 'isLeaf': isLeaf, 'index': index, 'domain': domain, 'measure': measure, }.toString(); } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/common/color.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'package:meta/meta.dart' show immutable; @immutable class Color { static const black = Color(r: 0, g: 0, b: 0); static const white = Color(r: 255, g: 255, b: 255); static const transparent = Color(r: 0, g: 0, b: 0, a: 0); static const _darkerPercentOfOrig = 0.7; static const _lighterPercentOfOrig = 0.1; final int r; final int g; final int b; final int a; final Color? _darker; final Color? _lighter; const Color({ required this.r, required this.g, required this.b, this.a = 255, Color? darker, Color? lighter, }) : _darker = darker, _lighter = lighter; Color.fromOther({required Color color, Color? darker, Color? lighter}) : r = color.r, g = color.g, b = color.b, a = color.a, _darker = darker ?? color._darker, _lighter = lighter ?? color._lighter; /// Construct the color from a hex code string, of the format #RRGGBB. factory Color.fromHex({required String code}) { var str = code.substring(1, 7); var bigint = int.parse(str, radix: 16); final r = (bigint >> 16) & 255; final g = (bigint >> 8) & 255; final b = bigint & 255; final a = 255; return Color(r: r, g: g, b: b, a: a); } Color get darker => _darker ?? Color( r: (r * _darkerPercentOfOrig).round(), g: (g * _darkerPercentOfOrig).round(), b: (b * _darkerPercentOfOrig).round(), a: a); Color get lighter => _lighter ?? Color( r: r + ((255 - r) * _lighterPercentOfOrig).round(), g: g + ((255 - g) * _lighterPercentOfOrig).round(), b: b + ((255 - b) * _lighterPercentOfOrig).round(), a: a); @override bool operator ==(Object other) => other is Color && other.r == r && other.g == g && other.b == b && other.a == a; @override int get hashCode { var hashcode = r.hashCode; hashcode = hashcode * 37 + g.hashCode; hashcode = hashcode * 37 + b.hashCode; hashcode = hashcode * 37 + a.hashCode; return hashcode; } @override String toString() => rgbaHexString; /// Converts the character into a #RGBA hex string. String get rgbaHexString => '#${_get2CharHex(r)}${_get2CharHex(g)}' '${_get2CharHex(b)}${_get2CharHex(a)}'; /// Converts the character into a #RGB hex string. String get hexString { // Alpha is not included in the hex string. assert(a == 255); return '#${_get2CharHex(r)}${_get2CharHex(g)}${_get2CharHex(b)}'; } String _get2CharHex(int num) { var str = num.toRadixString(16); while (str.length < 2) { str = '0' + str; } return str; } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/common/date_time_factory.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'package:intl/intl.dart' show DateFormat; /// Interface for factory that creates [DateTime] and [DateFormat]. /// /// This allows for creating of locale specific date time and date format. abstract class DateTimeFactory { // TODO: Per cbraun@, we need to allow setting the timezone that // is used globally (along with other settings like which day the week starts // on. Use DateTimeFactory - either return a local DateTime or a UTC date time // based on the setting. // TODO: We need to incorporate the time zoned calendar here // because Dart DateTime doesn't do this. TZDateTime implements DateTime, so // we can use DateTime as the interface. DateTime createDateTimeFromMilliSecondsSinceEpoch(int millisecondsSinceEpoch); DateTime createDateTime(int year, [int month = 1, int day = 1, int hour = 0, int minute = 0, int second = 0, int millisecond = 0, int microsecond = 0]); /// Returns a [DateFormat]. DateFormat createDateFormat(String? pattern); } /// A local time [DateTimeFactory]. class LocalDateTimeFactory implements DateTimeFactory { const LocalDateTimeFactory(); @override DateTime createDateTimeFromMilliSecondsSinceEpoch( int millisecondsSinceEpoch) { return DateTime.fromMillisecondsSinceEpoch(millisecondsSinceEpoch); } @override DateTime createDateTime(int year, [int month = 1, int day = 1, int hour = 0, int minute = 0, int second = 0, int millisecond = 0, int microsecond = 0]) { return DateTime( year, month, day, hour, minute, second, millisecond, microsecond); } /// Returns a [DateFormat]. @override DateFormat createDateFormat(String? pattern) => DateFormat(pattern); } /// An UTC time [DateTimeFactory]. class UTCDateTimeFactory implements DateTimeFactory { const UTCDateTimeFactory(); @override DateTime createDateTimeFromMilliSecondsSinceEpoch( int millisecondsSinceEpoch) { return DateTime.fromMillisecondsSinceEpoch(millisecondsSinceEpoch, isUtc: true); } @override DateTime createDateTime(int year, [int month = 1, int day = 1, int hour = 0, int minute = 0, int second = 0, int millisecond = 0, int microsecond = 0]) { return DateTime.utc( year, month, day, hour, minute, second, millisecond, microsecond); } /// Returns a [DateFormat]. @override DateFormat createDateFormat(String? pattern) => DateFormat(pattern); } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/common/gesture_listener.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math' show Point; /// Listener to touch gestures. /// /// [GestureListeners] can override only the gestures it is interested in. /// /// Each gesture returns true if the event is consumed or false if it should /// continue to alert other listeners. class GestureListener { static final GestureCancelCallback defaultTapCancel = () {}; static final GestureSinglePointCallback defaultTapTest = (_) => false; /// Called before all gestures (except onHover) as a preliminary test to /// see who is interested in an event. /// /// All listeners that return true will get the next gesture event. /// /// Any listener that returns false will only get the next gesture event if /// no one returned true. /// /// This is useful for figuring out who is claiming a gesture event. /// Example: SelectNearest returns true for onTapTest if the point is within /// the drawArea. SeriesLegend returns true for onTapTest if the point is /// within the legend. If the tap occurs in either of those places the /// corresponding listener. If the tap occurs outside of both targets, then /// both will be given the event so they can deselect everything in the /// selection model. /// /// Defaults to function that returns false allowing other listeners to preempt. final GestureSinglePointCallback onTapTest; /// Called if onTapTest was previously called, but listener is being preempted. final GestureCancelCallback onTapCancel; /// Called after the tap event has been going on for a period of time (500ms) /// without moving much (20px). /// The onTap or onDragStart gestures can still trigger after this gesture. final GestureSinglePointCallback? onLongPress; /// Called on tap up if not dragging. final GestureSinglePointCallback? onTap; /// Called when a mouse hovers over the chart. (No tap event). final GestureSinglePointCallback? onHover; /// Called when the chart is focused. final GestureCallback? onFocus; /// Called when the chart is blured. final GestureCallback? onBlur; /// Called when the tap event has moved beyond a threshold indicating that /// the user is dragging. /// /// This will only be called once per drag gesture independent of how many /// touches are going on until the last touch is complete. onDragUpdate is /// called as touches move updating the scale as determined by the first /// two points. onDragEnd is called when the last touch event lifts and the /// velocity is calculated from the final movement. /// /// onDragStart, onDragUpdate, and onDragEnd are also called for mouse wheel /// with the scale and point updated given the WheelEvent (deltaY updates the /// scale, deltaX updates the event point/pans). /// /// TODO: Add a "discrete" flag that tells drag listeners whether /// they should be expecting a series of continuous updates, or one large /// update. This will mostly be used to control whether we animate the chart /// between onDragUpdate calls. /// /// TODO: Investigate low performance of chart rendering from /// flutter when animation is enabled and we pinch to zoom on the chart. final GestureDragStartCallback? onDragStart; final GestureDragUpdateCallback? onDragUpdate; final GestureDragEndCallback? onDragEnd; GestureListener({ GestureSinglePointCallback? onTapTest, GestureCancelCallback? onTapCancel, this.onLongPress, this.onTap, this.onHover, this.onDragStart, this.onDragUpdate, this.onDragEnd, this.onFocus, this.onBlur, }) : onTapTest = onTapTest ?? defaultTapTest, onTapCancel = onTapCancel ?? defaultTapCancel; } typedef GestureCancelCallback = void Function(); typedef GestureCallback = bool Function(); typedef GestureSinglePointCallback = bool Function(Point localPosition); typedef GestureDragStartCallback = bool Function(Point localPosition); typedef GestureDragUpdateCallback = bool Function( Point localPosition, double scale); typedef GestureDragEndCallback = bool Function( Point localPosition, double scale, double pixelsPerSec); ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/common/graphics_factory.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'line_style.dart' show LineStyle; import 'text_element.dart' show TextElement; import 'text_style.dart' show TextStyle; /// Interface to native platform graphics functions. abstract class GraphicsFactory { LineStyle createLinePaint(); /// Returns a [TextStyle] object. TextStyle createTextPaint(); /// Returns a text element from [text]. TextElement createTextElement(String text); } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/common/line_style.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'paint_style.dart' show PaintStyle; abstract class LineStyle extends PaintStyle { List? get dashPattern; set dashPattern(List? dashPattern); int get strokeWidth; set strokeWidth(int strokeWidth); } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/common/material_palette.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'color.dart' show Color; import 'palette.dart' show Palette; /// A canonical palette of colors from material.io. /// /// @link https://material.io/guidelines/style/color.html#color-color-palette class MaterialPalette { static const black = Color(r: 0, g: 0, b: 0); static const transparent = Color(r: 0, g: 0, b: 0, a: 0); static const white = Color(r: 255, g: 255, b: 255); static Palette get blue => const MaterialBlue(); static Palette get red => const MaterialRed(); static Palette get yellow => const MaterialYellow(); static Palette get green => const MaterialGreen(); static Palette get purple => const MaterialPurple(); static Palette get cyan => const MaterialCyan(); static Palette get deepOrange => const MaterialDeepOrange(); static Palette get lime => const MaterialLime(); static Palette get indigo => const MaterialIndigo(); static Palette get pink => const MaterialPink(); static Palette get teal => const MaterialTeal(); static MaterialGray get gray => const MaterialGray(); // Lazily-instantiated iterable, to avoid allocating colors that are not used. static final Iterable _orderedPalettes = [ () => blue, () => red, () => yellow, () => green, () => purple, () => cyan, () => deepOrange, () => lime, () => indigo, () => pink, () => teal ].map((f) => f()); static List getOrderedPalettes(int count) { return _orderedPalettes.take(count).toList(); } } class MaterialBlue extends Palette { static const _shade200 = Color(r: 0x90, g: 0xCA, b: 0xF9); //#90CAF9 static const _shade500 = Color(r: 0x21, g: 0x96, b: 0xF3, darker: _shade700, lighter: _shade200); static const _shade700 = Color(r: 0x19, g: 0x76, b: 0xD2); //#1976D2 const MaterialBlue(); @override Color get shadeDefault => _shade500; } class MaterialRed extends Palette { static const _shade200 = Color(r: 0xEF, g: 0x9A, b: 0x9A); //#EF9A9A static const _shade700 = Color(r: 0xD3, g: 0x2F, b: 0x2F); //#D32F2F static const _shade500 = Color(r: 0xF4, g: 0x43, b: 0x36, darker: _shade700, lighter: _shade200); const MaterialRed(); @override Color get shadeDefault => _shade500; } class MaterialYellow extends Palette { static const _shade200 = Color(r: 0xFF, g: 0xF5, b: 0x9D); //#FFF59D static const _shade700 = Color(r: 0xFB, g: 0xC0, b: 0x2D); //#FBC02D static const _shade500 = Color(r: 0xFF, g: 0xEB, b: 0x3B, darker: _shade700, lighter: _shade200); const MaterialYellow(); @override Color get shadeDefault => _shade500; } class MaterialGreen extends Palette { static const _shade200 = Color(r: 0xA5, g: 0xD6, b: 0xA7); //#A5D6A7 static const _shade700 = Color(r: 0x38, g: 0x8E, b: 0x3C); //#388E3C; static const _shade500 = Color(r: 0x4C, g: 0xAF, b: 0x50, darker: _shade700, lighter: _shade200); const MaterialGreen(); @override Color get shadeDefault => _shade500; } class MaterialPurple extends Palette { static const _shade200 = Color(r: 0xCE, g: 0x93, b: 0xD8); //#CE93D8 static const _shade700 = Color(r: 0x7B, g: 0x1F, b: 0xA2); //#7B1FA2 static const _shade500 = Color(r: 0x9C, g: 0x27, b: 0xB0, darker: _shade700, lighter: _shade200); const MaterialPurple(); @override Color get shadeDefault => _shade500; } class MaterialCyan extends Palette { static const _shade200 = Color(r: 0x80, g: 0xDE, b: 0xEA); //#80DEEA static const _shade700 = Color(r: 0x00, g: 0x97, b: 0xA7); //#0097A7 static const _shade500 = Color(r: 0x00, g: 0xBC, b: 0xD4, darker: _shade700, lighter: _shade200); const MaterialCyan(); @override Color get shadeDefault => _shade500; } class MaterialDeepOrange extends Palette { static const _shade200 = Color(r: 0xFF, g: 0xAB, b: 0x91); //#FFAB91 static const _shade700 = Color(r: 0xE6, g: 0x4A, b: 0x19); //#E64A19 static const _shade500 = Color(r: 0xFF, g: 0x57, b: 0x22, darker: _shade700, lighter: _shade200); const MaterialDeepOrange(); @override Color get shadeDefault => _shade500; } class MaterialLime extends Palette { static const _shade200 = Color(r: 0xE6, g: 0xEE, b: 0x9C); //#E6EE9C static const _shade700 = Color(r: 0xAF, g: 0xB4, b: 0x2B); //#AFB42B static const _shade500 = Color(r: 0xCD, g: 0xDC, b: 0x39, darker: _shade700, lighter: _shade200); const MaterialLime(); @override Color get shadeDefault => _shade500; } class MaterialIndigo extends Palette { static const _shade200 = Color(r: 0x9F, g: 0xA8, b: 0xDA); //#9FA8DA static const _shade700 = Color(r: 0x30, g: 0x3F, b: 0x9F); //#303F9F static const _shade500 = Color(r: 0x3F, g: 0x51, b: 0xB5, darker: _shade700, lighter: _shade200); const MaterialIndigo(); @override Color get shadeDefault => _shade500; } class MaterialPink extends Palette { static const _shade200 = Color(r: 0xF4, g: 0x8F, b: 0xB1); //#F48FB1 static const _shade700 = Color(r: 0xC2, g: 0x18, b: 0x5B); //#C2185B static const _shade500 = Color(r: 0xE9, g: 0x1E, b: 0x63, darker: _shade700, lighter: _shade200); const MaterialPink(); @override Color get shadeDefault => _shade500; } class MaterialTeal extends Palette { static const _shade200 = Color(r: 0x80, g: 0xCB, b: 0xC4); //#80CBC4 static const _shade700 = Color(r: 0x00, g: 0x79, b: 0x6B); //#00796B static const _shade500 = Color(r: 0x00, g: 0x96, b: 0x88, darker: _shade700, lighter: _shade200); const MaterialTeal(); @override Color get shadeDefault => _shade500; } class MaterialGray extends Palette { static const _shade200 = Color(r: 0xEE, g: 0xEE, b: 0xEE); //#EEEEEE static const _shade700 = Color(r: 0x61, g: 0x61, b: 0x61); //#616161 static const _shade500 = Color(r: 0x9E, g: 0x9E, b: 0x9E, darker: _shade700, lighter: _shade200); const MaterialGray(); @override Color get shadeDefault => _shade500; Color get shade50 => Color(r: 0xFA, g: 0xFA, b: 0xFA); //#FAFAFA Color get shade100 => Color(r: 0xF5, g: 0xF5, b: 0xF5); //#F5F5F5 Color get shade200 => _shade200; Color get shade300 => Color(r: 0xE0, g: 0xE0, b: 0xE0); //#E0E0E0 Color get shade400 => Color(r: 0xBD, g: 0xBD, b: 0xBD); //#BDBDBD Color get shade500 => _shade500; Color get shade600 => Color(r: 0x75, g: 0x75, b: 0x75); //#757575 Color get shade700 => _shade700; Color get shade800 => Color(r: 0x42, g: 0x42, b: 0x42); //#424242 Color get shade900 => Color(r: 0x21, g: 0x21, b: 0x21); //#212121 } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/common/math.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math' show max, min, sqrt, Point; import 'package:vector_math/vector_math.dart' show Vector2; const _defaultEpsilon = 2e-10; /// Takes a value along with an upper and lower bound and returns whether or not /// the value falls inclusively within the bounds. /// /// [value] The input number. /// [lowerBound] The lower bound. /// [upperBound] The upper bound. /// [epsilon] Maximum valid difference between [value] and the bounds. Defaults /// to 2e-10. bool withinBounds(num value, num lowerBound, num upperBound, {double epsilon = _defaultEpsilon}) { return value + epsilon >= lowerBound && value - epsilon <= upperBound; } /// Takes a number and clamps it to within the provided bounds. /// /// Returns the input number if it is within bounds, or the nearest number /// within the bounds. /// /// [value] The input number. /// [minValue] The minimum value to return. /// [maxValue] The maximum value to return. num clamp(num value, num minValue, num maxValue) { return min(max(value, minValue), maxValue); } /// Returns the minimum distance between point p and the line segment vw. /// /// [p] The point. /// [v] Start point for the line segment. /// [w] End point for the line segment. double distanceBetweenPointAndLineSegment(Vector2 p, Vector2 v, Vector2 w) { return sqrt(distanceBetweenPointAndLineSegmentSquared(p, v, w)); } /// Returns the squared minimum distance between point p and the line segment /// vw. /// /// [p] The point. /// [v] Start point for the line segment. /// [w] End point for the line segment. double distanceBetweenPointAndLineSegmentSquared( Vector2 p, Vector2 v, Vector2 w) { final lineLength = v.distanceToSquared(w); if (lineLength == 0) { return p.distanceToSquared(v); } var t0 = (p - v).dot(w - v) / lineLength; t0 = max(0.0, min(1.0, t0)); final projection = v + ((w - v) * t0); return p.distanceToSquared(projection); } /// A two-dimensional cartesian coordinate pair with potentially null coordinate /// values. class NullablePoint { final double? x; final double? y; /// Creates a point with the provided [x] and [y] coordinates. const NullablePoint(this.x, this.y); /// Creates a [NullablePoint] from a [Point]. NullablePoint.from(Point? point) : this(point?.x, point?.y); @override String toString() => 'NullablePoint($x, $y)'; /// Whether [other] is a point with the same coordinates as this point. /// /// Returns `true` if [other] is a [NullablePoint] with [x] and [y] /// coordinates equal to the corresponding coordinates of this point, /// and `false` otherwise. @override bool operator ==(Object other) => other is NullablePoint && x == other.x && y == other.y; @override int get hashCode => x.hashCode * 37 + y.hashCode; /// Converts this to a [Point]. /// /// Throws if [x] or [y] is null. Point toPoint() { assert(x != null); assert(y != null); return Point(x!, y!); } } extension NullablePointsToPoints on Iterable { /// Converts an [Iterable] of [NullablePoint]s to a [List] of [Point]s. /// /// Any [NullablePoint]s that have null values will be filtered out. List> toPoints() { return [ for (final nullablePoint in this) if (nullablePoint.x != null && nullablePoint.y != null) nullablePoint.toPoint(), ]; } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/common/paint_style.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'color.dart'; /// Style properties of a paintable object. abstract class PaintStyle { Color? get color; set color(Color? value); } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/common/palette.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'color.dart' show Color; /// A color palette. abstract class Palette { const Palette(); /// The default shade. Color get shadeDefault; /// Returns a list of colors for this color palette. List makeShades(int colorCnt) { final colors = [shadeDefault]; // If we need more than 2 colors, then [unselected] collides with one of the // generated colors. Otherwise divide the space between the top color // and white in half. final lighterColor = colorCnt < 3 ? shadeDefault.lighter : _getSteppedColor(shadeDefault, (colorCnt * 2) - 1, colorCnt * 2); // Divide the space between 255 and c500 evenly according to the colorCnt. for (var i = 1; i < colorCnt; i++) { colors.add(_getSteppedColor(shadeDefault, i, colorCnt, darker: shadeDefault.darker, lighter: lighterColor)); } colors.add(Color.fromOther(color: shadeDefault, lighter: lighterColor)); return colors; } Color _getSteppedColor(Color color, int index, int steps, {Color? darker, Color? lighter}) { final fraction = index / steps; return Color( r: color.r + ((255 - color.r) * fraction).round(), g: color.g + ((255 - color.g) * fraction).round(), b: color.b + ((255 - color.b) * fraction).round(), a: color.a + ((255 - color.a) * fraction).round(), darker: darker, lighter: lighter, ); } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/common/performance.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. typedef PerformanceCallback = void Function(String tag); class Performance { static PerformanceCallback time = (_) {}; static PerformanceCallback timeEnd = (_) {}; } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/common/proxy_gesture_listener.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math' show Point; import 'package:collection/collection.dart' show IterableExtension; import 'gesture_listener.dart' show GestureListener; /// Listens to all gestures and proxies to child listeners. class ProxyGestureListener { final _listeners = []; var _activeListeners = []; void add(GestureListener listener) { _listeners.add(listener); _activeListeners.clear(); } void remove(GestureListener listener) { _listeners.remove(listener); _activeListeners.clear(); } bool onTapTest(Point localPosition) { _activeListeners.clear(); return _populateActiveListeners(localPosition); } bool onLongPress(Point localPosition) { // Walk through listeners stopping at the first handled listener. final claimingListener = _activeListeners.firstWhereOrNull( (GestureListener listener) => listener.onLongPress?.call(localPosition) ?? false); // If someone claims the long press, then cancel everyone else. if (claimingListener != null) { _activeListeners = _cancel(all: _activeListeners, keep: [claimingListener]); return true; } return false; } bool onTap(Point localPosition) { // Walk through listeners stopping at the first handled listener. final claimingListener = _activeListeners.firstWhereOrNull( (GestureListener listener) => listener.onTap?.call(localPosition) ?? false); // If someone claims the tap, then cancel everyone else. // This should hopefully be rare, like for drilling. if (claimingListener != null) { _activeListeners = _cancel(all: _activeListeners, keep: [claimingListener]); return true; } return false; } bool onHover(Point localPosition) { // Cancel any previously active long lived gestures. _activeListeners = []; // Walk through listeners stopping at the first handled listener. return _listeners.any((GestureListener listener) => listener.onHover?.call(localPosition) ?? false); } bool onDragStart(Point localPosition) { // In Flutter, a tap test may not be triggered because a tap down event // may not be registered if the the drag gesture happens without any pause. if (_activeListeners.isEmpty) { _populateActiveListeners(localPosition); } // Walk through listeners stopping at the first handled listener. final claimingListener = _activeListeners.firstWhereOrNull( (GestureListener listener) => listener.onDragStart?.call(localPosition) ?? false); if (claimingListener != null) { _activeListeners = _cancel(all: _activeListeners, keep: [claimingListener]); return true; } return false; } bool onDragUpdate(Point localPosition, double scale) { return _activeListeners.any((GestureListener listener) => listener.onDragUpdate?.call(localPosition, scale) ?? false); } bool onDragEnd( Point localPosition, double scale, double pixelsPerSecond) { return _activeListeners.any((GestureListener listener) => listener.onDragEnd?.call(localPosition, scale, pixelsPerSecond) ?? false); } bool onFocus() { return _listeners .any((GestureListener listener) => listener.onFocus?.call() ?? false); } bool onBlur() { return _listeners .any((GestureListener listener) => listener.onBlur?.call() ?? false); } List _cancel({ required List all, required List keep, }) { all.forEach((GestureListener listener) { if (!keep.contains(listener)) { listener.onTapCancel(); } }); return keep; } bool _populateActiveListeners(Point localPosition) { var localListeners = List.of(_listeners); var previouslyClaimed = false; localListeners.forEach((GestureListener listener) { var claimed = listener.onTapTest(localPosition); if (claimed && !previouslyClaimed) { // Cancel any already added non-claiming listeners now that someone is // claiming it. _activeListeners = _cancel(all: _activeListeners, keep: [listener]); previouslyClaimed = true; } else if (claimed || !previouslyClaimed) { _activeListeners.add(listener); } }); return previouslyClaimed; } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/common/rate_limit_utils.dart ================================================ // Copyright 2019 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:async'; /// Function that accepts only one argument of type [T] with return type [R]. typedef UnaryFunction = R Function(T argument); /// A wrapper function that enforces the maximum number of times [callback] can /// be called over a period of time. /// /// [delay] specifies the amount of time to wait until [callback] can be called /// again. /// [defaultReturn] is used as the return value when throttle event occurs. UnaryFunction throttle(UnaryFunction callback, {Duration delay = Duration.zero, required R defaultReturn}) { Timer? timer; Stopwatch? stopwatch; return (T argument) { stopwatch ??= Stopwatch()..start(); // This event happened too soon. Do not call the [callback] function yet, // unless it turns out to be the very last event. [delay]s for a period of // time before calling the [callback] function again. if (stopwatch!.elapsedMilliseconds < delay.inMilliseconds) { timer?.cancel(); timer = Timer(delay, () { callback(argument); timer = null; stopwatch = null; }); return defaultReturn; } stopwatch = null; // This is a non-throttled event, go ahead and clear away the last throttled // event callback so that we do not move the hover point back in time. timer?.cancel(); return callback(argument); }; } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/common/rtl_spec.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /// Defines the behavior of the chart if it is RTL. class RTLSpec { /// Creates [RTLSpec]. If no parameters are specified, the defaults are used. const RTLSpec({ this.axisDirection = AxisDirection.reversed, }); /// Direction of the domain axis when the chart container is configured for /// RTL mode. final AxisDirection axisDirection; } /// Direction of the domain axis when the chart container is configured for /// RTL mode. /// /// [normal] Vertically rendered charts will have the primary measure axis on /// the left and secondary measure axis on the right. Domain axis is on the left /// and the domain output range starts from the left and grows to the right. /// Horizontally rendered charts will have the primary measure axis on the /// bottom and secondary measure axis on the right. Measure output range starts /// from the left and grows to the right. /// /// [reversed] Vertically rendered charts will have the primary measure axis on /// the right and secondary measure axis on the left. Domain axis is on the /// right and domain values grows from the right to the left. Horizontally /// rendered charts will have the primary measure axis on the top and secondary /// measure axis on the left. Measure output range is flipped and grows from the /// right to the left. enum AxisDirection { normal, reversed, } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/common/style/material_style.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import '../../chart/cartesian/axis/spec/axis_spec.dart' show LineStyleSpec; import '../color.dart' show Color; import '../graphics_factory.dart' show GraphicsFactory; import '../line_style.dart' show LineStyle; import '../material_palette.dart' show MaterialPalette; import '../palette.dart' show Palette; import 'style.dart' show Style; class MaterialStyle implements Style { const MaterialStyle(); @override Color get black => MaterialPalette.black; @override Color get transparent => MaterialPalette.transparent; @override Color get white => MaterialPalette.white; @override List getOrderedPalettes(int count) => MaterialPalette.getOrderedPalettes(count); @override LineStyle createAxisLineStyle( GraphicsFactory graphicsFactory, LineStyleSpec? spec) { return graphicsFactory.createLinePaint() ..color = spec?.color ?? MaterialPalette.gray.shadeDefault ..dashPattern = spec?.dashPattern ..strokeWidth = spec?.thickness ?? 1; } @override LineStyle createTickLineStyle( GraphicsFactory graphicsFactory, LineStyleSpec? spec) { return graphicsFactory.createLinePaint() ..color = spec?.color ?? MaterialPalette.gray.shadeDefault ..dashPattern = spec?.dashPattern ..strokeWidth = spec?.thickness ?? 1; } @override int get tickLength => 3; @override Color get tickColor => MaterialPalette.gray.shade800; @override LineStyle createGridlineStyle( GraphicsFactory graphicsFactory, LineStyleSpec? spec) { return graphicsFactory.createLinePaint() ..color = spec?.color ?? MaterialPalette.gray.shade300 ..dashPattern = spec?.dashPattern ..strokeWidth = spec?.thickness ?? 1; } @override Color get arcLabelOutsideLeaderLine => MaterialPalette.gray.shade600; @override Color get defaultSeriesColor => MaterialPalette.gray.shadeDefault; @override Color get arcStrokeColor => MaterialPalette.white; @override Color get legendEntryTextColor => MaterialPalette.gray.shade800; @override Color get legendTitleTextColor => MaterialPalette.gray.shade800; @override Color get linePointHighlighterColor => MaterialPalette.gray.shade600; @override Color get noDataColor => MaterialPalette.gray.shade200; @override Color get rangeAnnotationColor => MaterialPalette.gray.shade100; @override Color get sliderFillColor => MaterialPalette.white; @override Color get sliderStrokeColor => MaterialPalette.gray.shade600; @override Color get chartBackgroundColor => MaterialPalette.white; @override double get rangeBandSize => 0.65; } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/common/style/style.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import '../../chart/cartesian/axis/spec/axis_spec.dart' show LineStyleSpec; import '../color.dart' show Color; import '../graphics_factory.dart' show GraphicsFactory; import '../line_style.dart' show LineStyle; import '../palette.dart'; // TODO: Implementation of style will change drastically, see bug // for more details. This is an intermediate step in order to allow overriding // the default style using style factory. /// A set of styling rules that determines the default look and feel of charts. /// /// Get or set the [Style] that is used for the app using [StyleFactory.style]. abstract class Style { Color get black; Color get transparent; Color get white; /// Gets list with [count] of palettes. List getOrderedPalettes(int count); /// Creates [LineStyleSpec] for axis line from spec. /// /// Fill missing value(s) with default. LineStyle createAxisLineStyle( GraphicsFactory graphicsFactory, LineStyleSpec? spec); /// Creates [LineStyleSpec] for tick lines from spec. /// /// Fill missing value(s) with default. LineStyle createTickLineStyle( GraphicsFactory graphicsFactory, LineStyleSpec? spec); /// Default tick length. int get tickLength; /// Default tick color. Color get tickColor; /// /// Creates [LineStyle] for axis gridlines from spec. /// /// Fill missing value(s) with default. LineStyle createGridlineStyle( GraphicsFactory graphicsFactory, LineStyleSpec? spec); /// Default color for outside label leader lines for [ArcLabelDecorator]. Color get arcLabelOutsideLeaderLine; /// Default series color for legends, used as a fallback when a series has no /// data. Color get defaultSeriesColor; /// Default color for strokes for [ArcRendererConfig]. Color get arcStrokeColor; /// Default color for entry text for [Legend]. Color get legendEntryTextColor; /// Default color for title text for [Legend]. Color get legendTitleTextColor; /// Default color for [LinePointHighlighter]. Color get linePointHighlighterColor; /// Default color for "no data" states on charts. Color get noDataColor; /// Default color for [RangeAnnotation]. Color get rangeAnnotationColor; /// Default fill color for [Slider]. Color get sliderFillColor; /// Default stroke color for [Slider]. Color get sliderStrokeColor; /// Default background color for the chart. Color get chartBackgroundColor; /// The width of the band specified as fraction of step. double get rangeBandSize; } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/common/style/style_factory.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'material_style.dart' show MaterialStyle; import 'style.dart' show Style; class StyleFactory { static final StyleFactory _styleFactory = StyleFactory._internal(); Style _style = const MaterialStyle(); /// The [Style] that is used for all the charts in this application. static Style get style => _styleFactory._style; static set style(Style value) { _styleFactory._style = value; } StyleFactory._internal(); } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/common/symbol_renderer.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math' show Rectangle, Point, min, sqrt; import 'package:meta/meta.dart' show protected; import '../chart/common/chart_canvas.dart' show ChartCanvas, FillPatternType; import 'color.dart' show Color; import 'style/style_factory.dart' show StyleFactory; /// Strategy for rendering a symbol. abstract class BaseSymbolRenderer { bool shouldRepaint(covariant BaseSymbolRenderer oldRenderer); } /// Strategy for rendering a symbol bounded within a box. abstract class SymbolRenderer extends BaseSymbolRenderer { /// Whether the symbol should be rendered as a solid shape, or a hollow shape. /// /// If this is true, then fillColor and strokeColor will be used to fill in /// the shape, and draw a border, respectively. The stroke (border) will only /// be visible if a non-zero strokeWidthPx is configured. /// /// If this is false, then the shape will be filled in with a white color /// (overriding fillColor). strokeWidthPx will default to 2 if none was /// configured. final bool isSolid; SymbolRenderer({required this.isSolid}); void paint(ChartCanvas canvas, Rectangle bounds, {List? dashPattern, Color? fillColor, FillPatternType? fillPattern, Color? strokeColor, double? strokeWidthPx}); @protected double? getSolidStrokeWidthPx(double? strokeWidthPx) { return isSolid ? strokeWidthPx : (strokeWidthPx ?? 2.0); } @protected Color? getSolidFillColor(Color? fillColor) { return isSolid ? fillColor : StyleFactory.style.white; } @override bool operator ==(Object other) { return other is SymbolRenderer && other.isSolid == isSolid; } @override int get hashCode => isSolid.hashCode; } /// Strategy for rendering a symbol centered around a point. /// /// An optional second point can describe an extended symbol. abstract class PointSymbolRenderer extends BaseSymbolRenderer { void paint(ChartCanvas canvas, Point p1, double radius, {required Point p2, Color? fillColor, Color? strokeColor}); } /// Rounded rectangular symbol with corners having [radius]. class RoundedRectSymbolRenderer extends SymbolRenderer { final double radius; RoundedRectSymbolRenderer({bool isSolid = true, double? radius}) : radius = radius ?? 1.0, super(isSolid: isSolid); @override void paint(ChartCanvas canvas, Rectangle bounds, {List? dashPattern, Color? fillColor, FillPatternType? fillPattern, Color? strokeColor, double? strokeWidthPx}) { canvas.drawRRect(bounds, fill: getSolidFillColor(fillColor), fillPattern: fillPattern, stroke: strokeColor, radius: radius, roundTopLeft: true, roundTopRight: true, roundBottomRight: true, roundBottomLeft: true); } @override bool shouldRepaint(RoundedRectSymbolRenderer oldRenderer) { return this != oldRenderer; } @override bool operator ==(Object other) { return other is RoundedRectSymbolRenderer && other.radius == radius && super == other; } @override int get hashCode { var hashcode = super.hashCode; hashcode = (hashcode * 37) + radius.hashCode; return hashcode; } } /// Line symbol renderer. class LineSymbolRenderer extends SymbolRenderer { static const roundEndCapsPixels = 2; static const minLengthToRoundCaps = (roundEndCapsPixels * 2) + 1; static const strokeWidthForRoundEndCaps = 4.0; static const strokeWidthForNonRoundedEndCaps = 2.0; /// Thickness of the line stroke. final double strokeWidth; /// Dash pattern for the line. final List? _dashPattern; LineSymbolRenderer( {List? dashPattern, bool isSolid = true, double? strokeWidth}) : strokeWidth = strokeWidth ?? strokeWidthForRoundEndCaps, _dashPattern = dashPattern, super(isSolid: isSolid); @override void paint(ChartCanvas canvas, Rectangle bounds, {List? dashPattern, Color? fillColor, FillPatternType? fillPattern, Color? strokeColor, double? strokeWidthPx}) { final centerHeight = (bounds.bottom - bounds.top) / 2; // If we have a dash pattern, do not round the end caps, and set // strokeWidthPx to a smaller value. Using round end caps makes smaller // patterns blurry. final localDashPattern = dashPattern ?? _dashPattern; final roundEndCaps = localDashPattern == null; // If we have a dash pattern, the normal stroke width makes them look // strangely tall. final localStrokeWidthPx = localDashPattern == null ? getSolidStrokeWidthPx(strokeWidthPx ?? strokeWidth) : strokeWidthForNonRoundedEndCaps; // Adjust the length so the total width includes the rounded pixels. // Otherwise the cap is drawn past the bounds and appears to be cut off. // If bounds is not long enough to accommodate the line, do not adjust. var left = bounds.left; var right = bounds.right; if (roundEndCaps && bounds.width >= minLengthToRoundCaps) { left += roundEndCapsPixels; right -= roundEndCapsPixels; } // TODO: Pass in strokeWidth, roundEndCaps, and dashPattern from // line renderer config. canvas.drawLine( points: [Point(left, centerHeight), Point(right, centerHeight)], dashPattern: localDashPattern, fill: getSolidFillColor(fillColor), roundEndCaps: roundEndCaps, stroke: strokeColor, strokeWidthPx: localStrokeWidthPx, ); } @override bool shouldRepaint(LineSymbolRenderer oldRenderer) { return this != oldRenderer; } @override bool operator ==(Object other) { return other is LineSymbolRenderer && other.strokeWidth == strokeWidth && super == other; } @override int get hashCode { var hashcode = super.hashCode; hashcode = (hashcode * 37) + strokeWidth.hashCode; return hashcode; } } /// Circle symbol renderer. class CircleSymbolRenderer extends SymbolRenderer { CircleSymbolRenderer({bool isSolid = true}) : super(isSolid: isSolid); @override void paint(ChartCanvas canvas, Rectangle bounds, {List? dashPattern, Color? fillColor, FillPatternType? fillPattern, Color? strokeColor, double? strokeWidthPx}) { final center = Point( bounds.left + (bounds.width / 2), bounds.top + (bounds.height / 2), ); final radius = min(bounds.width, bounds.height) / 2; canvas.drawPoint( point: center, radius: radius, fill: getSolidFillColor(fillColor), stroke: strokeColor, strokeWidthPx: getSolidStrokeWidthPx(strokeWidthPx)); } @override bool shouldRepaint(CircleSymbolRenderer oldRenderer) { return this != oldRenderer; } @override bool operator ==(Object other) => other is CircleSymbolRenderer && super == other; @override int get hashCode { var hashcode = super.hashCode; hashcode = (hashcode * 37) + runtimeType.hashCode; return hashcode; } } /// Rectangle symbol renderer. class RectSymbolRenderer extends SymbolRenderer { RectSymbolRenderer({bool isSolid = true}) : super(isSolid: isSolid); @override void paint(ChartCanvas canvas, Rectangle bounds, {List? dashPattern, Color? fillColor, FillPatternType? fillPattern, Color? strokeColor, double? strokeWidthPx}) { canvas.drawRect(bounds, fill: getSolidFillColor(fillColor), stroke: strokeColor, strokeWidthPx: getSolidStrokeWidthPx(strokeWidthPx)); } @override bool shouldRepaint(RectSymbolRenderer oldRenderer) { return this != oldRenderer; } @override bool operator ==(Object other) => other is RectSymbolRenderer && super == other; @override int get hashCode { var hashcode = super.hashCode; hashcode = (hashcode * 37) + runtimeType.hashCode; return hashcode; } } /// This [SymbolRenderer] renders an upward pointing equilateral triangle. class TriangleSymbolRenderer extends SymbolRenderer { TriangleSymbolRenderer({bool isSolid = true}) : super(isSolid: isSolid); @override void paint(ChartCanvas canvas, Rectangle bounds, {List? dashPattern, Color? fillColor, FillPatternType? fillPattern, Color? strokeColor, double? strokeWidthPx}) { // To maximize the size of the triangle in the available space, we can use // the width as the length of each size. Set the bottom edge to be the full // width, and then calculate the height based on the 30/60/90 degree right // triangle whose tall side is the height of our equilateral triangle. final dy = sqrt(3) / 2 * bounds.width; final centerX = (bounds.left + bounds.right) / 2; canvas.drawPolygon( points: [ Point(bounds.left, bounds.top + dy), Point(bounds.right, bounds.top + dy), Point(centerX, bounds.top), ], fill: getSolidFillColor(fillColor), stroke: strokeColor, strokeWidthPx: getSolidStrokeWidthPx(strokeWidthPx)); } @override bool shouldRepaint(TriangleSymbolRenderer oldRenderer) { return this != oldRenderer; } @override // ignore: hash_and_equals bool operator ==(Object other) => other is TriangleSymbolRenderer && super == other; } /// Draws a cylindrical shape connecting two points. class CylinderSymbolRenderer extends PointSymbolRenderer { CylinderSymbolRenderer(); @override void paint(ChartCanvas canvas, Point p1, double radius, {required Point p2, Color? fillColor, Color? strokeColor, double? strokeWidthPx}) { if (p1 == null) { throw ArgumentError('Invalid point p1 "${p1}"'); } if (p2 == null) { throw ArgumentError('Invalid point p2 "${p2}"'); } final adjustedP1 = Point(p1.x, p1.y); final adjustedP2 = Point(p2.x, p2.y); canvas.drawLine( points: [adjustedP1, adjustedP2], stroke: strokeColor, roundEndCaps: true, strokeWidthPx: radius * 2); } @override bool shouldRepaint(CylinderSymbolRenderer oldRenderer) { return this != oldRenderer; } @override bool operator ==(Object other) => other is CylinderSymbolRenderer; @override int get hashCode => runtimeType.hashCode; } /// Draws a rectangular shape connecting two points. class RectangleRangeSymbolRenderer extends PointSymbolRenderer { RectangleRangeSymbolRenderer(); @override void paint(ChartCanvas canvas, Point p1, double radius, {required Point p2, Color? fillColor, Color? strokeColor, double? strokeWidthPx}) { if (p1 == null) { throw ArgumentError('Invalid point p1 "${p1}"'); } if (p2 == null) { throw ArgumentError('Invalid point p2 "${p2}"'); } final adjustedP1 = Point(p1.x, p1.y); final adjustedP2 = Point(p2.x, p2.y); canvas.drawLine( points: [adjustedP1, adjustedP2], stroke: strokeColor, roundEndCaps: false, strokeWidthPx: radius * 2); } @override bool shouldRepaint(RectangleRangeSymbolRenderer oldRenderer) { return this != oldRenderer; } @override bool operator ==(Object other) => other is RectangleRangeSymbolRenderer; @override int get hashCode => runtimeType.hashCode; } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/common/text_element.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'text_measurement.dart' show TextMeasurement; import 'text_style.dart' show TextStyle; /// Interface for accessing text measurement and painter. abstract class TextElement { /// The [TextStyle] of this [TextElement]. TextStyle? get textStyle; set textStyle(TextStyle? value); /// The max width of this [TextElement] during measure and layout. /// /// If the text exceeds maxWidth, the [maxWidthStrategy] is used. int? get maxWidth; set maxWidth(int? value); /// The strategy to use if this [TextElement] exceeds the [maxWidth]. MaxWidthStrategy? get maxWidthStrategy; set maxWidthStrategy(MaxWidthStrategy? maxWidthStrategy); /// The opacity of this element, in addition to the alpha set on the color /// of this element. set opacity(double? opacity); // The text of this [TextElement]. String get text; /// The [TextMeasurement] of this [TextElement] as an approximate of what /// is actually printed. /// /// Will return the [maxWidth] if set and the actual text width is larger. TextMeasurement get measurement; /// The direction to render the text relative to the coordinate. TextDirection get textDirection; set textDirection(TextDirection direction); /// Return true if settings are all the same. /// /// Purposely excludes measurement because the measurement will request the /// native [TextElement] to layout, which is expensive. We want to avoid the /// layout by comparing with another [TextElement] to see if they have the /// same settings. static bool elementSettingsSame(TextElement a, TextElement b) { return a.textStyle == b.textStyle && a.maxWidth == b.maxWidth && a.maxWidthStrategy == b.maxWidthStrategy && a.text == b.text && a.textDirection == b.textDirection; } } enum TextDirection { ltr, rtl, center, } /// The strategy to use if a [TextElement] exceeds the [maxWidth]. enum MaxWidthStrategy { truncate, ellipsize, } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/common/text_measurement.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /// A measurement result for rendering text. class TextMeasurement { /// Rendered width of the text. final double horizontalSliceWidth; /// Vertical slice is likely based off the rendered text. /// /// This means that 'mo' and 'My' will have different heights so do not use /// this for centering vertical text. final double verticalSliceWidth; /// Baseline of the text for text vertical alignment. final double? baseline; TextMeasurement({ required this.horizontalSliceWidth, required this.verticalSliceWidth, this.baseline, }); } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/common/text_style.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'paint_style.dart' show PaintStyle; /// Paint properties of a text. abstract class TextStyle extends PaintStyle { int? get fontSize; set fontSize(int? value); String? get fontFamily; set fontFamily(String? fontFamily); double? get lineHeight; set lineHeight(double? value); } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/common/text_utils.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:collection'; import 'package:charts_common/src/common/graphics_factory.dart'; import 'package:charts_common/src/common/text_element.dart'; /// This function breaks original [labelElement] into multiple /// [TextElement] when [multiline] is true and the width of /// [labelElement] is larger than [maxWidth], maximum height is /// limited by [maxHeight]. Otherwise, ellipsize [labelElement] by /// [maxWidth] /// /// Returns a list of [TextElement] with given [textStyle]. const _defaultlabelDelimiter = ' '; List wrapLabelLines(TextElement labelElement, GraphicsFactory graphicsFactory, num maxWidth, num maxHeight, {required bool allowLabelOverflow, required bool multiline, String labelDelimiter = _defaultlabelDelimiter}) { final textStyle = labelElement.textStyle; final textDirection = labelElement.textDirection; final labelLineHeight = labelElement.measurement.verticalSliceWidth; final maxLines = (maxHeight / labelLineHeight).floor(); final maxWidthStrategy = labelElement.maxWidthStrategy ?? MaxWidthStrategy.ellipsize; if (maxWidth.toInt() <= 0 || maxLines <= 0) return []; final createTextElement = (String text) => graphicsFactory.createTextElement(text) ..textStyle = textStyle ..textDirection = textDirection; if (!multiline) { labelElement ..maxWidthStrategy = maxWidthStrategy ..maxWidth = maxWidth.toInt(); final labelFits = _doesLabelFit( allowLabelOverflow, labelElement, maxWidth, createTextElement); return [ if (labelFits) labelElement, ]; } final delimiterElement = createTextElement(labelDelimiter); final delimiterElementWidth = delimiterElement.measurement.horizontalSliceWidth; final labelPartElements = Queue() ..addAll(labelElement.text.split(labelDelimiter).map(createTextElement)); final labelElements = []; var currentLineElements = []; var currentLineNumber = 0; var currentWidth = 0.0; while (labelPartElements.isNotEmpty && currentLineNumber < maxLines) { final currentElement = labelPartElements.removeFirst(); var width = currentElement.measurement.horizontalSliceWidth + (currentLineElements.isEmpty ? 0 : currentWidth + delimiterElementWidth); // If the new word can fit in the left space of the line. if (width < maxWidth) { currentWidth = width; if (currentLineElements.isNotEmpty) { currentLineElements.add(delimiterElement); } currentLineElements.add(currentElement); } else { // If the new word can not fit in the left space of the line. var currentLineString = currentLineElements.map((element) => element.text).join(); currentLineNumber++; currentLineElements = []; currentWidth = 0; // If this is the last line, ellipsize the string of current line and // new word. if (currentLineNumber == maxLines) { if (currentLineString != '') currentLineString += labelDelimiter; currentLineString += currentElement.text; final truncatedLabelElement = createTextElement(currentLineString) ..maxWidthStrategy = maxWidthStrategy ..maxWidth = maxWidth.toInt(); if (_doesLabelFit(allowLabelOverflow, truncatedLabelElement, maxWidth, createTextElement)) { labelElements.add(truncatedLabelElement); } break; } else { // This is not the last line. if (currentLineString == '') { // When currentElement cannot fit in a whole line. final results = _splitLabel(currentElement.text, createTextElement, maxWidth); labelPartElements.addFirst(results[1]); labelElements.add(results[0]); } else { // Starts a new line. final currentLineTextElement = createTextElement(currentLineString); labelElements.add(currentLineTextElement); labelPartElements.addFirst(currentElement); } } } } if (currentLineElements.isNotEmpty) { final currentLineString = currentLineElements.map((element) => element.text).join(); final labelElement = createTextElement(currentLineString); labelElements.add(labelElement); } return labelElements; } /// Split label into two pieces, the first part should exactly fit into a /// single line. /// /// Returns a list of [TextElement] with length of 2. List _splitLabel( String text, TextElement Function(String) createTextElement, num maxWidth) { var l = 0; var r = text.length - 1; var m = ((l + r) / 2).floor(); while (l < r - 1) { final labelElement = createTextElement(text.substring(0, m)); if (labelElement.measurement.horizontalSliceWidth < maxWidth) { l = m; m = ((l + r) / 2).floor(); } else if (labelElement.measurement.horizontalSliceWidth == maxWidth) { l = m; break; } else { r = m; m = ((l + r) / 2).floor(); } } return [ createTextElement(text.substring(0, l)), createTextElement(text.substring(l, text.length)) ]; } /// Tests whether or not a given text element fits in the available space. bool _doesLabelFit(bool allowLabelOverflow, TextElement textElement, num maxWidth, TextElement Function(String) createTextElement) { if (textElement.maxWidthStrategy != MaxWidthStrategy.ellipsize || allowLabelOverflow) { return true; } // When allowLabelOverflow is disabled and maxWidthStrategy is ellipsize, // compares [textElement] width with [maxWidth]. final ellipsizedText = textElement.text; final ellipsizedElementWidth = (createTextElement(ellipsizedText) ..textStyle = textElement.textStyle) .measurement .horizontalSliceWidth; return ellipsizedElementWidth <= maxWidth; } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/common/typed_registry.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. class TypedRegistry { final _registry = , Object?>{}; R? getAttr(TypedKey key) { return _registry[key] as R?; } void setAttr(TypedKey key, R value) { _registry[key] = value; } void mergeFrom(TypedRegistry other) { _registry.addAll(other._registry); } } class TypedKey { final String uniqueKey; const TypedKey(this.uniqueKey); @override int get hashCode => uniqueKey.hashCode; @override bool operator ==(Object other) => other is TypedKey && uniqueKey == other.uniqueKey; } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/data/series.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import '../chart/cartesian/axis/spec/axis_spec.dart' show TextStyleSpec; import '../chart/common/chart_canvas.dart' show FillPatternType; import '../chart/common/datum_details.dart' show DomainFormatter, MeasureFormatter; import '../common/color.dart' show Color; import '../common/typed_registry.dart' show TypedRegistry, TypedKey; class Series { final String id; final String? displayName; /// Overlay series provided supplemental information on a chart, but are not /// considered to be primary data. They should not be selectable by user /// interaction. final bool overlaySeries; final String? seriesCategory; /// Color which represents the entire series in legends. /// /// If this is not provided in the original series object, it will be inferred /// from the color of the first datum in the series. /// /// If this is provided, but no [colorFn] is provided, then it will be treated /// as the color for each datum in the series. /// /// If neither are provided, then the chart will insert colors for each series /// on the chart using a mapping function. final Color? seriesColor; final List data; /// [keyFn] defines a globally unique identifier for each datum. /// /// The key for each datum is used during chart animation to smoothly /// transition data still in the series to its new state. /// /// Note: This is currently an optional function that is not fully used by all /// series renderers yet. final AccessorFn? keyFn; final AccessorFn domainFn; final AccessorFn>? domainFormatterFn; final AccessorFn? domainLowerBoundFn; final AccessorFn? domainUpperBoundFn; final AccessorFn measureFn; final AccessorFn? measureFormatterFn; final AccessorFn? measureLowerBoundFn; final AccessorFn? measureUpperBoundFn; final AccessorFn? measureOffsetFn; /// [areaColorFn] returns the area color for a given data value. If not /// provided, then some variation of the main [colorFn] will be used (e.g. /// 10% opacity). /// /// This color is used for supplemental information on the series, such as /// confidence intervals or area skirts. final AccessorFn? areaColorFn; /// [colorFn] returns the rendered stroke color for a given data value. /// /// If this is not provided, then [seriesColor] will be used for every datum. /// /// If neither are provided, then the chart will insert colors for each series /// on the chart using a mapping function. final AccessorFn? colorFn; /// [dashPatternFn] returns the dash pattern for a given data value. final AccessorFn?>? dashPatternFn; /// [fillColorFn] returns the rendered fill color for a given data value. If /// not provided, then [colorFn] will be used as a fallback. final AccessorFn? fillColorFn; /// [patternColorFn] returns the background color of tile when a /// [FillPatternType] beside `solid` is used. If not provided, then /// background color is used. final AccessorFn? patternColorFn; final AccessorFn? fillPatternFn; final AccessorFn? radiusPxFn; final AccessorFn? strokeWidthPxFn; final AccessorFn? labelAccessorFn; final AccessorFn? insideLabelStyleAccessorFn; final AccessorFn? outsideLabelStyleAccessorFn; // TODO: should this be immutable as well? If not, should any of // the non-required ones be final? final SeriesAttributes attributes = SeriesAttributes(); factory Series( {required String id, required List data, required TypedAccessorFn domainFn, required TypedAccessorFn measureFn, String? displayName, Color? seriesColor, TypedAccessorFn? areaColorFn, TypedAccessorFn? colorFn, TypedAccessorFn?>? dashPatternFn, TypedAccessorFn>? domainFormatterFn, TypedAccessorFn? domainLowerBoundFn, TypedAccessorFn? domainUpperBoundFn, TypedAccessorFn? fillColorFn, TypedAccessorFn? patternColorFn, TypedAccessorFn? fillPatternFn, TypedAccessorFn? keyFn, TypedAccessorFn? labelAccessorFn, TypedAccessorFn? insideLabelStyleAccessorFn, TypedAccessorFn? outsideLabelStyleAccessorFn, TypedAccessorFn? measureFormatterFn, TypedAccessorFn? measureLowerBoundFn, TypedAccessorFn? measureUpperBoundFn, TypedAccessorFn? measureOffsetFn, bool overlaySeries = false, TypedAccessorFn? radiusPxFn, String? seriesCategory, TypedAccessorFn? strokeWidthPxFn}) { // Wrap typed accessors. final _domainFn = (int? index) => domainFn(data[index!], index); final _measureFn = (int? index) => measureFn(data[index!], index); final _areaColorFn = areaColorFn == null ? null : (int? index) => areaColorFn(data[index!], index); final _colorFn = colorFn == null ? null : (int? index) => colorFn(data[index!], index); final _dashPatternFn = dashPatternFn == null ? null : (int? index) => dashPatternFn(data[index!], index); final _domainFormatterFn = domainFormatterFn == null ? null : (int? index) => domainFormatterFn(data[index!], index); final _domainLowerBoundFn = domainLowerBoundFn == null ? null : (int? index) => domainLowerBoundFn(data[index!], index); final _domainUpperBoundFn = domainUpperBoundFn == null ? null : (int? index) => domainUpperBoundFn(data[index!], index); final _fillColorFn = fillColorFn == null ? null : (int? index) => fillColorFn(data[index!], index); final _patternColorFn = patternColorFn == null ? null : (int? index) => patternColorFn(data[index!], index); final _fillPatternFn = fillPatternFn == null ? null : (int? index) => fillPatternFn(data[index!], index); final _labelAccessorFn = labelAccessorFn == null ? null : (int? index) => labelAccessorFn(data[index!], index); final _insideLabelStyleAccessorFn = insideLabelStyleAccessorFn == null ? null : (int? index) => insideLabelStyleAccessorFn(data[index!], index); final _outsideLabelStyleAccessorFn = outsideLabelStyleAccessorFn == null ? null : (int? index) => outsideLabelStyleAccessorFn(data[index!], index); final _measureFormatterFn = measureFormatterFn == null ? null : (int? index) => measureFormatterFn(data[index!], index); final _measureLowerBoundFn = measureLowerBoundFn == null ? null : (int? index) => measureLowerBoundFn(data[index!], index); final _measureUpperBoundFn = measureUpperBoundFn == null ? null : (int? index) => measureUpperBoundFn(data[index!], index); final _measureOffsetFn = measureOffsetFn == null ? null : (int? index) => measureOffsetFn(data[index!], index); final _radiusPxFn = radiusPxFn == null ? null : (int? index) => radiusPxFn(data[index!], index); final _strokeWidthPxFn = strokeWidthPxFn == null ? null : (int? index) => strokeWidthPxFn(data[index!], index); final _keyFn = keyFn == null ? null : (int? index) => keyFn(data[index!], index); return Series._internal( id: id, data: data, domainFn: _domainFn, measureFn: _measureFn, displayName: displayName, areaColorFn: _areaColorFn, colorFn: _colorFn, dashPatternFn: _dashPatternFn, domainFormatterFn: _domainFormatterFn, domainLowerBoundFn: _domainLowerBoundFn, domainUpperBoundFn: _domainUpperBoundFn, fillColorFn: _fillColorFn, fillPatternFn: _fillPatternFn, keyFn: _keyFn, patternColorFn: _patternColorFn, labelAccessorFn: _labelAccessorFn, insideLabelStyleAccessorFn: _insideLabelStyleAccessorFn, outsideLabelStyleAccessorFn: _outsideLabelStyleAccessorFn, measureFormatterFn: _measureFormatterFn, measureLowerBoundFn: _measureLowerBoundFn, measureUpperBoundFn: _measureUpperBoundFn, measureOffsetFn: _measureOffsetFn, overlaySeries: overlaySeries, radiusPxFn: _radiusPxFn, seriesCategory: seriesCategory, seriesColor: seriesColor, strokeWidthPxFn: _strokeWidthPxFn, ); } Series._internal({ required this.id, required this.data, required this.domainFn, required this.measureFn, required this.displayName, required this.areaColorFn, required this.colorFn, required this.dashPatternFn, required this.domainFormatterFn, required this.domainLowerBoundFn, required this.domainUpperBoundFn, required this.fillColorFn, required this.fillPatternFn, required this.patternColorFn, required this.keyFn, required this.labelAccessorFn, required this.insideLabelStyleAccessorFn, required this.outsideLabelStyleAccessorFn, required this.measureFormatterFn, required this.measureLowerBoundFn, required this.measureUpperBoundFn, required this.measureOffsetFn, required this.overlaySeries, required this.radiusPxFn, required this.seriesCategory, required this.seriesColor, required this.strokeWidthPxFn, }); void setAttribute(AttributeKey key, R value) { attributes.setAttr(key, value); } R? getAttribute(AttributeKey key) { return attributes.getAttr(key); } } /// Computed property on series. /// /// If the [index] argument is `null`, the accessor is asked to provide a /// property of [series] as a whole. Accessors are not required to support /// such usage. /// /// Otherwise, [index] must be a valid subscript into a list of `series.length`. typedef AccessorFn = R Function(int? index); typedef TypedAccessorFn = R Function(T datum, int? index); class AttributeKey extends TypedKey { const AttributeKey(String uniqueKey) : super(uniqueKey); } class SeriesAttributes extends TypedRegistry {} ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/lib/src/data/tree.dart ================================================ // Copyright 2019 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:collection'; import 'package:charts_common/src/chart/cartesian/axis/spec/axis_spec.dart'; import 'package:charts_common/src/chart/common/chart_canvas.dart'; import 'package:charts_common/src/common/color.dart'; import 'package:charts_common/src/common/typed_registry.dart'; import 'package:meta/meta.dart'; import 'series.dart' show AttributeKey, Series, TypedAccessorFn; /// A tree structure that contains metadata of a rendering tree. class Tree { /// Unique identifier for this [tree]. final String id; /// Root node of this tree. final TreeNode root; /// Accessor function that returns the domain for a tree node. final TypedAccessorFn, D> domainFn; /// Accessor function that returns the measure for a tree node. final TypedAccessorFn, num?> measureFn; /// Accessor function that returns the rendered stroke color for a tree node. final TypedAccessorFn, Color>? colorFn; /// Accessor function that returns the rendered fill color for a tree node. /// If not provided, then [colorFn] will be used as a fallback. final TypedAccessorFn, Color>? fillColorFn; /// Accessor function that returns the pattern color for a tree node /// If not provided, then background color is used as default. final TypedAccessorFn, Color>? patternColorFn; /// Accessor function that returns the fill pattern for a tree node. final TypedAccessorFn, FillPatternType>? fillPatternFn; /// Accessor function that returns the stroke width in pixel for a tree node. final TypedAccessorFn, num>? strokeWidthPxFn; /// Accessor function that returns the label for a tree node. final TypedAccessorFn, String>? labelFn; /// Accessor function that returns the style spec for a tree node label. final TypedAccessorFn, TextStyleSpec>? labelStyleFn; /// [attributes] stores additional key-value pairs of attributes this tree is /// associated with (e.g. rendererIdKey to renderer). final TreeAttributes attributes = TreeAttributes(); factory Tree({ required String id, required TreeNode root, required TypedAccessorFn domainFn, required TypedAccessorFn measureFn, TypedAccessorFn? colorFn, TypedAccessorFn? fillColorFn, TypedAccessorFn? patternColorFn, TypedAccessorFn? fillPatternFn, TypedAccessorFn? strokeWidthPxFn, TypedAccessorFn? labelFn, TypedAccessorFn? labelStyleFn, }) { return Tree._( id: id, root: root, domainFn: _castFrom(domainFn)!, measureFn: _castFrom(measureFn)!, colorFn: _castFrom(colorFn), fillColorFn: _castFrom(fillColorFn), fillPatternFn: _castFrom(fillPatternFn), patternColorFn: _castFrom(patternColorFn), strokeWidthPxFn: _castFrom(strokeWidthPxFn), labelFn: _castFrom(labelFn), labelStyleFn: _castFrom(labelStyleFn), ); } Tree._({ required this.id, required this.root, required this.domainFn, required this.measureFn, required this.colorFn, required this.fillColorFn, required this.fillPatternFn, required this.patternColorFn, required this.strokeWidthPxFn, required this.labelFn, required this.labelStyleFn, }); /// Creates a [Series] that contains all [TreeNode]s traversing from the /// [root] of this tree. /// /// Considers the following tree: /// ``` /// A /// / | \ /// B C D ---> [A, B, C, D, E, F] /// / \ /// E F /// ``` /// This method traverses from root node "A" in breadth-first order and /// adds all its children to a list. The order of [TreeNode]s in the list /// is based on the insertion order to children of a particular node. /// All [TreeNode]s are accessible through [Series].data. Series, D> toSeries() { final data = >[]; root.visit(data.add); return Series( id: id, data: data, domainFn: domainFn, measureFn: measureFn, colorFn: colorFn, fillColorFn: fillColorFn, fillPatternFn: fillPatternFn, patternColorFn: patternColorFn, strokeWidthPxFn: strokeWidthPxFn, labelAccessorFn: labelFn, insideLabelStyleAccessorFn: labelStyleFn, )..attributes.mergeFrom(attributes); } void setAttribute(AttributeKey key, R value) { attributes.setAttr(key, value); } R? getAttribute(AttributeKey key) { return attributes.getAttr(key); } } class TreeNode { /// Associated data this node stores. final T data; final List> _children = []; int _depth = 0; TreeNode? parent; TreeNode(this.data); /// Distance between this node and the root node. int get depth => _depth; @protected set depth(int val) { _depth = val; } /// List of child nodes. Iterable> get children => _children; /// Whether or not this node has any children. bool get hasChildren => _children.isNotEmpty; /// Adds a single child to this node. void addChild(TreeNode child) { child.parent = this; final delta = depth - child.depth + 1; if (delta != 0) child.visit((node) => node.depth += delta); _children.add(child); } /// Adds a list of children to this node. void addChildren(Iterable> newChildren) { newChildren.forEach(addChild); } /// Applies the function [f] to all child nodes rooted from this node in /// breadth first order. void visit(void Function(TreeNode node) f) { final queue = Queue>()..add(this); while (queue.isNotEmpty) { final node = queue.removeFirst(); f(node); queue.addAll(node.children); } } } /// A registry that stores key-value pairs of attributes. class TreeAttributes extends TypedRegistry {} /// Adapts a TypedAccessorFn type to a TypedAccessorFn, R>. TypedAccessorFn, R>? _castFrom(TypedAccessorFn? f) { return f == null ? null : (TreeNode node, int? index) => f(node.data, index); } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_common/pubspec.yaml ================================================ name: charts_common version: 0.11.0 description: A common library for charting packages. author: Charts Team homepage: https://github.com/google/charts environment: sdk: '>=2.12.0 <3.0.0' dependencies: collection: ^1.14.5 intl: ">=0.15.2 < 0.18.0" logging: any meta: ^1.1.1 vector_math: ^2.0.8 dev_dependencies: mockito: ^5.0.0 test: ^1.5.3 ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_flutter/CHANGELOG.md ================================================ # 0.11.0 * Null support * Update to latest from internal repo # 0.10.0 * Internal bug fixes * Bump versions of intl due to pull request # 0.9.0 * Internal bug fixes * Bump versions in Gemlock file due to security alerts # 0.8.1 * Update intl version. # 0.8.0 * Bug fixes from open source. # 0.7.0 * Added vertical bar label # 0.6.0 * Bars can now be rendered on line charts. * Negative measure values will now be rendered on bar charts as a separate stack from the positive values. * Added a Datum Legend, which displays one entry per value in the first series on the chart. This is useful for pie and scatter plot charts. * The AxisPosition enum in RTLSpec was refactored to AxisDirection to better reflect its effect on swapping the positions of all start and end components, and not just positioning the measure axes. * Added custom colors for line renderer area skirts and confidence intervals. A new "areaColorFn" has been added to Series, and corresponding data to the datum. We could not use the fillColorFn for these elements, because that color is already applied to the internal section of points on line charts (including highlighter behaviors). # 0.5.0 * SelectionModelConfig's listener parameter has been renamed to "changeListener". This is a breaking change. Please rename any existing uses of the "listener" parameter to "changeListener". This was named in order to add an additional listener "updateListener" that listens to any update requests, regardless if the selection model has changed. * CartesianChart's method getMeasureAxis(String axisId) has been changed to getMeasureAxis({String axisId) so that getting the primary measure axis will not need passing any id that does not match the secondary measure axis id. This affects users implementing custom behaviors using the existing method. # 0.4.0 * Fixed export file to export ChartsBehavior in the Flutter library instead of the one that resides in charts_common. The charts_common behavior should not be used except internally in the charts_flutter library. This is a breaking change if you are using charts_common behavior. * Declare compatibility with Dart 2. * BasicNumericTickFormatterSpec now takes in a callback instead of NumberFormat as the default constructor. Use named constructor withNumberFormat instead. This is a breaking change. * BarRendererConfig is no longer default of type String, please change current usage to BarRendererConfig. This is a breaking change. * BarTargetLineRendererConfig is no longer default of type String, please change current usage to BarTargetLineRendererConfig. This is a breaking change. # 0.3.0 * Simplified API by removing the requirement for specifying the datum type when creating a chart. For example, previously to construct a bar chart the syntax was 'new BarChart()'. The syntax is now cleaned up to be 'new BarChart()'. Please refer to the [online gallery](https://google.github.io/charts/flutter/gallery.html) for the correct syntax. * Added scatter plot charts * Added tap to hide for legends * Added support for rendering area skirts to line charts * Added support for configurable fill colors to bar charts # 0.2.0 * Update color palette. Please use MaterialPalette instead of QuantumPalette. * Dart2 fixes # 0.1.0 Initial release. ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_flutter/ExternalTapQueues.METADATA ================================================ # Format: google3/devtools/metadata/metadata.proto (go/google3metadata) # https://cs.corp.google.com/#google3/devtools/metadata/presubmit.proto # External Tap queues to run for CLs to Charts Flutter. # # All TAP projects added must comply with http://go/dart-charts-testing-sla. # Projects that violate the testing SLA will be removed immediately. presubmit { path_expression: "//depot/google3/third_party/dart/charts_flutter/..." path_expression: "//depot/google3/third_party/dart/charts_common/..." check_tests: { failure_status: ERROR # For each client TAP, please add team name, email alias, and buganizer # component ID. # Connect Sales Mobile, gt-fe-dev@, bug component ID: 122320. project: "greentea.flutter" } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_flutter/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_flutter/README.md ================================================ # Flutter Charting library [![pub package](https://img.shields.io/pub/v/charts_flutter.svg)](https://pub.dartlang.org/packages/charts_flutter) Material Design data visualization library written natively in Dart. ## Supported charts See the [online gallery](https://google.github.io/charts/flutter/gallery.html). ## Using the library The `/example/` folder inside `charts_flutter` in the [GitHub repo](https://github.com/google/charts) contains a full Flutter app with many demo examples. ## Development This project is developed internally at Google and published for external consumption, external contributions unfortunately cannot be taken at this time. ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_flutter/charts_flutter.gwsq ================================================ send_cls_to('dart-charts-team+reviews'); send_cls_to('dart-charts-team'); define Main { reassign_to_list(from_owners_file('third_party/dart/charts_common/OWNERS')); } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_flutter/lib/flutter.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. export 'package:charts_common/common.dart' show boundsLineRadiusPxFnKey, boundsLineRadiusPxKey, measureAxisIdKey, pointSymbolRendererFnKey, pointSymbolRendererIdKey, rendererIdKey, AnnotationLabelAnchor, AnnotationLabelDirection, AnnotationLabelPosition, ArcLabelDecorator, ArcLabelLeaderLineStyleSpec, ArcLabelPosition, ArcRenderer, ArcRendererConfig, AutoDateTimeTickFormatterSpec, AutoDateTimeTickProviderSpec, Axis, AxisDirection, AxisSpec, BarGroupingType, BarLabelAnchor, BarLabelDecorator, BarLabelPosition, BarLaneRendererConfig, BarRenderer, BarRendererConfig, BarTargetLineRenderer, BarTargetLineRendererConfig, BaseCartesianRenderer, BasicDateTimeTickFormatterSpec, BasicNumericTickFormatterSpec, BasicNumericTickProviderSpec, BasicOrdinalTickProviderSpec, BasicOrdinalTickFormatterSpec, BehaviorPosition, BucketingAxisSpec, BucketingNumericTickProviderSpec, CartesianChart, ChartCanvas, ChartContext, ChartTitleDirection, CircleSymbolRenderer, Color, ComparisonPointsDecorator, ConstCornerStrategy, CornerStrategy, CylinderSymbolRenderer, DateTimeAxisSpec, DateTimeEndPointsTickProviderSpec, DateTimeExtents, DateTimeFactory, DateTimeTickFormatter, DateTimeTickFormatterSpec, DateTimeTickProviderSpec, DayTickProviderSpec, DomainFormatter, EndPointsTimeAxisSpec, ExploreModeTrigger, FillPatternType, GestureListener, GraphicsFactory, GridlineRendererSpec, ImmutableSeries, InsideJustification, LayoutPosition, LayoutViewPaintOrder, LayoutViewPositionOrder, LegendDefaultMeasure, LegendTapHandling, LineAnnotationSegment, LinePointHighlighterFollowLineType, LineRenderer, LineRendererConfig, LineStyleSpec, LocalDateTimeFactory, LockSelection, MarginSpec, MaterialPalette, MaterialStyle, MaxWidthStrategy, MeasureFormatter, NoCornerStrategy, NoneRenderSpec, NumericAxis, NumericAxisSpec, NumericCartesianChart, NumericEndPointsTickProviderSpec, NumericExtents, NumericTickFormatterSpec, NumericTickProviderSpec, OrdinalAxis, OrdinalAxisSpec, OrdinalCartesianChart, OrdinalTickFormatterSpec, OrdinalTickProviderSpec, OrdinalViewport, OutsideJustification, PanningCompletedCallback, PercentAxisSpec, PercentInjectorTotalType, Performance, PointRenderer, PointRendererConfig, PointRendererDecorator, PointRendererElement, PointSymbolRenderer, QuantumPalette, RangeAnnotationAxisType, RangeAnnotationSegment, RectSymbolRenderer, RenderSpec, RTLSpec, SelectionModel, SelectionModelListener, SelectionModelType, SelectionTrigger, Series, SeriesDatum, SeriesDatumConfig, SeriesRenderer, SeriesRendererConfig, SimpleTickFormatterBase, SliderListenerCallback, SliderListenerDragState, SliderStyle, SmallTickRendererSpec, StaticDateTimeTickProviderSpec, StaticNumericTickProviderSpec, StaticOrdinalTickProviderSpec, StyleFactory, SymbolAnnotationRenderer, SymbolAnnotationRendererConfig, TextDirection, TextElement, TextStyle, TextStyleSpec, TickFormatter, TickFormatterSpec, TickLabelAnchor, TickLabelJustification, TickSpec, TimeFormatterSpec, TypedAccessorFn, UTCDateTimeFactory, ViewMargin, VocalizationCallback; export 'src/bar_chart.dart'; export 'src/base_chart.dart' show BaseChart, LayoutConfig; export 'src/behaviors/a11y/domain_a11y_explore_behavior.dart' show DomainA11yExploreBehavior; export 'src/behaviors/chart_behavior.dart' show ChartBehavior; export 'src/behaviors/domain_highlighter.dart' show DomainHighlighter; export 'src/behaviors/initial_selection.dart' show InitialSelection; export 'src/behaviors/calculation/percent_injector.dart' show PercentInjector; export 'src/behaviors/chart_title/chart_title.dart' show ChartTitle; export 'src/behaviors/legend/datum_legend.dart' show DatumLegend; export 'src/behaviors/legend/legend_content_builder.dart' show LegendContentBuilder, TabularLegendContentBuilder; export 'src/behaviors/legend/legend_entry_layout.dart' show LegendEntryLayout, SimpleLegendEntryLayout; export 'src/behaviors/legend/legend_layout.dart' show LegendLayout, TabularLegendLayout; export 'src/behaviors/legend/series_legend.dart' show SeriesLegend; export 'src/behaviors/line_point_highlighter.dart' show LinePointHighlighter; export 'src/behaviors/range_annotation.dart' show RangeAnnotation; export 'src/behaviors/select_nearest.dart' show SelectNearest; export 'src/behaviors/sliding_viewport.dart' show SlidingViewport; export 'src/behaviors/slider/slider.dart' show Slider; export 'src/behaviors/zoom/initial_hint_behavior.dart' show InitialHintBehavior; export 'src/behaviors/zoom/pan_and_zoom_behavior.dart' show PanAndZoomBehavior; export 'src/behaviors/zoom/pan_behavior.dart' show PanBehavior; export 'src/combo_chart/combo_chart.dart'; export 'src/line_chart.dart'; export 'src/pie_chart.dart'; export 'src/scatter_plot_chart.dart'; export 'src/selection_model_config.dart' show SelectionModelConfig; export 'src/symbol_renderer.dart' show CustomSymbolRenderer; export 'src/time_series_chart.dart'; export 'src/user_managed_state.dart' show UserManagedState, UserManagedSelectionModel; export 'src/util/color.dart' show ColorUtil; ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_flutter/lib/src/bar_chart.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:collection' show LinkedHashMap; import 'package:charts_common/common.dart' as common show AxisSpec, BarChart, BarGroupingType, BarRendererConfig, BarRendererDecorator, NumericAxisSpec, RTLSpec, Series, SeriesRendererConfig; import 'behaviors/domain_highlighter.dart' show DomainHighlighter; import 'behaviors/chart_behavior.dart' show ChartBehavior; import 'package:meta/meta.dart' show immutable; import 'base_chart.dart' show LayoutConfig; import 'base_chart_state.dart' show BaseChartState; import 'cartesian_chart.dart' show CartesianChart; import 'selection_model_config.dart' show SelectionModelConfig; import 'user_managed_state.dart' show UserManagedState; @immutable class BarChart extends CartesianChart { final bool vertical; final common.BarRendererDecorator? barRendererDecorator; BarChart( List> seriesList, { bool? animate, Duration? animationDuration, common.AxisSpec? domainAxis, common.NumericAxisSpec? primaryMeasureAxis, common.NumericAxisSpec? secondaryMeasureAxis, LinkedHashMap? disjointMeasureAxes, common.BarGroupingType? barGroupingType, common.BarRendererConfig? defaultRenderer, List>? customSeriesRenderers, List>? behaviors, List>? selectionModels, common.RTLSpec? rtlSpec, this.vertical = true, bool defaultInteractions = true, LayoutConfig? layoutConfig, UserManagedState? userManagedState, this.barRendererDecorator, bool? flipVerticalAxis, }) : super( seriesList, animate: animate, animationDuration: animationDuration, domainAxis: domainAxis, primaryMeasureAxis: primaryMeasureAxis, secondaryMeasureAxis: secondaryMeasureAxis, disjointMeasureAxes: disjointMeasureAxes, defaultRenderer: defaultRenderer ?? new common.BarRendererConfig( groupingType: barGroupingType, barRendererDecorator: barRendererDecorator), customSeriesRenderers: customSeriesRenderers, behaviors: behaviors, selectionModels: selectionModels, rtlSpec: rtlSpec, defaultInteractions: defaultInteractions, layoutConfig: layoutConfig, userManagedState: userManagedState, flipVerticalAxis: flipVerticalAxis, ); @override common.BarChart createCommonChart(BaseChartState chartState) { // Optionally create primary and secondary measure axes if the chart was // configured with them. If no axes were configured, then the chart will // use its default types (usually a numeric axis). return new common.BarChart( vertical: vertical, layoutConfig: layoutConfig?.commonLayoutConfig, primaryMeasureAxis: primaryMeasureAxis?.createAxis(), secondaryMeasureAxis: secondaryMeasureAxis?.createAxis(), disjointMeasureAxes: createDisjointMeasureAxes()); } @override void addDefaultInteractions(List behaviors) { super.addDefaultInteractions(behaviors); behaviors.add(new DomainHighlighter()); } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_flutter/lib/src/base_chart.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'package:charts_common/common.dart' as common show BaseChart, LayoutConfig, MarginSpec, Performance, RTLSpec, Series, SeriesRendererConfig, SelectionModelType, SelectionTrigger; import 'behaviors/select_nearest.dart' show SelectNearest; import 'package:meta/meta.dart' show immutable; import 'behaviors/chart_behavior.dart' show ChartBehavior, ChartStateBehavior, GestureType; import 'selection_model_config.dart' show SelectionModelConfig; import 'package:flutter/material.dart' show StatefulWidget; import 'base_chart_state.dart' show BaseChartState; import 'user_managed_state.dart' show UserManagedState; @immutable abstract class BaseChart extends StatefulWidget { /// Series list to draw. final List> seriesList; /// Animation transitions. final bool animate; final Duration animationDuration; /// Used to configure the margin sizes around the drawArea that the axis and /// other things render into. final LayoutConfig? layoutConfig; // Default renderer used to draw series data on the chart. final common.SeriesRendererConfig? defaultRenderer; /// Include the default interactions or not. final bool defaultInteractions; final List>? behaviors; final List>? selectionModels; // List of custom series renderers used to draw series data on the chart. // // Series assigned a rendererIdKey will be drawn with the matching renderer in // this list. Series without a rendererIdKey will be drawn by the default // renderer. final List>? customSeriesRenderers; /// The spec to use if RTL is enabled. final common.RTLSpec? rtlSpec; /// Optional state that overrides internally kept state, such as selection. final UserManagedState? userManagedState; BaseChart(this.seriesList, {bool? animate, Duration? animationDuration, this.defaultRenderer, this.customSeriesRenderers, this.behaviors, this.selectionModels, this.rtlSpec, this.defaultInteractions = true, this.layoutConfig, this.userManagedState}) : this.animate = animate ?? true, this.animationDuration = animationDuration ?? const Duration(milliseconds: 300); @override BaseChartState createState() => new BaseChartState(); /// Creates and returns a [common.BaseChart]. common.BaseChart createCommonChart(BaseChartState chartState); /// Updates the [common.BaseChart]. void updateCommonChart(common.BaseChart chart, BaseChart? oldWidget, BaseChartState chartState) { common.Performance.time('chartsUpdateRenderers'); // Set default renderer if one was provided. if (defaultRenderer != null && defaultRenderer != oldWidget?.defaultRenderer) { chart.defaultRenderer = defaultRenderer!.build(); chartState.markChartDirty(); } // Add custom series renderers if any were provided. if (customSeriesRenderers != null) { // TODO: This logic does not remove old renderers and // shouldn't require the series configs to remain in the same order. for (var i = 0; i < customSeriesRenderers!.length; i++) { if (oldWidget == null || (oldWidget.customSeriesRenderers != null && i > oldWidget.customSeriesRenderers!.length) || customSeriesRenderers![i] != oldWidget.customSeriesRenderers![i]) { chart.addSeriesRenderer(customSeriesRenderers![i].build()); chartState.markChartDirty(); } } } common.Performance.timeEnd('chartsUpdateRenderers'); common.Performance.time('chartsUpdateBehaviors'); _updateBehaviors(chart, chartState); common.Performance.timeEnd('chartsUpdateBehaviors'); _updateSelectionModel(chart, chartState); chart.transition = animate ? animationDuration : Duration.zero; } void _updateBehaviors(common.BaseChart chart, BaseChartState chartState) { final behaviorList = List>.from(behaviors ?? []); // Insert automatic behaviors to the front of the behavior list. if (defaultInteractions) { if (chartState.autoBehaviorWidgets.isEmpty) { addDefaultInteractions(chartState.autoBehaviorWidgets); } // Add default interaction behaviors to the front of the list if they // don't conflict with user behaviors by role. chartState.autoBehaviorWidgets.reversed .where(_notACustomBehavior) .forEach((ChartBehavior behavior) { behaviorList.insert(0, behavior); }); } // Remove any behaviors from the chart that are not in the incoming list. // Walk in reverse order they were added. // Also, remove any persisting behaviors from incoming list. for (int i = chartState.addedBehaviorWidgets.length - 1; i >= 0; i--) { final addedBehavior = chartState.addedBehaviorWidgets[i]; if (!behaviorList.remove(addedBehavior)) { final role = addedBehavior.role; chartState.addedBehaviorWidgets.remove(addedBehavior); chartState.addedCommonBehaviorsByRole.remove(role); chart.removeBehavior(chartState.addedCommonBehaviorsByRole[role]); chartState.markChartDirty(); } } // Add any remaining/new behaviors. behaviorList.forEach((ChartBehavior behaviorWidget) { final commonBehavior = behaviorWidget.createCommonBehavior(); // Assign the chart state to any behavior that needs it. if (commonBehavior is ChartStateBehavior) { (commonBehavior as ChartStateBehavior).chartState = chartState; } chart.addBehavior(commonBehavior); chartState.addedBehaviorWidgets.add(behaviorWidget); chartState.addedCommonBehaviorsByRole[behaviorWidget.role] = commonBehavior; chartState.markChartDirty(); }); } /// Create the list of default interaction behaviors. void addDefaultInteractions(List behaviors) { // Update selection model behaviors.add(new SelectNearest( eventTrigger: common.SelectionTrigger.tap, selectionModelType: common.SelectionModelType.info, selectClosestSeries: true)); } bool _notACustomBehavior(ChartBehavior behavior) { return behaviors == null || !behaviors!.any( (ChartBehavior userBehavior) => userBehavior.role == behavior.role); } void _updateSelectionModel( common.BaseChart chart, BaseChartState chartState) { final prevTypes = new List.from( chartState.addedSelectionChangedListenersByType.keys); // Update any listeners for each type. selectionModels?.forEach((SelectionModelConfig model) { final selectionModel = chart.getSelectionModel(model.type); final prevChangedListener = chartState.addedSelectionChangedListenersByType[model.type]; if (!identical(model.changedListener, prevChangedListener)) { if (prevChangedListener != null) { selectionModel.removeSelectionChangedListener(prevChangedListener); } selectionModel.addSelectionChangedListener(model.changedListener!); chartState.addedSelectionChangedListenersByType[model.type] = model.changedListener!; } final prevUpdatedListener = chartState.addedSelectionUpdatedListenersByType[model.type]; if (!identical(model.updatedListener, prevUpdatedListener)) { if (prevUpdatedListener != null) { selectionModel.removeSelectionUpdatedListener(prevUpdatedListener); } selectionModel.addSelectionUpdatedListener(model.updatedListener!); chartState.addedSelectionUpdatedListenersByType[model.type] = model.updatedListener!; } prevTypes.remove(model.type); }); // Remove any lingering listeners. prevTypes.forEach((common.SelectionModelType type) { chart.getSelectionModel(type) ..removeSelectionChangedListener( chartState.addedSelectionChangedListenersByType[type]!) ..removeSelectionUpdatedListener( chartState.addedSelectionUpdatedListenersByType[type]!); }); } /// Gets distinct set of gestures this chart will subscribe to. /// /// This is needed to allow setup of the [GestureDetector] widget with only /// gestures we need to listen to and it must wrap [ChartContainer] widget. /// Gestures are then setup to be proxied in [common.BaseChart] and that is /// held by [ChartContainerRenderObject]. Set getDesiredGestures(BaseChartState chartState) { final types = new Set(); behaviors?.forEach((ChartBehavior behavior) { types.addAll(behavior.desiredGestures); }); if (defaultInteractions && chartState.autoBehaviorWidgets.isEmpty) { addDefaultInteractions(chartState.autoBehaviorWidgets); } chartState.autoBehaviorWidgets.forEach((ChartBehavior behavior) { types.addAll(behavior.desiredGestures); }); return types; } } @immutable class LayoutConfig { final common.MarginSpec leftMarginSpec; final common.MarginSpec topMarginSpec; final common.MarginSpec rightMarginSpec; final common.MarginSpec bottomMarginSpec; LayoutConfig({ required this.leftMarginSpec, required this.topMarginSpec, required this.rightMarginSpec, required this.bottomMarginSpec, }); common.LayoutConfig get commonLayoutConfig => new common.LayoutConfig( leftSpec: leftMarginSpec, topSpec: topMarginSpec, rightSpec: rightMarginSpec, bottomSpec: bottomMarginSpec); } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_flutter/lib/src/base_chart_state.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:ui' show TextDirection; import 'package:flutter/material.dart' show AnimationController, BuildContext, State, TickerProviderStateMixin, Widget; import 'package:charts_common/common.dart' as common; import 'package:flutter/widgets.dart' show Directionality, LayoutId, CustomMultiChildLayout; import 'behaviors/chart_behavior.dart' show BuildableBehavior, ChartBehavior, ChartStateBehavior; import 'base_chart.dart' show BaseChart; import 'chart_container.dart' show ChartContainer; import 'chart_state.dart' show ChartState; import 'chart_gesture_detector.dart' show ChartGestureDetector; import 'widget_layout_delegate.dart'; class BaseChartState extends State> with TickerProviderStateMixin implements ChartState { // Animation late AnimationController _animationController; double _animationValue = 0.0; BaseChart? _oldWidget; ChartGestureDetector? _chartGestureDetector; bool _configurationChanged = false; final autoBehaviorWidgets = >[]; final addedBehaviorWidgets = >[]; final addedCommonBehaviorsByRole = {}; final addedSelectionChangedListenersByType = >{}; final addedSelectionUpdatedListenersByType = >{}; final _behaviorAnimationControllers = {}; static const chartContainerLayoutID = 'chartContainer'; @override void initState() { super.initState(); _animationController = new AnimationController(vsync: this) ..addListener(_animationTick); } @override void requestRebuild() { setState(() {}); } @override void markChartDirty() { _configurationChanged = true; } @override void resetChartDirtyFlag() { _configurationChanged = false; } @override bool get chartIsDirty => _configurationChanged; @override void setState(fn) { if (mounted) { super.setState(fn); } } /// Builds the common chart canvas widget. Widget _buildChartContainer() { final chartContainer = new ChartContainer( oldChartWidget: _oldWidget, chartWidget: widget, chartState: this, animationValue: _animationValue, rtl: Directionality.of(context) == TextDirection.rtl, rtlSpec: widget.rtlSpec, userManagedState: widget.userManagedState, ); _oldWidget = widget; final desiredGestures = widget.getDesiredGestures(this); if (desiredGestures.isNotEmpty) { _chartGestureDetector ??= new ChartGestureDetector(); return _chartGestureDetector! .makeWidget(context, chartContainer, desiredGestures); } else { return chartContainer; } } @override Widget build(BuildContext context) { final chartWidgets = []; final idAndBehaviorMap = {}; // Add the common chart canvas widget. chartWidgets.add(new LayoutId( id: chartContainerLayoutID, child: _buildChartContainer())); // Add widget for each behavior that can build widgets addedCommonBehaviorsByRole.forEach((id, behavior) { if (behavior is BuildableBehavior) { assert(id != chartContainerLayoutID); final buildableBehavior = behavior as BuildableBehavior; idAndBehaviorMap[id] = buildableBehavior; final widget = buildableBehavior.build(context); chartWidgets.add(new LayoutId(id: id, child: widget)); } }); final isRTL = Directionality.of(context) == TextDirection.rtl; return new CustomMultiChildLayout( delegate: new WidgetLayoutDelegate( chartContainerLayoutID, idAndBehaviorMap, isRTL), children: chartWidgets); } @override void dispose() { _animationController.dispose(); _behaviorAnimationControllers .forEach((_, controller) => controller.dispose()); _behaviorAnimationControllers.clear(); super.dispose(); } @override void setAnimation(Duration transition) { _playAnimation(transition); } void _playAnimation(Duration duration) { _animationController.duration = duration; _animationController.forward(from: (duration == Duration.zero) ? 1.0 : 0.0); _animationValue = _animationController.value; } void _animationTick() { setState(() { _animationValue = _animationController.value; }); } /// Get animation controller to be used by [behavior]. AnimationController getAnimationController(ChartStateBehavior behavior) { _behaviorAnimationControllers[behavior] ??= new AnimationController(vsync: this); return _behaviorAnimationControllers[behavior]!; } /// Dispose of animation controller used by [behavior]. void disposeAnimationController(ChartStateBehavior behavior) { final controller = _behaviorAnimationControllers.remove(behavior); controller?.dispose(); } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_flutter/lib/src/behaviors/a11y/domain_a11y_explore_behavior.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'package:charts_common/common.dart' as common show ChartBehavior, DomainA11yExploreBehavior, VocalizationCallback, ExploreModeTrigger; import 'package:flutter/widgets.dart' show hashValues; import '../chart_behavior.dart' show ChartBehavior, GestureType; /// Behavior that generates semantic nodes for each domain. class DomainA11yExploreBehavior extends ChartBehavior { /// Returns a string for a11y vocalization from a list of series datum. final common.VocalizationCallback? vocalizationCallback; final Set desiredGestures; /// The gesture that activates explore mode. Defaults to long press. /// /// Turning on explore mode asks this [A11yBehavior] to generate nodes within /// this chart. final common.ExploreModeTrigger? exploreModeTrigger; /// Minimum width of the bounding box for the a11y focus. /// /// Must be 1 or higher because invisible semantic nodes should not be added. final double? minimumWidth; /// Optionally notify the OS when explore mode is enabled. final String? exploreModeEnabledAnnouncement; /// Optionally notify the OS when explore mode is disabled. final String? exploreModeDisabledAnnouncement; DomainA11yExploreBehavior._internal( {this.vocalizationCallback, this.exploreModeTrigger, required this.desiredGestures, this.minimumWidth, this.exploreModeEnabledAnnouncement, this.exploreModeDisabledAnnouncement}); factory DomainA11yExploreBehavior({ common.VocalizationCallback? vocalizationCallback, common.ExploreModeTrigger? exploreModeTrigger, double? minimumWidth, String? exploreModeEnabledAnnouncement, String? exploreModeDisabledAnnouncement, }) { final desiredGestures = new Set(); exploreModeTrigger ??= common.ExploreModeTrigger.pressHold; switch (exploreModeTrigger) { case common.ExploreModeTrigger.pressHold: desiredGestures..add(GestureType.onLongPress); break; case common.ExploreModeTrigger.tap: desiredGestures..add(GestureType.onTap); break; } return new DomainA11yExploreBehavior._internal( vocalizationCallback: vocalizationCallback, desiredGestures: desiredGestures, exploreModeTrigger: exploreModeTrigger, minimumWidth: minimumWidth, exploreModeEnabledAnnouncement: exploreModeEnabledAnnouncement, exploreModeDisabledAnnouncement: exploreModeDisabledAnnouncement, ); } @override common.DomainA11yExploreBehavior createCommonBehavior() { return new common.DomainA11yExploreBehavior( vocalizationCallback: vocalizationCallback, exploreModeTrigger: exploreModeTrigger, minimumWidth: minimumWidth, exploreModeEnabledAnnouncement: exploreModeEnabledAnnouncement, exploreModeDisabledAnnouncement: exploreModeDisabledAnnouncement); } @override void updateCommonBehavior(common.ChartBehavior commonBehavior) {} @override String get role => 'DomainA11yExplore-${exploreModeTrigger}'; @override bool operator ==(Object o) => o is DomainA11yExploreBehavior && vocalizationCallback == o.vocalizationCallback && exploreModeTrigger == o.exploreModeTrigger && minimumWidth == o.minimumWidth && exploreModeEnabledAnnouncement == o.exploreModeEnabledAnnouncement && exploreModeDisabledAnnouncement == o.exploreModeDisabledAnnouncement; @override int get hashCode { return hashValues(minimumWidth, vocalizationCallback, exploreModeTrigger, exploreModeEnabledAnnouncement, exploreModeDisabledAnnouncement); } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_flutter/lib/src/behaviors/calculation/percent_injector.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'package:charts_common/common.dart' as common show ChartBehavior, PercentInjector, PercentInjectorTotalType; import 'package:meta/meta.dart' show immutable; import '../chart_behavior.dart' show ChartBehavior, GestureType; /// Chart behavior that can inject series or domain percentages into each datum. /// /// [totalType] configures the type of total to be calculated. /// /// The measure values of each datum will be replaced by the percent of the /// total measure value that each represents. The "raw" measure accessor /// function on [MutableSeries] can still be used to get the original values. /// /// Note that the results for measureLowerBound and measureUpperBound are not /// currently well defined when converted into percentage values. This behavior /// will replace them as percents to prevent bad axis results, but no effort is /// made to bound them to within a "0 to 100%" data range. /// /// Note that if the chart has a [Legend] that is capable of hiding series data, /// then this behavior must be added after the [Legend] to ensure that it /// calculates values after series have been potentially removed from the list. @immutable class PercentInjector extends ChartBehavior { final desiredGestures = new Set(); /// The type of data total to be calculated. final common.PercentInjectorTotalType totalType; /// Constructs a [PercentInjector]. /// /// [totalType] configures the type of data total to be calculated. PercentInjector({this.totalType = common.PercentInjectorTotalType.domain}); @override common.PercentInjector createCommonBehavior() => new common.PercentInjector(totalType: totalType); @override void updateCommonBehavior(common.ChartBehavior commonBehavior) {} @override String get role => 'PercentInjector'; @override bool operator ==(Object o) { return o is PercentInjector && totalType == o.totalType; } @override int get hashCode => totalType.hashCode; } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_flutter/lib/src/behaviors/chart_behavior.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math' show Rectangle; import 'package:charts_common/common.dart' as common show BehaviorPosition, InsideJustification, OutsideJustification, ChartBehavior; import 'package:meta/meta.dart' show immutable; import 'package:flutter/widgets.dart' show BuildContext, Widget; import '../base_chart_state.dart' show BaseChartState; /// Flutter wrapper for chart behaviors. @immutable abstract class ChartBehavior { Set get desiredGestures; common.ChartBehavior createCommonBehavior(); void updateCommonBehavior(common.ChartBehavior commonBehavior); String get role; } /// A chart behavior that depends on Flutter [State]. abstract class ChartStateBehavior { set chartState(BaseChartState chartState); } /// A chart behavior that can build a Flutter [Widget]. abstract class BuildableBehavior { /// Builds a [Widget] based on the information passed in. /// /// [context] Flutter build context for extracting inherited properties such /// as Directionality. Widget build(BuildContext context); /// The position on the widget. common.BehaviorPosition get position; /// Justification of the widget, if [position] is top, bottom, start, or end. common.OutsideJustification get outsideJustification; /// Justification of the widget if [position] is [common.BehaviorPosition.inside]. common.InsideJustification get insideJustification; /// Chart's draw area bounds are used for positioning. Rectangle? get drawAreaBounds; } /// Types of gestures accepted by a chart. enum GestureType { onLongPress, onTap, onHover, onDrag, } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_flutter/lib/src/behaviors/chart_title/chart_title.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'package:charts_common/common.dart' as common show BehaviorPosition, ChartBehavior, ChartTitle, ChartTitleDirection, MaxWidthStrategy, OutsideJustification, TextStyleSpec; import 'package:flutter/widgets.dart' show hashValues; import 'package:meta/meta.dart' show immutable; import '../chart_behavior.dart' show ChartBehavior, GestureType; /// Chart behavior that adds a ChartTitle widget to a chart. @immutable class ChartTitle extends ChartBehavior { final desiredGestures = new Set(); final common.BehaviorPosition? behaviorPosition; /// Minimum size of the legend component. Optional. /// /// If the legend is positioned in the top or bottom margin, then this /// configures the legend's height. If positioned in the start or end /// position, this configures the legend's width. final int? layoutMinSize; /// Preferred size of the legend component. Defaults to 0. /// /// If the legend is positioned in the top or bottom margin, then this /// configures the legend's height. If positioned in the start or end /// position, this configures the legend's width. final int? layoutPreferredSize; /// Strategy for handling title text that is too large to fit. Defaults to /// truncating the text with ellipses. final common.MaxWidthStrategy? maxWidthStrategy; /// Primary text for the title. final String title; /// Direction of the chart title text. /// /// This defaults to horizontal for a title in the top or bottom /// [behaviorPosition], or vertical for start or end [behaviorPosition]. final common.ChartTitleDirection? titleDirection; /// Justification of the title text if it is positioned outside of the draw /// area. final common.OutsideJustification? titleOutsideJustification; /// Space between the title and sub-title text, if defined. /// /// This padding is not used if no sub-title is provided. final int? titlePadding; /// Style of the [title] text. final common.TextStyleSpec? titleStyleSpec; /// Secondary text for the sub-title. /// /// [subTitle] is rendered on a second line below the [title], and may be /// styled differently. final String? subTitle; /// Style of the [subTitle] text. final common.TextStyleSpec? subTitleStyleSpec; /// Space between the "inside" of the chart, and the title behavior itself. /// /// This padding is applied to all the edge of the title that is in the /// direction of the draw area. For a top positioned title, this is applied /// to the bottom edge. [outerPadding] is applied to the top, left, and right /// edges. /// /// If a sub-title is defined, this is the space between the sub-title text /// and the inside of the chart. Otherwise, it is the space between the title /// text and the inside of chart. final int? innerPadding; /// Space between the "outside" of the chart, and the title behavior itself. /// /// This padding is applied to all 3 edges of the title that are not in the /// direction of the draw area. For a top positioned title, this is applied /// to the top, left, and right edges. [innerPadding] is applied to the /// bottom edge. final int? outerPadding; /// Constructs a [ChartTitle]. /// /// [title] primary text for the title. /// /// [behaviorPosition] layout position for the title. Defaults to the top of /// the chart. /// /// [innerPadding] space between the "inside" of the chart, and the title /// behavior itself. /// /// [maxWidthStrategy] strategy for handling title text that is too large to /// fit. Defaults to truncating the text with ellipses. /// /// [titleDirection] direction of the chart title text. /// /// [titleOutsideJustification] Justification of the title text if it is /// positioned outside of the draw. Defaults to the middle of the margin area. /// /// [titlePadding] space between the title and sub-title text, if defined. /// /// [titleStyleSpec] style of the [title] text. /// /// [subTitle] secondary text for the sub-title. Optional. /// /// [subTitleStyleSpec] style of the [subTitle] text. ChartTitle( this.title, { this.behaviorPosition, this.innerPadding, this.layoutMinSize, this.layoutPreferredSize, this.outerPadding, this.maxWidthStrategy, this.titleDirection, this.titleOutsideJustification, this.titlePadding, this.titleStyleSpec, this.subTitle, this.subTitleStyleSpec, }); @override common.ChartTitle createCommonBehavior() => new common.ChartTitle(title, behaviorPosition: behaviorPosition, innerPadding: innerPadding, layoutMinSize: layoutMinSize, layoutPreferredSize: layoutPreferredSize, outerPadding: outerPadding, maxWidthStrategy: maxWidthStrategy, titleDirection: titleDirection, titleOutsideJustification: titleOutsideJustification, titlePadding: titlePadding, titleStyleSpec: titleStyleSpec, subTitle: subTitle, subTitleStyleSpec: subTitleStyleSpec); @override void updateCommonBehavior(common.ChartBehavior commonBehavior) {} @override String get role => 'ChartTitle-${behaviorPosition.toString()}'; @override bool operator ==(Object o) { return o is ChartTitle && behaviorPosition == o.behaviorPosition && layoutMinSize == o.layoutMinSize && layoutPreferredSize == o.layoutPreferredSize && maxWidthStrategy == o.maxWidthStrategy && title == o.title && titleDirection == o.titleDirection && titleOutsideJustification == o.titleOutsideJustification && titleStyleSpec == o.titleStyleSpec && subTitle == o.subTitle && subTitleStyleSpec == o.subTitleStyleSpec && innerPadding == o.innerPadding && titlePadding == o.titlePadding && outerPadding == o.outerPadding; } @override int get hashCode { return hashValues( behaviorPosition, layoutMinSize, layoutPreferredSize, maxWidthStrategy, title, titleDirection, titleOutsideJustification, titleStyleSpec, subTitle, subTitleStyleSpec, innerPadding, titlePadding, outerPadding); } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_flutter/lib/src/behaviors/domain_highlighter.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'package:charts_common/common.dart' as common show ChartBehavior, DomainHighlighter, SelectionModelType; import 'package:meta/meta.dart' show immutable; import 'chart_behavior.dart' show ChartBehavior, GestureType; /// Chart behavior that monitors the specified [SelectionModel] and darkens the /// color for selected data. /// /// This is typically used for bars and pies to highlight segments. /// /// It is used in combination with SelectNearest to update the selection model /// and expand selection out to the domain value. @immutable class DomainHighlighter extends ChartBehavior { final desiredGestures = new Set(); final common.SelectionModelType selectionModelType; DomainHighlighter([this.selectionModelType = common.SelectionModelType.info]); @override common.DomainHighlighter createCommonBehavior() => new common.DomainHighlighter(selectionModelType); @override void updateCommonBehavior(common.ChartBehavior commonBehavior) {} @override String get role => 'domainHighlight-${selectionModelType.toString()}'; @override bool operator ==(Object o) => o is DomainHighlighter && selectionModelType == o.selectionModelType; @override int get hashCode => selectionModelType.hashCode; } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_flutter/lib/src/behaviors/initial_selection.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'package:collection/collection.dart' show ListEquality; import 'package:charts_common/common.dart' as common show ChartBehavior, InitialSelection, SeriesDatumConfig, SelectionModelType; import 'package:meta/meta.dart' show immutable; import 'chart_behavior.dart' show ChartBehavior, GestureType; /// Chart behavior that sets the initial selection for a [selectionModelType]. @immutable class InitialSelection extends ChartBehavior { final desiredGestures = new Set(); final common.SelectionModelType selectionModelType; final List? selectedSeriesConfig; final List>? selectedDataConfig; InitialSelection( {this.selectionModelType = common.SelectionModelType.info, this.selectedSeriesConfig, this.selectedDataConfig}); @override common.InitialSelection createCommonBehavior() => new common.InitialSelection( selectionModelType: selectionModelType, selectedDataConfig: selectedDataConfig, selectedSeriesConfig: selectedSeriesConfig); @override void updateCommonBehavior(common.ChartBehavior commonBehavior) {} @override String get role => 'InitialSelection-${selectionModelType.toString()}'; @override bool operator ==(Object o) { return o is InitialSelection && selectionModelType == o.selectionModelType && new ListEquality() .equals(selectedSeriesConfig, o.selectedSeriesConfig) && new ListEquality().equals(selectedDataConfig, o.selectedDataConfig); } @override int get hashCode { int hashcode = selectionModelType.hashCode; hashcode = hashcode * 37 + (selectedSeriesConfig?.hashCode ?? 0); hashcode = hashcode * 37 + (selectedDataConfig?.hashCode ?? 0); return hashcode; } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_flutter/lib/src/behaviors/legend/datum_legend.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'package:charts_common/common.dart' as common show BehaviorPosition, ChartBehavior, DatumLegend, InsideJustification, LegendEntry, MeasureFormatter, LegendDefaultMeasure, OutsideJustification, SelectionModelType, TextStyleSpec; import 'package:flutter/widgets.dart' show BuildContext, EdgeInsets, Widget, hashValues; import 'package:meta/meta.dart' show immutable; import '../../chart_container.dart' show ChartContainerRenderObject; import '../chart_behavior.dart' show BuildableBehavior, ChartBehavior, GestureType; import 'legend.dart' show TappableLegend; import 'legend_content_builder.dart' show LegendContentBuilder, TabularLegendContentBuilder; import 'legend_layout.dart' show TabularLegendLayout; /// Datum legend behavior for charts. /// /// By default this behavior creates one legend entry per datum in the first /// series rendered on the chart. @immutable class DatumLegend extends ChartBehavior { static const defaultBehaviorPosition = common.BehaviorPosition.top; static const defaultOutsideJustification = common.OutsideJustification.startDrawArea; static const defaultInsideJustification = common.InsideJustification.topStart; final desiredGestures = new Set(); final common.SelectionModelType? selectionModelType; /// Builder for creating custom legend content. final LegendContentBuilder contentBuilder; /// Position of the legend relative to the chart. final common.BehaviorPosition position; /// Justification of the legend relative to the chart final common.OutsideJustification outsideJustification; final common.InsideJustification insideJustification; /// Whether or not the legend should show measures. /// /// By default this is false, measures are not shown. When set to true, the /// default behavior is to show measure only if there is selected data. /// Please set [legendDefaultMeasure] to something other than none to enable /// showing measures when there is no selection. /// /// This flag is used by the [contentBuilder], so a custom content builder /// has to choose if it wants to use this flag. final bool showMeasures; /// Option to show measures when selection is null. /// /// By default this is set to none, so no measures are shown when there is /// no selection. final common.LegendDefaultMeasure? legendDefaultMeasure; /// Formatter for measure value(s) if the measures are shown on the legend. final common.MeasureFormatter? measureFormatter; /// Formatter for secondary measure value(s) if the measures are shown on the /// legend and the series uses the secondary axis. final common.MeasureFormatter? secondaryMeasureFormatter; /// Styles for legend entry label text. final common.TextStyleSpec? entryTextStyle; static const defaultCellPadding = const EdgeInsets.all(8.0); /// Create a new tabular layout legend. /// /// By default, the legend is place above the chart and horizontally aligned /// to the start of the draw area. /// /// [position] the legend will be positioned relative to the chart. Default /// position is top. /// /// [outsideJustification] justification of the legend relative to the chart /// if the position is top, bottom, left, right. Default to start of the draw /// area. /// /// [insideJustification] justification of the legend relative to the chart if /// the position is inside. Default to top of the chart, start of draw area. /// Start of draw area means left for LTR directionality, and right for RTL. /// /// [horizontalFirst] if true, legend entries will grow horizontally first /// instead of vertically first. If the position is top, bottom, or inside, /// this defaults to true. Otherwise false. /// /// [desiredMaxRows] the max rows to use before layout out items in a new /// column. By default there is no limit. The max columns created is the /// smaller of desiredMaxRows and number of legend entries. /// /// [desiredMaxColumns] the max columns to use before laying out items in a /// new row. By default there is no limit. The max columns created is the /// smaller of desiredMaxColumns and number of legend entries. /// /// [showMeasures] show measure values for each series. /// /// [legendDefaultMeasure] if measure should show when there is no selection. /// This is set to none by default (only shows measure for selected data). /// /// [measureFormatter] formats measure value if measures are shown. /// /// [secondaryMeasureFormatter] formats measures if measures are shown for the /// series that uses secondary measure axis. factory DatumLegend({ common.BehaviorPosition? position, common.OutsideJustification? outsideJustification, common.InsideJustification? insideJustification, bool? horizontalFirst, int? desiredMaxRows, int? desiredMaxColumns, EdgeInsets? cellPadding, bool? showMeasures, common.LegendDefaultMeasure? legendDefaultMeasure, common.MeasureFormatter? measureFormatter, common.MeasureFormatter? secondaryMeasureFormatter, common.TextStyleSpec? entryTextStyle, }) { // Set defaults if empty. position ??= defaultBehaviorPosition; outsideJustification ??= defaultOutsideJustification; insideJustification ??= defaultInsideJustification; cellPadding ??= defaultCellPadding; // Set the tabular layout settings to match the position if it is not // specified. horizontalFirst ??= (position == common.BehaviorPosition.top || position == common.BehaviorPosition.bottom || position == common.BehaviorPosition.inside); final layoutBuilder = horizontalFirst ? new TabularLegendLayout.horizontalFirst( desiredMaxColumns: desiredMaxColumns, cellPadding: cellPadding) : new TabularLegendLayout.verticalFirst( desiredMaxRows: desiredMaxRows, cellPadding: cellPadding); return new DatumLegend._internal( contentBuilder: new TabularLegendContentBuilder(legendLayout: layoutBuilder), selectionModelType: common.SelectionModelType.info, position: position, outsideJustification: outsideJustification, insideJustification: insideJustification, showMeasures: showMeasures ?? false, legendDefaultMeasure: legendDefaultMeasure ?? common.LegendDefaultMeasure.none, measureFormatter: measureFormatter, secondaryMeasureFormatter: secondaryMeasureFormatter, entryTextStyle: entryTextStyle); } /// Create a legend with custom layout. /// /// By default, the legend is place above the chart and horizontally aligned /// to the start of the draw area. /// /// [contentBuilder] builder for the custom layout. /// /// [position] the legend will be positioned relative to the chart. Default /// position is top. /// /// [outsideJustification] justification of the legend relative to the chart /// if the position is top, bottom, left, right. Default to start of the draw /// area. /// /// [insideJustification] justification of the legend relative to the chart if /// the position is inside. Default to top of the chart, start of draw area. /// Start of draw area means left for LTR directionality, and right for RTL. /// /// [showMeasures] show measure values for each series. /// /// [legendDefaultMeasure] if measure should show when there is no selection. /// This is set to none by default (only shows measure for selected data). /// /// [measureFormatter] formats measure value if measures are shown. /// /// [secondaryMeasureFormatter] formats measures if measures are shown for the /// series that uses secondary measure axis. factory DatumLegend.customLayout( LegendContentBuilder contentBuilder, { common.BehaviorPosition? position, common.OutsideJustification? outsideJustification, common.InsideJustification? insideJustification, bool? showMeasures, common.LegendDefaultMeasure? legendDefaultMeasure, common.MeasureFormatter? measureFormatter, common.MeasureFormatter? secondaryMeasureFormatter, common.TextStyleSpec? entryTextStyle, }) { // Set defaults if empty. position ??= defaultBehaviorPosition; outsideJustification ??= defaultOutsideJustification; insideJustification ??= defaultInsideJustification; return new DatumLegend._internal( contentBuilder: contentBuilder, selectionModelType: common.SelectionModelType.info, position: position, outsideJustification: outsideJustification, insideJustification: insideJustification, showMeasures: showMeasures ?? false, legendDefaultMeasure: legendDefaultMeasure ?? common.LegendDefaultMeasure.none, measureFormatter: measureFormatter, secondaryMeasureFormatter: secondaryMeasureFormatter, entryTextStyle: entryTextStyle, ); } DatumLegend._internal({ required this.contentBuilder, this.selectionModelType, required this.position, required this.outsideJustification, required this.insideJustification, required this.showMeasures, this.legendDefaultMeasure, this.measureFormatter, this.secondaryMeasureFormatter, this.entryTextStyle, }); @override common.DatumLegend createCommonBehavior() => new _FlutterDatumLegend(this); @override void updateCommonBehavior(common.ChartBehavior commonBehavior) { (commonBehavior as _FlutterDatumLegend).config = this; } /// All Legend behaviors get the same role ID, because you should only have /// one legend on a chart. @override String get role => 'legend'; @override bool operator ==(Object o) { return o is DatumLegend && selectionModelType == o.selectionModelType && contentBuilder == o.contentBuilder && position == o.position && outsideJustification == o.outsideJustification && insideJustification == o.insideJustification && showMeasures == o.showMeasures && legendDefaultMeasure == o.legendDefaultMeasure && measureFormatter == o.measureFormatter && secondaryMeasureFormatter == o.secondaryMeasureFormatter && entryTextStyle == o.entryTextStyle; } @override int get hashCode { return hashValues( selectionModelType, contentBuilder, position, outsideJustification, insideJustification, showMeasures, legendDefaultMeasure, measureFormatter, secondaryMeasureFormatter, entryTextStyle); } } /// Flutter specific wrapper on the common Legend for building content. class _FlutterDatumLegend extends common.DatumLegend implements BuildableBehavior, TappableLegend { DatumLegend config; _FlutterDatumLegend(this.config) : super( selectionModelType: config.selectionModelType, measureFormatter: config.measureFormatter, secondaryMeasureFormatter: config.secondaryMeasureFormatter, legendDefaultMeasure: config.legendDefaultMeasure, ) { super.entryTextStyle = config.entryTextStyle; } @override void updateLegend() { (chartContext as ChartContainerRenderObject).requestRebuild(); } @override common.BehaviorPosition get position => config.position; @override common.OutsideJustification get outsideJustification => config.outsideJustification; @override common.InsideJustification get insideJustification => config.insideJustification; @override Widget build(BuildContext context) { final hasSelection = legendState.legendEntries.any((entry) => entry.isSelected); // Show measures if [showMeasures] is true and there is a selection or if // showing measures when there is no selection. final showMeasures = config.showMeasures && (hasSelection || legendDefaultMeasure != common.LegendDefaultMeasure.none); return config.contentBuilder .build(context, legendState, this, showMeasures: showMeasures); } /// TODO: Maybe highlight the pie wedge. @override onLegendEntryTapUp(common.LegendEntry detail) {} } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_flutter/lib/src/behaviors/legend/legend.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'package:charts_common/common.dart' show LegendEntry, LegendTapHandling; abstract class TappableLegend { /// Delegates handling of legend entry clicks according to the configured /// [LegendTapHandling] strategy. onLegendEntryTapUp(LegendEntry detail); } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_flutter/lib/src/behaviors/legend/legend_content_builder.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'package:charts_common/common.dart' as common show Legend, LegendState, SeriesLegend; import 'package:flutter/widgets.dart' show BuildContext, hashValues, Widget; import 'legend.dart'; import 'legend_entry_layout.dart'; import 'legend_layout.dart'; /// Strategy for building a legend content widget. abstract class LegendContentBuilder { const LegendContentBuilder(); Widget build(BuildContext context, common.LegendState legendState, common.Legend legend, {bool showMeasures}); } /// Base strategy for building a legend content widget. /// /// Each legend entry is passed to a [LegendLayout] strategy to create a widget /// for each legend entry. These widgets are then passed to a /// [LegendEntryLayout] strategy to create the legend widget. abstract class BaseLegendContentBuilder implements LegendContentBuilder { /// Strategy for creating one widget or each legend entry. LegendEntryLayout get legendEntryLayout; /// Strategy for creating the legend content widget from a list of widgets. /// /// This is typically the list of widgets from legend entries. LegendLayout get legendLayout; @override Widget build(BuildContext context, common.LegendState legendState, common.Legend legend, {bool showMeasures = false}) { final entryWidgets = legendState.legendEntries.map((entry) { var isHidden = false; if (legend is common.SeriesLegend) { isHidden = legend.isSeriesHidden(entry.series.id); } return legendEntryLayout.build( context, entry, legend as TappableLegend, isHidden, showMeasures: showMeasures); }).toList(); return legendLayout.build(context, entryWidgets); } } // TODO: Expose settings for tabular layout. /// Strategy that builds a tabular legend. /// /// [legendEntryLayout] custom strategy for creating widgets for each legend /// entry. /// [legendLayout] custom strategy for creating legend widget from list of /// widgets that represent a legend entry. class TabularLegendContentBuilder extends BaseLegendContentBuilder { final LegendEntryLayout legendEntryLayout; final LegendLayout legendLayout; TabularLegendContentBuilder( {LegendEntryLayout? legendEntryLayout, LegendLayout? legendLayout}) : this.legendEntryLayout = legendEntryLayout ?? const SimpleLegendEntryLayout(), this.legendLayout = legendLayout ?? new TabularLegendLayout.horizontalFirst(); @override bool operator ==(Object o) { return o is TabularLegendContentBuilder && legendEntryLayout == o.legendEntryLayout && legendLayout == o.legendLayout; } @override int get hashCode => hashValues(legendEntryLayout, legendLayout); } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_flutter/lib/src/behaviors/legend/legend_entry_layout.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'package:charts_common/common.dart' as common; import 'package:charts_flutter/src/util/color.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter/material.dart' show GestureDetector, GestureTapUpCallback, TapUpDetails, Theme; import '../../symbol_renderer.dart'; import 'legend.dart' show TappableLegend; /// Strategy for building one widget from one [common.LegendEntry]. abstract class LegendEntryLayout { Widget build(BuildContext context, common.LegendEntry legendEntry, TappableLegend legend, bool isHidden, {bool showMeasures}); } /// Builds one legend entry as a row with symbol and label from the series. /// /// If directionality from the chart context indicates RTL, the symbol is placed /// to the right of the text instead of the left of the text. class SimpleLegendEntryLayout implements LegendEntryLayout { const SimpleLegendEntryLayout(); Widget createSymbol(BuildContext context, common.LegendEntry legendEntry, TappableLegend legend, bool isHidden) { // TODO: Consider allowing scaling the size for the symbol. // A custom symbol renderer can ignore this size and use their own. final materialSymbolSize = new Size(12.0, 12.0); final entryColor = legendEntry.color; final color = entryColor == null ? null : ColorUtil.toDartColor(entryColor); // Get the SymbolRendererBuilder wrapping a common.SymbolRenderer if needed. final SymbolRendererBuilder symbolRendererBuilder = legendEntry.symbolRenderer! is SymbolRendererBuilder ? legendEntry.symbolRenderer! as SymbolRendererBuilder : new SymbolRendererCanvas( legendEntry.symbolRenderer!, legendEntry.dashPattern); return new GestureDetector( child: symbolRendererBuilder.build( context, size: materialSymbolSize, color: color, enabled: !isHidden, ), onTapUp: makeTapUpCallback(context, legendEntry, legend)); } Widget createLabel(BuildContext context, common.LegendEntry legendEntry, TappableLegend legend, bool isHidden) { TextStyle style = _convertTextStyle(isHidden, context, legendEntry.textStyle); return new GestureDetector( child: new Text(legendEntry.label, style: style), onTapUp: makeTapUpCallback(context, legendEntry, legend)); } Widget createMeasureValue(BuildContext context, common.LegendEntry legendEntry, TappableLegend legend, bool isHidden) { return new GestureDetector( child: new Text(legendEntry.formattedValue!), onTapUp: makeTapUpCallback(context, legendEntry, legend)); } @override Widget build(BuildContext context, common.LegendEntry legendEntry, TappableLegend legend, bool isHidden, {bool showMeasures = false}) { final rowChildren = []; // TODO: Allow setting to configure the padding. final padding = new EdgeInsets.only(right: 8.0); // Material default. final symbol = createSymbol(context, legendEntry, legend, isHidden); final label = createLabel(context, legendEntry, legend, isHidden); final measure = showMeasures ? createMeasureValue(context, legendEntry, legend, isHidden) : null; rowChildren.add(symbol); rowChildren.add(new Container(padding: padding)); rowChildren.add(label); if (measure != null) { rowChildren.add(new Container(padding: padding)); rowChildren.add(measure); } // Row automatically reverses the content if Directionality is rtl. return new Row(children: rowChildren); } GestureTapUpCallback makeTapUpCallback(BuildContext context, common.LegendEntry legendEntry, TappableLegend legend) { return (TapUpDetails d) { legend.onLegendEntryTapUp(legendEntry); }; } bool operator ==(Object other) => other is SimpleLegendEntryLayout; int get hashCode { return this.runtimeType.hashCode; } /// Convert the charts common TextStlyeSpec into a standard TextStyle, while /// reducing the color opacity to 26% if the entry is hidden. /// /// For non-specified values, override the hidden text color to use the body 1 /// theme, but allow other properties of [Text] to be inherited. TextStyle _convertTextStyle( bool isHidden, BuildContext context, common.TextStyleSpec? textStyle) { Color? color = textStyle?.color != null ? ColorUtil.toDartColor(textStyle!.color!) : null; if (isHidden) { // Use a default color for hidden legend entries if none is provided. color ??= Theme.of(context).textTheme.bodyText2!.color; color = color!.withOpacity(0.26); } return new TextStyle( inherit: true, fontFamily: textStyle?.fontFamily, fontSize: textStyle?.fontSize != null ? textStyle!.fontSize!.toDouble() : null, color: color); } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_flutter/lib/src/behaviors/legend/legend_layout.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math' show min; import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; /// Strategy for building legend from legend entry widgets. abstract class LegendLayout { Widget build(BuildContext context, List legendEntryWidgets); } /// Layout legend entries in tabular format. class TabularLegendLayout implements LegendLayout { /// No limit for max rows or max columns. static const _noLimit = -1; /// Default EdgeInsets for padding rows to the max column count static const defaultCellPadding = const EdgeInsets.all(8.0); final bool isHorizontalFirst; final int desiredMaxRows; final int desiredMaxColumns; final EdgeInsets? cellPadding; TabularLegendLayout._internal( {required this.isHorizontalFirst, required this.desiredMaxRows, required this.desiredMaxColumns, this.cellPadding}); /// Layout horizontally until columns exceed [desiredMaxColumns]. /// /// [desiredMaxColumns] the max columns to use before laying out items in a /// new row. By default there is no limit. The max columns created is the /// smaller of desiredMaxColumns and number of legend entries. /// /// [cellPadding] the [EdgeInsets] for each widget. factory TabularLegendLayout.horizontalFirst({ int? desiredMaxColumns, EdgeInsets? cellPadding, }) { return new TabularLegendLayout._internal( isHorizontalFirst: true, desiredMaxRows: _noLimit, desiredMaxColumns: desiredMaxColumns ?? _noLimit, cellPadding: cellPadding, ); } /// Layout vertically, until rows exceed [desiredMaxRows]. /// /// [desiredMaxRows] the max rows to use before layout out items in a new /// column. By default there is no limit. The max columns created is the /// smaller of desiredMaxRows and number of legend entries. /// /// [cellPadding] the [EdgeInsets] for each widget. factory TabularLegendLayout.verticalFirst({ int? desiredMaxRows, EdgeInsets? cellPadding, }) { return new TabularLegendLayout._internal( isHorizontalFirst: false, desiredMaxRows: desiredMaxRows ?? _noLimit, desiredMaxColumns: _noLimit, cellPadding: cellPadding, ); } @override Widget build(BuildContext context, List legendEntries) { final paddedLegendEntries = ((cellPadding == null) ? legendEntries : legendEntries .map((entry) => new Padding(padding: cellPadding!, child: entry)) .toList()); return isHorizontalFirst ? _buildHorizontalFirst(paddedLegendEntries) : _buildVerticalFirst(paddedLegendEntries); } @override bool operator ==(o) => o is TabularLegendLayout && desiredMaxRows == o.desiredMaxRows && desiredMaxColumns == o.desiredMaxColumns && isHorizontalFirst == o.isHorizontalFirst && cellPadding == o.cellPadding; @override int get hashCode => hashValues( desiredMaxRows, desiredMaxColumns, isHorizontalFirst, cellPadding); Widget _buildHorizontalFirst(List legendEntries) { final maxColumns = (desiredMaxColumns == _noLimit) ? legendEntries.length : min(legendEntries.length, desiredMaxColumns); final rows = []; for (var i = 0; i < legendEntries.length; i += maxColumns) { rows.add(new TableRow( children: legendEntries .sublist(i, min(i + maxColumns, legendEntries.length)) .toList())); } return _buildTableFromRows(rows); } Widget _buildVerticalFirst(List legendEntries) { final maxRows = (desiredMaxRows == _noLimit) ? legendEntries.length : min(legendEntries.length, desiredMaxRows); final rows = new List.generate(maxRows, (_) => new TableRow(children: [])); for (var i = 0; i < legendEntries.length; i++) { rows[i % maxRows].children!.add(legendEntries[i]); } return _buildTableFromRows(rows); } Table _buildTableFromRows(List rows) { final padWidget = Padding(padding: cellPadding ?? defaultCellPadding); // Pad rows to the max column count, because each TableRow in a table is // required to have the same number of children. final columnCount = rows .map((r) => r.children!.length) .fold(0, (max, current) => (current > max) ? current : max); for (var i = 0; i < rows.length; i++) { final rowChildren = rows[i].children; final padCount = columnCount - rowChildren!.length; if (padCount > 0) { rowChildren .addAll(new Iterable.generate(padCount, (_) => padWidget)); } } // TODO: Investigate other means of creating the tabular legend // Sizing the column width using [IntrinsicColumnWidth] is expensive per // Flutter's documentation, but has to be used if the table is desired to // have a width that is tight on each column. return new Table( children: rows, defaultColumnWidth: new IntrinsicColumnWidth()); } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_flutter/lib/src/behaviors/legend/series_legend.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'package:charts_common/common.dart' as common show BehaviorPosition, ChartBehavior, InsideJustification, LegendEntry, LegendTapHandling, MeasureFormatter, LegendDefaultMeasure, OutsideJustification, SeriesLegend, SelectionModelType, TextStyleSpec; import 'package:collection/collection.dart' show ListEquality; import 'package:flutter/widgets.dart' show BuildContext, EdgeInsets, Widget, hashValues; import 'package:meta/meta.dart' show immutable; import '../../chart_container.dart' show ChartContainerRenderObject; import '../chart_behavior.dart' show BuildableBehavior, ChartBehavior, GestureType; import 'legend.dart' show TappableLegend; import 'legend_content_builder.dart' show LegendContentBuilder, TabularLegendContentBuilder; import 'legend_layout.dart' show TabularLegendLayout; /// Series legend behavior for charts. @immutable class SeriesLegend extends ChartBehavior { static const defaultBehaviorPosition = common.BehaviorPosition.top; static const defaultOutsideJustification = common.OutsideJustification.startDrawArea; static const defaultInsideJustification = common.InsideJustification.topStart; final desiredGestures = new Set(); final common.SelectionModelType? selectionModelType; /// Builder for creating custom legend content. final LegendContentBuilder contentBuilder; /// Position of the legend relative to the chart. final common.BehaviorPosition position; /// Justification of the legend relative to the chart final common.OutsideJustification outsideJustification; final common.InsideJustification insideJustification; /// Whether or not the legend should show measures. /// /// By default this is false, measures are not shown. When set to true, the /// default behavior is to show measure only if there is selected data. /// Please set [legendDefaultMeasure] to something other than none to enable /// showing measures when there is no selection. /// /// This flag is used by the [contentBuilder], so a custom content builder /// has to choose if it wants to use this flag. final bool showMeasures; /// Option to show measures when selection is null. /// /// By default this is set to none, so no measures are shown when there is /// no selection. final common.LegendDefaultMeasure? legendDefaultMeasure; /// Formatter for measure value(s) if the measures are shown on the legend. final common.MeasureFormatter? measureFormatter; /// Formatter for secondary measure value(s) if the measures are shown on the /// legend and the series uses the secondary axis. final common.MeasureFormatter? secondaryMeasureFormatter; /// Styles for legend entry label text. final common.TextStyleSpec? entryTextStyle; static const defaultCellPadding = const EdgeInsets.all(8.0); final List? defaultHiddenSeries; /// Create a new tabular layout legend. /// /// By default, the legend is place above the chart and horizontally aligned /// to the start of the draw area. /// /// [position] the legend will be positioned relative to the chart. Default /// position is top. /// /// [outsideJustification] justification of the legend relative to the chart /// if the position is top, bottom, left, right. Default to start of the draw /// area. /// /// [insideJustification] justification of the legend relative to the chart if /// the position is inside. Default to top of the chart, start of draw area. /// Start of draw area means left for LTR directionality, and right for RTL. /// /// [horizontalFirst] if true, legend entries will grow horizontally first /// instead of vertically first. If the position is top, bottom, or inside, /// this defaults to true. Otherwise false. /// /// [desiredMaxRows] the max rows to use before layout out items in a new /// column. By default there is no limit. The max columns created is the /// smaller of desiredMaxRows and number of legend entries. /// /// [desiredMaxColumns] the max columns to use before laying out items in a /// new row. By default there is no limit. The max columns created is the /// smaller of desiredMaxColumns and number of legend entries. /// /// [defaultHiddenSeries] lists the IDs of series that should be hidden on /// first chart draw. /// /// [showMeasures] show measure values for each series. /// /// [legendDefaultMeasure] if measure should show when there is no selection. /// This is set to none by default (only shows measure for selected data). /// /// [measureFormatter] formats measure value if measures are shown. /// /// [secondaryMeasureFormatter] formats measures if measures are shown for the /// series that uses secondary measure axis. factory SeriesLegend({ common.BehaviorPosition? position, common.OutsideJustification? outsideJustification, common.InsideJustification? insideJustification, bool? horizontalFirst, int? desiredMaxRows, int? desiredMaxColumns, EdgeInsets? cellPadding, List? defaultHiddenSeries, bool? showMeasures, common.LegendDefaultMeasure? legendDefaultMeasure, common.MeasureFormatter? measureFormatter, common.MeasureFormatter? secondaryMeasureFormatter, common.TextStyleSpec? entryTextStyle, }) { // Set defaults if empty. position ??= defaultBehaviorPosition; outsideJustification ??= defaultOutsideJustification; insideJustification ??= defaultInsideJustification; cellPadding ??= defaultCellPadding; // Set the tabular layout settings to match the position if it is not // specified. horizontalFirst ??= (position == common.BehaviorPosition.top || position == common.BehaviorPosition.bottom || position == common.BehaviorPosition.inside); final layoutBuilder = horizontalFirst ? new TabularLegendLayout.horizontalFirst( desiredMaxColumns: desiredMaxColumns, cellPadding: cellPadding) : new TabularLegendLayout.verticalFirst( desiredMaxRows: desiredMaxRows, cellPadding: cellPadding); return new SeriesLegend._internal( contentBuilder: new TabularLegendContentBuilder(legendLayout: layoutBuilder), selectionModelType: common.SelectionModelType.info, position: position, outsideJustification: outsideJustification, insideJustification: insideJustification, defaultHiddenSeries: defaultHiddenSeries, showMeasures: showMeasures ?? false, legendDefaultMeasure: legendDefaultMeasure ?? common.LegendDefaultMeasure.none, measureFormatter: measureFormatter, secondaryMeasureFormatter: secondaryMeasureFormatter, entryTextStyle: entryTextStyle); } /// Create a legend with custom layout. /// /// By default, the legend is place above the chart and horizontally aligned /// to the start of the draw area. /// /// [contentBuilder] builder for the custom layout. /// /// [position] the legend will be positioned relative to the chart. Default /// position is top. /// /// [outsideJustification] justification of the legend relative to the chart /// if the position is top, bottom, left, right. Default to start of the draw /// area. /// /// [insideJustification] justification of the legend relative to the chart if /// the position is inside. Default to top of the chart, start of draw area. /// Start of draw area means left for LTR directionality, and right for RTL. /// /// [defaultHiddenSeries] lists the IDs of series that should be hidden on /// first chart draw. /// /// [showMeasures] show measure values for each series. /// /// [legendDefaultMeasure] if measure should show when there is no selection. /// This is set to none by default (only shows measure for selected data). /// /// [measureFormatter] formats measure value if measures are shown. /// /// [secondaryMeasureFormatter] formats measures if measures are shown for the /// series that uses secondary measure axis. factory SeriesLegend.customLayout( LegendContentBuilder contentBuilder, { common.BehaviorPosition? position, common.OutsideJustification? outsideJustification, common.InsideJustification? insideJustification, List? defaultHiddenSeries, bool? showMeasures, common.LegendDefaultMeasure? legendDefaultMeasure, common.MeasureFormatter? measureFormatter, common.MeasureFormatter? secondaryMeasureFormatter, common.TextStyleSpec? entryTextStyle, }) { // Set defaults if empty. position ??= defaultBehaviorPosition; outsideJustification ??= defaultOutsideJustification; insideJustification ??= defaultInsideJustification; return new SeriesLegend._internal( contentBuilder: contentBuilder, selectionModelType: common.SelectionModelType.info, position: position, outsideJustification: outsideJustification, insideJustification: insideJustification, defaultHiddenSeries: defaultHiddenSeries, showMeasures: showMeasures ?? false, legendDefaultMeasure: legendDefaultMeasure ?? common.LegendDefaultMeasure.none, measureFormatter: measureFormatter, secondaryMeasureFormatter: secondaryMeasureFormatter, entryTextStyle: entryTextStyle, ); } SeriesLegend._internal({ required this.contentBuilder, this.selectionModelType, required this.position, required this.outsideJustification, required this.insideJustification, this.defaultHiddenSeries, required this.showMeasures, this.legendDefaultMeasure, this.measureFormatter, this.secondaryMeasureFormatter, this.entryTextStyle, }); @override common.SeriesLegend createCommonBehavior() => new _FlutterSeriesLegend(this); @override void updateCommonBehavior(common.ChartBehavior commonBehavior) { (commonBehavior as _FlutterSeriesLegend).config = this; } /// All Legend behaviors get the same role ID, because you should only have /// one legend on a chart. @override String get role => 'legend'; @override bool operator ==(Object o) { return o is SeriesLegend && selectionModelType == o.selectionModelType && contentBuilder == o.contentBuilder && position == o.position && outsideJustification == o.outsideJustification && insideJustification == o.insideJustification && new ListEquality().equals(defaultHiddenSeries, o.defaultHiddenSeries) && showMeasures == o.showMeasures && legendDefaultMeasure == o.legendDefaultMeasure && measureFormatter == o.measureFormatter && secondaryMeasureFormatter == o.secondaryMeasureFormatter && entryTextStyle == o.entryTextStyle; } @override int get hashCode { return hashValues( selectionModelType, contentBuilder, position, outsideJustification, insideJustification, defaultHiddenSeries, showMeasures, legendDefaultMeasure, measureFormatter, secondaryMeasureFormatter, entryTextStyle); } } /// Flutter specific wrapper on the common Legend for building content. class _FlutterSeriesLegend extends common.SeriesLegend implements BuildableBehavior, TappableLegend { SeriesLegend config; _FlutterSeriesLegend(this.config) : super( selectionModelType: config.selectionModelType, measureFormatter: config.measureFormatter, secondaryMeasureFormatter: config.secondaryMeasureFormatter, legendDefaultMeasure: config.legendDefaultMeasure, ) { super.defaultHiddenSeries = config.defaultHiddenSeries; super.entryTextStyle = config.entryTextStyle; } @override void updateLegend() { (chartContext as ChartContainerRenderObject).requestRebuild(); } @override common.BehaviorPosition get position => config.position; @override common.OutsideJustification get outsideJustification => config.outsideJustification; @override common.InsideJustification get insideJustification => config.insideJustification; @override Widget build(BuildContext context) { final hasSelection = legendState.legendEntries != null && legendState.legendEntries.any((entry) => entry.isSelected); // Show measures if [showMeasures] is true and there is a selection or if // showing measures when there is no selection. final showMeasures = config.showMeasures && (hasSelection || legendDefaultMeasure != common.LegendDefaultMeasure.none); return config.contentBuilder .build(context, legendState, this, showMeasures: showMeasures); } @override onLegendEntryTapUp(common.LegendEntry detail) { switch (legendTapHandling) { case common.LegendTapHandling.hide: _hideSeries(detail); break; case common.LegendTapHandling.none: default: break; } } /// Handles tap events by hiding or un-hiding entries tapped in the legend. /// /// Tapping on a visible series in the legend will hide it. Tapping on a /// hidden series will make it visible again. void _hideSeries(common.LegendEntry detail) { final seriesId = detail.series.id; // Handle the event by toggling the hidden state of the target. if (isSeriesHidden(seriesId)) { showSeries(seriesId); } else { hideSeries(seriesId); } // Redraw the chart to actually hide hidden series. chart.redraw(skipLayout: true, skipAnimation: false); } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_flutter/lib/src/behaviors/line_point_highlighter.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'package:collection/collection.dart' show ListEquality; import 'package:charts_common/common.dart' as common show ChartBehavior, LinePointHighlighter, LinePointHighlighterFollowLineType, SelectionModelType, SymbolRenderer; import 'package:flutter/widgets.dart' show hashValues; import 'package:meta/meta.dart' show immutable; import 'chart_behavior.dart' show ChartBehavior, GestureType; /// Chart behavior that monitors the specified [SelectionModel] and darkens the /// color for selected data. /// /// This is typically used for bars and pies to highlight segments. /// /// It is used in combination with SelectNearest to update the selection model /// and expand selection out to the domain value. @immutable class LinePointHighlighter extends ChartBehavior { final desiredGestures = new Set(); final common.SelectionModelType? selectionModelType; /// Default radius of the dots if the series has no radius mapping function. /// /// When no radius mapping function is provided, this value will be used as /// is. [radiusPaddingPx] will not be added to [defaultRadiusPx]. final double? defaultRadiusPx; /// Additional radius value added to the radius of the selected data. /// /// This value is only used when the series has a radius mapping function /// defined. final double? radiusPaddingPx; final common.LinePointHighlighterFollowLineType? showHorizontalFollowLine; final common.LinePointHighlighterFollowLineType? showVerticalFollowLine; /// The dash pattern to be used for drawing the line. /// /// To disable dash pattern (to draw a solid line), pass in an empty list. /// This is because if dashPattern is null or not set, it defaults to [1,3]. final List? dashPattern; /// Whether or not follow lines should be drawn across the entire chart draw /// area, or just from the axis to the point. /// /// When disabled, measure follow lines will be drawn from the primary measure /// axis to the point. In RTL mode, this means from the right-hand axis. In /// LTR mode, from the left-hand axis. final bool? drawFollowLinesAcrossChart; /// Renderer used to draw the highlighted points. final common.SymbolRenderer? symbolRenderer; LinePointHighlighter( {this.selectionModelType, this.defaultRadiusPx, this.radiusPaddingPx, this.showHorizontalFollowLine, this.showVerticalFollowLine, this.dashPattern, this.drawFollowLinesAcrossChart, this.symbolRenderer}); @override common.LinePointHighlighter createCommonBehavior() => new common.LinePointHighlighter( selectionModelType: selectionModelType, defaultRadiusPx: defaultRadiusPx, radiusPaddingPx: radiusPaddingPx, showHorizontalFollowLine: showHorizontalFollowLine, showVerticalFollowLine: showVerticalFollowLine, dashPattern: dashPattern, drawFollowLinesAcrossChart: drawFollowLinesAcrossChart, symbolRenderer: symbolRenderer, ); @override void updateCommonBehavior(common.ChartBehavior commonBehavior) {} @override String get role => 'LinePointHighlighter-${selectionModelType.toString()}'; @override bool operator ==(Object o) { return o is LinePointHighlighter && defaultRadiusPx == o.defaultRadiusPx && radiusPaddingPx == o.radiusPaddingPx && showHorizontalFollowLine == o.showHorizontalFollowLine && showVerticalFollowLine == o.showVerticalFollowLine && selectionModelType == o.selectionModelType && new ListEquality().equals(dashPattern, o.dashPattern) && drawFollowLinesAcrossChart == o.drawFollowLinesAcrossChart; } @override int get hashCode { return hashValues( selectionModelType, defaultRadiusPx, radiusPaddingPx, showHorizontalFollowLine, showVerticalFollowLine, dashPattern, drawFollowLinesAcrossChart, ); } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_flutter/lib/src/behaviors/range_annotation.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'package:charts_common/common.dart' as common show AnnotationLabelAnchor, AnnotationLabelDirection, AnnotationLabelPosition, AnnotationSegment, ChartBehavior, Color, MaterialPalette, RangeAnnotation, TextStyleSpec; import 'package:collection/collection.dart' show ListEquality; import 'package:flutter/widgets.dart' show hashValues; import 'package:meta/meta.dart' show immutable; import 'chart_behavior.dart' show ChartBehavior, GestureType; /// Chart behavior that annotations domain ranges with a solid fill color. /// /// The annotations will be drawn underneath series data and chart axes. /// /// This is typically used for line charts to call out sections of the data /// range. @immutable class RangeAnnotation extends ChartBehavior { final desiredGestures = new Set(); /// List of annotations to render on the chart. final List> annotations; /// Configures where to anchor annotation label text. final common.AnnotationLabelAnchor? defaultLabelAnchor; /// Direction of label text on the annotations. final common.AnnotationLabelDirection? defaultLabelDirection; /// Configures where to place labels relative to the annotation. final common.AnnotationLabelPosition? defaultLabelPosition; /// Configures the style of label text. final common.TextStyleSpec? defaultLabelStyleSpec; /// Default color for annotations. final common.Color? defaultColor; /// Whether or not the range of the axis should be extended to include the /// annotation start and end values. final bool? extendAxis; /// Space before and after label text. final int? labelPadding; /// Configures the order in which the behavior should be painted. /// This value should be relative to LayoutPaintViewOrder.rangeAnnotation. /// (e.g. LayoutViewPaintOrder.rangeAnnotation + 1) final int? layoutPaintOrder; RangeAnnotation(this.annotations, {common.Color? defaultColor, this.defaultLabelAnchor, this.defaultLabelDirection, this.defaultLabelPosition, this.defaultLabelStyleSpec, this.extendAxis, this.labelPadding, this.layoutPaintOrder}) : this.defaultColor = defaultColor ?? common.MaterialPalette.gray.shade100; @override common.RangeAnnotation createCommonBehavior() => new common.RangeAnnotation(annotations, defaultColor: defaultColor, defaultLabelAnchor: defaultLabelAnchor, defaultLabelDirection: defaultLabelDirection, defaultLabelPosition: defaultLabelPosition, defaultLabelStyleSpec: defaultLabelStyleSpec, extendAxis: extendAxis, labelPadding: labelPadding, layoutPaintOrder: layoutPaintOrder); @override void updateCommonBehavior(common.ChartBehavior commonBehavior) {} @override String get role => 'RangeAnnotation'; @override bool operator ==(Object o) { return o is RangeAnnotation && new ListEquality().equals(annotations, o.annotations) && defaultColor == o.defaultColor && extendAxis == o.extendAxis && defaultLabelAnchor == o.defaultLabelAnchor && defaultLabelDirection == o.defaultLabelDirection && defaultLabelPosition == o.defaultLabelPosition && defaultLabelStyleSpec == o.defaultLabelStyleSpec && labelPadding == o.labelPadding && layoutPaintOrder == o.layoutPaintOrder; } @override int get hashCode => hashValues( annotations, defaultColor, extendAxis, defaultLabelAnchor, defaultLabelDirection, defaultLabelPosition, defaultLabelStyleSpec, labelPadding, layoutPaintOrder); } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_flutter/lib/src/behaviors/select_nearest.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'package:charts_common/common.dart' as common show ChartBehavior, SelectNearest, SelectionMode, SelectionModelType, SelectionTrigger; import 'package:meta/meta.dart' show immutable; import 'chart_behavior.dart' show ChartBehavior, GestureType; /// Chart behavior that listens to the given eventTrigger and updates the /// specified [SelectionModel]. This is used to pair input events to behaviors /// that listen to selection changes. /// /// Input event types: /// hover (default) - Mouse over/near data. /// tap - Mouse/Touch on/near data. /// pressHold - Mouse/Touch and drag across the data instead of panning. /// longPressHold - Mouse/Touch for a while in one place then drag across the data. /// /// SelectionModels that can be updated: /// info - To view the details of the selected items (ie: hover for web). /// action - To select an item as an input, drill, or other selection. /// /// Other options available /// selectionMode - Optional mode for expanding the selection beyond the /// nearest datum. Defaults to expandToDomain. /// selectClosestSeries - mark the series for the closest data point as /// selected. (Default: true) /// /// You can add one SelectNearest for each model type that you are updating. /// Any previous SelectNearest behavior for that selection model will be /// removed. @immutable class SelectNearest extends ChartBehavior { final Set desiredGestures; final common.SelectionModelType selectionModelType; final common.SelectionTrigger eventTrigger; final common.SelectionMode selectionMode; final bool selectAcrossAllDrawAreaComponents; final bool selectClosestSeries; final int? maximumDomainDistancePx; SelectNearest._internal( {required this.selectionModelType, this.selectionMode = common.SelectionMode.expandToDomain, this.selectAcrossAllDrawAreaComponents = false, this.selectClosestSeries = true, required this.eventTrigger, required this.desiredGestures, this.maximumDomainDistancePx}); factory SelectNearest( {common.SelectionModelType selectionModelType = common.SelectionModelType.info, common.SelectionMode selectionMode = common.SelectionMode.expandToDomain, bool selectAcrossAllDrawAreaComponents = false, bool selectClosestSeries = true, common.SelectionTrigger eventTrigger = common.SelectionTrigger.tap, int? maximumDomainDistancePx}) { return new SelectNearest._internal( selectionModelType: selectionModelType, selectionMode: selectionMode, selectAcrossAllDrawAreaComponents: selectAcrossAllDrawAreaComponents, selectClosestSeries: selectClosestSeries, eventTrigger: eventTrigger, desiredGestures: SelectNearest._getDesiredGestures(eventTrigger), maximumDomainDistancePx: maximumDomainDistancePx); } static Set _getDesiredGestures( common.SelectionTrigger eventTrigger) { final desiredGestures = new Set(); switch (eventTrigger) { case common.SelectionTrigger.tap: desiredGestures..add(GestureType.onTap); break; case common.SelectionTrigger.tapAndDrag: desiredGestures..add(GestureType.onTap)..add(GestureType.onDrag); break; case common.SelectionTrigger.pressHold: case common.SelectionTrigger.longPressHold: desiredGestures ..add(GestureType.onTap) ..add(GestureType.onLongPress) ..add(GestureType.onDrag); break; case common.SelectionTrigger.hover: default: desiredGestures..add(GestureType.onHover); break; } return desiredGestures; } @override common.SelectNearest createCommonBehavior() { return new common.SelectNearest( selectionModelType: selectionModelType, eventTrigger: eventTrigger, selectionMode: selectionMode, selectClosestSeries: selectClosestSeries, maximumDomainDistancePx: maximumDomainDistancePx); } @override void updateCommonBehavior(common.ChartBehavior commonBehavior) {} // TODO: Explore the performance impact of calculating this once // at the constructor for this and common ChartBehaviors. @override String get role => 'SelectNearest-${selectionModelType.toString()}}'; bool operator ==(Object other) { if (other is SelectNearest) { return (selectionModelType == other.selectionModelType) && (eventTrigger == other.eventTrigger) && (selectionMode == other.selectionMode) && (selectClosestSeries == other.selectClosestSeries) && (maximumDomainDistancePx == other.maximumDomainDistancePx); } else { return false; } } int get hashCode { int hashcode = selectionModelType.hashCode; hashcode = hashcode * 37 + eventTrigger.hashCode; hashcode = hashcode * 37 + selectionMode.hashCode; hashcode = hashcode * 37 + selectClosestSeries.hashCode; hashcode = hashcode * 37 + maximumDomainDistancePx.hashCode; return hashcode; } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_flutter/lib/src/behaviors/slider/slider.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math' show Rectangle; import 'package:charts_common/common.dart' as common show ChartBehavior, LayoutViewPaintOrder, RectSymbolRenderer, SelectionTrigger, Slider, SliderListenerCallback, SliderStyle, SymbolRenderer; import 'package:flutter/widgets.dart' show hashValues; import 'package:meta/meta.dart' show immutable; import '../chart_behavior.dart' show ChartBehavior, GestureType; /// Chart behavior that adds a slider widget to a chart. When the slider is /// dropped after drag, it will report its domain position and nearest datum /// value. This behavior only supports charts that use continuous scales. /// /// Input event types: /// tapAndDrag - Mouse/Touch on the handle and drag across the chart. /// pressHold - Mouse/Touch on the handle and drag across the chart instead of /// panning. /// longPressHold - Mouse/Touch for a while on the handle, then drag across /// the data. @immutable class Slider extends ChartBehavior { final Set desiredGestures; /// Type of input event for the slider. /// /// Input event types: /// tapAndDrag - Mouse/Touch on the handle and drag across the chart. /// pressHold - Mouse/Touch on the handle and drag across the chart instead /// of panning. /// longPressHold - Mouse/Touch for a while on the handle, then drag across /// the data. final common.SelectionTrigger eventTrigger; /// The order to paint slider on the canvas. /// /// The smaller number is drawn first. This value should be relative to /// LayoutPaintViewOrder.slider (e.g. LayoutViewPaintOrder.slider + 1). final int? layoutPaintOrder; /// Initial domain position of the slider, in domain units. final dynamic? initialDomainValue; /// Callback function that will be called when the position of the slider /// changes during a drag event. /// /// The callback will be given the current domain position of the slider. final common.SliderListenerCallback? onChangeCallback; /// Custom role ID for this slider final String? roleId; /// Whether or not the slider will snap onto the nearest datum (by domain /// distance) when dragged. final bool snapToDatum; /// Color and size styles for the slider. final common.SliderStyle? style; /// Renderer for the handle. Defaults to a rectangle. final common.SymbolRenderer? handleRenderer; Slider._internal( {required this.eventTrigger, this.onChangeCallback, this.initialDomainValue, this.roleId, required this.snapToDatum, this.style, this.handleRenderer, required this.desiredGestures, this.layoutPaintOrder}); /// Constructs a [Slider]. /// /// [eventTrigger] sets the type of gesture handled by the slider. /// /// [handleRenderer] draws a handle for the slider. Defaults to a rectangle. /// /// [initialDomainValue] sets the initial position of the slider in domain /// units. The default is the center of the chart. /// /// [onChangeCallback] will be called when the position of the slider /// changes during a drag event. /// /// [snapToDatum] configures the slider to snap snap onto the nearest datum /// (by domain distance) when dragged. By default, the slider can be /// positioned anywhere along the domain axis. /// /// [style] configures the color and sizing of the slider line and handle. /// /// [layoutPaintOrder] configures the order in which the behavior should be /// painted. This value should be relative to LayoutPaintViewOrder.slider. /// (e.g. LayoutViewPaintOrder.slider + 1). factory Slider( {common.SelectionTrigger? eventTrigger, common.SymbolRenderer? handleRenderer, dynamic? initialDomainValue, String? roleId, common.SliderListenerCallback? onChangeCallback, bool snapToDatum = false, common.SliderStyle? style, int layoutPaintOrder = common.LayoutViewPaintOrder.slider}) { eventTrigger ??= common.SelectionTrigger.tapAndDrag; handleRenderer ??= new common.RectSymbolRenderer(); // Default the handle size large enough to tap on a mobile device. style ??= new common.SliderStyle(handleSize: Rectangle(0, 0, 20, 30)); return new Slider._internal( eventTrigger: eventTrigger, handleRenderer: handleRenderer, initialDomainValue: initialDomainValue, onChangeCallback: onChangeCallback, roleId: roleId, snapToDatum: snapToDatum, style: style, desiredGestures: Slider._getDesiredGestures(eventTrigger), layoutPaintOrder: layoutPaintOrder); } static Set _getDesiredGestures( common.SelectionTrigger eventTrigger) { final desiredGestures = new Set(); switch (eventTrigger) { case common.SelectionTrigger.tapAndDrag: desiredGestures..add(GestureType.onTap)..add(GestureType.onDrag); break; case common.SelectionTrigger.pressHold: case common.SelectionTrigger.longPressHold: desiredGestures ..add(GestureType.onTap) ..add(GestureType.onLongPress) ..add(GestureType.onDrag); break; default: throw new ArgumentError( 'Slider does not support the event trigger ' + '"${eventTrigger}"'); } return desiredGestures; } @override common.Slider createCommonBehavior() => new common.Slider( eventTrigger: eventTrigger, handleRenderer: handleRenderer, initialDomainValue: initialDomainValue as D, onChangeCallback: onChangeCallback, roleId: roleId, snapToDatum: snapToDatum, style: style); @override void updateCommonBehavior(common.ChartBehavior commonBehavior) {} @override String get role => 'Slider-${eventTrigger.toString()}'; @override bool operator ==(Object o) { return o is Slider && eventTrigger == o.eventTrigger && handleRenderer == o.handleRenderer && initialDomainValue == o.initialDomainValue && onChangeCallback == o.onChangeCallback && roleId == o.roleId && snapToDatum == o.snapToDatum && style == o.style && layoutPaintOrder == o.layoutPaintOrder; } @override int get hashCode { return hashValues(eventTrigger, handleRenderer, initialDomainValue, roleId, snapToDatum, style, layoutPaintOrder); } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_flutter/lib/src/behaviors/sliding_viewport.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'package:charts_common/common.dart' as common show ChartBehavior, SelectionModelType, SlidingViewport; import 'package:meta/meta.dart' show immutable; import 'chart_behavior.dart' show ChartBehavior, GestureType; /// Chart behavior that centers the viewport on the selected domain. /// /// It is used in combination with SelectNearest to update the selection model /// and notify this behavior to update the viewport on selection change. /// /// This behavior can only be used on [CartesianChart]. @immutable class SlidingViewport extends ChartBehavior { final desiredGestures = new Set(); final common.SelectionModelType selectionModelType; SlidingViewport([this.selectionModelType = common.SelectionModelType.info]); @override common.SlidingViewport createCommonBehavior() => new common.SlidingViewport(selectionModelType); @override void updateCommonBehavior(common.ChartBehavior commonBehavior) {} @override String get role => 'slidingViewport-${selectionModelType.toString()}'; @override bool operator ==(Object o) => o is SlidingViewport && selectionModelType == o.selectionModelType; @override int get hashCode => selectionModelType.hashCode; } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_flutter/lib/src/behaviors/zoom/initial_hint_behavior.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'package:flutter/widgets.dart' show AnimationController; import 'package:charts_common/common.dart' as common show BaseChart, ChartBehavior, InitialHintBehavior; import 'package:meta/meta.dart' show immutable; import '../../base_chart_state.dart' show BaseChartState; import '../chart_behavior.dart' show ChartBehavior, ChartStateBehavior, GestureType; @immutable class InitialHintBehavior extends ChartBehavior { final desiredGestures = new Set(); final Duration? hintDuration; final double? maxHintTranslate; final double? maxHintScaleFactor; InitialHintBehavior( {this.hintDuration, this.maxHintTranslate, this.maxHintScaleFactor}); @override common.InitialHintBehavior createCommonBehavior() { final behavior = new FlutterInitialHintBehavior(); if (hintDuration != null) { behavior.hintDuration = hintDuration!; } if (maxHintTranslate != null) { behavior.maxHintTranslate = maxHintTranslate!; } if (maxHintScaleFactor != null) { behavior.maxHintScaleFactor = maxHintScaleFactor!; } return behavior; } @override void updateCommonBehavior(common.ChartBehavior commonBehavior) {} @override String get role => 'InitialHint'; bool operator ==(Object other) { return other is InitialHintBehavior && other.hintDuration == hintDuration; } int get hashCode { return hintDuration.hashCode; } } /// Adds a native animation controller required for [common.InitialHintBehavior] /// to function. class FlutterInitialHintBehavior extends common.InitialHintBehavior implements ChartStateBehavior { AnimationController? _hintAnimator; BaseChartState? _chartState; set chartState(BaseChartState chartState) { assert(chartState != null); _chartState = chartState; _hintAnimator = chartState.getAnimationController(this); _hintAnimator?.addListener(onHintTick); } @override void startHintAnimation() { super.startHintAnimation(); _hintAnimator! ..duration = hintDuration ..forward(from: 0.0); } @override void stopHintAnimation() { super.stopHintAnimation(); _hintAnimator?.stop(); // Hint animation occurs only on the first draw. The hint animator is no // longer needed after the hint animation stops and is removed. _chartState!.disposeAnimationController(this); _hintAnimator = null; } @override double get hintAnimationPercent => _hintAnimator!.value; bool _skippedFirstTick = true; @override void onHintTick() { // Skip the first tick on Flutter because the widget rebuild scheduled // during onAnimation fails on an assert on render object in the framework. if (_skippedFirstTick) { _skippedFirstTick = false; return; } super.onHintTick(); } @override removeFrom(common.BaseChart chart) { _chartState!.disposeAnimationController(this); _hintAnimator = null; super.removeFrom(chart); } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_flutter/lib/src/behaviors/zoom/pan_and_zoom_behavior.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'package:charts_common/common.dart' as common show ChartBehavior, PanAndZoomBehavior, PanningCompletedCallback; import 'package:meta/meta.dart' show immutable; import '../chart_behavior.dart' show ChartBehavior, GestureType; import 'pan_behavior.dart' show FlutterPanBehaviorMixin; @immutable class PanAndZoomBehavior extends ChartBehavior { final _desiredGestures = new Set.from([ GestureType.onDrag, ]); Set get desiredGestures => _desiredGestures; /// Optional callback that is called when pan / zoom is completed. /// /// When flinging this callback is called after the fling is completed. /// This is because panning is only completed when the flinging stops. final common.PanningCompletedCallback? panningCompletedCallback; PanAndZoomBehavior({this.panningCompletedCallback}); @override common.PanAndZoomBehavior createCommonBehavior() { return new FlutterPanAndZoomBehavior() ..panningCompletedCallback = panningCompletedCallback; } @override void updateCommonBehavior(common.ChartBehavior commonBehavior) {} @override String get role => 'PanAndZoom'; bool operator ==(Object other) { return other is PanAndZoomBehavior && other.panningCompletedCallback == panningCompletedCallback; } int get hashCode { return panningCompletedCallback.hashCode; } } /// Adds fling gesture support to [common.PanAndZoomBehavior], by way of /// [FlutterPanBehaviorMixin]. class FlutterPanAndZoomBehavior extends common.PanAndZoomBehavior with FlutterPanBehaviorMixin {} ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_flutter/lib/src/behaviors/zoom/pan_behavior.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math' show max, pow, Point; import 'dart:ui' show lerpDouble; import 'package:flutter/widgets.dart' show AnimationController; import 'package:charts_common/common.dart' as common show BaseChart, ChartBehavior, PanBehavior, PanningCompletedCallback; import 'package:meta/meta.dart' show immutable; import '../../base_chart_state.dart' show BaseChartState; import '../chart_behavior.dart' show ChartBehavior, ChartStateBehavior, GestureType; @immutable class PanBehavior extends ChartBehavior { final _desiredGestures = new Set.from([ GestureType.onDrag, ]); /// Optional callback that is called when panning is completed. /// /// When flinging this callback is called after the fling is completed. /// This is because panning is only completed when the flinging stops. final common.PanningCompletedCallback? panningCompletedCallback; PanBehavior({this.panningCompletedCallback}); Set get desiredGestures => _desiredGestures; @override common.PanBehavior createCommonBehavior() { return new FlutterPanBehavior() ..panningCompletedCallback = panningCompletedCallback; } @override void updateCommonBehavior(common.ChartBehavior commonBehavior) {} @override String get role => 'Pan'; bool operator ==(Object other) { return other is PanBehavior && other.panningCompletedCallback == panningCompletedCallback; } int get hashCode { return panningCompletedCallback.hashCode; } } /// Class extending [common.PanBehavior] with fling gesture support. class FlutterPanBehavior = common.PanBehavior with FlutterPanBehaviorMixin; /// Mixin that adds fling gesture support to [common.PanBehavior] or subclasses /// thereof. mixin FlutterPanBehaviorMixin on common.PanBehavior implements ChartStateBehavior { late BaseChartState _chartState; set chartState(BaseChartState chartState) { assert(chartState != null); _chartState = chartState; _flingAnimator = chartState.getAnimationController(this); _flingAnimator?.addListener(_onFlingTick); } AnimationController? _flingAnimator; double _flingAnimationInitialTranslatePx = 0; double _flingAnimationTargetTranslatePx = 0; bool _isFlinging = false; static const flingDistanceMultiplier = 0.15; static const flingDeceleratorFactor = 1.0; static const flingDurationMultiplier = 0.15; static const minimumFlingVelocity = 300.0; @override removeFrom(common.BaseChart chart) { stopFlingAnimation(); _chartState.disposeAnimationController(this); _flingAnimator = null; super.removeFrom(chart); } @override bool onTapTest(Point chartPoint) { super.onTapTest(chartPoint); stopFlingAnimation(); return true; } @override bool onDragEnd( Point localPosition, double scale, double pixelsPerSec) { if (isPanning) { // Ignore slow drag gestures to avoid jitter. if (pixelsPerSec.abs() < minimumFlingVelocity) { onPanEnd(); return true; } _startFling(pixelsPerSec); } return super.onDragEnd(localPosition, scale, pixelsPerSec); } /// Starts a 'fling' in the direction and speed given by [pixelsPerSec]. void _startFling(double pixelsPerSec) { final domainAxis = chart!.domainAxis; _flingAnimationInitialTranslatePx = domainAxis!.viewportTranslatePx; _flingAnimationTargetTranslatePx = _flingAnimationInitialTranslatePx + pixelsPerSec * flingDistanceMultiplier; final flingDuration = new Duration( milliseconds: max(200, (pixelsPerSec * flingDurationMultiplier).abs().round())); _flingAnimator! ..duration = flingDuration ..forward(from: 0.0); _isFlinging = true; } /// Decelerates a fling event. double _decelerate(double value) => flingDeceleratorFactor == 1.0 ? 1.0 - (1.0 - value) * (1.0 - value) : 1.0 - pow(1.0 - value, 2 * flingDeceleratorFactor); /// Updates the chart axis state on each tick of the [AnimationController]. void _onFlingTick() { if (!_isFlinging) { return; } final percent = _flingAnimator!.value; final deceleratedPercent = _decelerate(percent); final translation = lerpDouble(_flingAnimationInitialTranslatePx, _flingAnimationTargetTranslatePx, deceleratedPercent); final domainAxis = chart!.domainAxis!; domainAxis.setViewportSettings( domainAxis.viewportScalingFactor, translation!, drawAreaWidth: chart!.drawAreaBounds.width); if (percent >= 1.0) { stopFlingAnimation(); onPanEnd(); chart!.redraw(); } else { chart!.redraw(skipAnimation: true, skipLayout: true); } } /// Stops any current fling animations that may be executing. void stopFlingAnimation() { if (_isFlinging) { _isFlinging = false; _flingAnimator?.stop(); } } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_flutter/lib/src/canvas/circle_sector_painter.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math' show cos, pi, sin, Point; import 'package:flutter/material.dart'; import 'package:charts_common/common.dart' as common show Color; /// Draws a sector of a circle, with an optional hole in the center. class CircleSectorPainter { /// Draws a sector of a circle, with an optional hole in the center. /// /// [center] The x, y coordinates of the circle's center. /// [radius] The radius of the circle. /// [innerRadius] Optional radius of a hole in the center of the circle that /// should not be filled in as part of the sector. /// [startAngle] The angle at which the arc starts, measured clockwise from /// the positive x axis and expressed in radians. /// [endAngle] The angle at which the arc ends, measured clockwise from the /// positive x axis and expressed in radians. /// [fill] Fill color for the sector. /// [stroke] Stroke color of the arc and radius lines. /// [strokeWidthPx] Stroke width of the arc and radius lines. static void draw({ required Canvas canvas, required Paint paint, required Point center, required double radius, required double innerRadius, required double startAngle, required double endAngle, common.Color? fill, }) { paint.color = new Color.fromARGB(fill!.a, fill.r, fill.g, fill.b); paint.style = PaintingStyle.fill; final innerRadiusStartPoint = new Point( innerRadius * cos(startAngle) + center.x, innerRadius * sin(startAngle) + center.y); final innerRadiusEndPoint = new Point( innerRadius * cos(endAngle) + center.x, innerRadius * sin(endAngle) + center.y); final radiusStartPoint = new Point( radius * cos(startAngle) + center.x, radius * sin(startAngle) + center.y); final centerOffset = new Offset(center.x.toDouble(), center.y.toDouble()); final isFullCircle = startAngle != null && endAngle != null && endAngle - startAngle == 2 * pi; final midpointAngle = (endAngle + startAngle) / 2; final path = new Path() ..moveTo(innerRadiusStartPoint.x, innerRadiusStartPoint.y); path.lineTo(radiusStartPoint.x, radiusStartPoint.y); // For full circles, draw the arc in two parts. if (isFullCircle) { path.arcTo(new Rect.fromCircle(center: centerOffset, radius: radius), startAngle, midpointAngle - startAngle, true); path.arcTo(new Rect.fromCircle(center: centerOffset, radius: radius), midpointAngle, endAngle - midpointAngle, true); } else { path.arcTo(new Rect.fromCircle(center: centerOffset, radius: radius), startAngle, endAngle - startAngle, true); } path.lineTo(innerRadiusEndPoint.x, innerRadiusEndPoint.y); // For full circles, draw the arc in two parts. if (isFullCircle) { path.arcTo(new Rect.fromCircle(center: centerOffset, radius: innerRadius), endAngle, midpointAngle - endAngle, true); path.arcTo(new Rect.fromCircle(center: centerOffset, radius: innerRadius), midpointAngle, startAngle - midpointAngle, true); } else { path.arcTo(new Rect.fromCircle(center: centerOffset, radius: innerRadius), endAngle, startAngle - endAngle, true); } // Drawing two copies of this line segment, before and after the arcs, // ensures that the path actually gets closed correctly. path.lineTo(radiusStartPoint.x, radiusStartPoint.y); canvas.drawPath(path, paint); } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_flutter/lib/src/canvas/line_painter.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:ui' as ui show Shader; import 'dart:math' show Point, Rectangle; import 'package:flutter/material.dart'; import 'package:charts_common/common.dart' as common show Color; /// Draws a simple line. /// /// Lines may be styled with dash patterns similar to stroke-dasharray in SVG /// path elements. Dash patterns are currently only supported between vertical /// or horizontal line segments at this time. class LinePainter { /// Draws a simple line. /// /// [dashPattern] controls the pattern of dashes and gaps in a line. It is a /// list of lengths of alternating dashes and gaps. The rendering is similar /// to stroke-dasharray in SVG path elements. An odd number of values in the /// pattern will be repeated to derive an even number of values. "1,2,3" is /// equivalent to "1,2,3,1,2,3." static void draw( {required Canvas canvas, required Paint paint, required List points, Rectangle? clipBounds, common.Color? fill, common.Color? stroke, bool? roundEndCaps, double? strokeWidthPx, List? dashPattern, ui.Shader? shader}) { if (points.isEmpty) { return; } // Apply clip bounds as a clip region. if (clipBounds != null) { canvas ..save() ..clipRect(new Rect.fromLTWH( clipBounds.left.toDouble(), clipBounds.top.toDouble(), clipBounds.width.toDouble(), clipBounds.height.toDouble())); } paint.color = new Color.fromARGB(stroke!.a, stroke.r, stroke.g, stroke.b); if (shader != null) { paint.shader = shader; } // If the line has a single point, draw a circle. if (points.length == 1) { final point = points.first; paint.style = PaintingStyle.fill; canvas.drawCircle(new Offset(point.x.toDouble(), point.y.toDouble()), strokeWidthPx ?? 0, paint); } else { if (strokeWidthPx != null) { paint.strokeWidth = strokeWidthPx; } paint.strokeJoin = StrokeJoin.round; paint.style = PaintingStyle.stroke; if (dashPattern == null || dashPattern.isEmpty) { if (roundEndCaps == true) { paint.strokeCap = StrokeCap.round; } _drawSolidLine(canvas, paint, points); } else { _drawDashedLine(canvas, paint, points, dashPattern); } } if (clipBounds != null) { canvas.restore(); } } /// Draws solid lines between each point. static void _drawSolidLine(Canvas canvas, Paint paint, List points) { // TODO: Extract a native line component which constructs the // appropriate underlying data structures to avoid conversion. final path = new Path() ..moveTo(points.first.x.toDouble(), points.first.y.toDouble()); for (var point in points) { path.lineTo(point.x.toDouble(), point.y.toDouble()); } canvas.drawPath(path, paint); } /// Draws dashed lines lines between each point. static void _drawDashedLine( Canvas canvas, Paint paint, List points, List dashPattern) { final localDashPattern = new List.from(dashPattern); // If an odd number of parts are defined, repeat the pattern to get an even // number. if (dashPattern.length % 2 == 1) { localDashPattern.addAll(dashPattern); } // Stores the previous point in the series. var previousSeriesPoint = _getOffset(points.first); var remainder = 0; var solid = true; var dashPatternIndex = 0; // Gets the next segment in the dash pattern, looping back to the // beginning once the end has been reached. var getNextDashPatternSegment = () { final dashSegment = localDashPattern[dashPatternIndex]; dashPatternIndex = (dashPatternIndex + 1) % localDashPattern.length; return dashSegment; }; // Array of points that is used to draw a connecting path when only a // partial dash pattern segment can be drawn in the remaining length of a // line segment (between two defined points in the shape). var remainderPoints; // Draw the path through all the rest of the points in the series. for (var pointIndex = 1; pointIndex < points.length; pointIndex++) { // Stores the current point in the series. final seriesPoint = _getOffset(points[pointIndex]); if (previousSeriesPoint == seriesPoint) { // Bypass dash pattern handling if the points are the same. } else { // Stores the previous point along the current series line segment where // we rendered a dash (or left a gap). var previousPoint = previousSeriesPoint; var d = _getOffsetDistance(previousSeriesPoint, seriesPoint); while (d > 0) { var dashSegment = remainder > 0 ? remainder : getNextDashPatternSegment(); remainder = 0; // Create a unit vector in the direction from previous to next point. final v = seriesPoint - previousPoint; final u = new Offset(v.dx / v.distance, v.dy / v.distance); // If the remaining distance is less than the length of the dash // pattern segment, then cut off the pattern segment for this portion // of the overall line. final distance = d < dashSegment ? d : dashSegment.toDouble(); // Compute a vector representing the length of dash pattern segment to // be drawn. final nextPoint = previousPoint + (u * distance); // If we are in a solid portion of the dash pattern, draw a line. // Else, move on. if (solid) { if (remainderPoints != null) { // If we had a partial un-drawn dash from the previous point along // the line, draw a path that includes it and the end of the dash // pattern segment in the current line segment. remainderPoints.add(new Offset(nextPoint.dx, nextPoint.dy)); final path = new Path() ..moveTo(remainderPoints.first.dx, remainderPoints.first.dy); for (var p in remainderPoints) { path.lineTo(p.dx, p.dy); } canvas.drawPath(path, paint); remainderPoints = null; } else { if (d < dashSegment && pointIndex < points.length - 1) { // If the remaining distance d is too small to fit this dash, // and we have more points in the line, save off a series of // remainder points so that we can draw a path segment moving in // the direction of the next point. // // Note that we don't need to save anything off for the "blank" // portions of the pattern because we still take the remaining // distance into account before starting the next dash in the // next line segment. remainderPoints = [ new Offset(previousPoint.dx, previousPoint.dy), new Offset(nextPoint.dx, nextPoint.dy) ]; } else { // Otherwise, draw a simple line segment for this dash. canvas.drawLine(previousPoint, nextPoint, paint); } } } solid = !solid; previousPoint = nextPoint; d = d - dashSegment; } // Save off the remaining distance so that we can continue the dash (or // gap) into the next line segment. remainder = -d.round(); // If we have a remaining un-drawn distance for the current dash (or // gap), revert the last change to "solid" so that we will continue // either drawing a dash or leaving a gap. if (remainder > 0) { solid = !solid; } } previousSeriesPoint = seriesPoint; } } /// Converts a [Point] into an [Offset]. static Offset _getOffset(Point point) => new Offset(point.x.toDouble(), point.y.toDouble()); /// Computes the distance between two [Offset]s, as if they were [Point]s. static num _getOffsetDistance(Offset o1, Offset o2) { final p1 = new Point(o1.dx, o1.dy); final p2 = new Point(o2.dx, o2.dy); return p1.distanceTo(p2); } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_flutter/lib/src/canvas/pie_painter.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math' show cos, sin, Point; import 'package:flutter/material.dart'; import 'package:charts_common/common.dart' as common show CanvasPie; import 'circle_sector_painter.dart' show CircleSectorPainter; /// Draws a pie chart, with an optional hole in the center. class PiePainter { /// Draws a pie chart, with an optional hole in the center. static void draw(Canvas canvas, Paint paint, common.CanvasPie canvasPie) { final center = canvasPie.center; final radius = canvasPie.radius; final innerRadius = canvasPie.innerRadius; for (var slice in canvasPie.slices) { CircleSectorPainter.draw( canvas: canvas, paint: paint, center: center, radius: radius, innerRadius: innerRadius, startAngle: slice.startAngle, endAngle: slice.endAngle, fill: slice.fill); } // Draw stroke lines between pie slices. This is done after the slices are // drawn to ensure that they appear on top. if (canvasPie.stroke != null && canvasPie.strokeWidthPx != null && canvasPie.slices.length > 1) { paint.color = new Color.fromARGB(canvasPie.stroke!.a, canvasPie.stroke!.r, canvasPie.stroke!.g, canvasPie.stroke!.b); paint.strokeWidth = canvasPie.strokeWidthPx; paint.strokeJoin = StrokeJoin.bevel; paint.style = PaintingStyle.stroke; final path = new Path(); for (var slice in canvasPie.slices) { final innerRadiusStartPoint = new Point( innerRadius * cos(slice.startAngle) + center.x, innerRadius * sin(slice.startAngle) + center.y); final innerRadiusEndPoint = new Point( innerRadius * cos(slice.endAngle) + center.x, innerRadius * sin(slice.endAngle) + center.y); final radiusStartPoint = new Point( radius * cos(slice.startAngle) + center.x, radius * sin(slice.startAngle) + center.y); final radiusEndPoint = new Point( radius * cos(slice.endAngle) + center.x, radius * sin(slice.endAngle) + center.y); path.moveTo(innerRadiusStartPoint.x, innerRadiusStartPoint.y); path.lineTo(radiusStartPoint.x, radiusStartPoint.y); path.moveTo(innerRadiusEndPoint.x, innerRadiusEndPoint.y); path.lineTo(radiusEndPoint.x, radiusEndPoint.y); } canvas.drawPath(path, paint); } } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_flutter/lib/src/canvas/point_painter.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math' show Point; import 'package:flutter/material.dart'; import 'package:charts_common/common.dart' as common show Color; /// Draws a simple point. /// /// TODO: Support for more shapes than circles? class PointPainter { static void draw( {required Canvas canvas, required Paint paint, required Point point, required double radius, common.Color? fill, common.Color? stroke, double? strokeWidthPx}) { if (point == null) { return; } if (fill != null) { paint.color = new Color.fromARGB(fill.a, fill.r, fill.g, fill.b); paint.style = PaintingStyle.fill; canvas.drawCircle( new Offset(point.x.toDouble(), point.y.toDouble()), radius, paint); } // [Canvas.drawCircle] does not support drawing a circle with both a fill // and a stroke at this time. Use a separate circle for the stroke. if (stroke != null && strokeWidthPx != null && strokeWidthPx > 0.0) { paint.color = new Color.fromARGB(stroke.a, stroke.r, stroke.g, stroke.b); paint.strokeWidth = strokeWidthPx; paint.strokeJoin = StrokeJoin.bevel; paint.style = PaintingStyle.stroke; canvas.drawCircle( new Offset(point.x.toDouble(), point.y.toDouble()), radius, paint); } } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_flutter/lib/src/canvas/polygon_painter.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math' show Point, Rectangle; import 'package:flutter/material.dart'; import 'package:charts_common/common.dart' as common show Color; /// Draws a simple line. /// /// Lines may be styled with dash patterns similar to stroke-dasharray in SVG /// path elements. Dash patterns are currently only supported between vertical /// or horizontal line segments at this time. class PolygonPainter { /// Draws a simple line. /// /// [dashPattern] controls the pattern of dashes and gaps in a line. It is a /// list of lengths of alternating dashes and gaps. The rendering is similar /// to stroke-dasharray in SVG path elements. An odd number of values in the /// pattern will be repeated to derive an even number of values. "1,2,3" is /// equivalent to "1,2,3,1,2,3." static void draw( {required Canvas canvas, required Paint paint, required List points, Rectangle? clipBounds, common.Color? fill, common.Color? stroke, double? strokeWidthPx}) { if (points.isEmpty) { return; } // Apply clip bounds as a clip region. if (clipBounds != null) { canvas ..save() ..clipRect(new Rect.fromLTWH( clipBounds.left.toDouble(), clipBounds.top.toDouble(), clipBounds.width.toDouble(), clipBounds.height.toDouble())); } final strokeColor = stroke != null ? new Color.fromARGB(stroke.a, stroke.r, stroke.g, stroke.b) : null; final fillColor = fill != null ? new Color.fromARGB(fill.a, fill.r, fill.g, fill.b) : null; // If the line has a single point, draw a circle. if (points.length == 1) { final point = points.first; if (fillColor != null) { paint.color = fillColor; } paint.style = PaintingStyle.fill; canvas.drawCircle(new Offset(point.x.toDouble(), point.y.toDouble()), strokeWidthPx!, paint); } else { if (strokeColor != null && strokeWidthPx != null) { paint.strokeWidth = strokeWidthPx; paint.strokeJoin = StrokeJoin.bevel; paint.style = PaintingStyle.stroke; } if (fillColor != null) { paint.color = fillColor; paint.style = PaintingStyle.fill; } final path = new Path() ..moveTo(points.first.x.toDouble(), points.first.y.toDouble()); for (var point in points) { path.lineTo(point.x.toDouble(), point.y.toDouble()); } canvas.drawPath(path, paint); } if (clipBounds != null) { canvas.restore(); } } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_flutter/lib/src/cartesian_chart.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:collection' show LinkedHashMap; import 'package:meta/meta.dart' show immutable, protected; import 'package:charts_common/common.dart' as common show AxisSpec, BaseChart, CartesianChart, NumericAxis, NumericAxisSpec, RTLSpec, Series, SeriesRendererConfig; import 'base_chart_state.dart' show BaseChartState; import 'behaviors/chart_behavior.dart' show ChartBehavior; import 'base_chart.dart' show BaseChart, LayoutConfig; import 'selection_model_config.dart' show SelectionModelConfig; import 'user_managed_state.dart' show UserManagedState; @immutable abstract class CartesianChart extends BaseChart { final common.AxisSpec? domainAxis; final common.NumericAxisSpec? primaryMeasureAxis; final common.NumericAxisSpec? secondaryMeasureAxis; final LinkedHashMap? disjointMeasureAxes; final bool? flipVerticalAxis; CartesianChart( List> seriesList, { bool? animate, Duration? animationDuration, this.domainAxis, this.primaryMeasureAxis, this.secondaryMeasureAxis, this.disjointMeasureAxes, common.SeriesRendererConfig? defaultRenderer, List>? customSeriesRenderers, List>? behaviors, List>? selectionModels, common.RTLSpec? rtlSpec, bool defaultInteractions = true, LayoutConfig? layoutConfig, UserManagedState? userManagedState, this.flipVerticalAxis, }) : super( seriesList, animate: animate, animationDuration: animationDuration, defaultRenderer: defaultRenderer, customSeriesRenderers: customSeriesRenderers, behaviors: behaviors, selectionModels: selectionModels, rtlSpec: rtlSpec, defaultInteractions: defaultInteractions, layoutConfig: layoutConfig, userManagedState: userManagedState, ); @override void updateCommonChart(common.BaseChart baseChart, BaseChart? oldWidget, BaseChartState chartState) { super.updateCommonChart(baseChart, oldWidget, chartState); final prev = oldWidget as CartesianChart?; final chart = baseChart as common.CartesianChart; if (flipVerticalAxis != null) { chart.flipVerticalAxisOutput = flipVerticalAxis!; } if (domainAxis != null && domainAxis != prev?.domainAxis) { chart.domainAxisSpec = domainAxis!; chartState.markChartDirty(); } if (primaryMeasureAxis != prev?.primaryMeasureAxis) { chart.primaryMeasureAxisSpec = primaryMeasureAxis; chartState.markChartDirty(); } if (secondaryMeasureAxis != prev?.secondaryMeasureAxis) { chart.secondaryMeasureAxisSpec = secondaryMeasureAxis; chartState.markChartDirty(); } if (disjointMeasureAxes != prev?.disjointMeasureAxes) { chart.disjointMeasureAxisSpecs = disjointMeasureAxes; chartState.markChartDirty(); } } @protected LinkedHashMap? createDisjointMeasureAxes() { if (disjointMeasureAxes != null) { final disjointAxes = new LinkedHashMap(); disjointMeasureAxes! .forEach((String axisId, common.NumericAxisSpec axisSpec) { disjointAxes[axisId] = axisSpec.createAxis(); }); return disjointAxes; } else { return null; } } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_flutter/lib/src/chart_canvas.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:ui' as ui show Gradient, Shader; import 'dart:math' show Point, Rectangle, max; import 'package:charts_common/common.dart' as common show BlendMode, ChartCanvas, CanvasBarStack, CanvasPie, Color, FillPatternType, GraphicsFactory, StyleFactory, TextElement, TextDirection; import 'package:flutter/material.dart'; import 'text_element.dart' show TextElement; import 'canvas/circle_sector_painter.dart' show CircleSectorPainter; import 'canvas/line_painter.dart' show LinePainter; import 'canvas/pie_painter.dart' show PiePainter; import 'canvas/point_painter.dart' show PointPainter; import 'canvas/polygon_painter.dart' show PolygonPainter; class ChartCanvas implements common.ChartCanvas { /// Pixels to allow to overdraw above the draw area that fades to transparent. static const double rect_top_gradient_pixels = 5; final Canvas canvas; final common.GraphicsFactory graphicsFactory; final _paint = new Paint(); ChartCanvas(this.canvas, this.graphicsFactory); @override void drawCircleSector(Point center, double radius, double innerRadius, double startAngle, double endAngle, {common.Color? fill, common.Color? stroke, double? strokeWidthPx}) { CircleSectorPainter.draw( canvas: canvas, paint: _paint, center: center, radius: radius, innerRadius: innerRadius, startAngle: startAngle, endAngle: endAngle, fill: fill, ); } @override void drawLine( {required List points, Rectangle? clipBounds, common.Color? fill, common.Color? stroke, bool? roundEndCaps, double? strokeWidthPx, List? dashPattern}) { LinePainter.draw( canvas: canvas, paint: _paint, points: points, clipBounds: clipBounds, fill: fill, stroke: stroke, roundEndCaps: roundEndCaps, strokeWidthPx: strokeWidthPx, dashPattern: dashPattern); } @override void drawPie(common.CanvasPie canvasPie) { PiePainter.draw(canvas, _paint, canvasPie); } @override void drawPoint( {required Point point, required double radius, common.Color? fill, common.Color? stroke, double? strokeWidthPx, common.BlendMode? blendMode}) { PointPainter.draw( canvas: canvas, paint: _paint, point: point, radius: radius, fill: fill, stroke: stroke, strokeWidthPx: strokeWidthPx); } @override void drawPolygon( {required List points, Rectangle? clipBounds, common.Color? fill, common.Color? stroke, double? strokeWidthPx}) { PolygonPainter.draw( canvas: canvas, paint: _paint, points: points, clipBounds: clipBounds, fill: fill, stroke: stroke, strokeWidthPx: strokeWidthPx); } /// Creates a bottom to top gradient that transitions [fill] to transparent. ui.Gradient _createHintGradient(double left, double top, common.Color fill) { return new ui.Gradient.linear( new Offset(left, top), new Offset(left, top - rect_top_gradient_pixels), [ new Color.fromARGB(fill.a, fill.r, fill.g, fill.b), new Color.fromARGB(0, fill.r, fill.g, fill.b) ], ); } @override void drawRect(Rectangle bounds, {common.Color? fill, common.FillPatternType? pattern, common.Color? stroke, double? strokeWidthPx, Rectangle? drawAreaBounds}) { final drawStroke = (strokeWidthPx != null && strokeWidthPx > 0.0 && stroke != null); final strokeWidthOffset = (drawStroke ? strokeWidthPx : 0); // Factor out stroke width, if a stroke is enabled. final fillRectBounds = new Rectangle( bounds.left + strokeWidthOffset! / 2, bounds.top + strokeWidthOffset / 2, bounds.width - strokeWidthOffset, bounds.height - strokeWidthOffset); switch (pattern) { case common.FillPatternType.forwardHatch: _drawForwardHatchPattern(fillRectBounds, canvas, fill: fill!, drawAreaBounds: drawAreaBounds); break; case common.FillPatternType.solid: default: // Use separate rect for drawing stroke _paint.color = new Color.fromARGB(fill!.a, fill.r, fill.g, fill.b); _paint.style = PaintingStyle.fill; // Apply a gradient to the top [rect_top_gradient_pixels] to transparent // if the rectangle is higher than the [drawAreaBounds] top. if (drawAreaBounds != null && bounds.top < drawAreaBounds.top) { _paint.shader = _createHintGradient(drawAreaBounds.left.toDouble(), drawAreaBounds.top.toDouble(), fill); } canvas.drawRect(_getRect(fillRectBounds), _paint); break; } // [Canvas.drawRect] does not support drawing a rectangle with both a fill // and a stroke at this time. Use a separate rect for the stroke. if (drawStroke) { _paint.color = new Color.fromARGB(stroke!.a, stroke.r, stroke.g, stroke.b); // Set shader to null if no draw area bounds so it can use the color // instead. _paint.shader = drawAreaBounds != null ? _createHintGradient(drawAreaBounds.left.toDouble(), drawAreaBounds.top.toDouble(), stroke) : null; _paint.strokeJoin = StrokeJoin.round; _paint.strokeWidth = strokeWidthPx!; _paint.style = PaintingStyle.stroke; canvas.drawRect(_getRect(bounds), _paint); } // Reset the shader. _paint.shader = null; } @override void drawRRect(Rectangle bounds, {common.Color? fill, common.Color? stroke, common.Color? patternColor, common.FillPatternType? fillPattern, double? patternStrokeWidthPx, double? strokeWidthPx, num? radius, bool roundTopLeft = false, bool roundTopRight = false, bool roundBottomLeft = false, bool roundBottomRight = false}) { // Use separate rect for drawing stroke _paint.color = new Color.fromARGB(fill!.a, fill.r, fill.g, fill.b); _paint.style = PaintingStyle.fill; canvas.drawRRect( _getRRect(bounds, radius: radius?.toDouble() ?? 0.0, roundTopLeft: roundTopLeft, roundTopRight: roundTopRight, roundBottomLeft: roundBottomLeft, roundBottomRight: roundBottomRight), _paint); } @override void drawBarStack(common.CanvasBarStack barStack, {Rectangle? drawAreaBounds}) { // only clip if rounded rect. // Clip a rounded rect for the whole region if rounded bars. final roundedCorners = barStack.radius != null && 0 < barStack.radius!; if (roundedCorners) { canvas ..save() ..clipRRect(_getRRect( barStack.fullStackRect, radius: barStack.radius!.toDouble(), roundTopLeft: barStack.roundTopLeft, roundTopRight: barStack.roundTopRight, roundBottomLeft: barStack.roundBottomLeft, roundBottomRight: barStack.roundBottomRight, )); } // Draw each bar. for (var barIndex = 0; barIndex < barStack.segments.length; barIndex++) { // TODO: Add configuration for hiding stack line. // TODO: Don't draw stroke on bottom of bars. final segment = barStack.segments[barIndex]; drawRect(segment.bounds, fill: segment.fill, pattern: segment.pattern, stroke: segment.stroke, strokeWidthPx: segment.strokeWidthPx, drawAreaBounds: drawAreaBounds); } if (roundedCorners) { canvas.restore(); } } @override void drawText(common.TextElement textElement, int offsetX, int offsetY, {double rotation = 0.0}) { // Must be Flutter TextElement. assert(textElement is TextElement); final flutterTextElement = textElement as TextElement; final textDirection = flutterTextElement.textDirection; final measurement = flutterTextElement.measurement; if (rotation != 0) { // TODO: Remove once textAnchor works. if (textDirection == common.TextDirection.rtl) { offsetY += measurement.horizontalSliceWidth.toInt(); } offsetX -= flutterTextElement.verticalFontShift; canvas.save(); canvas.translate(offsetX.toDouble(), offsetY.toDouble()); canvas.rotate(rotation); textElement.textPainter!.paint(canvas, new Offset(0.0, 0.0)); canvas.restore(); } else { // TODO: Remove once textAnchor works. if (textDirection == common.TextDirection.rtl) { offsetX -= measurement.horizontalSliceWidth.toInt(); } // Account for missing center alignment. if (textDirection == common.TextDirection.center) { offsetX -= (measurement.horizontalSliceWidth / 2).ceil(); } offsetY -= flutterTextElement.verticalFontShift; textElement.textPainter! .paint(canvas, new Offset(offsetX.toDouble(), offsetY.toDouble())); } } @override void setClipBounds(Rectangle clipBounds) { canvas ..save() ..clipRect(_getRect(clipBounds)); } @override void resetClipBounds() { canvas.restore(); } /// Convert dart:math [Rectangle] to Flutter [Rect]. Rect _getRect(Rectangle rectangle) { return new Rect.fromLTWH( rectangle.left.toDouble(), rectangle.top.toDouble(), rectangle.width.toDouble(), rectangle.height.toDouble()); } /// Convert dart:math [Rectangle] and to Flutter [RRect]. RRect _getRRect( Rectangle rectangle, { double radius = 0, bool roundTopLeft = false, bool roundTopRight = false, bool roundBottomLeft = false, bool roundBottomRight = false, }) { final cornerRadius = radius == 0 ? Radius.zero : new Radius.circular(radius); return new RRect.fromLTRBAndCorners( rectangle.left.toDouble(), rectangle.top.toDouble(), rectangle.right.toDouble(), rectangle.bottom.toDouble(), topLeft: roundTopLeft ? cornerRadius : Radius.zero, topRight: roundTopRight ? cornerRadius : Radius.zero, bottomLeft: roundBottomLeft ? cornerRadius : Radius.zero, bottomRight: roundBottomRight ? cornerRadius : Radius.zero); } /// Draws a forward hatch pattern in the given bounds. _drawForwardHatchPattern( Rectangle bounds, Canvas canvas, { common.Color? background, common.Color? fill, double fillWidthPx = 4.0, Rectangle? drawAreaBounds, }) { background ??= common.StyleFactory.style.white; fill ??= common.StyleFactory.style.black; // Fill in the shape with a solid background color. _paint.color = new Color.fromARGB( background.a, background.r, background.g, background.b); _paint.style = PaintingStyle.fill; // Apply a gradient the background if bounds exceed the draw area. if (drawAreaBounds != null && bounds.top < drawAreaBounds.top) { _paint.shader = _createHintGradient(drawAreaBounds.left.toDouble(), drawAreaBounds.top.toDouble(), background); } canvas.drawRect(_getRect(bounds), _paint); // As a simplification, we will treat the bounds as a large square and fill // it up with lines from the bottom-left corner to the top-right corner. // Get the longer side of the bounds here for the size of this square. final size = max(bounds.width, bounds.height); final x0 = bounds.left + size + fillWidthPx; final x1 = bounds.left - fillWidthPx; final y0 = bounds.bottom - size - fillWidthPx; final y1 = bounds.bottom + fillWidthPx; final offset = 8; final isVertical = bounds.height >= bounds.width; // The "first" line segment will be drawn from the bottom left corner of the // bounds, up and towards the right. Start the loop N iterations "back" to // draw partial line segments beneath (or to the left) of this segment, // where N is the number of offsets that fit inside the smaller dimension of // the bounds. final smallSide = isVertical ? bounds.width : bounds.height; final start = -(smallSide / offset).round() * offset; // Keep going until we reach the top or right of the bounds, depending on // whether the rectangle is oriented vertically or horizontally. final end = size + offset; // Create gradient for line painter if top bounds exceeded. ui.Shader? lineShader; if (drawAreaBounds != null && bounds.top < drawAreaBounds.top) { lineShader = _createHintGradient( drawAreaBounds.left.toDouble(), drawAreaBounds.top.toDouble(), fill); } for (int i = start; i < end; i = i + offset) { // For vertical bounds, we need to draw lines from top to bottom. For // bounds, we need to draw lines from left to right. final modifier = isVertical ? -1 * i : i; // Draw a line segment in the bottom right corner of the pattern. LinePainter.draw( canvas: canvas, paint: _paint, points: [ new Point(x0 + modifier, y0), new Point(x1 + modifier, y1), ], stroke: fill, strokeWidthPx: fillWidthPx, shader: lineShader); } } @override set drawingView(String? viewName) {} } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_flutter/lib/src/chart_container.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'package:charts_common/common.dart' as common show A11yNode, AxisDirection, BaseChart, ChartContext, DateTimeFactory, LocalDateTimeFactory, ProxyGestureListener, RTLSpec, SelectionModelType, Series, Performance; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/scheduler.dart'; import 'package:logging/logging.dart'; import 'chart_canvas.dart' show ChartCanvas; import 'base_chart.dart' show BaseChart; import 'base_chart_state.dart' show BaseChartState; import 'graphics_factory.dart' show GraphicsFactory; import 'time_series_chart.dart' show TimeSeriesChart; import 'user_managed_state.dart' show UserManagedState; /// Widget that inflates to a [CustomPaint] that implements common [ChartContext]. class ChartContainer extends CustomPaint { final BaseChart chartWidget; final BaseChart? oldChartWidget; final BaseChartState chartState; final double animationValue; final bool rtl; final common.RTLSpec? rtlSpec; final UserManagedState? userManagedState; ChartContainer( {this.oldChartWidget, required this.chartWidget, required this.chartState, required this.animationValue, required this.rtl, this.rtlSpec, this.userManagedState}); @override RenderCustomPaint createRenderObject(BuildContext context) { return new ChartContainerRenderObject()..reconfigure(this, context); } @override void updateRenderObject( BuildContext context, ChartContainerRenderObject renderObject) { renderObject.reconfigure(this, context); } } /// [RenderCustomPaint] that implements common [ChartContext]. class ChartContainerRenderObject extends RenderCustomPaint implements common.ChartContext { common.BaseChart? _chart; List>? _seriesList; late BaseChartState _chartState; bool _chartContainerIsRtl = false; common.RTLSpec? _rtlSpec; common.DateTimeFactory? _dateTimeFactory; bool _exploreMode = false; List? _a11yNodes; final Logger _log = new Logger('charts_flutter.charts_container'); /// Keeps the last time the configuration was changed and chart draw on the /// common chart is called. /// /// An assert uses this value to check if the configuration changes more /// frequently than a threshold. This is to notify developers of something /// wrong in the configuration of their charts if it keeps changes (usually /// due to equality checks not being implemented and when a new object is /// created inside a new chart widget, a change is detected even if nothing /// has changed). DateTime? _lastConfigurationChangeTime; /// The minimum time required before the next configuration change. static const configurationChangeThresholdMs = 500; void reconfigure(ChartContainer config, BuildContext context) { _chartState = config.chartState; _dateTimeFactory = (config.chartWidget is TimeSeriesChart) ? (config.chartWidget as TimeSeriesChart).dateTimeFactory : null; _dateTimeFactory ??= new common.LocalDateTimeFactory(); if (_chart == null) { common.Performance.time('chartsCreate'); _chart = config.chartWidget.createCommonChart(_chartState); _chart!.init(this, new GraphicsFactory(context)); common.Performance.timeEnd('chartsCreate'); } common.Performance.time('chartsConfig'); config.chartWidget .updateCommonChart(_chart!, config.oldChartWidget, _chartState); _rtlSpec = config.rtlSpec; _chartContainerIsRtl = config.rtl; common.Performance.timeEnd('chartsConfig'); // If the configuration is changed more frequently than the threshold, // log the occurrence and reset the configurationChanged flag to false // to skip calling chart draw and avoid getting into an infinite rebuild // cycle. // // One common cause for the configuration changing on every chart build // is because a behavior is detected to have changed when it has not. // A common case is when a setting is passed to a behavior is an object // and doesn't override the equality checks. if (_chartState.chartIsDirty) { final currentTime = DateTime.now(); final lastConfigurationBelowThreshold = _lastConfigurationChangeTime != null && currentTime.difference(_lastConfigurationChangeTime!).inMilliseconds < configurationChangeThresholdMs; _lastConfigurationChangeTime = currentTime; if (lastConfigurationBelowThreshold) { _chartState.resetChartDirtyFlag(); _log.warning( 'Chart configuration is changing more frequent than threshold' ' of $configurationChangeThresholdMs. Check if your behavior, axis,' ' or renderer config is missing equality checks that may be causing' ' configuration to be detected as changed. '); } } if (_chartState.chartIsDirty) { _chart!.configurationChanged(); } // If series list changes or other configuration changed that triggered the // _chartState.configurationChanged flag to be set (such as axis, behavior, // and renderer changes). Otherwise, the chart only requests repainting and // does not reprocess the series. // // Series list is considered "changed" based on the instance. if (_seriesList != config.chartWidget.seriesList || _chartState.chartIsDirty) { _chartState.resetChartDirtyFlag(); _seriesList = config.chartWidget.seriesList; // Clear out the a11y nodes generated. _a11yNodes = null; common.Performance.time('chartsDraw'); _chart!.draw(_seriesList!); common.Performance.timeEnd('chartsDraw'); // This is needed because when a series changes we need to reset flutter's // animation value from 1.0 back to 0.0. _chart!.animationPercent = 0.0; markNeedsLayout(); } else { _chart!.animationPercent = config.animationValue; markNeedsPaint(); } _updateUserManagedState(config.userManagedState); // Set the painter used for calling common chart for paint. // This painter is also used to generate semantic nodes for a11y. _setNewPainter(); } /// If user managed state is set, check each setting to see if it is different /// than internal chart state and only update if different. _updateUserManagedState(UserManagedState? newState) { if (newState == null) { return; } // Only override the selection model if it is different than the existing // selection model so update listeners are not unnecessarily triggered. for (common.SelectionModelType type in newState.selectionModels.keys) { final model = _chart!.getSelectionModel(type); final userModel = newState.selectionModels[type]!.getModel(_chart!.currentSeriesList); if (model != userModel) { model.updateSelection( userModel.selectedDatum, userModel.selectedSeries); } } } @override void performLayout() { common.Performance.time('chartsLayout'); _chart! .measure(constraints.maxWidth.toInt(), constraints.maxHeight.toInt()); _chart!.layout(constraints.maxWidth.toInt(), constraints.maxHeight.toInt()); common.Performance.timeEnd('chartsLayout'); size = constraints.biggest; // Check if the gestures registered in gesture registry matches what the // common chart is listening to. // TODO: Still need a test for this for sanity sake. // assert(_desiredGestures // .difference(_chart!.gestureProxy.listenedGestures) // .isEmpty); } @override void markNeedsLayout() { super.markNeedsLayout(); if (parent != null) { markParentNeedsLayout(); } } @override bool hitTestSelf(Offset position) => true; @override void requestRedraw() {} @override void requestAnimation(Duration transition) { void startAnimationController(_) { _chartState.setAnimation(transition); } // Sometimes chart behaviors try to draw the chart outside of a Flutter draw // cycle. Schedule a frame manually to handle these cases. if (!SchedulerBinding.instance!.hasScheduledFrame) { SchedulerBinding.instance!.scheduleFrame(); } SchedulerBinding.instance!.addPostFrameCallback(startAnimationController); } /// Request Flutter to rebuild the widget/container of chart. /// /// This is different than requesting redraw and paint because those only /// affect the chart widget. This is for requesting rebuild of the Flutter /// widget that contains the chart widget. This is necessary for supporting /// Flutter widgets that are layout with the chart. /// /// Example is legends, a legend widget can be layout on top of the chart /// widget or along the sides of the chart. Requesting a rebuild allows /// the legend to layout and redraw itself. void requestRebuild() { void doRebuild(_) { _chartState.requestRebuild(); } // Flutter does not allow requesting rebuild during the build cycle, this // schedules rebuild request to happen after the current build cycle. // This is needed to request rebuild after the legend has been added in the // post process phase of the chart, which happens during the chart widget's // build cycle. SchedulerBinding.instance!.addPostFrameCallback(doRebuild); } /// When Flutter's markNeedsLayout is called, layout and paint are both /// called. If animations are off, Flutter's paint call after layout will /// paint the chart. If animations are on, Flutter's paint is called with the /// initial animation value and then the animation controller is started after /// this first build cycle. @override void requestPaint() { markNeedsPaint(); } @override double get pixelsPerDp => 1.0; @override bool get chartContainerIsRtl => _chartContainerIsRtl; @override common.RTLSpec? get rtlSpec => _rtlSpec; @override bool get isRtl => _chartContainerIsRtl && (_rtlSpec == null || _rtlSpec?.axisDirection == common.AxisDirection.reversed); @override bool get isTappable => _chart!.isTappable; @override common.DateTimeFactory get dateTimeFactory => _dateTimeFactory!; /// Gets the chart's gesture listener. common.ProxyGestureListener get gestureProxy => _chart!.gestureProxy; TextDirection get textDirection => _chartContainerIsRtl ? TextDirection.rtl : TextDirection.ltr; @override void enableA11yExploreMode(List nodes, {String? announcement}) { _a11yNodes = nodes; _exploreMode = true; _setNewPainter(); requestRebuild(); if (announcement != null) { SemanticsService.announce(announcement, textDirection); } } @override void disableA11yExploreMode({String? announcement}) { _a11yNodes = []; _exploreMode = false; _setNewPainter(); requestRebuild(); if (announcement != null) { SemanticsService.announce(announcement, textDirection); } } void _setNewPainter() { painter = new ChartContainerCustomPaint( oldPainter: painter as ChartContainerCustomPaint?, chart: _chart!, exploreMode: _exploreMode, a11yNodes: _a11yNodes ?? [], textDirection: textDirection); } } class ChartContainerCustomPaint extends CustomPainter { final common.BaseChart chart; final bool exploreMode; final List a11yNodes; final TextDirection textDirection; factory ChartContainerCustomPaint( {ChartContainerCustomPaint? oldPainter, required common.BaseChart chart, bool exploreMode = false, List a11yNodes = const [], TextDirection textDirection = TextDirection.ltr}) { if (oldPainter != null && oldPainter.exploreMode == exploreMode && oldPainter.a11yNodes == a11yNodes && oldPainter.textDirection == textDirection) { return oldPainter; } else { return new ChartContainerCustomPaint._internal( chart: chart, exploreMode: exploreMode, a11yNodes: a11yNodes, textDirection: textDirection); } } ChartContainerCustomPaint._internal( {required this.chart, required this.exploreMode, required this.a11yNodes, required this.textDirection}); @override void paint(Canvas canvas, Size size) { common.Performance.time('chartsPaint'); final chartsCanvas = new ChartCanvas(canvas, chart.graphicsFactory!); chart.paint(chartsCanvas); common.Performance.timeEnd('chartsPaint'); } /// Common chart requests rebuild that handle repaint requests. @override bool shouldRepaint(ChartContainerCustomPaint oldPainter) => false; /// Rebuild semantics when explore mode is toggled semantic properties change. @override bool shouldRebuildSemantics(ChartContainerCustomPaint oldDelegate) { return exploreMode != oldDelegate.exploreMode || a11yNodes != oldDelegate.a11yNodes || textDirection != textDirection; } @override SemanticsBuilderCallback get semanticsBuilder => _buildSemantics; List _buildSemantics(Size size) { final nodes = []; for (common.A11yNode node in a11yNodes) { final rect = new Rect.fromLTWH( node.boundingBox.left.toDouble(), node.boundingBox.top.toDouble(), node.boundingBox.width.toDouble(), node.boundingBox.height.toDouble()); nodes.add(new CustomPainterSemantics( rect: rect, properties: new SemanticsProperties( value: node.label, textDirection: textDirection, onDidGainAccessibilityFocus: node.onFocus))); } return nodes; } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_flutter/lib/src/chart_gesture_detector.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:async' show Timer; import 'dart:math' show Point; import 'package:flutter/material.dart' show BuildContext, GestureDetector, RenderBox, ScaleEndDetails, ScaleStartDetails, ScaleUpdateDetails, TapDownDetails, TapUpDetails; import 'behaviors/chart_behavior.dart' show GestureType; import 'chart_container.dart' show ChartContainer, ChartContainerRenderObject; import 'util.dart' show getChartContainerRenderObject; // From https://docs.flutter.io/flutter/gestures/kLongPressTimeout-constant.html const Duration _kLongPressTimeout = const Duration(milliseconds: 500); class ChartGestureDetector { bool _listeningForLongPress = false; bool _isDragging = false; Timer? _longPressTimer; Point? _lastTapPoint; double? _lastScale; late _ContainerResolver _containerResolver; makeWidget(BuildContext context, ChartContainer chartContainer, Set desiredGestures) { _containerResolver = () { final renderObject = context.findRenderObject()!; return getChartContainerRenderObject(renderObject as RenderBox); }; final wantTapDown = desiredGestures.isNotEmpty; final wantTap = desiredGestures.contains(GestureType.onTap); final wantDrag = desiredGestures.contains(GestureType.onDrag); // LongPress is special, we'd like to be able to trigger long press before // Drag/Press to trigger tooltips then explore with them. This means we // can't rely on gesture detection since it will block out the scale // gestures. _listeningForLongPress = desiredGestures.contains(GestureType.onLongPress); return new GestureDetector( child: chartContainer, onTapDown: wantTapDown ? onTapDown : null, onTapUp: wantTap ? onTapUp : null, onScaleStart: wantDrag ? onScaleStart : null, onScaleUpdate: wantDrag ? onScaleUpdate : null, onScaleEnd: wantDrag ? onScaleEnd : null, ); } void onTapDown(TapDownDetails d) { final container = _containerResolver(); final localPosition = container.globalToLocal(d.globalPosition); _lastTapPoint = new Point(localPosition.dx, localPosition.dy); container.gestureProxy.onTapTest(_lastTapPoint!); // Kick off a timer to see if this is a LongPress. if (_listeningForLongPress) { _longPressTimer = new Timer(_kLongPressTimeout, () { onLongPress(); _longPressTimer = null; }); } } void onTapUp(TapUpDetails d) { _longPressTimer?.cancel(); final container = _containerResolver(); final localPosition = container.globalToLocal(d.globalPosition); _lastTapPoint = new Point(localPosition.dx, localPosition.dy); container.gestureProxy.onTap(_lastTapPoint!); } void onLongPress() { final container = _containerResolver(); container.gestureProxy.onLongPress(_lastTapPoint!); } void onScaleStart(ScaleStartDetails d) { _longPressTimer?.cancel(); final container = _containerResolver(); final localPosition = container.globalToLocal(d.focalPoint); _lastTapPoint = new Point(localPosition.dx, localPosition.dy); _isDragging = container.gestureProxy.onDragStart(_lastTapPoint!); } void onScaleUpdate(ScaleUpdateDetails d) { if (!_isDragging) { return; } final container = _containerResolver(); final localPosition = container.globalToLocal(d.focalPoint); _lastTapPoint = new Point(localPosition.dx, localPosition.dy); _lastScale = d.scale; container.gestureProxy.onDragUpdate(_lastTapPoint!, d.scale); } void onScaleEnd(ScaleEndDetails d) { if (!_isDragging) { return; } final container = _containerResolver(); container.gestureProxy .onDragEnd(_lastTapPoint!, _lastScale!, d.velocity.pixelsPerSecond.dx); } } // Exposed for testing. typedef ChartContainerRenderObject _ContainerResolver(); ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_flutter/lib/src/chart_state.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. abstract class ChartState { void setAnimation(Duration transition); /// Request to the native platform to rebuild the chart. void requestRebuild(); /// Informs the chart that the configuration has changed. /// /// This flag is set by checks that detect if a configuration has changed, /// such as behaviors, axis, and renderers. /// /// This flag is read on chart rebuild, if chart is marked as dirty, then the /// chart will call a base chart draw. void markChartDirty(); /// Reset the chart dirty flag. void resetChartDirtyFlag(); /// Gets if the chart is dirty. bool get chartIsDirty; } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_flutter/lib/src/combo_chart/combo_chart.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'package:charts_common/common.dart' as common show AxisSpec, NumericCartesianChart, OrdinalCartesianChart, NumericAxisSpec, RTLSpec, Series, SeriesRendererConfig; import '../behaviors/chart_behavior.dart' show ChartBehavior; import '../base_chart.dart' show LayoutConfig; import '../base_chart_state.dart' show BaseChartState; import '../cartesian_chart.dart' show CartesianChart; import '../selection_model_config.dart' show SelectionModelConfig; /// A numeric combo chart supports rendering each series of data with different /// series renderers. /// /// Note that if you have DateTime data, you should use [TimeSeriesChart]. We do /// not expose a separate DateTimeComboChart because it would just be a copy of /// that chart. class NumericComboChart extends CartesianChart { NumericComboChart( List> seriesList, { bool? animate, Duration? animationDuration, common.AxisSpec? domainAxis, common.NumericAxisSpec? primaryMeasureAxis, common.NumericAxisSpec? secondaryMeasureAxis, common.SeriesRendererConfig? defaultRenderer, List>? customSeriesRenderers, List>? behaviors, List>? selectionModels, common.RTLSpec? rtlSpec, LayoutConfig? layoutConfig, bool defaultInteractions = true, }) : super( seriesList, animate: animate, animationDuration: animationDuration, domainAxis: domainAxis, primaryMeasureAxis: primaryMeasureAxis, secondaryMeasureAxis: secondaryMeasureAxis, defaultRenderer: defaultRenderer, customSeriesRenderers: customSeriesRenderers, behaviors: behaviors, selectionModels: selectionModels, rtlSpec: rtlSpec, layoutConfig: layoutConfig, defaultInteractions: defaultInteractions, ); @override common.NumericCartesianChart createCommonChart(BaseChartState chartState) { // Optionally create primary and secondary measure axes if the chart was // configured with them. If no axes were configured, then the chart will // use its default types (usually a numeric axis). return new common.NumericCartesianChart( layoutConfig: layoutConfig?.commonLayoutConfig, primaryMeasureAxis: primaryMeasureAxis?.createAxis(), secondaryMeasureAxis: secondaryMeasureAxis?.createAxis()); } } /// An ordinal combo chart supports rendering each series of data with different /// series renderers. class OrdinalComboChart extends CartesianChart { OrdinalComboChart( List> seriesList, { bool? animate, Duration? animationDuration, common.AxisSpec? domainAxis, common.NumericAxisSpec? primaryMeasureAxis, common.NumericAxisSpec? secondaryMeasureAxis, common.SeriesRendererConfig? defaultRenderer, List>? customSeriesRenderers, List>? behaviors, List>? selectionModels, common.RTLSpec? rtlSpec, LayoutConfig? layoutConfig, bool defaultInteractions = true, }) : super( seriesList, animate: animate, animationDuration: animationDuration, domainAxis: domainAxis, primaryMeasureAxis: primaryMeasureAxis, secondaryMeasureAxis: secondaryMeasureAxis, defaultRenderer: defaultRenderer, customSeriesRenderers: customSeriesRenderers, behaviors: behaviors, selectionModels: selectionModels, rtlSpec: rtlSpec, layoutConfig: layoutConfig, defaultInteractions: defaultInteractions, ); @override common.OrdinalCartesianChart createCommonChart(BaseChartState chartState) { // Optionally create primary and secondary measure axes if the chart was // configured with them. If no axes were configured, then the chart will // use its default types (usually a numeric axis). return new common.OrdinalCartesianChart( layoutConfig: layoutConfig?.commonLayoutConfig, primaryMeasureAxis: primaryMeasureAxis?.createAxis(), secondaryMeasureAxis: secondaryMeasureAxis?.createAxis()); } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_flutter/lib/src/graphics_factory.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'package:charts_common/common.dart' as common show GraphicsFactory, LineStyle, TextElement, TextStyle; import 'package:flutter/widgets.dart' show BuildContext, DefaultTextStyle, MediaQuery; import 'line_style.dart' show LineStyle; import 'text_element.dart' show TextElement; import 'text_style.dart' show TextStyle; class GraphicsFactory implements common.GraphicsFactory { final double textScaleFactor; final DefaultTextStyle defaultTextStyle; GraphicsFactory(BuildContext context, {GraphicsFactoryHelper helper = const GraphicsFactoryHelper()}) : textScaleFactor = helper.getTextScaleFactorOf(context), defaultTextStyle = DefaultTextStyle.of(context); /// Returns a [TextStyle] object. @override common.TextStyle createTextPaint() => TextStyle()..fontFamily = defaultTextStyle.style.fontFamily; /// Returns a text element from [text]. @override common.TextElement createTextElement(String text) { return TextElement(text, textScaleFactor: textScaleFactor) ..textStyle = createTextPaint(); } @override common.LineStyle createLinePaint() => LineStyle(); } /// Wraps the MediaQuery function to allow for testing. class GraphicsFactoryHelper { const GraphicsFactoryHelper(); double getTextScaleFactorOf(BuildContext context) => MediaQuery.textScaleFactorOf(context); } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_flutter/lib/src/line_chart.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:collection' show LinkedHashMap; import 'package:charts_common/common.dart' as common show AxisSpec, LineChart, NumericAxisSpec, RTLSpec, Series, LineRendererConfig, SeriesRendererConfig; import 'behaviors/line_point_highlighter.dart' show LinePointHighlighter; import 'behaviors/chart_behavior.dart' show ChartBehavior; import 'base_chart.dart' show LayoutConfig; import 'base_chart_state.dart' show BaseChartState; import 'cartesian_chart.dart' show CartesianChart; import 'selection_model_config.dart' show SelectionModelConfig; import 'user_managed_state.dart' show UserManagedState; class LineChart extends CartesianChart { LineChart( List> seriesList, { bool? animate, Duration? animationDuration, common.AxisSpec? domainAxis, common.NumericAxisSpec? primaryMeasureAxis, common.NumericAxisSpec? secondaryMeasureAxis, LinkedHashMap? disjointMeasureAxes, common.LineRendererConfig? defaultRenderer, List>? customSeriesRenderers, List>? behaviors, List>? selectionModels, common.RTLSpec? rtlSpec, LayoutConfig? layoutConfig, bool defaultInteractions = true, bool? flipVerticalAxis, UserManagedState? userManagedState, }) : super( seriesList, animate: animate, animationDuration: animationDuration, domainAxis: domainAxis, primaryMeasureAxis: primaryMeasureAxis, secondaryMeasureAxis: secondaryMeasureAxis, disjointMeasureAxes: disjointMeasureAxes, defaultRenderer: defaultRenderer, customSeriesRenderers: customSeriesRenderers, behaviors: behaviors, selectionModels: selectionModels, rtlSpec: rtlSpec, layoutConfig: layoutConfig, defaultInteractions: defaultInteractions, flipVerticalAxis: flipVerticalAxis, userManagedState: userManagedState, ); @override common.LineChart createCommonChart(BaseChartState chartState) { // Optionally create primary and secondary measure axes if the chart was // configured with them. If no axes were configured, then the chart will // use its default types (usually a numeric axis). return new common.LineChart( layoutConfig: layoutConfig?.commonLayoutConfig, primaryMeasureAxis: primaryMeasureAxis?.createAxis(), secondaryMeasureAxis: secondaryMeasureAxis?.createAxis(), disjointMeasureAxes: createDisjointMeasureAxes()); } @override void addDefaultInteractions(List behaviors) { super.addDefaultInteractions(behaviors); behaviors.add(new LinePointHighlighter()); } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_flutter/lib/src/line_style.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'package:charts_common/common.dart' as common show Color, LineStyle; class LineStyle implements common.LineStyle { @override common.Color? color; @override List? dashPattern; @override int strokeWidth; LineStyle({this.strokeWidth = 0}); } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_flutter/lib/src/pie_chart.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'package:charts_common/common.dart' as common show ArcRendererConfig, PieChart, RTLSpec, Series; import 'behaviors/chart_behavior.dart' show ChartBehavior; import 'base_chart.dart' show BaseChart, LayoutConfig; import 'base_chart_state.dart' show BaseChartState; import 'selection_model_config.dart' show SelectionModelConfig; class PieChart extends BaseChart { PieChart( List> seriesList, { bool? animate, Duration? animationDuration, common.ArcRendererConfig? defaultRenderer, List>? behaviors, List>? selectionModels, common.RTLSpec? rtlSpec, LayoutConfig? layoutConfig, bool defaultInteractions = true, }) : super( seriesList, animate: animate, animationDuration: animationDuration, defaultRenderer: defaultRenderer, behaviors: behaviors, selectionModels: selectionModels, rtlSpec: rtlSpec, layoutConfig: layoutConfig, defaultInteractions: defaultInteractions, ); @override common.PieChart createCommonChart(BaseChartState chartState) => new common.PieChart(layoutConfig: layoutConfig?.commonLayoutConfig); } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_flutter/lib/src/scatter_plot_chart.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:collection' show LinkedHashMap; import 'package:charts_common/common.dart' as common show AxisSpec, NumericAxisSpec, PointRendererConfig, RTLSpec, ScatterPlotChart, SeriesRendererConfig, Series; import 'behaviors/chart_behavior.dart' show ChartBehavior; import 'base_chart.dart' show LayoutConfig; import 'base_chart_state.dart' show BaseChartState; import 'cartesian_chart.dart' show CartesianChart; import 'selection_model_config.dart' show SelectionModelConfig; import 'user_managed_state.dart' show UserManagedState; class ScatterPlotChart extends CartesianChart { ScatterPlotChart( List> seriesList, { bool? animate, Duration? animationDuration, common.AxisSpec? domainAxis, common.NumericAxisSpec? primaryMeasureAxis, common.NumericAxisSpec? secondaryMeasureAxis, LinkedHashMap? disjointMeasureAxes, common.PointRendererConfig? defaultRenderer, List>? customSeriesRenderers, List>? behaviors, List>? selectionModels, common.RTLSpec? rtlSpec, LayoutConfig? layoutConfig, bool defaultInteractions = true, bool? flipVerticalAxis, UserManagedState? userManagedState, }) : super( seriesList, animate: animate, animationDuration: animationDuration, domainAxis: domainAxis, primaryMeasureAxis: primaryMeasureAxis, secondaryMeasureAxis: secondaryMeasureAxis, disjointMeasureAxes: disjointMeasureAxes, defaultRenderer: defaultRenderer, customSeriesRenderers: customSeriesRenderers, behaviors: behaviors, selectionModels: selectionModels, rtlSpec: rtlSpec, layoutConfig: layoutConfig, defaultInteractions: defaultInteractions, flipVerticalAxis: flipVerticalAxis, userManagedState: userManagedState, ); @override common.ScatterPlotChart createCommonChart(BaseChartState chartState) { // Optionally create primary and secondary measure axes if the chart was // configured with them. If no axes were configured, then the chart will // use its default types (usually a numeric axis). return new common.ScatterPlotChart( layoutConfig: layoutConfig?.commonLayoutConfig, primaryMeasureAxis: primaryMeasureAxis?.createAxis(), secondaryMeasureAxis: secondaryMeasureAxis?.createAxis(), disjointMeasureAxes: createDisjointMeasureAxes()); } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_flutter/lib/src/selection_model_config.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'package:meta/meta.dart' show immutable; import 'package:charts_common/common.dart' as common; @immutable class SelectionModelConfig { final common.SelectionModelType type; /// Listens for change in selection. final common.SelectionModelListener? changedListener; /// Listens anytime update selection is called. final common.SelectionModelListener? updatedListener; SelectionModelConfig( {this.type = common.SelectionModelType.info, this.changedListener, this.updatedListener}); } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_flutter/lib/src/symbol_renderer.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:math' show Rectangle; import 'package:charts_common/common.dart' as common show ChartCanvas, Color, FillPatternType, SymbolRenderer; import 'package:flutter/widgets.dart'; import 'chart_canvas.dart' show ChartCanvas; import 'graphics_factory.dart' show GraphicsFactory; /// Flutter widget responsible for painting a common SymbolRenderer from the /// chart. /// /// If you want to customize the symbol, then use [CustomSymbolRenderer]. class SymbolRendererCanvas implements SymbolRendererBuilder { final common.SymbolRenderer commonSymbolRenderer; final List? dashPattern; SymbolRendererCanvas(this.commonSymbolRenderer, this.dashPattern); @override Widget build(BuildContext context, {Color? color, required Size size, bool enabled = true}) { if (color != null && !enabled) { color = color.withOpacity(0.26); } return new SizedBox.fromSize( size: size, child: new CustomPaint( painter: new _SymbolCustomPaint( context, commonSymbolRenderer, color, dashPattern))); } } /// Convenience class allowing you to pass your Widget builder through the /// common chart so that it is created for you by the Legend. /// /// This allows a custom SymbolRenderer in Flutter without having to create /// a completely custom legend. abstract class CustomSymbolRenderer extends common.SymbolRenderer implements SymbolRendererBuilder { CustomSymbolRenderer() : super(isSolid: false); /// Must override this method to build the custom Widget with the given color /// as @override Widget build(BuildContext context, {Color? color, required Size size, bool enabled = true}); @override void paint(common.ChartCanvas canvas, Rectangle bounds, {List? dashPattern, common.Color? fillColor, common.FillPatternType? fillPattern, common.Color? strokeColor, double? strokeWidthPx}) { // Intentionally ignored (never called). } @override bool shouldRepaint(common.SymbolRenderer oldRenderer) { return false; // Repainting is handled directly in Flutter. } } /// Common interface for [CustomSymbolRenderer] & [SymbolRendererCanvas] for /// convenience for [LegendEntryLayout]. abstract class SymbolRendererBuilder { Widget build(BuildContext context, {Color? color, required Size size, bool enabled}); } /// The Widget which fulfills the guts of [SymbolRendererCanvas] actually /// painting the symbol to a canvas using [CustomPainter]. class _SymbolCustomPaint extends CustomPainter { final BuildContext context; final common.SymbolRenderer symbolRenderer; final Color? color; final List? dashPattern; _SymbolCustomPaint( this.context, this.symbolRenderer, this.color, this.dashPattern); @override void paint(Canvas canvas, Size size) { final bounds = new Rectangle(0, 0, size.width.toInt(), size.height.toInt()); final commonColor = color == null ? null : new common.Color( r: color!.red, g: color!.green, b: color!.blue, a: color!.alpha); symbolRenderer.paint( new ChartCanvas(canvas, GraphicsFactory(context)), bounds, fillColor: commonColor, strokeColor: commonColor, dashPattern: dashPattern); } @override bool shouldRepaint(_SymbolCustomPaint oldDelegate) { return symbolRenderer.shouldRepaint(oldDelegate.symbolRenderer); } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_flutter/lib/src/text_element.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:ui' show TextAlign, TextDirection; import 'package:charts_common/common.dart' as common show MaxWidthStrategy, TextElement, TextDirection, TextMeasurement, TextStyle; import 'package:flutter/rendering.dart' show Color, TextBaseline, TextPainter, TextSpan, TextStyle; /// Flutter implementation for text measurement and painter. class TextElement implements common.TextElement { static const ellipsis = '\u{2026}'; @override final String text; final double? textScaleFactor; var _painterReady = false; common.TextStyle? _textStyle; common.TextDirection _textDirection = common.TextDirection.ltr; int? _maxWidth; common.MaxWidthStrategy? _maxWidthStrategy; late TextPainter _textPainter; late common.TextMeasurement _measurement; double? _opacity; TextElement(this.text, {common.TextStyle? style, this.textScaleFactor}) : _textStyle = style; @override common.TextStyle? get textStyle => _textStyle; @override set textStyle(common.TextStyle? value) { if (_textStyle == value) { return; } _textStyle = value; _painterReady = false; } @override set textDirection(common.TextDirection direction) { if (_textDirection == direction) { return; } _textDirection = direction; _painterReady = false; } @override common.TextDirection get textDirection => _textDirection; @override int? get maxWidth => _maxWidth; @override set maxWidth(int? value) { if (_maxWidth == value) { return; } _maxWidth = value; _painterReady = false; } @override common.MaxWidthStrategy? get maxWidthStrategy => _maxWidthStrategy; @override set maxWidthStrategy(common.MaxWidthStrategy? maxWidthStrategy) { if (_maxWidthStrategy == maxWidthStrategy) { return; } _maxWidthStrategy = maxWidthStrategy; _painterReady = false; } @override set opacity(double? opacity) { if (opacity != _opacity) { _painterReady = false; _opacity = opacity; } } @override common.TextMeasurement get measurement { if (!_painterReady) { _refreshPainter(); } return _measurement; } /// The estimated distance between where we asked to draw the text (top, left) /// and where it visually started (top + verticalFontShift, left). /// /// 10% of reported font height seems to be about right. int get verticalFontShift { if (!_painterReady) { _refreshPainter(); } return (_textPainter.height * 0.1).ceil(); } TextPainter? get textPainter { if (!_painterReady) { _refreshPainter(); } return _textPainter; } /// Create text painter and measure based on current settings void _refreshPainter() { _opacity ??= 1.0; var color = (textStyle == null || textStyle!.color == null) ? null : new Color.fromARGB( (textStyle!.color!.a * _opacity!).round(), textStyle!.color!.r, textStyle!.color!.g, textStyle!.color!.b, ); _textPainter = new TextPainter( text: new TextSpan( text: text, style: new TextStyle( color: color, fontSize: textStyle?.fontSize?.toDouble(), fontFamily: textStyle?.fontFamily, height: textStyle?.lineHeight))) ..textDirection = TextDirection.ltr // TODO Flip once textAlign works ..textAlign = TextAlign.left // ..textAlign = _textDirection == common.TextDirection.rtl ? // TextAlign.right : TextAlign.left ..ellipsis = maxWidthStrategy == common.MaxWidthStrategy.ellipsize ? ellipsis : null; if (textScaleFactor != null) { _textPainter.textScaleFactor = textScaleFactor!; } _textPainter.layout(maxWidth: maxWidth?.toDouble() ?? double.infinity); final baseline = _textPainter.computeDistanceToActualBaseline(TextBaseline.alphabetic); // Estimating the actual draw height to 70% of measures size. // // The font reports a size larger than the drawn size, which makes it // difficult to shift the text around to get it to visually line up // vertically with other components. _measurement = new common.TextMeasurement( horizontalSliceWidth: _textPainter.width, verticalSliceWidth: _textPainter.height * 0.70, baseline: baseline); _painterReady = true; } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_flutter/lib/src/text_style.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:ui' show hashValues; import 'package:charts_common/common.dart' as common show Color, TextStyle; class TextStyle implements common.TextStyle { int? fontSize; String? fontFamily; common.Color? color; double? lineHeight; @override bool operator ==(Object other) => other is TextStyle && fontSize == other.fontSize && fontFamily == other.fontFamily && color == other.color && lineHeight == other.lineHeight; @override int get hashCode => hashValues(fontSize, fontFamily, color, lineHeight); } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_flutter/lib/src/time_series_chart.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:collection' show LinkedHashMap; import 'package:charts_common/common.dart' as common show AxisSpec, DateTimeFactory, LocalDateTimeFactory, NumericAxisSpec, Series, SeriesRendererConfig, TimeSeriesChart; import 'behaviors/chart_behavior.dart' show ChartBehavior; import 'behaviors/line_point_highlighter.dart' show LinePointHighlighter; import 'cartesian_chart.dart' show CartesianChart; import 'base_chart.dart' show LayoutConfig; import 'base_chart_state.dart' show BaseChartState; import 'selection_model_config.dart' show SelectionModelConfig; import 'user_managed_state.dart' show UserManagedState; class TimeSeriesChart extends CartesianChart { final common.DateTimeFactory? dateTimeFactory; /// Create a [TimeSeriesChart]. /// /// [dateTimeFactory] allows specifying a factory that creates [DateTime] to /// be used for the time axis. If none specified, local date time is used. TimeSeriesChart( List> seriesList, { bool? animate, Duration? animationDuration, common.AxisSpec? domainAxis, common.NumericAxisSpec? primaryMeasureAxis, common.NumericAxisSpec? secondaryMeasureAxis, LinkedHashMap? disjointMeasureAxes, common.SeriesRendererConfig? defaultRenderer, List>? customSeriesRenderers, List>? behaviors, List>? selectionModels, LayoutConfig? layoutConfig, this.dateTimeFactory, bool defaultInteractions = true, bool? flipVerticalAxis, UserManagedState? userManagedState, }) : super( seriesList, animate: animate, animationDuration: animationDuration, domainAxis: domainAxis, primaryMeasureAxis: primaryMeasureAxis, secondaryMeasureAxis: secondaryMeasureAxis, disjointMeasureAxes: disjointMeasureAxes, defaultRenderer: defaultRenderer, customSeriesRenderers: customSeriesRenderers, behaviors: behaviors, selectionModels: selectionModels, layoutConfig: layoutConfig, defaultInteractions: defaultInteractions, flipVerticalAxis: flipVerticalAxis, userManagedState: userManagedState, ); @override common.TimeSeriesChart createCommonChart(BaseChartState chartState) { // Optionally create primary and secondary measure axes if the chart was // configured with them. If no axes were configured, then the chart will // use its default types (usually a numeric axis). return new common.TimeSeriesChart( layoutConfig: layoutConfig?.commonLayoutConfig, primaryMeasureAxis: primaryMeasureAxis?.createAxis(), secondaryMeasureAxis: secondaryMeasureAxis?.createAxis(), disjointMeasureAxes: createDisjointMeasureAxes(), dateTimeFactory: dateTimeFactory ?? const common.LocalDateTimeFactory()); } @override void addDefaultInteractions(List behaviors) { super.addDefaultInteractions(behaviors); behaviors.add(new LinePointHighlighter()); } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_flutter/lib/src/user_managed_state.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'package:charts_common/common.dart' as common show ImmutableSeries, SelectionModel, SelectionModelType, SeriesDatumConfig; /// Contains override settings for the internal chart state. /// /// The chart will check non null settings and apply them if they differ from /// the internal chart state and trigger the appropriate level of redrawing. class UserManagedState { /// The expected selection(s) on the chart. /// /// If this is set and the model for the selection model type differs from /// what is in the internal chart state, the selection will be applied and /// repainting will occur such that behaviors that draw differently on /// selection change can update, such as the line point highlighter. /// /// If more than one type of selection model is used, only the one(s) /// specified in this list will override what is kept in the internally. /// /// To clear the selection, add an empty selection model. final selectionModels = >{}; } /// Container for the user managed selection model. /// /// This container is needed because the selection model generated by selection /// events is a [SelectionModel], while any user defined selection has to be /// specified by passing in [selectedSeriesConfig] and [selectedDataConfig]. /// The configuration is converted to a selection model after the series data /// has been processed. class UserManagedSelectionModel { final List? selectedSeriesConfig; final List>? selectedDataConfig; common.SelectionModel? _model; /// Creates a [UserManagedSelectionModel] that holds [SelectionModel]. /// /// [selectedSeriesConfig] and [selectedDataConfig] is set to null because the /// [_model] is returned when [getModel] is called. UserManagedSelectionModel({common.SelectionModel? model}) : _model = model ?? common.SelectionModel(), selectedSeriesConfig = null, selectedDataConfig = null; /// Creates a [UserManagedSelectionModel] with configuration that is converted /// to a [SelectionModel] when [getModel] provides a processed series list. UserManagedSelectionModel.fromConfig( {List? selectedSeriesConfig, List>? selectedDataConfig}) : this.selectedSeriesConfig = selectedSeriesConfig, this.selectedDataConfig = selectedDataConfig; /// Gets the selection model. If the model is null, create one from /// configuration and the processed [seriesList] passed in. common.SelectionModel getModel( List> seriesList) { _model ??= common.SelectionModel.fromConfig( selectedDataConfig, selectedSeriesConfig, seriesList); return _model!; } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_flutter/lib/src/util/color.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'package:charts_common/common.dart' as common show Color; import 'dart:ui' as ui; class ColorUtil { static ui.Color toDartColor(common.Color color) { return ui.Color.fromARGB(color.a, color.r, color.g, color.b); } static common.Color fromDartColor(ui.Color color) { return common.Color( r: color.red, g: color.green, b: color.blue, a: color.alpha); } } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_flutter/lib/src/util.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'package:flutter/rendering.dart' show RenderBox, RenderSemanticsGestureHandler, RenderPointerListener, RenderCustomMultiChildLayoutBox; import 'chart_container.dart' show ChartContainerRenderObject; /// Get the [ChartContainerRenderObject] from a [RenderBox]. /// /// [RenderBox] is expected to be a [RenderSemanticsGestureHandler] with child /// of [RenderPointerListener] with child of [ChartContainerRenderObject]. ChartContainerRenderObject getChartContainerRenderObject(RenderBox box) { assert(box is RenderCustomMultiChildLayoutBox); final semanticHandler = (box as RenderCustomMultiChildLayoutBox) .getChildrenAsList() .firstWhere((child) => child is RenderSemanticsGestureHandler); assert(semanticHandler is RenderSemanticsGestureHandler); final renderPointerListener = (semanticHandler as RenderSemanticsGestureHandler).child; assert(renderPointerListener is RenderPointerListener); final chartContainerRenderObject = (renderPointerListener as RenderPointerListener).child; assert(chartContainerRenderObject is ChartContainerRenderObject); return chartContainerRenderObject as ChartContainerRenderObject; } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_flutter/lib/src/widget_layout_delegate.dart ================================================ // Copyright 2018 the Charts project authors. Please see the AUTHORS file // for details. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import 'dart:ui' show Offset; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; import 'package:charts_common/common.dart' as common show BehaviorPosition, InsideJustification, OutsideJustification; import 'behaviors/chart_behavior.dart' show BuildableBehavior; /// Layout delegate that layout chart widget with [BuildableBehavior] widgets. class WidgetLayoutDelegate extends MultiChildLayoutDelegate { /// ID of the common chart widget. final String chartID; /// Directionality of the widget. final isRTL; /// ID and [BuildableBehavior] of the widgets for calculating offset. final Map idAndBehavior; WidgetLayoutDelegate(this.chartID, this.idAndBehavior, this.isRTL); @override void performLayout(Size size) { // TODO: Change this to a layout manager that supports more // than one buildable behavior that changes chart size. Remove assert when // this is possible. assert(idAndBehavior.keys.isEmpty || idAndBehavior.keys.length == 1); // Size available for the chart widget. var availableWidth = size.width; var availableHeight = size.height; var chartOffset = Offset.zero; // Measure the first buildable behavior. final behaviorID = idAndBehavior.keys.isNotEmpty ? idAndBehavior.keys.first : null; var behaviorSize = Size.zero; if (behaviorID != null) { if (hasChild(behaviorID)) { final leftPosition = isRTL ? common.BehaviorPosition.end : common.BehaviorPosition.start; final rightPosition = isRTL ? common.BehaviorPosition.start : common.BehaviorPosition.end; final behaviorPosition = idAndBehavior[behaviorID]!.position; behaviorSize = layoutChild(behaviorID, new BoxConstraints.loose(size)); if (behaviorPosition == common.BehaviorPosition.top) { chartOffset = new Offset(0.0, behaviorSize.height); availableHeight -= behaviorSize.height; } else if (behaviorPosition == common.BehaviorPosition.bottom) { availableHeight -= behaviorSize.height; } else if (behaviorPosition == leftPosition) { chartOffset = new Offset(behaviorSize.width, 0.0); availableWidth -= behaviorSize.width; } else if (behaviorPosition == rightPosition) { availableWidth -= behaviorSize.width; } } } // Layout chart. final chartSize = new Size(availableWidth, availableHeight); if (hasChild(chartID)) { layoutChild(chartID, new BoxConstraints.tight(chartSize)); positionChild(chartID, chartOffset); } // Position buildable behavior. if (behaviorID != null) { // TODO: Unable to relayout with new smaller width. // In the delegate, all children are required to have layout called // exactly once. final behaviorOffset = _getBehaviorOffset(idAndBehavior[behaviorID]!, behaviorSize: behaviorSize, chartSize: chartSize, isRTL: isRTL); positionChild(behaviorID, behaviorOffset); } } @override bool shouldRelayout(MultiChildLayoutDelegate oldDelegate) { // TODO: Deep equality check because the instance will not be // the same on each build, even if the buildable behavior has not changed. return idAndBehavior != (oldDelegate as WidgetLayoutDelegate).idAndBehavior; } // Calculate buildable behavior's offset. Offset _getBehaviorOffset(BuildableBehavior behavior, {required Size behaviorSize, required Size chartSize, required bool isRTL}) { late Offset behaviorOffset; final behaviorPosition = behavior.position; final outsideJustification = behavior.outsideJustification; final insideJustification = behavior.insideJustification; if (behaviorPosition == common.BehaviorPosition.top || behaviorPosition == common.BehaviorPosition.bottom) { final heightOffset = behaviorPosition == common.BehaviorPosition.bottom ? chartSize.height : 0.0; final horizontalJustification = getOutsideJustification(outsideJustification, isRTL); switch (horizontalJustification) { case _HorizontalJustification.leftDrawArea: behaviorOffset = new Offset( behavior.drawAreaBounds!.left.toDouble(), heightOffset); break; case _HorizontalJustification.left: behaviorOffset = new Offset(0.0, heightOffset); break; case _HorizontalJustification.rightDrawArea: behaviorOffset = new Offset( behavior.drawAreaBounds!.right - behaviorSize.width, heightOffset); break; case _HorizontalJustification.right: behaviorOffset = new Offset(chartSize.width - behaviorSize.width, heightOffset); break; } } else if (behaviorPosition == common.BehaviorPosition.start || behaviorPosition == common.BehaviorPosition.end) { final widthOffset = (isRTL && behaviorPosition == common.BehaviorPosition.start) || (!isRTL && behaviorPosition == common.BehaviorPosition.end) ? chartSize.width : 0.0; switch (outsideJustification) { case common.OutsideJustification.startDrawArea: case common.OutsideJustification.middleDrawArea: behaviorOffset = new Offset(widthOffset, behavior.drawAreaBounds!.top.toDouble()); break; case common.OutsideJustification.start: case common.OutsideJustification.middle: behaviorOffset = new Offset(widthOffset, 0.0); break; case common.OutsideJustification.endDrawArea: behaviorOffset = new Offset(widthOffset, behavior.drawAreaBounds!.bottom - behaviorSize.height); break; case common.OutsideJustification.end: behaviorOffset = new Offset(widthOffset, chartSize.height - behaviorSize.height); break; } } else if (behaviorPosition == common.BehaviorPosition.inside) { var rightOffset = new Offset(chartSize.width - behaviorSize.width, 0.0); switch (insideJustification) { case common.InsideJustification.topStart: behaviorOffset = isRTL ? rightOffset : Offset.zero; break; case common.InsideJustification.topEnd: behaviorOffset = isRTL ? Offset.zero : rightOffset; break; } } return behaviorOffset; } _HorizontalJustification getOutsideJustification( common.OutsideJustification justification, bool isRTL) { _HorizontalJustification mappedJustification; switch (justification) { case common.OutsideJustification.startDrawArea: case common.OutsideJustification.middleDrawArea: mappedJustification = isRTL ? _HorizontalJustification.rightDrawArea : _HorizontalJustification.leftDrawArea; break; case common.OutsideJustification.start: case common.OutsideJustification.middle: mappedJustification = isRTL ? _HorizontalJustification.right : _HorizontalJustification.left; break; case common.OutsideJustification.endDrawArea: mappedJustification = isRTL ? _HorizontalJustification.leftDrawArea : _HorizontalJustification.rightDrawArea; break; case common.OutsideJustification.end: mappedJustification = isRTL ? _HorizontalJustification.left : _HorizontalJustification.right; break; } return mappedJustification; } } enum _HorizontalJustification { leftDrawArea, left, rightDrawArea, right, } ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_flutter/minimum_os.bzl ================================================ """Minimum OS version definitions""" IOS_MINIMUM_OS = "9.0" ================================================ FILE: flutter/reddit_ticker/deps/charts/charts_flutter/pubspec.yaml ================================================ name: charts_flutter version: 0.11.0 description: Material Design charting library for flutter. author: Charts Team homepage: https://github.com/google/charts environment: sdk: '>=2.12.0 <3.0.0' dependencies: # Pointing this to a local path allows for pointing to the latest code # in Github for open source development. # # The pub version of charts_flutter will point to the pub version of charts_common. # The latest pub version is commented out and shown below as an example. charts_common: 0.11.0 # charts_common: # path: ../charts_common/ collection: ^1.14.5 flutter: sdk: flutter intl: ">=0.15.2 < 0.18.0" logging: any meta: ^1.1.1 dev_dependencies: mockito: ^5.0.0 build_runner: ^1.11.0 flutter_test: sdk: flutter test: ^1.3.0 ================================================ FILE: flutter/reddit_ticker/deps/charts/docs/Gemfile ================================================ # This file enables generation of documentation html from markdown locally. # # Ruby 2.10 or later is required. # To install needed dependencies: # # gem install bundler # bundle install --path .bundle # # To generate and serve html locally: # # bundle exec jekyll serve # # -------------------------------------------------------------------------- source 'https://rubygems.org' gem 'github-pages', group: :jekyll_plugins ================================================ FILE: flutter/reddit_ticker/deps/charts/docs/flutter/gallery.md ================================================ ## Gallery [//]: (Do_NOT_modify_gallery__generate_from_code) [//]: (START_EXAMPLES) ### Bar Charts | | | | | | --- | --- | --- | --- | | Simple
[![](example/bar_charts/simple_thumb.png)](example/bar_charts/simple) | Stacked
[![](example/bar_charts/stacked_thumb.png)](example/bar_charts/stacked) | Grouped
[![](example/bar_charts/grouped_thumb.png)](example/bar_charts/grouped) | Grouped Stacked
[![](example/bar_charts/grouped_stacked_thumb.png)](example/bar_charts/grouped_stacked) | | Grouped Target Line
[![](example/bar_charts/grouped_target_line_thumb.png)](example/bar_charts/grouped_target_line) | Stacked Horizontal
[![](example/bar_charts/stacked_horizontal_thumb.png)](example/bar_charts/stacked_horizontal) | Stacked Target Line
[![](example/bar_charts/stacked_target_line_thumb.png)](example/bar_charts/stacked_target_line) | Horizontal
[![](example/bar_charts/horizontal_thumb.png)](example/bar_charts/horizontal) | | Horizontal Bar Label
[![](example/bar_charts/horizontal_bar_label_thumb.png)](example/bar_charts/horizontal_bar_label) | Horizontal Bar Label Custom
[![](example/bar_charts/horizontal_bar_label_custom_thumb.png)](example/bar_charts/horizontal_bar_label_custom) | Vertical Bar Label
[![](example/bar_charts/vertical_bar_label_thumb.png)](example/bar_charts/vertical_bar_label) | Spark Bar
[![](example/bar_charts/spark_bar_thumb.png)](example/bar_charts/spark_bar) | | Grouped Fill Color
[![](example/bar_charts/grouped_fill_color_thumb.png)](example/bar_charts/grouped_fill_color) | Stacked Fill Color
[![](example/bar_charts/stacked_fill_color_thumb.png)](example/bar_charts/stacked_fill_color) | Pattern Forward Hatch
[![](example/bar_charts/pattern_forward_hatch_thumb.png)](example/bar_charts/pattern_forward_hatch) | Horizontal Pattern Forward Hatch
[![](example/bar_charts/horizontal_pattern_forward_hatch_thumb.png)](example/bar_charts/horizontal_pattern_forward_hatch) | | Grouped Stacked Weight Pattern
[![](example/bar_charts/grouped_stacked_weight_pattern_thumb.png)](example/bar_charts/grouped_stacked_weight_pattern) | Custom Rounded Bars
[![](example/bar_charts/custom_rounded_bars_thumb.png)](example/bar_charts/custom_rounded_bars) | | | ### Time Series Charts | | | | | | --- | --- | --- | --- | | Simple
[![](example/time_series_charts/simple_thumb.png)](example/time_series_charts/simple) | End Points Axis
[![](example/time_series_charts/end_points_axis_thumb.png)](example/time_series_charts/end_points_axis) | Confidence Interval
[![](example/time_series_charts/confidence_interval_thumb.png)](example/time_series_charts/confidence_interval) | Line Annotation
[![](example/time_series_charts/line_annotation_thumb.png)](example/time_series_charts/line_annotation) | | Range Annotation
[![](example/time_series_charts/range_annotation_thumb.png)](example/time_series_charts/range_annotation) | Range Annotation Margin
[![](example/time_series_charts/range_annotation_margin_thumb.png)](example/time_series_charts/range_annotation_margin) | Symbol Annotation
[![](example/time_series_charts/symbol_annotation_thumb.png)](example/time_series_charts/symbol_annotation) | With Bar Renderer
[![](example/time_series_charts/with_bar_renderer_thumb.png)](example/time_series_charts/with_bar_renderer) | ### Line Charts | | | | | | --- | --- | --- | --- | | Simple
[![](example/line_charts/simple_thumb.png)](example/line_charts/simple) | Points
[![](example/line_charts/points_thumb.png)](example/line_charts/points) | Stacked Area
[![](example/line_charts/stacked_area_thumb.png)](example/line_charts/stacked_area) | Stacked Area Custom Color
[![](example/line_charts/stacked_area_custom_color_thumb.png)](example/line_charts/stacked_area_custom_color) | | Area And Line
[![](example/line_charts/area_and_line_thumb.png)](example/line_charts/area_and_line) | Simple Nulls
[![](example/line_charts/simple_nulls_thumb.png)](example/line_charts/simple_nulls) | Stacked Area Nulls
[![](example/line_charts/stacked_area_nulls_thumb.png)](example/line_charts/stacked_area_nulls) | Dash Pattern
[![](example/line_charts/dash_pattern_thumb.png)](example/line_charts/dash_pattern) | | Segments
[![](example/line_charts/segments_thumb.png)](example/line_charts/segments) | Line Annotation
[![](example/line_charts/line_annotation_thumb.png)](example/line_charts/line_annotation) | Range Annotation
[![](example/line_charts/range_annotation_thumb.png)](example/line_charts/range_annotation) | Range Annotation Margin
[![](example/line_charts/range_annotation_margin_thumb.png)](example/line_charts/range_annotation_margin) | ### Scatter Plot Charts | | | | | | --- | --- | --- | --- | | Simple
[![](example/scatter_plot_charts/simple_thumb.png)](example/scatter_plot_charts/simple) | Shapes
[![](example/scatter_plot_charts/shapes_thumb.png)](example/scatter_plot_charts/shapes) | Comparison Points
[![](example/scatter_plot_charts/comparison_points_thumb.png)](example/scatter_plot_charts/comparison_points) | Bucketing Axis
[![](example/scatter_plot_charts/bucketing_axis_thumb.png)](example/scatter_plot_charts/bucketing_axis) | ### Combo Charts | | | | | | --- | --- | --- | --- | | Ordinal Bar Line
[![](example/combo_charts/ordinal_bar_line_thumb.png)](example/combo_charts/ordinal_bar_line) | Numeric Line Bar
[![](example/combo_charts/numeric_line_bar_thumb.png)](example/combo_charts/numeric_line_bar) | Numeric Line Point
[![](example/combo_charts/numeric_line_point_thumb.png)](example/combo_charts/numeric_line_point) | Date Time Line Point
[![](example/combo_charts/date_time_line_point_thumb.png)](example/combo_charts/date_time_line_point) | | Scatter Plot Line
[![](example/combo_charts/scatter_plot_line_thumb.png)](example/combo_charts/scatter_plot_line) | | | | ### Pie Charts | | | | | | --- | --- | --- | --- | | Simple
[![](example/pie_charts/simple_thumb.png)](example/pie_charts/simple) | Outside Label
[![](example/pie_charts/outside_label_thumb.png)](example/pie_charts/outside_label) | Partial Pie
[![](example/pie_charts/partial_pie_thumb.png)](example/pie_charts/partial_pie) | Donut
[![](example/pie_charts/donut_thumb.png)](example/pie_charts/donut) | | Auto Label
[![](example/pie_charts/auto_label_thumb.png)](example/pie_charts/auto_label) | Gauge
[![](example/pie_charts/gauge_thumb.png)](example/pie_charts/gauge) | | | ### TreeMap Charts | | | | | | --- | --- | --- | --- | ### Axes | | | | | | --- | --- | --- | --- | | Bar Secondary Axis
[![](example/axes/bar_secondary_axis_thumb.png)](example/axes/bar_secondary_axis) | Bar Secondary Axis Only
[![](example/axes/bar_secondary_axis_only_thumb.png)](example/axes/bar_secondary_axis_only) | Horizontal Bar Secondary Axis
[![](example/axes/horizontal_bar_secondary_axis_thumb.png)](example/axes/horizontal_bar_secondary_axis) | Flipped Vertical Axis
[![](example/axes/flipped_vertical_axis_thumb.png)](example/axes/flipped_vertical_axis) | | Short Tick Length Axis
[![](example/axes/short_tick_length_axis_thumb.png)](example/axes/short_tick_length_axis) | Custom Font Size And Color
[![](example/axes/custom_font_size_and_color_thumb.png)](example/axes/custom_font_size_and_color) | Measure Axis Label Alignment
[![](example/axes/measure_axis_label_alignment_thumb.png)](example/axes/measure_axis_label_alignment) | Hidden Ticks And Labels Axis
[![](example/axes/hidden_ticks_and_labels_axis_thumb.png)](example/axes/hidden_ticks_and_labels_axis) | | Custom Axis Tick Formatters
[![](example/axes/custom_axis_tick_formatters_thumb.png)](example/axes/custom_axis_tick_formatters) | Custom Measure Tick Count
[![](example/axes/custom_measure_tick_count_thumb.png)](example/axes/custom_measure_tick_count) | Integer Only Measure Axis
[![](example/axes/integer_only_measure_axis_thumb.png)](example/axes/integer_only_measure_axis) | Nonzero Bound Measure Axis
[![](example/axes/nonzero_bound_measure_axis_thumb.png)](example/axes/nonzero_bound_measure_axis) | | Nonzero Bound Measure Axis
[![](example/axes/nonzero_bound_measure_axis_thumb.png)](example/axes/nonzero_bound_measure_axis) | Statically Provided Ticks
[![](example/axes/statically_provided_ticks_thumb.png)](example/axes/statically_provided_ticks) | Ordinal Initial Viewport
[![](example/axes/ordinal_initial_viewport_thumb.png)](example/axes/ordinal_initial_viewport) | Numeric Initial Viewport
[![](example/axes/numeric_initial_viewport_thumb.png)](example/axes/numeric_initial_viewport) | | Gridline Dash Pattern
[![](example/axes/gridline_dash_pattern_thumb.png)](example/axes/gridline_dash_pattern) | Line Disjoint Axis
[![](example/axes/line_disjoint_axis_thumb.png)](example/axes/line_disjoint_axis) | | | ### Legends | | | | | | --- | --- | --- | --- | | Simple Series Legend
[![](example/legends/simple_series_legend_thumb.png)](example/legends/simple_series_legend) | Series Legend Options
[![](example/legends/series_legend_options_thumb.png)](example/legends/series_legend_options) | Series Legend With Measures
[![](example/legends/series_legend_with_measures_thumb.png)](example/legends/series_legend_with_measures) | Legend Custom Symbol
[![](example/legends/legend_custom_symbol_thumb.png)](example/legends/legend_custom_symbol) | | Default Hidden Series Legend
[![](example/legends/default_hidden_series_legend_thumb.png)](example/legends/default_hidden_series_legend) | Simple Datum Legend
[![](example/legends/simple_datum_legend_thumb.png)](example/legends/simple_datum_legend) | Datum Legend Options
[![](example/legends/datum_legend_options_thumb.png)](example/legends/datum_legend_options) | Datum Legend With Measures
[![](example/legends/datum_legend_with_measures_thumb.png)](example/legends/datum_legend_with_measures) | ### Hovercards | | | | | | --- | --- | --- | --- | ### Behaviors | | | | | | --- | --- | --- | --- | | Initial Selection
[![](example/behaviors/initial_selection_thumb.png)](example/behaviors/initial_selection) | Selection Bar Highlight
[![](example/behaviors/selection_bar_highlight_thumb.png)](example/behaviors/selection_bar_highlight) | Selection Line Highlight
[![](example/behaviors/selection_line_highlight_thumb.png)](example/behaviors/selection_line_highlight) | Selection User Managed
[![](example/behaviors/selection_user_managed_thumb.png)](example/behaviors/selection_user_managed) | | Selection Callback Example
[![](example/behaviors/selection_callback_example_thumb.png)](example/behaviors/selection_callback_example) | Chart Title
[![](example/behaviors/chart_title_thumb.png)](example/behaviors/chart_title) | Slider
[![](example/behaviors/slider_thumb.png)](example/behaviors/slider) | Sliding Viewport On Selection
[![](example/behaviors/sliding_viewport_on_selection_thumb.png)](example/behaviors/sliding_viewport_on_selection) | | Percent Of Domain
[![](example/behaviors/percent_of_domain_thumb.png)](example/behaviors/percent_of_domain) | Percent Of Domain By Category
[![](example/behaviors/percent_of_domain_by_category_thumb.png)](example/behaviors/percent_of_domain_by_category) | Percent Of Series
[![](example/behaviors/percent_of_series_thumb.png)](example/behaviors/percent_of_series) | Initial Hint Animation
[![](example/behaviors/initial_hint_animation_thumb.png)](example/behaviors/initial_hint_animation) | ### a11y | | | | | | --- | --- | --- | --- | | Domain A11y Explore Bar Chart
[![](example/a11ys/domain_a11y_explore_bar_chart_thumb.png)](example/a11ys/domain_a11y_explore_bar_chart) | | | | ### i18n | | | | | | --- | --- | --- | --- | | Rtl Bar Chart
[![](example/i18ns/rtl_bar_chart_thumb.png)](example/i18ns/rtl_bar_chart) | Rtl Line Chart
[![](example/i18ns/rtl_line_chart_thumb.png)](example/i18ns/rtl_line_chart) | Rtl Line Segments
[![](example/i18ns/rtl_line_segments_thumb.png)](example/i18ns/rtl_line_segments) | Rtl Series Legend
[![](example/i18ns/rtl_series_legend_thumb.png)](example/i18ns/rtl_series_legend) | [//]: (END_EXAMPLES) ### Combo Charts ### Sizing & Margins ================================================ FILE: flutter/reddit_ticker/deps/charts/docs/index.md ================================================ # Charts The charting framework is currently supported for the [Flutter](https://flutter.io) platform. Please see the [charts_flutter page on pub dev](https://pub.dev/packages/charts_flutter) for reference or the [online gallery of Flutter charts](https://google.github.io/charts/flutter/gallery.html) for examples. ================================================ FILE: flutter/reddit_ticker/ios/.gitignore ================================================ *.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 ================================================ FILE: flutter/reddit_ticker/ios/Flutter/AppFrameworkInfo.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable App CFBundleIdentifier io.flutter.flutter.app CFBundleInfoDictionaryVersion 6.0 CFBundleName App CFBundlePackageType FMWK CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 MinimumOSVersion 9.0 ================================================ FILE: flutter/reddit_ticker/ios/Flutter/Debug.xcconfig ================================================ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" ================================================ FILE: flutter/reddit_ticker/ios/Flutter/Release.xcconfig ================================================ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" ================================================ FILE: flutter/reddit_ticker/ios/Podfile ================================================ # Uncomment this line to define a global platform for your project # platform :ios, '9.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } def flutter_root generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) unless File.exist?(generated_xcode_build_settings_path) raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" end File.foreach(generated_xcode_build_settings_path) do |line| matches = line.match(/FLUTTER_ROOT\=(.*)/) return matches[1].strip if matches end raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" end require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) flutter_ios_podfile_setup target 'Runner' do use_frameworks! use_modular_headers! flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) end end ================================================ FILE: flutter/reddit_ticker/ios/Runner/AppDelegate.swift ================================================ 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) } } ================================================ FILE: flutter/reddit_ticker/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "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" } } ================================================ FILE: flutter/reddit_ticker/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "LaunchImage.png", "scale" : "1x" }, { "idiom" : "universal", "filename" : "LaunchImage@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "LaunchImage@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: flutter/reddit_ticker/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md ================================================ # 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. ================================================ FILE: flutter/reddit_ticker/ios/Runner/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: flutter/reddit_ticker/ios/Runner/Base.lproj/Main.storyboard ================================================ ================================================ FILE: flutter/reddit_ticker/ios/Runner/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName reddit_ticker 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 ================================================ FILE: flutter/reddit_ticker/ios/Runner/Runner-Bridging-Header.h ================================================ #import "GeneratedPluginRegistrant.h" ================================================ FILE: flutter/reddit_ticker/ios/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: flutter/reddit_ticker/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: flutter/reddit_ticker/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings ================================================ PreviewsEnabled ================================================ FILE: flutter/reddit_ticker/lib/cubit/add_post_cubit.dart ================================================ import 'package:bloc/bloc.dart'; import 'package:meta/meta.dart'; import 'package:plugin/generated/rid_api.dart'; part 'add_post_state.dart'; // Requests to reddit can be slow especially on not so great internet final REQ_TIMEOUT = const Duration(seconds: 10); class AddPostCubit extends Cubit { final Store _store = Store.instance; AddPostCubit() : super(AddPostInactive()); Future addPost(String url) async { emit(AddPostPending(url)); final res = await _store.msgStartWatching(url, timeout: REQ_TIMEOUT); switch (res.type) { case Reply.StartedWatching: assert(res.data != null, 'Successful reply should include post id'); final post = _store.posts[res.data]; assert(post != null, 'Watched post should be in the map'); emit(AddPostSucceeded(post!)); break; case Reply.FailedRequest: assert(res.data != null, 'Failed reply should include error message'); assert(state is AddPostPending, 'Adding post should only fail if it was pending'); emit(AddPostFailed((state as AddPostPending).url, res.data!)); break; default: throw ArgumentError.value( res.type, 'StartWatching Reply', 'Invalid reply!'); } } } ================================================ FILE: flutter/reddit_ticker/lib/cubit/add_post_state.dart ================================================ part of 'add_post_cubit.dart'; @immutable abstract class AddPostState {} @immutable class AddPostInactive extends AddPostState {} @immutable class AddPostPending extends AddPostState { final String url; AddPostPending(this.url) : super(); } @immutable class AddPostSucceeded extends AddPostState { final Post post; AddPostSucceeded(this.post) : super(); } @immutable class AddPostFailed extends AddPostState { final String url; final String errorMessage; AddPostFailed(this.url, this.errorMessage) : super(); } ================================================ FILE: flutter/reddit_ticker/lib/cubit/post_cubit.dart ================================================ import 'dart:async'; import 'package:bloc/bloc.dart'; import 'package:flutter/material.dart'; import 'package:meta/meta.dart'; import 'package:plugin/generated/rid_api.dart'; part 'post_state.dart'; class PostCubit extends Cubit { final _store = Store.instance; StreamSubscription? scoreTickSub; PostCubit(Post post) : super(PostActive(post)) { _subscribe(); } void _subscribe() { assert(scoreTickSub == null, 'Should only subscribe to post ticks once'); scoreTickSub = rid.replyChannel.stream .where((x) => x.type == Reply.UpdatedScores) .listen((_) => _refreshState()); } Future _unsubscribe() async { await scoreTickSub?.cancel(); scoreTickSub = null; } Future _refreshState() async { assert(state is PostActive, 'Can only refresh active posts'); final postActive = state as PostActive; final post = _store.raw.runLocked((raw) => raw.posts.get(postActive.postId)); if (post == null) { emit(postActive.intoRemoved()); } else { emit(PostActive(post)); } } Future stopWatching() async { assert(state is PostActive, 'Can only remove active posts'); final post = (state as PostActive).post; await _store.msgStopWatching(post.id).then((_) => _refreshState()); emit(PostRemoved(post.id, post.url)); return true; } @override Future close() async { await _unsubscribe(); return super.close(); } } ================================================ FILE: flutter/reddit_ticker/lib/cubit/post_state.dart ================================================ part of 'post_cubit.dart'; @immutable abstract class PostState { final String postId; final String url; PostState(this.postId, this.url); } @immutable class PostActive extends PostState { final Post post; PostActive(this.post) : super(post.id, post.url); PostRemoved intoRemoved() => PostRemoved.fromPostActive(this); } @immutable class PostRemoved extends PostState { final String postId; PostRemoved(this.postId, String url) : super(postId, url); factory PostRemoved.fromPostActive(PostActive state) { final post = state.post; return PostRemoved(post.id, post.url); } } ================================================ FILE: flutter/reddit_ticker/lib/cubit/posts_cubit.dart ================================================ import 'dart:async'; import 'package:bloc/bloc.dart'; import 'package:meta/meta.dart'; import 'package:plugin/generated/rid_api.dart'; part 'posts_state.dart'; class PostsCubit extends Cubit { final _store = Store.instance; StreamSubscription? _postAddedOrRemovedSub; PostsCubit() : super(PostsState([])) { _subscribe(); _refresh(); } void _subscribe() { _postAddedOrRemovedSub = rid.replyChannel.stream .where((x) => x.type == Reply.StartedWatching || x.type == Reply.StoppedWatching) .listen((_) => _refresh()); } Future _unsubscribe() async { await _postAddedOrRemovedSub?.cancel(); _postAddedOrRemovedSub = null; } void _refresh() { final posts = _store.posts.values.toList(); // Show most recently added post first posts.sort((a, b) => a.scores.length.compareTo(b.scores.length)); emit(PostsState(posts)); } @override Future close() async { await _unsubscribe(); return super.close(); } } ================================================ FILE: flutter/reddit_ticker/lib/cubit/posts_state.dart ================================================ part of 'posts_cubit.dart'; @immutable class PostsState { final List posts; const PostsState(this.posts); } ================================================ FILE: flutter/reddit_ticker/lib/main.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:path_provider/path_provider.dart'; import 'package:plugin/generated/rid_api.dart'; import 'package:reddit_ticker/cubit/add_post_cubit.dart'; import 'package:reddit_ticker/cubit/posts_cubit.dart'; import 'package:reddit_ticker/rid/messaging.dart'; import 'package:reddit_ticker/views/add_post.dart'; import 'package:reddit_ticker/views/posts.dart'; void main() async { RidMessaging.init(); rid.debugLock = null; WidgetsFlutterBinding.ensureInitialized(); final appDir = await getApplicationSupportDirectory(); await Store.instance.msgInitialize(appDir.path); runApp(RedditTickerApp()); } class RedditTickerApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Reddit Ticker', theme: ThemeData(primarySwatch: Colors.indigo), home: MultiBlocProvider( providers: [ BlocProvider(create: (_) => PostsCubit()), BlocProvider(create: (_) => AddPostCubit()), ], child: RedditTickerPage(title: 'Reddit Ticker'), ), ); } } class RedditTickerPage extends StatefulWidget { final String title; RedditTickerPage({Key? key, required this.title}) : super(key: key); @override _RedditTickerPageState createState() => _RedditTickerPageState(); } class _RedditTickerPageState extends State { @override void initState() { super.initState(); ErrorHandler.instance.context = context; UserMsgHandler.instance.context = context; } @override Widget build(BuildContext context) { return SafeArea( child: Scaffold( appBar: AppBar( title: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(widget.title), Row( children: [ Image.asset( "assets/dash.png", height: 40.0, width: 40.0, ), Icon(Icons.favorite, color: Colors.red), Image.asset( "assets/ferris.png", height: 50.0, width: 50.0, ), ], ) ], ), ), body: PostsView(), floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, floatingActionButton: AddPostView(), ), ); } } ================================================ FILE: flutter/reddit_ticker/lib/rid/messaging.dart ================================================ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:plugin/generated/rid_api.dart'; const RUST_ICON = '🦀'; const WARN_ICON = '⚠️ '; const INFO_ICON = '💡'; const DEBG_ICON = '🪲'; const ERR_ICON = '❌'; const WARN_PREFIX = '$RUST_ICON $WARN_ICON'; const INFO_PREFIX = '$RUST_ICON $INFO_ICON'; const DEBG_PREFIX = '$RUST_ICON $DEBG_ICON'; const ERR_PREFIX = '$RUST_ICON $ERR_ICON'; const DETAILS_INDENT = '\n '; class LogMessageHandler { late final StreamSubscription? _logMessagesSub; LogMessageHandler._() { _logMessagesSub = rid.messageChannel.stream.listen((RidMessage msg) { late final String prefix; switch (msg.type) { case RidMessageType.Severe: case RidMessageType.Error: case RidMessageType.MsgWarn: case RidMessageType.MsgInfo: return; case RidMessageType.LogWarn: prefix = WARN_PREFIX; break; case RidMessageType.LogInfo: prefix = INFO_PREFIX; break; case RidMessageType.LogDebug: prefix = DEBG_PREFIX; break; } debugPrint('$prefix: ${msg.message}'); }); } void dispose() { _logMessagesSub?.cancel(); _logMessagesSub = null; } static LogMessageHandler? _instance; static LogMessageHandler get instance { _instance ??= LogMessageHandler._(); return _instance!; } } class ErrorHandler { BuildContext? _context; late final StreamSubscription? _errorSub; ErrorHandler._() { _errorSub = rid.messageChannel.stream.listen((RidMessage msg) { switch (msg.type) { case RidMessageType.LogWarn: case RidMessageType.LogInfo: case RidMessageType.LogDebug: case RidMessageType.MsgWarn: case RidMessageType.MsgInfo: return; case RidMessageType.Severe: final indentedDetails = msg.details?.split('\n').join(DETAILS_INDENT); debugPrint( '$ERR_PREFIX: ${msg.message}$DETAILS_INDENT$indentedDetails'); // Show UI message if we were provided a BuildContext if (_context != null) { ScaffoldMessenger.of(_context!).showMaterialBanner( MaterialBanner( backgroundColor: Colors.deepOrange, content: Text(msg.message), actions: [ TextButton( child: const Text('Dismiss'), onPressed: () => ScaffoldMessenger.of(_context!) .hideCurrentMaterialBanner(), ), ], ), ); } break; case RidMessageType.Error: final indentedDetails = msg.details?.split('\n').join('\n '); debugPrint('$ERR_PREFIX: ${msg.message}\n $indentedDetails'); // Show UI message if we were provided a BuildContext if (_context != null) { ScaffoldMessenger.of(_context!).showSnackBar( SnackBar( backgroundColor: Colors.orange, content: Text(msg.message), ), ); } break; } }); } set context(BuildContext val) => _context = val; void dispose() { _errorSub?.cancel(); _errorSub = null; } static ErrorHandler? _instance; static ErrorHandler get instance { _instance ??= ErrorHandler._(); return _instance!; } } class UserMsgHandler { BuildContext? _context; late final StreamSubscription? _errorSub; UserMsgHandler._() { _errorSub = rid.messageChannel.stream.listen((RidMessage msg) { switch (msg.type) { case RidMessageType.LogWarn: case RidMessageType.LogInfo: case RidMessageType.LogDebug: case RidMessageType.Severe: case RidMessageType.Error: return; case RidMessageType.MsgWarn: debugPrint('$WARN_PREFIX ${msg.message}'); // Show UI message if we were provided a BuildContext if (_context != null) { ScaffoldMessenger.of(_context!).showMaterialBanner( MaterialBanner( backgroundColor: Colors.orange, content: Text(msg.message), actions: [ TextButton( child: const Text('Dismiss'), onPressed: () => ScaffoldMessenger.of(_context!) .hideCurrentMaterialBanner(), ), ], ), ); } else { debugPrint( 'WARN: cannot show user message since no `BuildContext` was provided to UserMsgHandler'); } break; case RidMessageType.MsgInfo: debugPrint('$INFO_PREFIX ${msg.message}'); // Show UI message if we were provided a BuildContext if (_context != null) { ScaffoldMessenger.of(_context!).showSnackBar( SnackBar( backgroundColor: Colors.orange, content: Text(msg.message), ), ); } else { debugPrint( 'WARN: cannot show user message since no `BuildContext` was provided to UserMsgHandler'); } break; } }); } set context(BuildContext val) => _context = val; void dispose() { _errorSub?.cancel(); _errorSub = null; } static UserMsgHandler? _instance; static UserMsgHandler get instance { _instance ??= UserMsgHandler._(); return _instance!; } } class RidMessaging { static void init() { LogMessageHandler.instance; ErrorHandler.instance; UserMsgHandler.instance; } } ================================================ FILE: flutter/reddit_ticker/lib/views/add_post.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:reddit_ticker/cubit/add_post_cubit.dart'; class AddPostView extends StatefulWidget { @override State createState() => _AddPostViewState(); } class _AddPostViewState extends State { final _textFieldController = TextEditingController(); String? addPostURL; @override Widget build(BuildContext context) { return FloatingActionButton( onPressed: () async { _textFieldController.clear(); await _addPostDialog(context); final url = addPostURL; if (url != null && url.trim().isNotEmpty) { context.read().addPost(url); } }, tooltip: 'Add URL of Post to Watch', child: BlocListener( listener: (context, state) { if (state is AddPostFailed) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( backgroundColor: Colors.red, content: Text('Failed to add Post'), ), ); } }, child: Icon(Icons.add), ), ); } Future _addPostDialog(BuildContext context) async { return showDialog( context: context, builder: (context) { return AlertDialog( title: Text('Enter Post URL'), content: TextField( controller: _textFieldController, decoration: InputDecoration( hintText: "https://www.reddit.com/r/rust/comments/ncc9vc/rid_integrate_rust_into_your_dart_or_flutter_app/", ), autofocus: true, onSubmitted: (_) => _onSubmitted(), ), actions: [ TextButton( child: Text('Done'), onPressed: _onSubmitted, ), ], ); }); } void _onSubmitted() { addPostURL = _textFieldController.value.text; Navigator.pop(context); } } ================================================ FILE: flutter/reddit_ticker/lib/views/post.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:plugin/generated/rid_api.dart'; import 'package:reddit_ticker/cubit/post_cubit.dart'; import 'package:charts_flutter/flutter.dart' as charts; charts.Series _toChartData(List scores) { return charts.Series( id: 'Scores', colorFn: (_, __) => charts.MaterialPalette.blue.shadeDefault, domainFn: (Score score, _) => score.secsSincePostAdded / 60.0, measureFn: (Score score, _) => score.score, data: scores); } class PostView extends StatelessWidget { @override Widget build(BuildContext context) { return BlocBuilder(builder: (context, state) { if (state is PostActive) { final post = state.post; final chartData = _toChartData(post.scores); final chart = charts.LineChart([chartData], animate: true); return Dismissible( key: Key("Post Dismissible ${state.post.id}"), child: Card( child: InkWell( child: ListTile( title: Center( child: MouseRegion( cursor: SystemMouseCursors.click, child: Text( post.title, style: Theme.of(context).textTheme.headline6!.copyWith( decoration: TextDecoration.underline, overflow: TextOverflow.ellipsis, color: Colors.blue, ), ), ), ), subtitle: SizedBox( height: 140, child: chart, ), onTap: () => {/* TODO: launch post url */}, ), ), ), confirmDismiss: (_) => context.read().stopWatching(), background: Padding( padding: EdgeInsets.all(5.0), child: Container(color: Colors.red), ), ); } else { return Card(child: Text('Post removed')); } }); } } ================================================ FILE: flutter/reddit_ticker/lib/views/posts.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:reddit_ticker/cubit/post_cubit.dart'; import 'package:reddit_ticker/cubit/posts_cubit.dart'; import 'package:reddit_ticker/views/post.dart'; class PostsView extends StatelessWidget { @override Widget build(BuildContext context) { return Center( child: BlocBuilder(builder: (context, state) { final posts = state.posts; return ListView.builder( itemCount: posts.length, itemBuilder: (context, index) { final post = posts[index]; return MultiBlocProvider( providers: [ BlocProvider(create: (_) => PostCubit(post)), ], child: PostView(), key: Key(post.hashCode.toString()), ); }); }), ); } } ================================================ FILE: flutter/reddit_ticker/macos/.gitignore ================================================ # Flutter-related **/Flutter/ephemeral/ **/Pods/ # Xcode-related **/xcuserdata/ ================================================ FILE: flutter/reddit_ticker/macos/Flutter/Flutter-Debug.xcconfig ================================================ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" ================================================ FILE: flutter/reddit_ticker/macos/Flutter/Flutter-Release.xcconfig ================================================ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" ================================================ FILE: flutter/reddit_ticker/macos/Flutter/GeneratedPluginRegistrant.swift ================================================ // // Generated file. Do not edit. // import FlutterMacOS import Foundation import path_provider_macos import plugin func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) Plugin.register(with: registry.registrar(forPlugin: "Plugin")) } ================================================ FILE: flutter/reddit_ticker/macos/Podfile ================================================ platform :osx, '10.11' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } def flutter_root generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) unless File.exist?(generated_xcode_build_settings_path) raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" end File.foreach(generated_xcode_build_settings_path) do |line| matches = line.match(/FLUTTER_ROOT\=(.*)/) return matches[1].strip if matches end raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" end require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) flutter_macos_podfile_setup target 'Runner' do use_frameworks! use_modular_headers! flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_macos_build_settings(target) end end ================================================ FILE: flutter/reddit_ticker/macos/Runner/AppDelegate.swift ================================================ import Cocoa import FlutterMacOS @NSApplicationMain class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } } ================================================ FILE: flutter/reddit_ticker/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "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" } } ================================================ FILE: flutter/reddit_ticker/macos/Runner/Base.lproj/MainMenu.xib ================================================ ================================================ FILE: flutter/reddit_ticker/macos/Runner/Configs/AppInfo.xcconfig ================================================ // 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 = reddit_ticker // The application's bundle identifier PRODUCT_BUNDLE_IDENTIFIER = com.example.redditTicker // The copyright displayed in application information PRODUCT_COPYRIGHT = Copyright © 2021 com.example. All rights reserved. ================================================ FILE: flutter/reddit_ticker/macos/Runner/Configs/Debug.xcconfig ================================================ #include "../../Flutter/Flutter-Debug.xcconfig" #include "Warnings.xcconfig" ================================================ FILE: flutter/reddit_ticker/macos/Runner/Configs/Release.xcconfig ================================================ #include "../../Flutter/Flutter-Release.xcconfig" #include "Warnings.xcconfig" ================================================ FILE: flutter/reddit_ticker/macos/Runner/Configs/Warnings.xcconfig ================================================ 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 ================================================ FILE: flutter/reddit_ticker/macos/Runner/DebugProfile.entitlements ================================================ com.apple.security.app-sandbox com.apple.security.cs.allow-jit com.apple.security.network.server com.apple.security.network.client ================================================ FILE: flutter/reddit_ticker/macos/Runner/Info.plist ================================================ 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 ================================================ FILE: flutter/reddit_ticker/macos/Runner/MainFlutterWindow.swift ================================================ 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() } } ================================================ FILE: flutter/reddit_ticker/macos/Runner/Release.entitlements ================================================ com.apple.security.app-sandbox com.apple.security.network.client ================================================ FILE: flutter/reddit_ticker/macos/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: flutter/reddit_ticker/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: flutter/reddit_ticker/plugin/.gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ # IntelliJ related *.iml *.ipr *.iws .idea/ # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. #.vscode/ # Flutter/Dart/Pub related # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. /pubspec.lock **/doc/api/ .dart_tool/ .packages build/ ================================================ FILE: flutter/reddit_ticker/plugin/LICENSE ================================================ TODO: Add your license here. ================================================ FILE: flutter/reddit_ticker/plugin/analysis_options.yaml ================================================ include: package:flutter_lints/flutter.yaml # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options ================================================ FILE: flutter/reddit_ticker/plugin/android/.gitignore ================================================ *.iml .gradle /local.properties /.idea/workspace.xml /.idea/libraries .DS_Store /build /captures ================================================ FILE: flutter/reddit_ticker/plugin/android/build.gradle ================================================ group 'com.example.plugin' version '1.0-SNAPSHOT' buildscript { ext.kotlin_version = '1.3.50' repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:4.1.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } rootProject.allprojects { repositories { google() mavenCentral() } } apply plugin: 'com.android.library' apply plugin: 'kotlin-android' android { compileSdkVersion 30 compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } kotlinOptions { jvmTarget = '1.8' } sourceSets { main.java.srcDirs += 'src/main/kotlin' } defaultConfig { minSdkVersion 16 } } dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" } ================================================ FILE: flutter/reddit_ticker/plugin/android/gradle/wrapper/gradle-wrapper.properties ================================================ 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 ================================================ FILE: flutter/reddit_ticker/plugin/android/gradle.properties ================================================ org.gradle.jvmargs=-Xmx1536M android.useAndroidX=true android.enableJetifier=true ================================================ FILE: flutter/reddit_ticker/plugin/android/settings.gradle ================================================ rootProject.name = 'plugin' ================================================ FILE: flutter/reddit_ticker/plugin/android/src/main/AndroidManifest.xml ================================================ ================================================ FILE: flutter/reddit_ticker/plugin/android/src/main/kotlin/com/example/plugin/Plugin.kt ================================================ package com.example.plugin import androidx.annotation.NonNull import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel.MethodCallHandler import io.flutter.plugin.common.MethodChannel.Result /** Plugin */ class Plugin: FlutterPlugin, MethodCallHandler { /// The MethodChannel that will the communication between Flutter and native Android /// /// This local reference serves to register the plugin with the Flutter Engine and unregister it /// when the Flutter Engine is detached from the Activity private lateinit var channel : MethodChannel override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { channel = MethodChannel(flutterPluginBinding.binaryMessenger, "plugin") channel.setMethodCallHandler(this) } override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { if (call.method == "getPlatformVersion") { result.success("Android ${android.os.Build.VERSION.RELEASE}") } else { result.notImplemented() } } override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { channel.setMethodCallHandler(null) } } ================================================ FILE: flutter/reddit_ticker/plugin/ios/.gitignore ================================================ .idea/ .vagrant/ .sconsign.dblite .svn/ .DS_Store *.swp profile DerivedData/ build/ GeneratedPluginRegistrant.h GeneratedPluginRegistrant.m .generated/ *.pbxuser *.mode1v3 *.mode2v3 *.perspectivev3 !default.pbxuser !default.mode1v3 !default.mode2v3 !default.perspectivev3 xcuserdata *.moved-aside *.pyc *sync/ Icon? .tags* /Flutter/Generated.xcconfig /Flutter/ephemeral/ /Flutter/flutter_export_environment.sh ================================================ FILE: flutter/reddit_ticker/plugin/ios/Assets/.gitkeep ================================================ ================================================ FILE: flutter/reddit_ticker/plugin/ios/Classes/Plugin.h ================================================ #import @interface Plugin : NSObject @end ================================================ FILE: flutter/reddit_ticker/plugin/ios/Classes/Plugin.m ================================================ #import "Plugin.h" #if __has_include() #import #else // Support project import fallback if the generated compatibility header // is not copied when this plugin is created as a library. // https://forums.swift.org/t/swift-static-libraries-dont-copy-generated-objective-c-header/19816 #import "plugin-Swift.h" #endif @implementation Plugin + (void)registerWithRegistrar:(NSObject*)registrar { [SwiftPlugin registerWithRegistrar:registrar]; } @end ================================================ FILE: flutter/reddit_ticker/plugin/ios/Classes/SwiftPlugin.swift ================================================ import Flutter import UIKit public class SwiftPlugin: NSObject, FlutterPlugin { public static func register(with registrar: FlutterPluginRegistrar) { } public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { result(nil) } } // func dummyCallsToPreventTreeShaking() { _to_dart_for_Score(); rid_score_debug(nil); rid_score_debug_pretty(nil); rid_score_secs_since_post_added(nil); rid_score_score(nil); rid_cstring_free(nil); rid_init_msg_isolate(0); rid_init_reply_isolate(0); _to_dart_for_Post(); rid_post_debug(nil); rid_post_debug_pretty(nil); __include_dart_for_vec_score(); rid_post_id(nil); rid_post_id_len(nil); rid_post_title(nil); rid_post_title_len(nil); rid_post_url(nil); rid_post_url_len(nil); rid_post_scores(nil); rid_len_vec_score(nil); rid_get_item_vec_score(nil, 0); _to_dart_for_Store(); create_store(); rid_store_unlock(); rid_store_free(); __include_dart_for_hash_map_string_post(); rid_store_posts(nil); rid_export_rid_len_hash_map_string_post(nil); rid_export_rid_get_hash_map_string_post(nil, nil); rid_export_rid_contains_key_hash_map_string_post(nil, nil); rid_export_rid_keys_hash_map_string_post(nil); __include_dart_for_ridvec_string(); rid_free_ridvec_string(RidVec_Pointer_String()); rid_get_item_ridvec_string(RidVec_Pointer_String(), 0); _include_Store_field_wrappers(); rid_msg_Initialize(0, nil); rid_msg_StartWatching(0, nil); rid_msg_StopWatching(0, nil); } // ================================================ FILE: flutter/reddit_ticker/plugin/ios/plugin.podspec ================================================ # # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. # Run `pod lib lint plugin.podspec` to validate before publishing. # Pod::Spec.new do |s| s.name = 'plugin' s.version = '0.0.1' s.summary = 'Rust bridge.' s.description = <<-DESC A flutter Rust bridge project. DESC s.homepage = 'http://example.com' s.license = { :file => '../LICENSE' } s.author = { 'Your Company' => 'email@example.com' } s.source = { :path => '.' } s.source_files = 'Classes/**/*' s.dependency 'Flutter' s.platform = :ios, '8.0' # Flutter.framework does not contain a i386 slice. s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } s.swift_version = '5.0' s.public_header_files = 'Classes**/*.h' s.static_framework = true s.vendored_libraries = '**/*.a' end ================================================ FILE: flutter/reddit_ticker/plugin/lib/plugin.dart ================================================ import 'dart:async'; import 'package:flutter/services.dart'; class Plugin { static const MethodChannel _channel = MethodChannel('plugin'); static Future get platformVersion async { final String? version = await _channel.invokeMethod('getPlatformVersion'); return version; } } ================================================ FILE: flutter/reddit_ticker/plugin/macos/Classes/Plugin.swift ================================================ import Cocoa import FlutterMacOS public class Plugin: NSObject, FlutterPlugin { public static func register(with registrar: FlutterPluginRegistrar) { } public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { result(nil) } } // func dummyCallsToPreventTreeShaking() { _to_dart_for_Score(); rid_score_debug(nil); rid_score_debug_pretty(nil); rid_score_secs_since_post_added(nil); rid_score_score(nil); rid_cstring_free(nil); rid_init_msg_isolate(0); rid_init_reply_isolate(0); _to_dart_for_Post(); rid_post_debug(nil); rid_post_debug_pretty(nil); __include_dart_for_vec_score(); rid_post_id(nil); rid_post_id_len(nil); rid_post_title(nil); rid_post_title_len(nil); rid_post_url(nil); rid_post_url_len(nil); rid_post_scores(nil); rid_len_vec_score(nil); rid_get_item_vec_score(nil, 0); _to_dart_for_Store(); create_store(); rid_store_unlock(); rid_store_free(); __include_dart_for_hash_map_string_post(); rid_store_posts(nil); rid_export_rid_len_hash_map_string_post(nil); rid_export_rid_get_hash_map_string_post(nil, nil); rid_export_rid_contains_key_hash_map_string_post(nil, nil); rid_export_rid_keys_hash_map_string_post(nil); __include_dart_for_ridvec_string(); rid_free_ridvec_string(RidVec_Pointer_String()); rid_get_item_ridvec_string(RidVec_Pointer_String(), 0); _include_Store_field_wrappers(); rid_msg_Initialize(0, nil); rid_msg_StartWatching(0, nil); rid_msg_StopWatching(0, nil); } // ================================================ FILE: flutter/reddit_ticker/plugin/macos/plugin.podspec ================================================ # # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. # Run `pod lib lint plugin.podspec` to validate before publishing. # Pod::Spec.new do |s| s.name = 'plugin' s.version = '0.0.1' s.summary = 'Rust bridge.' s.description = <<-DESC A flutter Rust bridge project. DESC s.homepage = 'http://example.com' s.license = { :file => '../LICENSE' } s.author = { 'Your Company' => 'email@example.com' } s.source = { :path => '.' } s.source_files = 'Classes/**/*' s.dependency 'FlutterMacOS' s.platform = :osx, '10.11' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } s.swift_version = '5.0' s.public_header_files = 'Classes**/*.h' s.static_framework = true s.vendored_libraries = '**/*.a' end ================================================ FILE: flutter/reddit_ticker/plugin/pubspec.yaml ================================================ name: plugin description: Plugin to provide a bridge to Rust. version: 0.0.1 environment: sdk: ">=2.13.0 <3.0.0" flutter: ">=2.0.0" dependencies: ffi: ^1.0.0 ffigen: 4.0.0-dev.2 flutter: sdk: flutter flutter: # This section identifies this Flutter project as a plugin project. # The 'pluginClass' and Android 'package' identifiers should not ordinarily # be modified. They are used by the tooling to maintain consistency when # adding or updating assets for this project. plugin: platforms: android: package: com.example.plugin pluginClass: Plugin ios: pluginClass: Plugin macos: pluginClass: Plugin ================================================ FILE: flutter/reddit_ticker/pubspec.yaml ================================================ name: reddit_ticker description: A new Flutter project. publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 1.0.0+1 environment: sdk: ">=2.13.0 <3.0.0" dependencies: flutter: sdk: flutter plugin: path: plugin cupertino_icons: ^1.0.2 bloc: ^7.1.0 flutter_bloc: ^7.2.0 charts_flutter: path: ./deps/charts/charts_flutter path_provider: ^2.0.5 dev_dependencies: flutter_test: sdk: flutter flutter: uses-material-design: true assets: - assets/dash.png - assets/ferris.png ================================================ FILE: flutter/reddit_ticker/reddit_ticker.iml ================================================ ================================================ FILE: flutter/reddit_ticker/rid_build.rs ================================================ use rid_build::{build, BuildConfig, BuildTarget, FlutterConfig, FlutterPlatform, Project}; use std::env; fn main() { let crate_dir = env::var("CARGO_MANIFEST_DIR") .expect("Missing CARGO_MANIFEST_DIR, please run this via 'cargo run'"); let workspace_dir = &crate_dir; let crate_name = &env::var("CARGO_PKG_NAME") .expect("Missing CARGO_PKG_NAME, please run this via 'cargo run'"); let lib_name = &format!("lib{}", &crate_name); let project = match &env::var("TEST_DART") { Ok(_) => Project::Dart, Err(_) => Project::Flutter(FlutterConfig { plugin_name: "plugin".to_string(), platforms: vec![ // Mobile FlutterPlatform::ios(), FlutterPlatform::android(), // Desktop FlutterPlatform::macos(), ], }), }; let build_config = BuildConfig { target: BuildTarget::Debug, project, lib_name, crate_name, project_root: &crate_dir, workspace_root: Some(&workspace_dir), }; build(&build_config).expect("Build failed"); } ================================================ FILE: flutter/reddit_ticker/sh/android ================================================ #!/usr/bin/env bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" ANDROID_PLATFORM_VERSION=28 # x86 (i686) # ANDROID_BUILD_TARGET=i686-linux-android # x86_64 # ANDROID_BUILD_TARGET=x86_64-linux-android # arm64 ANDROID_BUILD_TARGET=arm64-v8a # armeabi # ANDROID_BUILD_TARGET=armeabi-v7a ANDROID_DIR=$DIR/../plugin/android JNI_LIBS_DIR=$ANDROID_DIR/src/main/jniLibs # Install Android NDK https://developer.android.com/studio/projects/install-ndk # https://github.com/bbqsrc/cargo-ndk cargo ndk \ --platform $ANDROID_PLATFORM_VERSION \ --target $ANDROID_BUILD_TARGET \ --output-dir $JNI_LIBS_DIR \ build ================================================ FILE: flutter/reddit_ticker/sh/bindgen ================================================ #!/usr/bin/env bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" cd $DIR/.. && cargo run rid_build ================================================ FILE: flutter/reddit_ticker/sh/bindgen-dart ================================================ #!/usr/bin/env bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" cd $DIR/.. && TEST_DART=1 cargo run rid_build ================================================ FILE: flutter/reddit_ticker/sh/clean ================================================ #!/usr/bin/env bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" (cd $DIR/../plugin && flutter clean && flutter pub get) (cd $DIR/.. && flutter clean && flutter pub get) ================================================ FILE: flutter/reddit_ticker/sh/ios ================================================ #!/usr/bin/env bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" IOS_TARGETS=x86_64-apple-ios # Alternative not using jq # WORKSPACE_CARGO_TOML=`cargo locate-project --workspace --message-format plain` # WORKSPACE_ROOT=$(dirname "${WORKSPACE_CARGO_TOML}") # TARGET_DIR=$WORKSPACE_ROOT/target TARGET_DIR=`cargo metadata --format-version 1 --no-deps | jq ".target_directory" | xargs echo` PROJECT_NAME=`cargo metadata --format-version 1 | jq ".resolve.root" | xargs echo | cut -d ' ' -f1` LIB_NAME=lib$PROJECT_NAME.a # /target/universal/debug UNIVERSAL_DEBUG_DIR="$TARGET_DIR/universal/debug" FLUTTER_IOS_DIR="$DIR/../plugin/ios" LIB_SOURCE_FILE="$UNIVERSAL_DEBUG_DIR/$LIB_NAME" LIB_TARGET_FILE="$FLUTTER_IOS_DIR/$LIB_NAME" cargo lipo --targets $IOS_TARGETS && \ cp $LIB_SOURCE_FILE $LIB_TARGET_FILE ================================================ FILE: flutter/reddit_ticker/sh/linux ================================================ #!/usr/bin/env bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" TARGET_DIR=`cargo metadata --format-version 1 --no-deps | jq ".target_directory" | xargs echo` PROJECT_NAME=`cargo metadata --format-version 1 | jq ".resolve.root" | xargs echo | cut -d ' ' -f1` LIB_NAME_STATIC=lib$PROJECT_NAME.a LIB_NAME_DYNAMIC=lib$PROJECT_NAME.so # /target/universal/debug UNIVERSAL_DEBUG_DIR="$TARGET_DIR/debug" FLUTTER_LINUX_DIR="$DIR/../plugin/linux" mkdir -p $FLUTTER_LINUX_DIR LIB_SOURCE_FILE_STATIC="$UNIVERSAL_DEBUG_DIR/$LIB_NAME_STATIC" LIB_SOURCE_FILE_DYNAMIC="$UNIVERSAL_DEBUG_DIR/$LIB_NAME_DYNAMIC" LIB_TARGET_FILE_STATIC="$FLUTTER_LINUX_DIR/$LIB_NAME_STATIC" LIB_TARGET_FILE_DYNAMIC="$FLUTTER_LINUX_DIR/$LIB_NAME_DYNAMIC" cargo build && \ cp $LIB_SOURCE_FILE_STATIC $LIB_TARGET_FILE_STATIC && \ cp $LIB_SOURCE_FILE_DYNAMIC $LIB_TARGET_FILE_DYNAMIC exit $? ================================================ FILE: flutter/reddit_ticker/sh/macos ================================================ #!/usr/bin/env bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" MACOS_TARGETS=aarch64-apple-ios TARGET_DIR=`cargo metadata --format-version 1 --no-deps | jq ".target_directory" | xargs echo` PROJECT_NAME=`cargo metadata --format-version 1 | jq ".resolve.root" | xargs echo | cut -d ' ' -f1` LIB_NAME=lib$PROJECT_NAME.a # /target/universal/debug UNIVERSAL_DEBUG_DIR="$TARGET_DIR/debug" FLUTTER_MACOS_DIR="$DIR/../plugin/macos" LIB_SOURCE_FILE="$UNIVERSAL_DEBUG_DIR/$LIB_NAME" LIB_TARGET_FILE="$FLUTTER_MACOS_DIR/$LIB_NAME" cargo build && \ cp $LIB_SOURCE_FILE $LIB_TARGET_FILE ================================================ FILE: flutter/reddit_ticker/sql/all-posts.sql ================================================ SELECT * from reddit_posts; ================================================ FILE: flutter/reddit_ticker/sql/all-scores.sql ================================================ SELECT * from reddit_scores; ================================================ FILE: flutter/reddit_ticker/src/db.rs ================================================ use std::time::{Duration, SystemTime, UNIX_EPOCH}; use anyhow::{anyhow, Result}; use rusqlite::{params, Connection, Row, NO_PARAMS}; use crate::reddit::{Post, Score}; pub const DB_NAME: &str = "reddit_ticker.sqlite"; pub struct DB { conn: Connection, } impl DB { pub fn new(path: &str) -> Result { let conn = Connection::open(path) .map_err(|err| anyhow!("Failed to open Database at: {}\nError: {}", path, err))?; let db = Self { conn }; db.init_tables()?; Ok(db) } fn init_tables(&self) -> Result<()> { self.conn .execute_batch( " BEGIN; CREATE TABLE IF NOT EXISTS reddit_posts ( post_id TEXT PRIMARY KEY, title TEXT, url TEXT, added INTEGER ); CREATE TABLE IF NOT EXISTS reddit_scores ( post_id TEXT, added INTEGER, score INTEGER ); CREATE INDEX IF NOT EXISTS idx_post_id ON reddit_scores (post_id); COMMIT; ", ) .map_err(|err| anyhow!("Failed to create Database tables:\nError: {}", err)) } // ----------------- // Insert Posts and Scores // ----------------- pub fn insert_post(&self, post: &Post) -> Result { let added: u32 = time_stamp_to_secs(post.added); self.conn .execute( " INSERT OR IGNORE INTO reddit_posts (post_id, title, url, added) VALUES (?1, ?2, ?3, ?4); ", params![post.id, post.title, post.url, added], ) .map_err(|err| anyhow!("Failed to add post with id {}:\nError: {}", post.id, err)) } pub fn insert_score(&self, post_id: &str, time_stamp: SystemTime, score: i32) -> Result { let added = time_stamp_to_secs(time_stamp); let res = self .conn .execute( " INSERT OR IGNORE INTO reddit_scores (post_id, added, score) VALUES (?1, ?2, ?3); ", params!(post_id, added, score), ) .map_err(|err| { anyhow!( "Failed to insert score for post {}:\nError: {}", post_id, err ) })?; Ok(res) } // ----------------- // Retrieving Posts and Scores // ----------------- pub fn get_scores(&self, post: &Post) -> Result> { let mut stmt = self.conn.prepare( " SELECT added, score FROM reddit_scores WHERE post_id = (?1) ", )?; let results: Vec<_> = stmt .query_map(params!(post.id), |row| try_extract_score(row, post.added))? .filter_map(|x| match x { Ok(score) => Some(score), Err(err) => { rid::log_warn!("Found invalid score in Database {}", err.to_string()); None } }) .collect(); Ok(results) } pub fn get_all_posts(&self) -> Result> { let mut stmt = self.conn.prepare( " SELECT post_id, title, url, added FROM reddit_posts; ", )?; let results = stmt.query_map(NO_PARAMS, try_extract_post)?; let mut posts: Vec<_> = results .filter_map(|res| match res { Ok(post) => Some(post), Err(err) => { rid::error!("A post couldn't be properly extracted", err.to_string()); None } }) .collect(); for mut post in posts.iter_mut() { let scores = self.get_scores(&post)?; post.scores = scores } Ok(posts) } // ----------------- // Deleting Posts and Scores // ----------------- pub fn delete_post(&self, post_id: &str) -> Result { let post_rows_removed = self .conn .execute( " DELETE FROM reddit_posts WHERE post_id = (?1); ", params!(post_id), ) .map_err(|err| anyhow!("Failed to remove post from table:\nError: {}", err))?; let score_rows_removed = self .conn .execute( " DELETE FROM reddit_scores WHERE post_id = (?1); ", params!(post_id), ) .map_err(|err| anyhow!("Failed to remove scores from table:\nError: {}", err))?; Ok(post_rows_removed + score_rows_removed) } } // ----------------- // Sqlite helpers // ----------------- fn try_extract_score(row: &Row, post_added: SystemTime) -> rusqlite::Result { // Score timestamps are stored [UNIX_EPOCH] seconds, however the rest // of the app treats score timestamps based on the time the post was added. let secs: u32 = row.get(0)?; let time_stamp = secs_to_time_stamp(secs); let secs_since_post_added = time_stamp .duration_since(post_added) .expect("Invalid timestamp") .as_secs(); let score: i32 = row.get(1)?; Ok(Score { secs_since_post_added, score, }) } fn try_extract_post(row: &Row) -> rusqlite::Result { Ok(Post { id: row.get(0)?, title: row.get(1)?, url: row.get(2)?, added: secs_to_time_stamp(row.get(3)?), scores: vec![], }) } fn time_stamp_to_secs(time_stamp: SystemTime) -> u32 { // max u32: 4,294,967,295 // UNIX_EPOCH secs: ~1,631,804,843 time_stamp.duration_since(UNIX_EPOCH).unwrap().as_secs() as u32 } fn secs_to_time_stamp(secs: u32) -> SystemTime { UNIX_EPOCH + Duration::from_secs(secs as u64) } ================================================ FILE: flutter/reddit_ticker/src/lib.rs ================================================ use core::time; use std::{ collections::HashMap, path::Path, sync::{RwLockReadGuard, RwLockWriteGuard}, thread, time::SystemTime, }; use anyhow::{anyhow, Result}; use db::{DB, DB_NAME}; use reddit::{query_page, Post}; use rid::RidStore; use crate::reddit::{query_score, Score, RESOLUTION_MILLIS}; mod db; mod reddit; // ----------------- // Store // ----------------- #[rid::store] #[rid::structs(Post)] #[derive(rid::Config)] pub struct Store { posts: HashMap, #[rid(skip)] polling: bool, #[rid(skip)] db: Option, } impl RidStore for Store { fn create() -> Self { Self { posts: HashMap::new(), polling: false, db: None, } } fn update(&mut self, req_id: u64, msg: Msg) { match msg { Msg::Initialize(app_dir) => { // Guard against more than one polling thread if !self.polling { self.polling = true; poll_posts(); } if self.db.is_none() { let db_path = Path::new(&app_dir) .join(DB_NAME) .to_string_lossy() .to_string(); match DB::new(&db_path) { Ok(db) => { self.db = Some(db); rid::log_info!("Initialized Database at '{}'", db_path); } Err(err) => { rid::severe!( format!("Failed to open Database at '{}'", db_path), err.to_string() ); } } } if let Some(db) = &self.db { self.posts = match db.get_all_posts() { Ok(posts) => { let mut map = HashMap::::new(); for post in posts { map.insert(post.id.clone(), post); } map } Err(err) => { rid::error!("Failed to retrieve existings posts", err); HashMap::new() } }; } rid::post(Reply::Initialized(req_id)); } Msg::StartWatching(url) => start_watching(req_id, url), Msg::StopWatching(id) => { self.posts.remove(&id); if let Some(db) = &self.db { match db.delete_post(&id) { Ok(rows) => { rid::log_debug!("Removed post and {} scores from Database", rows - 1); } Err(err) => { rid::error!("Failed to delete post from Database", err); } }; }; rid::post(Reply::StoppedWatching(req_id, id)); } } } } impl Store { fn read() -> RwLockReadGuard<'static, Store> { store::read() } fn write() -> RwLockWriteGuard<'static, Store> { store::write() } } // ----------------- // Message // ----------------- #[rid::message(Reply)] enum Msg { Initialize(String), StartWatching(String), StopWatching(String), } // ----------------- // Reply // ----------------- #[rid::reply] enum Reply { Initialized(u64), StartedWatching(u64, String), StoppedWatching(u64, String), FailedRequest(u64, String), UpdatedScores, } // ----------------- // Start watching Post // ----------------- fn start_watching(req_id: u64, url: String) { thread::spawn(move || match try_start_watching(url) { Ok(post) => { let id = post.id.clone(); Store::write().posts.insert(id.clone(), post); rid::post(Reply::StartedWatching(req_id, id)) } Err(err) => rid::post(Reply::FailedRequest(req_id, err.to_string())), }); } fn try_start_watching(url: String) -> Result { let page = query_page(&url) .map_err(|err| anyhow!("Failed to get valid page data: {}\nError: {} ", url, err))?; rid::log_debug!("Got page for url '{}' with id '{}'.", url, page.id); let added = SystemTime::now(); let post = Post { added, id: page.id, title: page.title, url: page.url, scores: vec![], }; if let Some(db) = Store::read().db.as_ref() { if let Err(err) = db.insert_post(&post) { rid::error!("Failed to insert post", err.to_string()); } } Ok(post) } // ----------------- // Polling Scores // ----------------- fn poll_posts() { rid::log_debug!("Creating thread to poll post data"); thread::spawn(move || loop { // First we query all posts and only take a write lock on the store once we have all the // data in order to limit the amount of time that the UI or other threads cannot access the // store. // In order to release the read lock on the store immediately, we clone the post ids. let post_ids: Vec<_> = { Store::read().posts.keys().cloned().collect() }; let scores: Vec<_> = post_ids .into_iter() .map(|id| { let score = query_score(&id); (id, score) }) // Filter out all cases where we couldn't update the score and send an error so that we // can log the problem and alert the user .filter_map(|(id, score_res)| match score_res { Ok(score) => Some((id, score)), Err(err) => { rid::error!("Failed to update score for a post", err.to_string()); None } }) .collect(); { // Aquire a write lock on the store once and make sure it gets dropped (at the end of // this block) when we no longer need it let mut store = Store::write(); for (id, score) in scores { // A post could have been removed in between getting the post ids and aquiring // the write lock. if !store.posts.contains_key(&id) { continue; } let time_stamp = SystemTime::now(); let post = &mut store.posts.get_mut(&id).unwrap(); let secs_since_post_added = time_stamp .duration_since(post.added) .expect("Getting duration") .as_secs(); post.scores.push(Score { secs_since_post_added, score, }); if let Some(db) = &store.db.as_ref() { if let Err(err) = db.insert_score(&id, time_stamp, score) { rid::error!("Failed to add score for post", err.to_string()); } } } } rid::post(Reply::UpdatedScores); thread::sleep(time::Duration::from_millis(RESOLUTION_MILLIS)); }); } ================================================ FILE: flutter/reddit_ticker/src/reddit/mod.rs ================================================ #![allow(unused_variables, dead_code)] mod reddit; mod reddit_api_response; mod reddit_page_response; use std::time::SystemTime; pub use reddit::*; pub use reddit_api_response::*; pub use reddit_page_response::*; pub const RESOLUTION_MILLIS: u64 = 5_000; // ----------------- // Reddit Page // ----------------- #[derive(Debug, Clone)] pub struct Page { pub id: String, pub title: String, pub url: String, } // ----------------- // Reddit Score // ----------------- #[rid::model] #[derive(Debug)] pub struct Score { pub secs_since_post_added: u64, pub score: i32, } // ----------------- // Reddit Post // ----------------- #[rid::model] #[rid::structs(Score)] #[derive(Debug, rid::Config)] pub struct Post { #[rid(skip)] pub added: SystemTime, pub id: String, pub title: String, pub url: String, pub scores: Vec, } ================================================ FILE: flutter/reddit_ticker/src/reddit/reddit.rs ================================================ use anyhow::{anyhow, Result}; use crate::reddit::ApiRoot; use super::{Page, PageRoot}; pub fn query_page(url: &str) -> Result { // Cut off query string let url = match url.find("?") { Some(idx) => &url[..idx], None => url, }; let url = format!("{}.json", url.trim_end_matches("/")); let page_response: PageRoot = ureq::get(&url) .set("User-Agent", "reddit-ticker") .call()? .into_json()?; // .data.children[0].data.{title, id} let data = &page_response .first() .ok_or_else(|| anyhow!("Page response did not contain any pages"))? .data .children .first() .ok_or_else(|| anyhow!("The page did not contain any childre"))? .data; let id = data.name.clone(); let title = data .title .as_ref() .ok_or_else(|| anyhow!("Page was missing a title"))? .clone(); let url = data .url .as_ref() .ok_or_else(|| anyhow!("Page was missing a url"))? .clone(); Ok(Page { id, title, url }) } const API_INFO_URL: &str = "https://api.reddit.com/api/info"; pub fn query_score(id: &str) -> Result { let url = format!("{}?id={}", API_INFO_URL, id); let api_response: ApiRoot = ureq::get(&url) .set("User-Agent", "reddit-ticker") .call()? .into_json()?; let score = api_response .data .children .first() .ok_or_else(|| anyhow!("Post info was missing children"))? .data .score; Ok(score) } ================================================ FILE: flutter/reddit_ticker/src/reddit/reddit_api_response.rs ================================================ use serde::{Deserialize, Serialize}; #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct ApiRoot { pub data: Data, } #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Data { pub children: Vec, } #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Children { pub data: Data2, } #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Data2 { pub score: i32, } ================================================ FILE: flutter/reddit_ticker/src/reddit/reddit_page_response.rs ================================================ use serde::{Deserialize, Serialize}; pub type PageRoot = Vec; #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct RedditPage { pub data: ChildContainer, } #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct ChildContainer { pub children: Vec, } #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Children { pub data: ChildData, } #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct ChildData { pub name: String, pub title: Option, pub url: Option, } ================================================ FILE: flutter/reddit_ticker/test/logging.dart ================================================ import 'dart:async'; import 'package:reddit_ticker/generated/rid_api.dart'; const RUST_ICON = '🦀'; const WARN_ICON = '⚠️ '; const INFO_ICON = '💡'; const DEBG_ICON = '🪲'; const ERR_ICON = '❌'; const WARN_PREFIX = '$RUST_ICON $WARN_ICON'; const INFO_PREFIX = '$RUST_ICON $INFO_ICON'; const DEBG_PREFIX = '$RUST_ICON $DEBG_ICON'; const ERR_PREFIX = '$RUST_ICON $ERR_ICON'; const DETAILS_INDENT = '\n '; class LogMessageHandler { late final StreamSubscription? _logMessagesSub; LogMessageHandler._() { _logMessagesSub = rid.messageChannel.stream.listen((RidMessage msg) { late final String prefix; switch (msg.type) { case RidMessageType.Severe: case RidMessageType.Error: return; case RidMessageType.LogWarn: prefix = WARN_PREFIX; break; case RidMessageType.LogInfo: prefix = INFO_PREFIX; break; case RidMessageType.LogDebug: prefix = DEBG_PREFIX; break; } print('$prefix: ${msg.message}'); }); } void dispose() { _logMessagesSub?.cancel(); _logMessagesSub = null; } static LogMessageHandler? _instance; static LogMessageHandler get instance { if (_instance == null) { _instance = LogMessageHandler._(); } return _instance!; } } class ErrorHandler { late final StreamSubscription? _errorSub; ErrorHandler._() { _errorSub = rid.messageChannel.stream.listen((RidMessage msg) { switch (msg.type) { case RidMessageType.LogWarn: case RidMessageType.LogInfo: case RidMessageType.LogDebug: return; case RidMessageType.Severe: final indentedDetails = msg.details?.split('\n').join(DETAILS_INDENT); print('$ERR_PREFIX: ${msg.message}$DETAILS_INDENT$indentedDetails'); break; case RidMessageType.Error: final indentedDetails = msg.details?.split('\n').join('\n '); print('$ERR_PREFIX: ${msg.message}\n $indentedDetails'); break; } }); } void dispose() { _errorSub?.cancel(); _errorSub = null; } static ErrorHandler? _instance; static ErrorHandler get instance { if (_instance == null) { _instance = ErrorHandler._(); } return _instance!; } } class RidMessaging { static void init() { LogMessageHandler.instance; ErrorHandler.instance; } } ================================================ FILE: flutter/reddit_ticker/test/wip.dart ================================================ import 'package:reddit_ticker/generated/rid_api.dart'; import 'logging.dart'; void main() async { RidMessaging.init(); final store = Store.instance; print('${store.posts}'); } ================================================ FILE: flutter/todo/.gitignore ================================================ .DS_Store .idea/ .metadata ## Flutter ### # Flutter/Dart/Pub related **/doc/api/ .dart_tool/ .flutter-plugins .flutter-plugins-dependencies .fvm/ .packages .pub-cache/ .pub/ build/ coverage/ lib/generated_plugin_registrant.dart # For library packages, don’t commit the pubspec.lock file. # Regenerating the pubspec.lock file lets you test your package against the latest compatible versions of its dependencies. # See https://dart.dev/guides/libraries/private-files#pubspeclock #pubspec.lock # Android related **/android/**/gradle-wrapper.jar **/android/.gradle **/android/captures/ **/android/gradlew **/android/gradlew.bat **/android/key.properties **/android/local.properties **/android/**/GeneratedPluginRegistrant.java # iOS/XCode related **/ios/**/*.mode1v3 **/ios/**/*.mode2v3 **/ios/**/*.moved-aside **/ios/**/*.pbxuser **/ios/**/*.perspectivev3 **/ios/**/*sync/ **/ios/**/.sconsign.dblite **/ios/**/.tags* **/ios/**/.vagrant/ **/ios/**/DerivedData/ **/ios/**/Icon? **/ios/**/Pods/ **/ios/**/.symlinks/ **/ios/**/profile **/ios/**/xcuserdata **/ios/.generated/ **/ios/Flutter/.last_build_id **/ios/Flutter/App.framework **/ios/Flutter/Flutter.framework **/ios/Flutter/Flutter.podspec **/ios/Flutter/Generated.xcconfig **/ios/Flutter/app.flx **/ios/Flutter/app.zip **/ios/Flutter/flutter_assets/ **/ios/Flutter/flutter_export_environment.sh **/ios/ServiceDefinitions.json **/ios/Runner/GeneratedPluginRegistrant.* # Exceptions to above rules. !**/ios/**/default.mode1v3 !**/ios/**/default.mode2v3 !**/ios/**/default.pbxuser !**/ios/**/default.perspectivev3 !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages ### Rust ### **/target/** **/Cargo.lock ### Rid ### **/generated/** **/Classes/bindings.h **/macos/*.a **/ios/*.a **/android/src/main/jniLibs/* ================================================ FILE: flutter/todo/Cargo.toml ================================================ [package] name = "todo" version = "0.1.0" authors = ["Thorsten Lorenz "] edition = "2018" [lib] crate-type = ["cdylib", "staticlib" ] [[bin]] name = "rid_build" path = "rid_build.rs" [dependencies] cbindgen = "0.18.0" rid = { path = "../../../rid" } rid_build = { path = "../../../rid/rid-build" } [build-dependencies] rid_build = { path = "../../../rid/rid-build" } ================================================ FILE: flutter/todo/README.md ================================================ # Todo App Rust integrated Dart Flutter Project ## Tutorial A tutorial on how to build this app will be provided shortly on the [rid homepage](https://thlorenz.com/rid-site/docs/examples/flutter-todo-app/). ## Getting Started _Please see [Caveats](#Caveats) first_. Use the below scripts to get the app ready to run with Flutter. ### 0. Make sure you have the dependencies ```sh # Install the nightly to use some needed features rustup toolchain install nightly # Switch to the nightly rustup default nightly ``` On linux, you also need ```sh sudo apt-get install libclang-dev jq ``` for flutter's `ffigen`. ### 1. Generate Glue Code Before generating the glue code for the first time, you need to run `flutter pub get` on the plugin folder ```sh cd plugin flutter pub get ``` ```sh ./sh/bindgen ``` ### 2. Build For Desired Target/Device Run any of the below three to build the binary for the specific device and have it placed into the devices specific plugin folder. For macos: ```sh ./sh/macos ``` For linux: ```sh ./sh/linux ``` ### 3. Run with Flutter Run on the device. ```sh flutter run -d macos ``` ```sh flutter run -d linux ``` For linux, you should pass the path to `libtodo.so`: On `~/rid-examples/flutter/todo$`: ``` LD_LIBRARY_PATH=$PWD/plugin/linux flutter run -d linux ``` ### 4. Develop Run step `1` whenever a function exposed to Flutter changes. Run step `2` whenever any of your Rust code changes. **Note** that to apply changes from Rust you need to restart the app to reload the compiled binary. A hot restart/reload does not achieve this. ## Folder Structure ``` ├── android ├── ios ├── macos ├── lib ├── plugin │ ├── android │ ├── ios │ ├── macos │ └── lib └── src ``` ### `./plugin` Provides connection from Flutter to Rust. Rust binaries are placed into the respective plugin folders `./ios, ./macos, ./android` when they are built. Generated Dart glue code is placed inside `./plugin/lib/generated` while `./plugin/lib/plugin.dart` just exposes the API to the app. ### `./src` Contains the starter Rust code inside `./src/lib.rs`. Keep developing the Rust part of your app here. ### `./lib` Contains the starter Flutter app inside `./lib/main.dart`. ### `./sh` Provides scripts to run build and code generation tasks. In the future a tool will provide the functionality currently provided by these scripts. - `bindgen` generates the `binding.h` header file for the extern Rust functions found inside `./src`. These are then placed inside the `./plugin` device folders were needed as well as `./plugin/lib/generated/binding.h` where they are used to generate Dart glue code - `ffigen` generates Dart glue code inside `./plugin/lib/generated/ffigen_binding.dart` using `./plugin/lib/generated/binding.h` as input - `./android` builds the Rust binary to run on Android devices/emulators and places it inside `./plugin/lib/android` - `./ios` builds the Rust binary to run on IOS devices/emulators and places it inside `./plugin/lib/ios` - `./macos` builds the Rust binary to run on MacOs directly and places it inside `./plugin/lib/macos`, this is the same format as running `cargo build` on your Mac ## Caveats At this point _Rid_ hasn't been published, therefore the build step cannot be performed and this example only serves to demonstrate what is possible once it _is_ published and open sourced. For more information please see [_Is Rid Open Sourced?_](../../README.md#is-rid-open-sourced) ================================================ FILE: flutter/todo/android/.gitignore ================================================ 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 **/*.keystore **/*.jks ================================================ FILE: flutter/todo/android/app/build.gradle ================================================ 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 compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } kotlinOptions { jvmTarget = '1.8' } 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.todo" 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" } ================================================ FILE: flutter/todo/android/app/src/debug/AndroidManifest.xml ================================================ ================================================ FILE: flutter/todo/android/app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: flutter/todo/android/app/src/main/kotlin/com/example/todo/MainActivity.kt ================================================ package com.example.todo import io.flutter.embedding.android.FlutterActivity class MainActivity: FlutterActivity() { } ================================================ FILE: flutter/todo/android/app/src/main/res/drawable/launch_background.xml ================================================ ================================================ FILE: flutter/todo/android/app/src/main/res/drawable-v21/launch_background.xml ================================================ ================================================ FILE: flutter/todo/android/app/src/main/res/values/styles.xml ================================================ ================================================ FILE: flutter/todo/android/app/src/main/res/values-night/styles.xml ================================================ ================================================ FILE: flutter/todo/android/app/src/profile/AndroidManifest.xml ================================================ ================================================ FILE: flutter/todo/android/build.gradle ================================================ 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 } ================================================ FILE: flutter/todo/android/gradle/wrapper/gradle-wrapper.properties ================================================ #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 ================================================ FILE: flutter/todo/android/gradle.properties ================================================ org.gradle.jvmargs=-Xmx1536M android.useAndroidX=true android.enableJetifier=true ================================================ FILE: flutter/todo/android/settings.gradle ================================================ 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" ================================================ FILE: flutter/todo/android/todo_android.iml ================================================ ================================================ FILE: flutter/todo/ios/.gitignore ================================================ *.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 ================================================ FILE: flutter/todo/ios/Flutter/AppFrameworkInfo.plist ================================================ 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 ================================================ FILE: flutter/todo/ios/Flutter/Debug.xcconfig ================================================ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" ================================================ FILE: flutter/todo/ios/Flutter/Release.xcconfig ================================================ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" ================================================ FILE: flutter/todo/ios/Podfile ================================================ # Uncomment this line to define a global platform for your project # platform :ios, '9.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } def flutter_root generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) unless File.exist?(generated_xcode_build_settings_path) raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" end File.foreach(generated_xcode_build_settings_path) do |line| matches = line.match(/FLUTTER_ROOT\=(.*)/) return matches[1].strip if matches end raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" end require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) flutter_ios_podfile_setup target 'Runner' do use_frameworks! use_modular_headers! flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) end end ================================================ FILE: flutter/todo/ios/Runner/AppDelegate.swift ================================================ 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) } } ================================================ FILE: flutter/todo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "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" } } ================================================ FILE: flutter/todo/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "LaunchImage.png", "scale" : "1x" }, { "idiom" : "universal", "filename" : "LaunchImage@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "LaunchImage@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: flutter/todo/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md ================================================ # 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. ================================================ FILE: flutter/todo/ios/Runner/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: flutter/todo/ios/Runner/Base.lproj/Main.storyboard ================================================ ================================================ FILE: flutter/todo/ios/Runner/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName todo 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 ================================================ FILE: flutter/todo/ios/Runner/Runner-Bridging-Header.h ================================================ #import "GeneratedPluginRegistrant.h" ================================================ FILE: flutter/todo/ios/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: flutter/todo/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: flutter/todo/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings ================================================ PreviewsEnabled ================================================ FILE: flutter/todo/lib/main.dart ================================================ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:plugin/generated/rid_api.dart'; import 'package:todo/views/menu.dart'; import 'package:todo/views/todos.dart'; const Color FILTER_SELECTED_COLOR = Colors.blue; const Color FILTER_UNSELECTED_COLOR = Colors.black; void configRid() { rid.debugReply = (reply) => debugPrint('$reply'); } void main() async { configRid(); await Store.instance.msgSetAutoExpireCompletedTodos(false); runApp(TodoApp()); } class TodoApp extends StatelessWidget with StatelessLock { @override Widget build(BuildContext context) { debugPrint(' build: TodoApp'); return MaterialApp( title: 'Rust/Flutter Todo App', theme: ThemeData( primarySwatch: Colors.blue, ), home: TodosPage(title: 'Todo App'), debugShowCheckedModeBanner: false, ); } } class TodosPage extends StatefulWidget with StatefulLock { final String title; TodosPage({Key? key, required this.title}) : super(key: key); @override _TodosPageState createState() => _TodosPageState(); } class _TodosPageState extends State with StateAsync { late final StreamSubscription sub; final _textFieldController = TextEditingController(); String? addTodoTitle; final Store _store = Store.instance; @override void initState() { // Completed todos are expired on a separate thread from Rust, i.e. not in // direct response to a user message. // We subscribe to this event here to update the list of filtered todos // when that happens. sub = rid.replyChannel.stream .where((reply) => reply.type == Reply.CompletedTodoExpired) .listen((_) { setState(() {}); }); super.initState(); } @override void dispose() { sub.cancel(); super.dispose(); } @override Widget build(BuildContext context) { debugPrint(' build: TodosPage'); final Filter filter = _store.filter; final filteredTodos = _store.filteredTodos(); final settings = _store.settings; debugPrint("filtered: \n ${filteredTodos.join('\n ')}"); return SafeArea( child: Scaffold( appBar: AppBar( title: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(widget.title), Row( children: [ Image.asset( "assets/dash.png", height: 40.0, width: 40.0, ), Icon(Icons.favorite, color: Colors.red), Image.asset( "assets/ferris.png", height: 50.0, width: 50.0, ), ], ) ], ), ), drawer: Drawer( child: Menu( settings, restartAll: () => setStateAsync(_store.msgRestartAll), completeAll: () => setStateAsync(_store.msgCompleteAll), removeCompleted: () => setStateAsync(_store.msgRemoveCompleted), setAutoExpireCompleted: (val) => setStateAsync(() => _store.msgSetAutoExpireCompletedTodos(val)), ), ), bottomNavigationBar: BottomAppBar( child: Row( children: [ IconButton( icon: Icon( Icons.calendar_today_rounded, color: filter == Filter.Pending ? FILTER_SELECTED_COLOR : FILTER_UNSELECTED_COLOR, ), onPressed: () => setStateAsync(() => _store.msgSetFilter(Filter.Pending)), ), Spacer(), IconButton( icon: Icon( Icons.check, color: filter == Filter.Completed ? FILTER_SELECTED_COLOR : FILTER_UNSELECTED_COLOR, ), onPressed: () => setStateAsync(() => _store.msgSetFilter(Filter.Completed)), ), IconButton( icon: Icon( Icons.all_inclusive, color: filter == Filter.All ? FILTER_SELECTED_COLOR : FILTER_UNSELECTED_COLOR, ), onPressed: () => setStateAsync(() => _store.msgSetFilter(Filter.All)), ), ], ), ), body: TodosView( filteredTodos, settings, getTodoById: (id) => _store.todoById(id), onToggleTodo: (id) async { await _store.msgToggleTodo(id); setState(() {}); }, onRemoveTodo: (id) async { await _store.msgRemoveTodo(id); setState(() {}); }, ), floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked, floatingActionButton: FloatingActionButton( onPressed: () async { _textFieldController.clear(); await _addTodoDialog(context); if (addTodoTitle != null && addTodoTitle!.trim().isNotEmpty) { await _store.msgAddTodo(addTodoTitle!); setState(() {}); debugPrint("${_store.raw.debug(true)}"); } }, tooltip: 'Add Todo', child: Icon(Icons.add), ), ), ); } Future _addTodoDialog(BuildContext context) async { return showDialog( context: context, builder: (context) { return AlertDialog( title: Text('Enter Todo Title'), content: TextField( controller: _textFieldController, decoration: InputDecoration(hintText: "Todo title"), autofocus: true, ), actions: [ TextButton( child: Text('Done'), onPressed: () { addTodoTitle = _textFieldController.value.text; Navigator.pop(context); }, ), ], ); }); } } ================================================ FILE: flutter/todo/lib/views/expiry.dart ================================================ import 'package:flutter/material.dart'; Color expiryColor(double completedExpiryMillis, double remaining) { return remaining > completedExpiryMillis * 0.80 ? Colors.greenAccent : remaining > completedExpiryMillis * 0.60 ? Colors.green : remaining > completedExpiryMillis * 0.4 ? Colors.orange : remaining > completedExpiryMillis * 0.2 ? Colors.redAccent : Colors.red; } class ExpiryWidget extends StatelessWidget { final double completedExpiryMillis; final double remainingMillis; const ExpiryWidget({ required this.completedExpiryMillis, required this.remainingMillis, }) : super(); Widget build(BuildContext context) { final totalWidth = MediaQuery.of(context).size.width * 0.8; final expiryWidth = (remainingMillis / completedExpiryMillis) * totalWidth; return Container( height: 10, width: totalWidth, decoration: BoxDecoration( border: Border.all( color: Colors.blueGrey, width: 1.0, style: BorderStyle.solid, ), borderRadius: BorderRadius.all(Radius.circular(2.0)), ), child: Container( margin: EdgeInsets.only(right: totalWidth - expiryWidth), color: expiryColor(completedExpiryMillis, remainingMillis), ), ); } } ================================================ FILE: flutter/todo/lib/views/menu.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:plugin/generated/rid_api.dart'; class Menu extends StatelessWidget { final Settings settings; final void Function() restartAll; final void Function() completeAll; final void Function() removeCompleted; final void Function(bool) setAutoExpireCompleted; const Menu( this.settings, { required this.restartAll, required this.completeAll, required this.removeCompleted, required this.setAutoExpireCompleted, Key? key, }) : super(key: key); @override Widget build(BuildContext context) { debugPrint(' build: Menu'); return ListView( padding: EdgeInsets.zero, children: [ ListTile(title: Text('Todo Actions')), Divider(), ListTile( title: Text('Restart All'), onTap: restartAll, ), ListTile( title: Text('Complete All'), onTap: completeAll, ), ListTile( title: Text('Remove Completed'), onTap: removeCompleted, ), ListTile( title: Row( mainAxisAlignment: MainAxisAlignment.start, children: [ Text('Expire Completed'), AutoRemoveCompletedWidget( autoExpireCompleted: settings.autoExpireCompletedTodos, setAutoExpireCompleted: setAutoExpireCompleted, ), ], ), ), ], ); } } class AutoRemoveCompletedWidget extends StatefulWidget with StatefulLock { final bool autoExpireCompleted; final void Function(bool) setAutoExpireCompleted; const AutoRemoveCompletedWidget({ Key? key, required this.autoExpireCompleted, required this.setAutoExpireCompleted, }) : super(key: key); @override State createState() => _AutoRemoveCompletedWidgetState(); } class _AutoRemoveCompletedWidgetState extends State with StateAsync { @override Widget build(BuildContext context) { debugPrint(' build: AutoRemoveCompleted'); return Checkbox( value: widget.autoExpireCompleted, onChanged: (val) { if (val != null) widget.setAutoExpireCompleted(val); }, ); } } ================================================ FILE: flutter/todo/lib/views/todo.dart ================================================ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:plugin/generated/rid_api.dart'; import 'package:todo/views/expiry.dart'; class TodoView extends StatefulWidget with StatefulLock { final Todo todo; final Settings settings; final Todo? Function(int) getTodoById; final Future Function(int) onToggleTodo; final Future Function(int) onRemoveTodo; TodoView( this.todo, this.settings, { required this.getTodoById, required this.onToggleTodo, required this.onRemoveTodo, }) : super(key: Key("Todo ${todo.hashCode}")); @override _TodoViewState createState() => _TodoViewState(todo, settings); } class _TodoViewState extends State with StateAsync { Todo todo; final Settings settings; late final StreamSubscription expirySub; _TodoViewState(this.todo, this.settings) : super(); bool _replyIsForThisTodo(PostedReply reply) { assert( reply.data != null, 'Reply.Tick should include data containing the id of the ticked todo', ); final id = int.tryParse(reply.data!); assert(id != null, 'Reply.Tick included invalid id ${reply.data}'); return id == todo.id; } @override void initState() { // Todos are expired on a separate thread in Rust tick by tick. Those tick // events aren't directly related to a user message and therefore we // subscribe to them. expirySub = rid.replyChannel.stream .where( (reply) => reply.type == Reply.Tick && _replyIsForThisTodo(reply)) .listen((_) { final update = widget.getTodoById(todo.id); if (update != null) { setState(() => {todo = update}); } }); super.initState(); } @override Widget build(BuildContext context) { debugPrint(' build: TodoView ${todo.title}'); return Dismissible( key: Key("Todo Dismissible ${todo.id}"), child: Card( child: InkWell( onTap: () => setStateAsync(() => widget.onToggleTodo(todo.id)), child: ListTile( leading: todo.completed ? Icon(Icons.check, color: Colors.green) : Icon(Icons.calendar_today_rounded), title: Text('${todo.title}'), subtitle: widget.settings.autoExpireCompletedTodos && todo.completed ? ExpiryWidget( completedExpiryMillis: settings.completedExpiryMillis.toDouble(), remainingMillis: todo.expiryMillis.toDouble(), ) : null, )), ), direction: DismissDirection.endToStart, // Make sure we removed the Todo and got the reply before updating the UI confirmDismiss: (_) => widget.onRemoveTodo(todo.id).then((_) => true), background: Padding( padding: EdgeInsets.all(5.0), child: Container(color: Colors.red), ), ); } @override void dispose() { expirySub.cancel(); super.dispose(); } } ================================================ FILE: flutter/todo/lib/views/todos.dart ================================================ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:todo/views/todo.dart'; import 'package:plugin/generated/rid_api.dart'; class TodosView extends StatelessWidget { final List todos; final Settings settings; final Todo? Function(int) getTodoById; final Future Function(int) onToggleTodo; final Future Function(int) onRemoveTodo; const TodosView( this.todos, this.settings, { required this.getTodoById, required this.onToggleTodo, required this.onRemoveTodo, Key? key, }) : super(key: key); @override Widget build(BuildContext context) { debugPrint(' build: TodosView ($todos)'); return Center( child: ListView.builder( itemCount: todos.length, itemBuilder: (context, index) { final todo = todos[index]; return TodoView( todo, settings, getTodoById: getTodoById, onToggleTodo: onToggleTodo, onRemoveTodo: onRemoveTodo, ); }), ); } } ================================================ FILE: flutter/todo/linux/.gitignore ================================================ flutter/ephemeral ================================================ FILE: flutter/todo/linux/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.10) project(runner LANGUAGES CXX) set(BINARY_NAME "rid_walker") set(APPLICATION_ID "com.example.rid_walker") 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}) find_library(TODO_LIBRARY NAMES todo libtodo HINTS "${CMAKE_CURRENT_SOURCE_DIR}/../plugin/linux" ) target_link_libraries(${BINARY_NAME} PRIVATE flutter ${TODO_LIBRARY}) 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() ================================================ FILE: flutter/todo/linux/flutter/CMakeLists.txt ================================================ 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} ) ================================================ FILE: flutter/todo/linux/flutter/generated_plugin_registrant.cc ================================================ // // Generated file. Do not edit. // #include "generated_plugin_registrant.h" void fl_register_plugins(FlPluginRegistry* registry) { } ================================================ FILE: flutter/todo/linux/flutter/generated_plugin_registrant.h ================================================ // // Generated file. Do not edit. // #ifndef GENERATED_PLUGIN_REGISTRANT_ #define GENERATED_PLUGIN_REGISTRANT_ #include // Registers Flutter plugins. void fl_register_plugins(FlPluginRegistry* registry); #endif // GENERATED_PLUGIN_REGISTRANT_ ================================================ FILE: flutter/todo/linux/flutter/generated_plugins.cmake ================================================ # # 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) ================================================ FILE: flutter/todo/linux/main.cc ================================================ #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); } ================================================ FILE: flutter/todo/linux/my_application.cc ================================================ #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, "rid_walker"); 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, "rid_walker"); } 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)); } ================================================ FILE: flutter/todo/linux/my_application.h ================================================ #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_ ================================================ FILE: flutter/todo/macos/.gitignore ================================================ # Flutter-related **/Flutter/ephemeral/ **/Pods/ # Xcode-related **/xcuserdata/ ================================================ FILE: flutter/todo/macos/Flutter/Flutter-Debug.xcconfig ================================================ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" ================================================ FILE: flutter/todo/macos/Flutter/Flutter-Release.xcconfig ================================================ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" ================================================ FILE: flutter/todo/macos/Flutter/GeneratedPluginRegistrant.swift ================================================ // // Generated file. Do not edit. // import FlutterMacOS import Foundation import plugin func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { Plugin.register(with: registry.registrar(forPlugin: "Plugin")) } ================================================ FILE: flutter/todo/macos/Podfile ================================================ platform :osx, '10.11' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } def flutter_root generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) unless File.exist?(generated_xcode_build_settings_path) raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" end File.foreach(generated_xcode_build_settings_path) do |line| matches = line.match(/FLUTTER_ROOT\=(.*)/) return matches[1].strip if matches end raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" end require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) flutter_macos_podfile_setup target 'Runner' do use_frameworks! use_modular_headers! flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_macos_build_settings(target) end end ================================================ FILE: flutter/todo/macos/Runner/AppDelegate.swift ================================================ import Cocoa import FlutterMacOS @NSApplicationMain class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } } ================================================ FILE: flutter/todo/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "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" } } ================================================ FILE: flutter/todo/macos/Runner/Base.lproj/MainMenu.xib ================================================ ================================================ FILE: flutter/todo/macos/Runner/Configs/AppInfo.xcconfig ================================================ // 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 = todo // The application's bundle identifier PRODUCT_BUNDLE_IDENTIFIER = com.example.todo // The copyright displayed in application information PRODUCT_COPYRIGHT = Copyright © 2021 com.example. All rights reserved. ================================================ FILE: flutter/todo/macos/Runner/Configs/Debug.xcconfig ================================================ #include "../../Flutter/Flutter-Debug.xcconfig" #include "Warnings.xcconfig" ================================================ FILE: flutter/todo/macos/Runner/Configs/Release.xcconfig ================================================ #include "../../Flutter/Flutter-Release.xcconfig" #include "Warnings.xcconfig" ================================================ FILE: flutter/todo/macos/Runner/Configs/Warnings.xcconfig ================================================ 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 ================================================ FILE: flutter/todo/macos/Runner/DebugProfile.entitlements ================================================ com.apple.security.app-sandbox com.apple.security.cs.allow-jit com.apple.security.network.server ================================================ FILE: flutter/todo/macos/Runner/Info.plist ================================================ 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 ================================================ FILE: flutter/todo/macos/Runner/MainFlutterWindow.swift ================================================ 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() } } ================================================ FILE: flutter/todo/macos/Runner/Release.entitlements ================================================ com.apple.security.app-sandbox ================================================ FILE: flutter/todo/macos/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: flutter/todo/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: flutter/todo/plugin/.gitignore ================================================ .DS_Store .dart_tool/ .packages .pub/ build/ ================================================ FILE: flutter/todo/plugin/LICENSE ================================================ TODO: Add your license here. ================================================ FILE: flutter/todo/plugin/android/.gitignore ================================================ *.iml .gradle /local.properties /.idea/workspace.xml /.idea/libraries .DS_Store /build /captures ================================================ FILE: flutter/todo/plugin/android/build.gradle ================================================ group 'com.example.plugin' version '1.0-SNAPSHOT' 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" } } rootProject.allprojects { repositories { google() jcenter() } } apply plugin: 'com.android.library' apply plugin: 'kotlin-android' android { compileSdkVersion 30 compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } kotlinOptions { jvmTarget = '1.8' } sourceSets { main.java.srcDirs += 'src/main/kotlin' } defaultConfig { minSdkVersion 16 } } dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" } ================================================ FILE: flutter/todo/plugin/android/gradle/wrapper/gradle-wrapper.properties ================================================ 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 ================================================ FILE: flutter/todo/plugin/android/gradle.properties ================================================ org.gradle.jvmargs=-Xmx1536M android.useAndroidX=true android.enableJetifier=true ================================================ FILE: flutter/todo/plugin/android/settings.gradle ================================================ rootProject.name = 'plugin' ================================================ FILE: flutter/todo/plugin/android/src/main/AndroidManifest.xml ================================================ ================================================ FILE: flutter/todo/plugin/android/src/main/kotlin/com/example/plugin/Plugin.kt ================================================ package com.example.plugin import androidx.annotation.NonNull import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel.MethodCallHandler import io.flutter.plugin.common.MethodChannel.Result /** Plugin */ class Plugin: FlutterPlugin, MethodCallHandler { /// The MethodChannel that will the communication between Flutter and native Android /// /// This local reference serves to register the plugin with the Flutter Engine and unregister it /// when the Flutter Engine is detached from the Activity private lateinit var channel : MethodChannel override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { channel = MethodChannel(flutterPluginBinding.binaryMessenger, "plugin") channel.setMethodCallHandler(this) } override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { if (call.method == "getPlatformVersion") { result.success("Android ${android.os.Build.VERSION.RELEASE}") } else { result.notImplemented() } } override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { channel.setMethodCallHandler(null) } } ================================================ FILE: flutter/todo/plugin/ios/.gitignore ================================================ .idea/ .vagrant/ .sconsign.dblite .svn/ .DS_Store *.swp profile DerivedData/ build/ GeneratedPluginRegistrant.h GeneratedPluginRegistrant.m .generated/ *.pbxuser *.mode1v3 *.mode2v3 *.perspectivev3 !default.pbxuser !default.mode1v3 !default.mode2v3 !default.perspectivev3 xcuserdata *.moved-aside *.pyc *sync/ Icon? .tags* /Flutter/Generated.xcconfig /Flutter/ephemeral/ /Flutter/flutter_export_environment.sh ================================================ FILE: flutter/todo/plugin/ios/Assets/.gitkeep ================================================ ================================================ FILE: flutter/todo/plugin/ios/Classes/Plugin.h ================================================ #import @interface Plugin : NSObject @end ================================================ FILE: flutter/todo/plugin/ios/Classes/Plugin.m ================================================ #import "Plugin.h" #if __has_include() #import #else // Support project import fallback if the generated compatibility header // is not copied when this plugin is created as a library. // https://forums.swift.org/t/swift-static-libraries-dont-copy-generated-objective-c-header/19816 #import "plugin-Swift.h" #endif @implementation Plugin + (void)registerWithRegistrar:(NSObject*)registrar { [SwiftPlugin registerWithRegistrar:registrar]; } @end ================================================ FILE: flutter/todo/plugin/ios/Classes/SwiftPlugin.swift ================================================ import Flutter import UIKit public class SwiftPlugin: NSObject, FlutterPlugin { public static func register(with registrar: FlutterPluginRegistrar) { } public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { result(nil) } } // func dummyCallsToPreventTreeShaking() { _export_dart_enum_Filter(); _to_dart_for_Store(); rid_store_debug(nil); rid_store_debug_pretty(nil); create_store(); rid_store_unlock(); rid_store_free(); __include_dart_for_vec_todo(); rid_store_last_added_id(nil); rid_store_todos(nil); rid_store_filter(nil); rid_store_settings(nil); rid_len_vec_todo(nil); rid_get_item_vec_todo(nil, 0); _include_Store_field_wrappers(); rid_cstring_free(nil); rid_init_msg_isolate(0); rid_init_reply_isolate(0); rid_export_Store_filtered_todos(nil); rid_export_Store_todo_by_id(nil, 0); __include_dart_for_ridvec_todo(); rid_free_ridvec_todo(RidVec_Pointer_Todo()); rid_get_item_ridvec_todo(RidVec_Pointer_Todo(), 0); _to_dart_for_Settings(); rid_settings_debug(nil); rid_settings_debug_pretty(nil); rid_settings_auto_expire_completed_todos(nil); rid_settings_completed_expiry_millis(nil); _to_dart_for_Todo(); rid_todo_debug(nil); rid_todo_debug_pretty(nil); rid_todo_id(nil); rid_todo_title(nil); rid_todo_title_len(nil); rid_todo_completed(nil); rid_todo_expiry_millis(nil); rid_filter_debug(0); rid_filter_debug_pretty(0); rid_msg_AddTodo(0, nil); rid_msg_RemoveTodo(0, 0); rid_msg_RemoveCompleted(0); rid_msg_CompleteTodo(0, 0); rid_msg_RestartTodo(0, 0); rid_msg_ToggleTodo(0, 0); rid_msg_CompleteAll(0); rid_msg_RestartAll(0); rid_msg_SetFilter(0, Filter(rawValue: 0)); rid_msg_SetAutoExpireCompletedTodos(0, 0); } // ================================================ FILE: flutter/todo/plugin/ios/plugin.podspec ================================================ # # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. # Run `pod lib lint plugin.podspec` to validate before publishing. # Pod::Spec.new do |s| s.name = 'plugin' s.version = '0.0.1' s.summary = 'Rust bridge.' s.description = <<-DESC A flutter Rust bridge project. DESC s.homepage = 'http://example.com' s.license = { :file => '../LICENSE' } s.author = { 'Your Company' => 'email@example.com' } s.source = { :path => '.' } s.source_files = 'Classes/**/*' s.dependency 'Flutter' s.platform = :ios, '8.0' # Flutter.framework does not contain a i386 slice. s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } s.swift_version = '5.0' s.public_header_files = 'Classes**/*.h' s.static_framework = true s.vendored_libraries = '**/*.a' end ================================================ FILE: flutter/todo/plugin/macos/Classes/Plugin.swift ================================================ import Cocoa import FlutterMacOS public class Plugin: NSObject, FlutterPlugin { public static func register(with registrar: FlutterPluginRegistrar) { } public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { result(nil) } } // func dummyCallsToPreventTreeShaking() { _export_dart_enum_Filter(); _to_dart_for_Store(); rid_store_debug(nil); rid_store_debug_pretty(nil); create_store(); rid_store_unlock(); rid_store_free(); __include_dart_for_vec_todo(); rid_store_last_added_id(nil); rid_store_todos(nil); rid_store_filter(nil); rid_store_settings(nil); rid_len_vec_todo(nil); rid_get_item_vec_todo(nil, 0); _include_Store_field_wrappers(); rid_cstring_free(nil); rid_init_msg_isolate(0); rid_init_reply_isolate(0); rid_export_Store_filtered_todos(nil); rid_export_Store_todo_by_id(nil, 0); __include_dart_for_ridvec_todo(); rid_free_ridvec_todo(RidVec_Pointer_Todo()); rid_get_item_ridvec_todo(RidVec_Pointer_Todo(), 0); _to_dart_for_Settings(); rid_settings_debug(nil); rid_settings_debug_pretty(nil); rid_settings_auto_expire_completed_todos(nil); rid_settings_completed_expiry_millis(nil); _to_dart_for_Todo(); rid_todo_debug(nil); rid_todo_debug_pretty(nil); rid_todo_id(nil); rid_todo_title(nil); rid_todo_title_len(nil); rid_todo_completed(nil); rid_todo_expiry_millis(nil); rid_filter_debug(0); rid_filter_debug_pretty(0); rid_msg_AddTodo(0, nil); rid_msg_RemoveTodo(0, 0); rid_msg_RemoveCompleted(0); rid_msg_CompleteTodo(0, 0); rid_msg_RestartTodo(0, 0); rid_msg_ToggleTodo(0, 0); rid_msg_CompleteAll(0); rid_msg_RestartAll(0); rid_msg_SetFilter(0, Filter(rawValue: 0)); rid_msg_SetAutoExpireCompletedTodos(0, 0); } // ================================================ FILE: flutter/todo/plugin/macos/plugin.podspec ================================================ # # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. # Run `pod lib lint plugin.podspec` to validate before publishing. # Pod::Spec.new do |s| s.name = 'plugin' s.version = '0.0.1' s.summary = 'Rust bridge.' s.description = <<-DESC A flutter Rust bridge project. DESC s.homepage = 'http://example.com' s.license = { :file => '../LICENSE' } s.author = { 'Your Company' => 'email@example.com' } s.source = { :path => '.' } s.source_files = 'Classes/**/*' s.dependency 'FlutterMacOS' s.platform = :osx, '10.11' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } s.swift_version = '5.0' s.public_header_files = 'Classes**/*.h' s.static_framework = true s.vendored_libraries = '**/*.a' end ================================================ FILE: flutter/todo/plugin/plugin.iml ================================================ ================================================ FILE: flutter/todo/plugin/pubspec.yaml ================================================ name: plugin description: Plugin to provide a bridge to Rust. version: 0.0.1 environment: sdk: ">=2.13.0 <3.0.0" flutter: ">=2.0.0" dependencies: ffi: ^1.0.0 ffigen: 4.0.0-dev.2 flutter: sdk: flutter flutter: # This section identifies this Flutter project as a plugin project. # The 'pluginClass' and Android 'package' identifiers should not ordinarily # be modified. They are used by the tooling to maintain consistency when # adding or updating assets for this project. plugin: platforms: android: package: com.example.plugin pluginClass: Plugin ios: pluginClass: Plugin macos: pluginClass: Plugin ================================================ FILE: flutter/todo/pubspec.yaml ================================================ name: todo description: A new Flutter project. publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 1.0.0+1 environment: sdk: ">=2.13.0 <3.0.0" dependencies: flutter: sdk: flutter plugin: path: plugin cupertino_icons: ^1.0.2 dev_dependencies: flutter_test: sdk: flutter flutter: uses-material-design: true assets: - assets/dash.png - assets/ferris.png ================================================ FILE: flutter/todo/rid_build.rs ================================================ use rid_build::{build, BuildConfig, BuildTarget, FlutterConfig, FlutterPlatform, Project}; use std::env; fn main() { let crate_dir = env::var("CARGO_MANIFEST_DIR") .expect("Missing CARGO_MANIFEST_DIR, please run this via 'cargo run'"); let workspace_dir = &crate_dir; let crate_name = &env::var("CARGO_PKG_NAME") .expect("Missing CARGO_PKG_NAME, please run this via 'cargo run'"); let lib_name = &format!("lib{}", &crate_name); let build_config = BuildConfig { target: BuildTarget::Debug, project: Project::Flutter(FlutterConfig { plugin_name: "plugin".to_string(), platforms: vec![ FlutterPlatform::ios(), FlutterPlatform::macos(), FlutterPlatform::android(), FlutterPlatform::linux(), ], }), lib_name, crate_name, project_root: &crate_dir, workspace_root: Some(&workspace_dir), }; build(&build_config).expect("Build failed"); } ================================================ FILE: flutter/todo/sh/android ================================================ #!/usr/bin/env bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" ANDROID_PLATFORM_VERSION=28 # x86 (i686) ANDROID_BUILD_TARGET=i686-linux-android # x86_64 # ANDROID_BUILD_TARGET=x86_64-linux-android # arm64 # ANDROID_BUILD_TARGET=arm64-v8a # armeabi # ANDROID_BUILD_TARGET=armeabi-v7a ANDROID_DIR=$DIR/../plugin/android JNI_LIBS_DIR=$ANDROID_DIR/src/main/jniLibs # Install Android NDK https://developer.android.com/studio/projects/install-ndk # https://github.com/bbqsrc/cargo-ndk cargo ndk \ --platform $ANDROID_PLATFORM_VERSION \ --target $ANDROID_BUILD_TARGET \ --output-dir $JNI_LIBS_DIR \ build ================================================ FILE: flutter/todo/sh/bindgen ================================================ #!/usr/bin/env bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" cd $DIR/.. && cargo run rid_build ================================================ FILE: flutter/todo/sh/clean ================================================ #!/usr/bin/env bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" (cd $DIR/../plugin && flutter clean && flutter pub get) (cd $DIR/.. && flutter clean && flutter pub get) ================================================ FILE: flutter/todo/sh/ios ================================================ #!/usr/bin/env bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" IOS_TARGETS=x86_64-apple-ios # Alternative not using jq # WORKSPACE_CARGO_TOML=`cargo locate-project --workspace --message-format plain` # WORKSPACE_ROOT=$(dirname "${WORKSPACE_CARGO_TOML}") # TARGET_DIR=$WORKSPACE_ROOT/target TARGET_DIR=`cargo metadata --format-version 1 --no-deps | jq ".target_directory" | xargs echo` PROJECT_NAME=`cargo metadata --format-version 1 | jq ".resolve.root" | xargs echo | cut -d ' ' -f1` LIB_NAME=lib$PROJECT_NAME.a # /target/universal/debug UNIVERSAL_DEBUG_DIR="$TARGET_DIR/universal/debug" FLUTTER_IOS_DIR="$DIR/../plugin/ios" LIB_SOURCE_FILE="$UNIVERSAL_DEBUG_DIR/$LIB_NAME" LIB_TARGET_FILE="$FLUTTER_IOS_DIR/$LIB_NAME" cargo lipo \ --targets $IOS_TARGETS cp $LIB_SOURCE_FILE $LIB_TARGET_FILE ================================================ FILE: flutter/todo/sh/linux ================================================ #!/usr/bin/env bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" TARGET_DIR=`cargo metadata --format-version 1 --no-deps | jq ".target_directory" | xargs echo` PROJECT_NAME=`cargo metadata --format-version 1 | jq ".resolve.root" | xargs echo | cut -d ' ' -f1` LIB_NAME_STATIC=lib$PROJECT_NAME.a LIB_NAME_DYNAMIC=lib$PROJECT_NAME.so # /target/universal/debug UNIVERSAL_DEBUG_DIR="$TARGET_DIR/debug" FLUTTER_LINUX_DIR="$DIR/../plugin/linux" mkdir -p $FLUTTER_LINUX_DIR LIB_SOURCE_FILE_STATIC="$UNIVERSAL_DEBUG_DIR/$LIB_NAME_STATIC" LIB_SOURCE_FILE_DYNAMIC="$UNIVERSAL_DEBUG_DIR/$LIB_NAME_DYNAMIC" LIB_TARGET_FILE_STATIC="$FLUTTER_LINUX_DIR/$LIB_NAME_STATIC" LIB_TARGET_FILE_DYNAMIC="$FLUTTER_LINUX_DIR/$LIB_NAME_DYNAMIC" cargo build && cp $LIB_SOURCE_FILE_STATIC $LIB_TARGET_FILE_STATIC && cp $LIB_SOURCE_FILE_DYNAMIC $LIB_TARGET_FILE_DYNAMIC exit $? ================================================ FILE: flutter/todo/sh/macos ================================================ #!/usr/bin/env bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" MACOS_TARGETS=aarch64-apple-ios TARGET_DIR=`cargo metadata --format-version 1 --no-deps | jq ".target_directory" | xargs echo` PROJECT_NAME=`cargo metadata --format-version 1 | jq ".resolve.root" | xargs echo | cut -d ' ' -f1` LIB_NAME=lib$PROJECT_NAME.a # /target/universal/debug UNIVERSAL_DEBUG_DIR="$TARGET_DIR/debug" FLUTTER_MACOS_DIR="$DIR/../plugin/macos" LIB_SOURCE_FILE="$UNIVERSAL_DEBUG_DIR/$LIB_NAME" LIB_TARGET_FILE="$FLUTTER_MACOS_DIR/$LIB_NAME" cargo build && cp $LIB_SOURCE_FILE $LIB_TARGET_FILE exit $? ================================================ FILE: flutter/todo/src/lib.rs ================================================ use core::time; use std::{ sync::{RwLockReadGuard, RwLockWriteGuard}, thread, }; use rid::RidStore; const COMPLETED_EXPIRY_MILLIS: u64 = 7000; const EXPIRY_STEP: u64 = 7; // ----------------- // Store // ----------------- #[rid::store] #[rid::structs(Todo, Settings)] #[rid::enums(Filter)] #[derive(Debug)] pub struct Store { last_added_id: u32, todos: Vec, filter: Filter, settings: Settings, } impl RidStore for Store { fn create() -> Self { let first_todo = Todo { id: 0, title: "Learn Flutter".to_string(), completed: true, expiry_millis: COMPLETED_EXPIRY_MILLIS, }; let second_todo = Todo { id: 1, title: "Learn Rust".to_string(), completed: true, expiry_millis: COMPLETED_EXPIRY_MILLIS, }; let third_todo = Todo { id: 2, title: "Learn Rid".to_string(), completed: false, expiry_millis: COMPLETED_EXPIRY_MILLIS, }; let fourth_todo = Todo { id: 3, title: "Build Awesome Apps".to_string(), completed: false, expiry_millis: COMPLETED_EXPIRY_MILLIS, }; Self { last_added_id: 3, todos: vec![first_todo, second_todo, third_todo, fourth_todo], filter: Filter::All, settings: Settings { auto_expire_completed_todos: false, completed_expiry_millis: COMPLETED_EXPIRY_MILLIS, }, } } fn update(&mut self, req_id: u64, msg: Msg) { use Msg::*; match msg { AddTodo(title) => { self.last_added_id += 1; let todo = Todo { id: self.last_added_id, title, completed: false, expiry_millis: COMPLETED_EXPIRY_MILLIS, }; self.todos.push(todo); rid::post(Reply::AddedTodo(req_id, self.last_added_id.to_string())); } RemoveTodo(id) => { self.remove_todo(id); rid::post(Reply::RemovedTodo(req_id, self.last_added_id.to_string())); } RemoveCompleted => { self.todos.retain(|todo| !todo.completed); rid::post(Reply::RemovedCompleted(req_id)); } CompleteTodo(id) => { self.update_todo(id, |todo| todo.set_completed(true)); rid::post(Reply::CompletedTodo(req_id, id.to_string())); } RestartTodo(id) => { self.update_todo(id, |todo| todo.set_completed(false)); rid::post(Reply::RestartedTodo(req_id, id.to_string())); } ToggleTodo(id) => { self.update_todo(id, |todo| todo.set_completed(!todo.completed)); rid::post(Reply::ToggledTodo(req_id, id.to_string())); } CompleteAll => { self.todos.iter_mut().for_each(|x| x.set_completed(true)); rid::post(Reply::CompletedAll(req_id)); } RestartAll => { self.todos.iter_mut().for_each(|x| x.set_completed(false)); rid::post(Reply::RestartedAll(req_id)); } SetFilter(filter) => { self.filter = filter; rid::post(Reply::SetFilter(req_id)); } SetAutoExpireCompletedTodos(expire) => { self.set_auto_expire_completed_todos(expire); rid::post(Reply::SetAutoExpireCompletedTodos(req_id)); } }; } } #[rid::export] #[rid::structs(Todo)] impl Store { fn remove_todo(&mut self, id: u32) { let mut enumerated = self.todos.iter().enumerate(); let idx = match enumerated.find(|(_, todo)| todo.id == id) { Some((idx, _)) => idx, None => return eprintln!("Could not find Todo with id '{}'", id), }; self.todos.remove(idx); } fn update_todo(&mut self, id: u32, update: F) { match self.todos.iter_mut().find(|x| x.id == id) { Some(todo) => update(todo), None => eprintln!("Could not find Todo with id '{}'", id), }; } #[rid::export] fn filtered_todos(&self) -> Vec<&Todo> { let mut vec: Vec<&Todo> = match self.filter { Filter::Completed => self.todos.iter().filter(|x| x.completed).collect(), Filter::Pending => self.todos.iter().filter(|x| !x.completed).collect(), Filter::All => self.todos.iter().collect(), }; vec.sort(); vec } #[rid::export] fn todo_by_id(&self, id: u32) -> Option<&Todo> { self.todos.iter().find(|x| x.id == id) } // The below read/write wrappers help with auto complete since procmacros // aren't very well supported by the rust analyzer yet fn read() -> RwLockReadGuard<'static, Store> { store::read() } fn write() -> RwLockWriteGuard<'static, Store> { store::write() } pub fn set_auto_expire_completed_todos(&mut self, expire: bool) { self.settings.auto_expire_completed_todos = expire; if expire { thread::spawn(move || { eprintln!( "rust: thread {:?} started auto expiring", thread::current().id() ); while Store::read().settings.auto_expire_completed_todos { thread::sleep(time::Duration::from_millis(EXPIRY_STEP)); { let ids_to_update: Vec<(u32, bool)> = Store::write() .todos .iter_mut() .filter(|x| x.completed) .map(|x: &mut Todo| { let next_value = x.expiry_millis - EXPIRY_STEP; if next_value <= 0 { (x.id, true) } else { x.expiry_millis = next_value; (x.id, false) } }) .collect(); for (id, remove) in ids_to_update { if remove { Store::write().remove_todo(id); rid::post(Reply::CompletedTodoExpired); } else { rid::post(Reply::Tick(id.to_string())); } } } } eprintln!( "rust: thread {:?} stopped auto expiring", thread::current().id() ); }); } } } // ----------------- // Settings // ----------------- #[rid::model] #[derive(Debug)] pub struct Settings { auto_expire_completed_todos: bool, completed_expiry_millis: u64, } // ----------------- // Todo Model // ----------------- #[rid::model] #[derive(PartialEq, Eq, PartialOrd, Debug)] pub struct Todo { id: u32, title: String, completed: bool, expiry_millis: u64, } impl Todo { fn set_completed(&mut self, completed: bool) { self.completed = completed; self.expiry_millis = COMPLETED_EXPIRY_MILLIS; } } impl Ord for Todo { fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.id.cmp(&other.id) } } // ----------------- // Filter // ----------------- #[rid::model] #[derive(Clone, Debug)] pub enum Filter { Completed, Pending, All, } // ----------------- // Msg // ----------------- #[rid::message(Reply)] #[rid::enums(Filter)] #[derive(Debug)] pub enum Msg { AddTodo(String), RemoveTodo(u32), RemoveCompleted, CompleteTodo(u32), RestartTodo(u32), ToggleTodo(u32), CompleteAll, RestartAll, SetFilter(Filter), SetAutoExpireCompletedTodos(bool), } // ----------------- // Reply // ----------------- #[rid::reply] pub enum Reply { // Message Replies AddedTodo(u64, String), RemovedTodo(u64, String), RemovedCompleted(u64), CompletedTodo(u64, String), RestartedTodo(u64, String), ToggledTodo(u64, String), CompletedAll(u64), RestartedAll(u64), SetFilter(u64), SetAutoExpireCompletedTodos(u64), // Application Events CompletedTodoExpired, Tick(String), } ================================================ FILE: flutter/todo/test/widget_test.dart ================================================ // This is a basic Flutter widget test. // // To perform an interaction with a widget in your test, use the WidgetTester // utility that Flutter provides. For example, you can send tap and scroll // gestures. You can also use WidgetTester to find child widgets in the widget // tree, read text, and verify that the values of widget properties are correct. import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:todo/main.dart'; void main() { testWidgets('Counter increments smoke test', (WidgetTester tester) async { // Build our app and trigger a frame. await tester.pumpWidget(MyApp()); // Verify that our counter starts at 0. expect(find.text('0'), findsOneWidget); expect(find.text('1'), findsNothing); // Tap the '+' icon and trigger a frame. await tester.tap(find.byIcon(Icons.add)); await tester.pump(); // Verify that our counter has incremented. expect(find.text('0'), findsNothing); expect(find.text('1'), findsOneWidget); }); } ================================================ FILE: flutter/todo/todo.iml ================================================ ================================================ FILE: flutter/todo_cubit/.gitignore ================================================ .DS_Store .idea/ .metadata ## Flutter ### # Flutter/Dart/Pub related **/doc/api/ .dart_tool/ .flutter-plugins .flutter-plugins-dependencies .fvm/ .packages .pub-cache/ .pub/ build/ coverage/ lib/generated_plugin_registrant.dart # For library packages, don’t commit the pubspec.lock file. # Regenerating the pubspec.lock file lets you test your package against the latest compatible versions of its dependencies. # See https://dart.dev/guides/libraries/private-files#pubspeclock #pubspec.lock # Android related **/android/**/gradle-wrapper.jar **/android/.gradle **/android/captures/ **/android/gradlew **/android/gradlew.bat **/android/key.properties **/android/local.properties **/android/**/GeneratedPluginRegistrant.java # iOS/XCode related **/ios/**/*.mode1v3 **/ios/**/*.mode2v3 **/ios/**/*.moved-aside **/ios/**/*.pbxuser **/ios/**/*.perspectivev3 **/ios/**/*sync/ **/ios/**/.sconsign.dblite **/ios/**/.tags* **/ios/**/.vagrant/ **/ios/**/DerivedData/ **/ios/**/Icon? **/ios/**/Pods/ **/ios/**/.symlinks/ **/ios/**/profile **/ios/**/xcuserdata **/ios/.generated/ **/ios/Flutter/.last_build_id **/ios/Flutter/App.framework **/ios/Flutter/Flutter.framework **/ios/Flutter/Flutter.podspec **/ios/Flutter/Generated.xcconfig **/ios/Flutter/app.flx **/ios/Flutter/app.zip **/ios/Flutter/flutter_assets/ **/ios/Flutter/flutter_export_environment.sh **/ios/ServiceDefinitions.json **/ios/Runner/GeneratedPluginRegistrant.* # Exceptions to above rules. !**/ios/**/default.mode1v3 !**/ios/**/default.mode2v3 !**/ios/**/default.pbxuser !**/ios/**/default.perspectivev3 !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages ### Rust ### **/target/** **/Cargo.lock ### Rid ### **/generated/** **/Classes/bindings.h **/macos/*.a **/ios/*.a **/android/src/main/jniLibs/* ================================================ FILE: flutter/todo_cubit/Cargo.toml ================================================ [package] name = "todo_cubit" version = "0.1.0" authors = ["Thorsten Lorenz "] edition = "2018" [lib] crate-type = ["cdylib", "staticlib" ] [[bin]] name = "rid_build" path = "rid_build.rs" [dependencies] cbindgen = "0.18.0" rid_build = { path = "../../../rid/rid-build" } rid = { path = "../../../rid" } ================================================ FILE: flutter/todo_cubit/README.md ================================================ # todo_cubit Rust integrated Dart Flutter Project ## Getting Started Use the below scripts to get the app ready to run with Flutter. ### 1. Generate Glue Code ```sh ./sh/bindgen ``` ### 2. Build For Desired Target/Device Run any of the below three to build the binary for the specific device and have it placed into the devices specific plugin folder. ```sh ./sh/macos ``` ### 3. Run with Flutter Run on the device. ```sh flutter run -d macos ``` ### 4. Develop Run step `1` whenever a function exposed to Flutter changes. Run step `2` whenever any of your Rust code changes. **Note** that to apply changes from Rust you need to restart the app to reload the compiled binary. A hot restart/reload does not achieve this. ## Folder Structure ``` ├── android ├── ios ├── macos ├── lib ├── plugin │ ├── android │ ├── ios │ ├── macos │ └── lib └── src ``` ### `./plugin` Provides connection from Flutter to Rust. Rust binaries are placed into the respective plugin folders `./ios, ./macos, ./android` when they are built. Generated Dart glue code is placed inside `./plugin/lib/generated` while `./plugin/lib/plugin.dart` just exposes the API to the app. ### `./src` Contains the starter Rust code inside `./src/lib.rs`. Keep developing the Rust part of your app here. ### `./lib` Contains the starter Flutter app inside `./lib/main.dart`. ### `./sh` Provides scripts to run build and code generation tasks. In the future a tool will provide the functionality currently provided by these scripts. - `bindgen` generates the `binding.h` header file for the extern Rust functions found inside `./src`. These are then placed inside the `./plugin` device folders were needed as well as `./plugin/lib/generated/binding.h` where they are used to generate Dart glue code - as part of this script `ffigen` generates Dart glue code inside `./plugin/lib/generated/ffigen_binding.dart` using `./plugin/lib/generated/binding.h` as input - `./android` builds the Rust binary to run on Android devices/emulators and places it inside `./plugin/lib/android` - `./ios` builds the Rust binary to run on IOS devices/emulators and places it inside `./plugin/lib/ios` - `./macos` builds the Rust binary to run on MacOs directly and places it inside `./plugin/lib/macos`, this is the same format as running `cargo build` on your Mac - `clean` cleans both the Flutter plugin and application, run this to reset Flutter when things aren't working ================================================ FILE: flutter/todo_cubit/analysis_options.yaml ================================================ # This file configures the analyzer, which statically analyzes Dart code to # check for errors, warnings, and lints. # # The issues identified by the analyzer are surfaced in the UI of Dart-enabled # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be # invoked from the command line by running `flutter analyze`. # The following line activates a set of recommended lints for Flutter apps, # packages, and plugins designed to encourage good coding practices. include: package:flutter_lints/flutter.yaml linter: # The lint rules applied to this project can be customized in the # section below to disable rules from the `package:flutter_lints/flutter.yaml` # included above or to enable additional rules. A list of all available lints # and their documentation is published at # https://dart-lang.github.io/linter/lints/index.html. # # Instead of disabling a lint rule for the entire project in the # section below, it can also be suppressed for a single line of code # or a specific dart file by using the `// ignore: name_of_lint` and # `// ignore_for_file: name_of_lint` syntax on the line or in the file # producing the lint. rules: # avoid_print: false # Uncomment to disable the `avoid_print` rule # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options ================================================ FILE: flutter/todo_cubit/android/.gitignore ================================================ 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 **/*.keystore **/*.jks ================================================ FILE: flutter/todo_cubit/android/app/build.gradle ================================================ 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 compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } kotlinOptions { jvmTarget = '1.8' } 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.todo_cubit" 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" } ================================================ FILE: flutter/todo_cubit/android/app/src/debug/AndroidManifest.xml ================================================ ================================================ FILE: flutter/todo_cubit/android/app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: flutter/todo_cubit/android/app/src/main/kotlin/com/example/todo_cubit/MainActivity.kt ================================================ package com.example.todo_cubit import io.flutter.embedding.android.FlutterActivity class MainActivity: FlutterActivity() { } ================================================ FILE: flutter/todo_cubit/android/app/src/main/res/drawable/launch_background.xml ================================================ ================================================ FILE: flutter/todo_cubit/android/app/src/main/res/drawable-v21/launch_background.xml ================================================ ================================================ FILE: flutter/todo_cubit/android/app/src/main/res/values/styles.xml ================================================ ================================================ FILE: flutter/todo_cubit/android/app/src/main/res/values-night/styles.xml ================================================ ================================================ FILE: flutter/todo_cubit/android/app/src/profile/AndroidManifest.xml ================================================ ================================================ FILE: flutter/todo_cubit/android/build.gradle ================================================ buildscript { ext.kotlin_version = '1.3.50' repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:4.1.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } allprojects { repositories { google() mavenCentral() } } rootProject.buildDir = '../build' subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" project.evaluationDependsOn(':app') } task clean(type: Delete) { delete rootProject.buildDir } ================================================ FILE: flutter/todo_cubit/android/gradle/wrapper/gradle-wrapper.properties ================================================ #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 ================================================ FILE: flutter/todo_cubit/android/gradle.properties ================================================ org.gradle.jvmargs=-Xmx1536M android.useAndroidX=true android.enableJetifier=true ================================================ FILE: flutter/todo_cubit/android/settings.gradle ================================================ 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" ================================================ FILE: flutter/todo_cubit/android/todo_cubit_android.iml ================================================ ================================================ FILE: flutter/todo_cubit/ios/.gitignore ================================================ *.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 ================================================ FILE: flutter/todo_cubit/ios/Flutter/AppFrameworkInfo.plist ================================================ 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 ================================================ FILE: flutter/todo_cubit/ios/Flutter/Debug.xcconfig ================================================ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" ================================================ FILE: flutter/todo_cubit/ios/Flutter/Release.xcconfig ================================================ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" ================================================ FILE: flutter/todo_cubit/ios/Podfile ================================================ # Uncomment this line to define a global platform for your project # platform :ios, '9.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } def flutter_root generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) unless File.exist?(generated_xcode_build_settings_path) raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" end File.foreach(generated_xcode_build_settings_path) do |line| matches = line.match(/FLUTTER_ROOT\=(.*)/) return matches[1].strip if matches end raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" end require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) flutter_ios_podfile_setup target 'Runner' do use_frameworks! use_modular_headers! flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) end end ================================================ FILE: flutter/todo_cubit/ios/Runner/AppDelegate.swift ================================================ 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) } } ================================================ FILE: flutter/todo_cubit/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "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" } } ================================================ FILE: flutter/todo_cubit/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "LaunchImage.png", "scale" : "1x" }, { "idiom" : "universal", "filename" : "LaunchImage@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "LaunchImage@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: flutter/todo_cubit/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md ================================================ # 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. ================================================ FILE: flutter/todo_cubit/ios/Runner/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: flutter/todo_cubit/ios/Runner/Base.lproj/Main.storyboard ================================================ ================================================ FILE: flutter/todo_cubit/ios/Runner/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName todo_cubit 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 ================================================ FILE: flutter/todo_cubit/ios/Runner/Runner-Bridging-Header.h ================================================ #import "GeneratedPluginRegistrant.h" ================================================ FILE: flutter/todo_cubit/ios/Runner.xcodeproj/project.pbxproj ================================================ // !$*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.todoCubit; 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.todoCubit; 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.todoCubit; 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 */; } ================================================ FILE: flutter/todo_cubit/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: flutter/todo_cubit/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: flutter/todo_cubit/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings ================================================ PreviewsEnabled ================================================ FILE: flutter/todo_cubit/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ================================================ ================================================ FILE: flutter/todo_cubit/ios/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: flutter/todo_cubit/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: flutter/todo_cubit/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings ================================================ PreviewsEnabled ================================================ FILE: flutter/todo_cubit/lib/blocs/cubit/filter_cubit.dart ================================================ import 'package:bloc/bloc.dart'; import 'package:plugin/generated/rid_api.dart'; class FilterCubit extends Cubit { final Store _store = Store.instance; FilterCubit() : super(Store.instance.filter); Future setFilter(Filter filter) async { await _store.msgSetFilter(filter); emit(_store.filter); } } ================================================ FILE: flutter/todo_cubit/lib/blocs/cubit/settings_cubit.dart ================================================ import 'package:bloc/bloc.dart'; import 'package:plugin/generated/rid_api.dart'; class SettingsCubit extends Cubit { final Store _store = Store.instance; SettingsCubit() : super(Store.instance.settings); Future setAutoExpireCompleted(bool val) async { await _store.msgSetAutoExpireCompletedTodos(val); emit(_store.settings); } } ================================================ FILE: flutter/todo_cubit/lib/blocs/cubit/todo_cubit.dart ================================================ import 'dart:async'; import 'package:bloc/bloc.dart'; import 'package:flutter/foundation.dart'; import 'package:meta/meta.dart'; import 'package:plugin/generated/rid_api.dart'; part 'todo_state.dart'; class TodoCubit extends Cubit { final Store _store = Store.instance; late final StreamSubscription tickSub; TodoCubit(Todo todo) : super(ExistingTodo(todo)) { _subscribe(); } // Reply.Tick is a reply that includes data, in this case the id // of the completed todo whose life is ticking away bool _tickIsForThisTodo(PostedReply reply) { // We make sure that the data is a parseable int id assert( reply.data != null, 'Reply.Tick should include data containing the id of the ticked todo', ); final id = int.tryParse(reply.data!); assert(id != null, 'Reply.Tick included invalid id ${reply.data}'); return id == state.id; } void _subscribe() { tickSub = rid.replyChannel.stream .where((x) => x.type == Reply.Tick && _tickIsForThisTodo(x)) .listen(_refreshState); } @override Future close() { tickSub.cancel(); return super.close(); } void _refreshState(PostedReply _reply) async { final todo = _store.todoById(state.id); if (todo == null) { emit(MissingTodo(state.id)); } else { emit(ExistingTodo(todo)); } } Future toggleCompleted() => _store.msgToggleTodo(state.id).then(_refreshState); Future removeTodo(int id) => _store.msgRemoveTodo(id).then(_refreshState); } ================================================ FILE: flutter/todo_cubit/lib/blocs/cubit/todo_state.dart ================================================ part of 'todo_cubit.dart'; @immutable abstract class TodoState { final int id; TodoState(this.id); @override bool operator ==(Object other) => identical(this, other) || other is TodoState && other.id == id; @override int get hashCode => id; } class ExistingTodo extends TodoState { final Todo todo; ExistingTodo(this.todo) : super(todo.id); @override bool operator ==(Object other) => identical(this, other) || other is ExistingTodo && other.todo == todo; @override int get hashCode => todo.hashCode; } class MissingTodo extends TodoState { MissingTodo(int id) : super(id); @override bool operator ==(Object other) => identical(this, other) || other is MissingTodo && other.id == id; @override int get hashCode => super.hashCode; } ================================================ FILE: flutter/todo_cubit/lib/blocs/cubit/todos_cubit.dart ================================================ import 'dart:async'; import 'package:bloc/bloc.dart'; import 'package:flutter/foundation.dart'; import 'package:meta/meta.dart'; import 'package:plugin/generated/rid_api.dart'; part 'todos_state.dart'; class TodosCubit extends Cubit { late final StreamSubscription removedTodosSub; final Store _store = Store.instance; TodosCubit() : super(TodosState(Store.instance.filteredTodos())) { _subscribe(); } void _subscribe() { removedTodosSub = rid.replyChannel.stream .where((x) => x.type == Reply.RemovedTodo || x.type == Reply.RemovedCompleted || x.type == Reply.CompletedTodoExpired || x.type == Reply.SetFilter) .listen(_refreshList); } @override Future close() async { await removedTodosSub.cancel(); return super.close(); } void _refreshList(PostedReply _reply) async { final todos = _store.filteredTodos(); emit(TodosState(todos)); debugPrint('${_store.raw.debug(true)}'); } Future addTodo(String title) => _store.msgAddTodo(title).then(_refreshList); Future restartAll() => _store.msgRestartAll().then(_refreshList); Future completeAll() => _store.msgCompleteAll().then(_refreshList); Future removeCompleted() => _store.msgRemoveCompleted().then(_refreshList); } ================================================ FILE: flutter/todo_cubit/lib/blocs/cubit/todos_state.dart ================================================ part of 'todos_cubit.dart'; @immutable class TodosState { final List todos; const TodosState(this.todos); @override bool operator ==(Object other) => identical(this, other) || other is TodosState && other.todos == todos; @override int get hashCode => todos.hashCode; } ================================================ FILE: flutter/todo_cubit/lib/main.dart ================================================ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:plugin/generated/rid_api.dart'; import 'package:todo_cubit/blocs/cubit/filter_cubit.dart'; import 'package:todo_cubit/blocs/cubit/settings_cubit.dart'; import 'package:todo_cubit/blocs/cubit/todos_cubit.dart'; import 'package:todo_cubit/views/menu.dart'; import 'package:todo_cubit/views/todos.dart'; const Color FILTER_SELECTED_COLOR = Colors.blue; const Color FILTER_UNSELECTED_COLOR = Colors.black; void configRid() { rid.debugReply = (reply) => debugPrint('$reply'); } void main() { configRid(); runApp(TodoApp()); } class TodoApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Rust/Flutter Cubit Todo App', theme: ThemeData( primarySwatch: Colors.blue, ), home: MultiBlocProvider( providers: [ BlocProvider(create: (_) => SettingsCubit()), BlocProvider(create: (_) => TodosCubit()), BlocProvider(create: (_) => FilterCubit()) ], child: TodosPage(title: 'Cubit Todo App'), ), debugShowCheckedModeBanner: false, ); } } class TodosPage extends StatefulWidget { final String title; TodosPage({Key? key, required this.title}) : super(key: key); @override _TodosPageState createState() => _TodosPageState(); } class _TodosPageState extends State { final _textFieldController = TextEditingController(); String? addTodoTitle; @override Widget build(BuildContext context) { final filter = context.watch().state; return SafeArea( child: Scaffold( appBar: AppBar( title: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(widget.title), Row( children: [ Image.asset( "assets/dash.png", height: 40.0, width: 40.0, ), Icon(Icons.favorite, color: Colors.red), Image.asset( "assets/ferris.png", height: 50.0, width: 50.0, ), ], ) ], ), ), drawer: Drawer(child: Menu()), bottomNavigationBar: BottomAppBar( child: Row( children: [ IconButton( icon: Icon( Icons.calendar_today_rounded, color: filter == Filter.Pending ? FILTER_SELECTED_COLOR : FILTER_UNSELECTED_COLOR, ), onPressed: () => context.read().setFilter(Filter.Pending)), Spacer(), IconButton( icon: Icon( Icons.check, color: filter == Filter.Completed ? FILTER_SELECTED_COLOR : FILTER_UNSELECTED_COLOR, ), onPressed: () => context.read().setFilter(Filter.Completed)), IconButton( icon: Icon( Icons.all_inclusive, color: filter == Filter.All ? FILTER_SELECTED_COLOR : FILTER_UNSELECTED_COLOR, ), onPressed: () => context.read().setFilter(Filter.All)), ], ), ), body: TodosView(), floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked, floatingActionButton: FloatingActionButton( onPressed: () async { _textFieldController.clear(); await _addTodoDialog(context); final title = addTodoTitle; if (title != null && title.trim().isNotEmpty) { context.read().addTodo(title); } }, tooltip: 'Add Todo', child: Icon(Icons.add), ), ), ); } Future _addTodoDialog(BuildContext context) async { return showDialog( context: context, builder: (context) { return AlertDialog( title: Text('Enter Todo Title'), content: TextField( controller: _textFieldController, decoration: InputDecoration(hintText: "Todo title"), autofocus: true, ), actions: [ TextButton( child: Text('Done'), onPressed: () { addTodoTitle = _textFieldController.value.text; Navigator.pop(context); }, ), ], ); }); } } ================================================ FILE: flutter/todo_cubit/lib/views/expiry.dart ================================================ import 'package:flutter/material.dart'; Color expiryColor(double completedExpiryMillis, double remaining) { return remaining > completedExpiryMillis * 0.80 ? Colors.greenAccent : remaining > completedExpiryMillis * 0.60 ? Colors.green : remaining > completedExpiryMillis * 0.4 ? Colors.orange : remaining > completedExpiryMillis * 0.2 ? Colors.redAccent : Colors.red; } class ExpiryWidget extends StatelessWidget { final double completedExpiryMillis; final double remainingMillis; const ExpiryWidget({ required this.completedExpiryMillis, required this.remainingMillis, }) : super(); Widget build(BuildContext context) { final totalWidth = MediaQuery.of(context).size.width * 0.8; final expiryWidth = (remainingMillis / completedExpiryMillis) * totalWidth; return Container( height: 10, width: totalWidth, decoration: BoxDecoration( border: Border.all( color: Colors.blueGrey, width: 1.0, style: BorderStyle.solid, ), borderRadius: BorderRadius.all(Radius.circular(2.0)), ), child: Container( margin: EdgeInsets.only(right: totalWidth - expiryWidth), color: expiryColor(completedExpiryMillis, remainingMillis), ), ); } } ================================================ FILE: flutter/todo_cubit/lib/views/menu.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:todo_cubit/blocs/cubit/settings_cubit.dart'; import 'package:todo_cubit/blocs/cubit/todos_cubit.dart'; class Menu extends StatelessWidget { @override Widget build(BuildContext context) { return ListView( padding: EdgeInsets.zero, children: [ ListTile(title: Text('Todo Actions')), Divider(), ListTile( title: Text('Restart All'), onTap: () => context.read().restartAll(), ), ListTile( title: Text('Complete All'), onTap: () => context.read().completeAll(), ), ListTile( title: Text('Remove Completed'), onTap: () => context.read().removeCompleted(), ), ListTile( title: Row( mainAxisAlignment: MainAxisAlignment.start, children: [ Text('Expire Completed'), AutoRemoveCompletedWidget(), ], ), ), ], ); } } class AutoRemoveCompletedWidget extends StatelessWidget { @override Widget build(BuildContext context) { return Checkbox( value: context.select( (x) => x.state.autoExpireCompletedTodos, ), onChanged: (val) { if (val != null) context.read().setAutoExpireCompleted(val); }, ); } } ================================================ FILE: flutter/todo_cubit/lib/views/todo.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:plugin/generated/rid_api.dart'; import 'package:todo_cubit/blocs/cubit/settings_cubit.dart'; import 'package:todo_cubit/blocs/cubit/todo_cubit.dart'; import 'package:todo_cubit/views/expiry.dart'; class TodoView extends StatelessWidget { @override Widget build(BuildContext context) { return BlocBuilder(builder: (context, state) { if (state is MissingTodo) { return Text('Todo ${state.id} missing, most likely removed'); } if (state is ExistingTodo) { final todo = state.todo; return Dismissible( key: Key("Todo Dismissible ${todo.id}"), child: Card( child: InkWell( onTap: () => context.read().toggleCompleted(), child: ListTile( leading: todo.completed ? Icon(Icons.check, color: Colors.green) : Icon(Icons.calendar_today_rounded), title: Text('${todo.title}'), subtitle: BlocBuilder( builder: (context, settings) { return settings.autoExpireCompletedTodos && todo.completed ? ExpiryWidget( completedExpiryMillis: settings.completedExpiryMillis.toDouble(), remainingMillis: todo.expiryMillis.toDouble(), ) : Container(); }, ), ), ), ), direction: DismissDirection.endToStart, // Make sure we removed the Todo and got the reply before updating the UI confirmDismiss: (_) => context.read().removeTodo(todo.id).then((_) => true), background: Padding( padding: EdgeInsets.all(5.0), child: Container(color: Colors.red), ), ); } else { return Text('Unkown TodoState type $state'); } }); } } ================================================ FILE: flutter/todo_cubit/lib/views/todos.dart ================================================ import 'package:flutter/material.dart'; import 'package:todo_cubit/blocs/cubit/todo_cubit.dart'; import 'package:todo_cubit/blocs/cubit/todos_cubit.dart'; import 'package:todo_cubit/views/todo.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class TodosView extends StatelessWidget { @override Widget build(BuildContext context) { return Center( child: BlocBuilder(builder: (context, state) { final todos = state.todos; return ListView.builder( itemCount: todos.length, itemBuilder: (context, index) { final todo = todos[index]; return BlocProvider( create: (_) => TodoCubit(todo), child: TodoView(), key: Key(todo.hashCode.toString()), ); }); }), ); } } ================================================ FILE: flutter/todo_cubit/linux/.gitignore ================================================ flutter/ephemeral ================================================ FILE: flutter/todo_cubit/linux/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.10) project(runner LANGUAGES CXX) set(BINARY_NAME "todo_cubit") set(APPLICATION_ID "com.example.todo_cubit") 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() ================================================ FILE: flutter/todo_cubit/linux/flutter/CMakeLists.txt ================================================ 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} ) ================================================ FILE: flutter/todo_cubit/linux/flutter/generated_plugin_registrant.cc ================================================ // // Generated file. Do not edit. // #include "generated_plugin_registrant.h" void fl_register_plugins(FlPluginRegistry* registry) { } ================================================ FILE: flutter/todo_cubit/linux/flutter/generated_plugin_registrant.h ================================================ // // Generated file. Do not edit. // #ifndef GENERATED_PLUGIN_REGISTRANT_ #define GENERATED_PLUGIN_REGISTRANT_ #include // Registers Flutter plugins. void fl_register_plugins(FlPluginRegistry* registry); #endif // GENERATED_PLUGIN_REGISTRANT_ ================================================ FILE: flutter/todo_cubit/linux/flutter/generated_plugins.cmake ================================================ # # 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) ================================================ FILE: flutter/todo_cubit/linux/main.cc ================================================ #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); } ================================================ FILE: flutter/todo_cubit/linux/my_application.cc ================================================ #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, "todo_cubit"); 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, "todo_cubit"); } 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)); } ================================================ FILE: flutter/todo_cubit/linux/my_application.h ================================================ #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_ ================================================ FILE: flutter/todo_cubit/macos/.gitignore ================================================ # Flutter-related **/Flutter/ephemeral/ **/Pods/ # Xcode-related **/xcuserdata/ ================================================ FILE: flutter/todo_cubit/macos/Flutter/Flutter-Debug.xcconfig ================================================ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" ================================================ FILE: flutter/todo_cubit/macos/Flutter/Flutter-Release.xcconfig ================================================ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" ================================================ FILE: flutter/todo_cubit/macos/Flutter/GeneratedPluginRegistrant.swift ================================================ // // Generated file. Do not edit. // import FlutterMacOS import Foundation import plugin func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { Plugin.register(with: registry.registrar(forPlugin: "Plugin")) } ================================================ FILE: flutter/todo_cubit/macos/Podfile ================================================ platform :osx, '10.11' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } def flutter_root generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) unless File.exist?(generated_xcode_build_settings_path) raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" end File.foreach(generated_xcode_build_settings_path) do |line| matches = line.match(/FLUTTER_ROOT\=(.*)/) return matches[1].strip if matches end raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" end require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) flutter_macos_podfile_setup target 'Runner' do use_frameworks! use_modular_headers! flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_macos_build_settings(target) end end ================================================ FILE: flutter/todo_cubit/macos/Runner/AppDelegate.swift ================================================ import Cocoa import FlutterMacOS @NSApplicationMain class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } } ================================================ FILE: flutter/todo_cubit/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "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" } } ================================================ FILE: flutter/todo_cubit/macos/Runner/Base.lproj/MainMenu.xib ================================================ ================================================ FILE: flutter/todo_cubit/macos/Runner/Configs/AppInfo.xcconfig ================================================ // 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 = todo_cubit // The application's bundle identifier PRODUCT_BUNDLE_IDENTIFIER = com.example.todoCubit // The copyright displayed in application information PRODUCT_COPYRIGHT = Copyright © 2021 com.example. All rights reserved. ================================================ FILE: flutter/todo_cubit/macos/Runner/Configs/Debug.xcconfig ================================================ #include "../../Flutter/Flutter-Debug.xcconfig" #include "Warnings.xcconfig" ================================================ FILE: flutter/todo_cubit/macos/Runner/Configs/Release.xcconfig ================================================ #include "../../Flutter/Flutter-Release.xcconfig" #include "Warnings.xcconfig" ================================================ FILE: flutter/todo_cubit/macos/Runner/Configs/Warnings.xcconfig ================================================ 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 ================================================ FILE: flutter/todo_cubit/macos/Runner/DebugProfile.entitlements ================================================ com.apple.security.app-sandbox com.apple.security.cs.allow-jit com.apple.security.network.server ================================================ FILE: flutter/todo_cubit/macos/Runner/Info.plist ================================================ 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 ================================================ FILE: flutter/todo_cubit/macos/Runner/MainFlutterWindow.swift ================================================ 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() } } ================================================ FILE: flutter/todo_cubit/macos/Runner/Release.entitlements ================================================ com.apple.security.app-sandbox ================================================ FILE: flutter/todo_cubit/macos/Runner.xcodeproj/project.pbxproj ================================================ // !$*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 */; }; 7CC513913857B230490BB8FC /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FE832B76C3274791B6E1CF37 /* Pods_Runner.framework */; }; /* 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 /* todo_cubit.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = todo_cubit.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 = ""; }; 8F9D38D36A436B4B23027013 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 901C4FCA7B7F499425F18AE0 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; D4AD4E5E6EDB6DEA300EA746 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; FE832B76C3274791B6E1CF37 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 33CC10EA2044A3C60003C045 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 7CC513913857B230490BB8FC /* Pods_Runner.framework in Frameworks */, ); 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 */, A4F61188EAFD80961AEC2007 /* Pods */, ); sourceTree = ""; }; 33CC10EE2044A3C60003C045 /* Products */ = { isa = PBXGroup; children = ( 33CC10ED2044A3C60003C045 /* todo_cubit.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 = ""; }; A4F61188EAFD80961AEC2007 /* Pods */ = { isa = PBXGroup; children = ( D4AD4E5E6EDB6DEA300EA746 /* Pods-Runner.debug.xcconfig */, 8F9D38D36A436B4B23027013 /* Pods-Runner.release.xcconfig */, 901C4FCA7B7F499425F18AE0 /* Pods-Runner.profile.xcconfig */, ); name = Pods; path = Pods; sourceTree = ""; }; D73912EC22F37F3D000D13A0 /* Frameworks */ = { isa = PBXGroup; children = ( FE832B76C3274791B6E1CF37 /* Pods_Runner.framework */, ); name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 33CC10EC2044A3C60003C045 /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( 233F5DA7F94578156A27D962 /* [CP] Check Pods Manifest.lock */, 33CC10E92044A3C60003C045 /* Sources */, 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, ); buildRules = ( ); dependencies = ( 33CC11202044C79F0003C045 /* PBXTargetDependency */, ); name = Runner; productName = Runner; productReference = 33CC10ED2044A3C60003C045 /* todo_cubit.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 */ 233F5DA7F94578156A27D962 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( ); outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; 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 */; } ================================================ FILE: flutter/todo_cubit/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: flutter/todo_cubit/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ================================================ ================================================ FILE: flutter/todo_cubit/macos/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: flutter/todo_cubit/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: flutter/todo_cubit/plugin/.gitignore ================================================ .DS_Store .dart_tool/ .packages .pub/ build/ ================================================ FILE: flutter/todo_cubit/plugin/LICENSE ================================================ TODO: Add your license here. ================================================ FILE: flutter/todo_cubit/plugin/analysis_options.yaml ================================================ include: package:flutter_lints/flutter.yaml # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options ================================================ FILE: flutter/todo_cubit/plugin/android/.gitignore ================================================ *.iml .gradle /local.properties /.idea/workspace.xml /.idea/libraries .DS_Store /build /captures ================================================ FILE: flutter/todo_cubit/plugin/android/build.gradle ================================================ group 'com.example.plugin' version '1.0-SNAPSHOT' buildscript { ext.kotlin_version = '1.3.50' repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:4.1.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } rootProject.allprojects { repositories { google() mavenCentral() } } apply plugin: 'com.android.library' apply plugin: 'kotlin-android' android { compileSdkVersion 30 compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } kotlinOptions { jvmTarget = '1.8' } sourceSets { main.java.srcDirs += 'src/main/kotlin' } defaultConfig { minSdkVersion 16 } } dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" } ================================================ FILE: flutter/todo_cubit/plugin/android/gradle/wrapper/gradle-wrapper.properties ================================================ 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 ================================================ FILE: flutter/todo_cubit/plugin/android/gradle.properties ================================================ org.gradle.jvmargs=-Xmx1536M android.useAndroidX=true android.enableJetifier=true ================================================ FILE: flutter/todo_cubit/plugin/android/settings.gradle ================================================ rootProject.name = 'plugin' ================================================ FILE: flutter/todo_cubit/plugin/android/src/main/AndroidManifest.xml ================================================ ================================================ FILE: flutter/todo_cubit/plugin/android/src/main/kotlin/com/example/plugin/Plugin.kt ================================================ package com.example.plugin import androidx.annotation.NonNull import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel.MethodCallHandler import io.flutter.plugin.common.MethodChannel.Result /** Plugin */ class Plugin: FlutterPlugin, MethodCallHandler { /// The MethodChannel that will the communication between Flutter and native Android /// /// This local reference serves to register the plugin with the Flutter Engine and unregister it /// when the Flutter Engine is detached from the Activity private lateinit var channel : MethodChannel override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { channel = MethodChannel(flutterPluginBinding.binaryMessenger, "plugin") channel.setMethodCallHandler(this) } override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { if (call.method == "getPlatformVersion") { result.success("Android ${android.os.Build.VERSION.RELEASE}") } else { result.notImplemented() } } override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { channel.setMethodCallHandler(null) } } ================================================ FILE: flutter/todo_cubit/plugin/ios/.gitignore ================================================ .idea/ .vagrant/ .sconsign.dblite .svn/ .DS_Store *.swp profile DerivedData/ build/ GeneratedPluginRegistrant.h GeneratedPluginRegistrant.m .generated/ *.pbxuser *.mode1v3 *.mode2v3 *.perspectivev3 !default.pbxuser !default.mode1v3 !default.mode2v3 !default.perspectivev3 xcuserdata *.moved-aside *.pyc *sync/ Icon? .tags* /Flutter/Generated.xcconfig /Flutter/ephemeral/ /Flutter/flutter_export_environment.sh ================================================ FILE: flutter/todo_cubit/plugin/ios/Assets/.gitkeep ================================================ ================================================ FILE: flutter/todo_cubit/plugin/ios/Classes/Plugin.h ================================================ #import @interface Plugin : NSObject @end ================================================ FILE: flutter/todo_cubit/plugin/ios/Classes/Plugin.m ================================================ #import "Plugin.h" #if __has_include() #import #else // Support project import fallback if the generated compatibility header // is not copied when this plugin is created as a library. // https://forums.swift.org/t/swift-static-libraries-dont-copy-generated-objective-c-header/19816 #import "plugin-Swift.h" #endif @implementation Plugin + (void)registerWithRegistrar:(NSObject*)registrar { [SwiftPlugin registerWithRegistrar:registrar]; } @end ================================================ FILE: flutter/todo_cubit/plugin/ios/Classes/SwiftPlugin.swift ================================================ import Flutter import UIKit public class SwiftPlugin: NSObject, FlutterPlugin { public static func register(with registrar: FlutterPluginRegistrar) { } public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { result(nil) } } // func dummyCallsToPreventTreeShaking() { _export_dart_enum_Filter(); _to_dart_for_Store(); rid_store_debug(nil); rid_store_debug_pretty(nil); create_store(); rid_store_unlock(); rid_store_free(); __include_dart_for_vec_todo(); rid_store_last_added_id(nil); rid_store_todos(nil); rid_store_filter(nil); rid_store_settings(nil); rid_len_vec_todo(nil); rid_get_item_vec_todo(nil, 0); _include_Store_field_wrappers(); rid_cstring_free(nil); rid_init_msg_isolate(0); rid_init_reply_isolate(0); rid_export_Store_filtered_todos(nil); rid_export_Store_todo_by_id(nil, 0); __include_dart_for_ridvec_todo(); rid_free_ridvec_todo(RidVec_Pointer_Todo()); rid_get_item_ridvec_todo(RidVec_Pointer_Todo(), 0); _to_dart_for_Settings(); rid_settings_debug(nil); rid_settings_debug_pretty(nil); rid_settings_auto_expire_completed_todos(nil); rid_settings_completed_expiry_millis(nil); _to_dart_for_Todo(); rid_todo_debug(nil); rid_todo_debug_pretty(nil); rid_todo_id(nil); rid_todo_title(nil); rid_todo_title_len(nil); rid_todo_completed(nil); rid_todo_expiry_millis(nil); rid_filter_debug(0); rid_filter_debug_pretty(0); rid_msg_AddTodo(0, nil); rid_msg_RemoveTodo(0, 0); rid_msg_RemoveCompleted(0); rid_msg_CompleteTodo(0, 0); rid_msg_RestartTodo(0, 0); rid_msg_ToggleTodo(0, 0); rid_msg_CompleteAll(0); rid_msg_RestartAll(0); rid_msg_SetFilter(0, Filter(rawValue: 0)); rid_msg_SetAutoExpireCompletedTodos(0, 0); } // ================================================ FILE: flutter/todo_cubit/plugin/ios/plugin.podspec ================================================ # # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. # Run `pod lib lint plugin.podspec` to validate before publishing. # Pod::Spec.new do |s| s.name = 'plugin' s.version = '0.0.1' s.summary = 'Rust bridge.' s.description = <<-DESC A flutter Rust bridge project. DESC s.homepage = 'http://example.com' s.license = { :file => '../LICENSE' } s.author = { 'Your Company' => 'email@example.com' } s.source = { :path => '.' } s.source_files = 'Classes/**/*' s.dependency 'Flutter' s.platform = :ios, '8.0' # Flutter.framework does not contain a i386 slice. s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } s.swift_version = '5.0' s.public_header_files = 'Classes**/*.h' s.static_framework = true s.vendored_libraries = '**/*.a' end ================================================ FILE: flutter/todo_cubit/plugin/macos/Classes/Plugin.swift ================================================ import Cocoa import FlutterMacOS public class Plugin: NSObject, FlutterPlugin { public static func register(with registrar: FlutterPluginRegistrar) { } public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { result(nil) } } // func dummyCallsToPreventTreeShaking() { _export_dart_enum_Filter(); _to_dart_for_Store(); rid_store_debug(nil); rid_store_debug_pretty(nil); create_store(); rid_store_unlock(); rid_store_free(); __include_dart_for_vec_todo(); rid_store_last_added_id(nil); rid_store_todos(nil); rid_store_filter(nil); rid_store_settings(nil); rid_len_vec_todo(nil); rid_get_item_vec_todo(nil, 0); _include_Store_field_wrappers(); rid_cstring_free(nil); rid_init_msg_isolate(0); rid_init_reply_isolate(0); rid_export_Store_filtered_todos(nil); rid_export_Store_todo_by_id(nil, 0); __include_dart_for_ridvec_todo(); rid_free_ridvec_todo(RidVec_Pointer_Todo()); rid_get_item_ridvec_todo(RidVec_Pointer_Todo(), 0); _to_dart_for_Settings(); rid_settings_debug(nil); rid_settings_debug_pretty(nil); rid_settings_auto_expire_completed_todos(nil); rid_settings_completed_expiry_millis(nil); _to_dart_for_Todo(); rid_todo_debug(nil); rid_todo_debug_pretty(nil); rid_todo_id(nil); rid_todo_title(nil); rid_todo_title_len(nil); rid_todo_completed(nil); rid_todo_expiry_millis(nil); rid_filter_debug(0); rid_filter_debug_pretty(0); rid_msg_AddTodo(0, nil); rid_msg_RemoveTodo(0, 0); rid_msg_RemoveCompleted(0); rid_msg_CompleteTodo(0, 0); rid_msg_RestartTodo(0, 0); rid_msg_ToggleTodo(0, 0); rid_msg_CompleteAll(0); rid_msg_RestartAll(0); rid_msg_SetFilter(0, Filter(rawValue: 0)); rid_msg_SetAutoExpireCompletedTodos(0, 0); } // ================================================ FILE: flutter/todo_cubit/plugin/macos/plugin.podspec ================================================ # # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. # Run `pod lib lint plugin.podspec` to validate before publishing. # Pod::Spec.new do |s| s.name = 'plugin' s.version = '0.0.1' s.summary = 'Rust bridge.' s.description = <<-DESC A flutter Rust bridge project. DESC s.homepage = 'http://example.com' s.license = { :file => '../LICENSE' } s.author = { 'Your Company' => 'email@example.com' } s.source = { :path => '.' } s.source_files = 'Classes/**/*' s.dependency 'FlutterMacOS' s.platform = :osx, '10.11' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } s.swift_version = '5.0' s.public_header_files = 'Classes**/*.h' s.static_framework = true s.vendored_libraries = '**/*.a' end ================================================ FILE: flutter/todo_cubit/plugin/plugin.iml ================================================ ================================================ FILE: flutter/todo_cubit/plugin/pubspec.yaml ================================================ name: plugin description: Plugin to provide a bridge to Rust. version: 0.0.1 environment: sdk: ">=2.13.0 <3.0.0" flutter: ">=2.0.0" dependencies: ffi: ^1.0.0 ffigen: 4.0.0-dev.2 flutter: sdk: flutter flutter: # This section identifies this Flutter project as a plugin project. # The 'pluginClass' and Android 'package' identifiers should not ordinarily # be modified. They are used by the tooling to maintain consistency when # adding or updating assets for this project. plugin: platforms: android: package: com.example.plugin pluginClass: Plugin ios: pluginClass: Plugin macos: pluginClass: Plugin ================================================ FILE: flutter/todo_cubit/pubspec.yaml ================================================ name: todo_cubit description: A new Flutter project. publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 1.0.0+1 environment: sdk: ">=2.13.0 <3.0.0" dependencies: flutter: sdk: flutter plugin: path: plugin cupertino_icons: ^1.0.2 bloc: ^7.0.0 flutter_bloc: ^7.0.1 provider: ^5.0.0 dev_dependencies: flutter_test: sdk: flutter flutter: uses-material-design: true assets: - assets/dash.png - assets/ferris.png ================================================ FILE: flutter/todo_cubit/rid_build.rs ================================================ use rid_build::{build, BuildConfig, BuildTarget, FlutterConfig, FlutterPlatform, Project}; use std::env; fn main() { let crate_dir = env::var("CARGO_MANIFEST_DIR") .expect("Missing CARGO_MANIFEST_DIR, please run this via 'cargo run'"); let workspace_dir = &crate_dir; let crate_name = &env::var("CARGO_PKG_NAME") .expect("Missing CARGO_PKG_NAME, please run this via 'cargo run'"); let lib_name = &format!("lib{}", &crate_name); let build_config = BuildConfig { target: BuildTarget::Debug, project: Project::Flutter(FlutterConfig { plugin_name: "plugin".to_string(), platforms: vec![ FlutterPlatform::ios(), FlutterPlatform::macos(), FlutterPlatform::android(), ], }), lib_name, crate_name, project_root: &crate_dir, workspace_root: Some(&workspace_dir), }; build(&build_config).expect("Build failed"); } ================================================ FILE: flutter/todo_cubit/sh/android ================================================ #!/usr/bin/env bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" ANDROID_PLATFORM_VERSION=28 # x86 (i686) ANDROID_BUILD_TARGET=i686-linux-android # x86_64 # ANDROID_BUILD_TARGET=x86_64-linux-android # arm64 # ANDROID_BUILD_TARGET=arm64-v8a # armeabi # ANDROID_BUILD_TARGET=armeabi-v7a ANDROID_DIR=$DIR/../plugin/android JNI_LIBS_DIR=$ANDROID_DIR/src/main/jniLibs # Install Android NDK https://developer.android.com/studio/projects/install-ndk # https://github.com/bbqsrc/cargo-ndk cargo ndk \ --platform $ANDROID_PLATFORM_VERSION \ --target $ANDROID_BUILD_TARGET \ --output-dir $JNI_LIBS_DIR \ build ================================================ FILE: flutter/todo_cubit/sh/android-emulator ================================================ #!/usr/bin/env bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" ANDROID_PLATFORM_VERSION=28 # x86 (i686) ANDROID_BUILD_TARGET=i686-linux-android # x86_64 # ANDROID_BUILD_TARGET=x86_64-linux-android # arm64 # ANDROID_BUILD_TARGET=arm64-v8a # armeabi # ANDROID_BUILD_TARGET=armeabi-v7a ANDROID_DIR=$DIR/../plugin/android JNI_LIBS_DIR=$ANDROID_DIR/src/main/jniLibs # Install Android NDK https://developer.android.com/studio/projects/install-ndk # https://github.com/bbqsrc/cargo-ndk cargo ndk \ --platform $ANDROID_PLATFORM_VERSION \ --target $ANDROID_BUILD_TARGET \ --output-dir $JNI_LIBS_DIR \ build ================================================ FILE: flutter/todo_cubit/sh/bindgen ================================================ #!/usr/bin/env bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" cd $DIR/.. && cargo run rid_build ================================================ FILE: flutter/todo_cubit/sh/clean ================================================ #!/usr/bin/env bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" (cd $DIR/../plugin && flutter clean && flutter pub get) (cd $DIR/.. && flutter clean && flutter pub get) ================================================ FILE: flutter/todo_cubit/sh/ios ================================================ #!/usr/bin/env bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" IOS_TARGETS=x86_64-apple-ios # Alternative not using jq # WORKSPACE_CARGO_TOML=`cargo locate-project --workspace --message-format plain` # WORKSPACE_ROOT=$(dirname "${WORKSPACE_CARGO_TOML}") # TARGET_DIR=$WORKSPACE_ROOT/target TARGET_DIR=`cargo metadata --format-version 1 --no-deps | jq ".target_directory" | xargs echo` PROJECT_NAME=`cargo metadata --format-version 1 | jq ".resolve.root" | xargs echo | cut -d ' ' -f1` LIB_NAME=lib$PROJECT_NAME.a # /target/universal/debug UNIVERSAL_DEBUG_DIR="$TARGET_DIR/universal/debug" FLUTTER_IOS_DIR="$DIR/../plugin/ios" LIB_SOURCE_FILE="$UNIVERSAL_DEBUG_DIR/$LIB_NAME" LIB_TARGET_FILE="$FLUTTER_IOS_DIR/$LIB_NAME" cargo lipo \ --targets $IOS_TARGETS cp $LIB_SOURCE_FILE $LIB_TARGET_FILE ================================================ FILE: flutter/todo_cubit/sh/linux ================================================ #!/usr/bin/env bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" TARGET_DIR=`cargo metadata --format-version 1 --no-deps | jq ".target_directory" | xargs echo` PROJECT_NAME=`cargo metadata --format-version 1 | jq ".resolve.root" | xargs echo | cut -d ' ' -f1` LIB_NAME_STATIC=lib$PROJECT_NAME.a LIB_NAME_DYNAMIC=lib$PROJECT_NAME.so # /target/universal/debug UNIVERSAL_DEBUG_DIR="$TARGET_DIR/debug" FLUTTER_LINUX_DIR="$DIR/../plugin/linux" mkdir -p $FLUTTER_LINUX_DIR LIB_SOURCE_FILE_STATIC="$UNIVERSAL_DEBUG_DIR/$LIB_NAME_STATIC" LIB_SOURCE_FILE_DYNAMIC="$UNIVERSAL_DEBUG_DIR/$LIB_NAME_DYNAMIC" LIB_TARGET_FILE_STATIC="$FLUTTER_LINUX_DIR/$LIB_NAME_STATIC" LIB_TARGET_FILE_DYNAMIC="$FLUTTER_LINUX_DIR/$LIB_NAME_DYNAMIC" cargo build && cp $LIB_SOURCE_FILE_STATIC $LIB_TARGET_FILE_STATIC && cp $LIB_SOURCE_FILE_DYNAMIC $LIB_TARGET_FILE_DYNAMIC exit $? ================================================ FILE: flutter/todo_cubit/sh/macos ================================================ #!/usr/bin/env bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" MACOS_TARGETS=aarch64-apple-ios TARGET_DIR=`cargo metadata --format-version 1 --no-deps | jq ".target_directory" | xargs echo` PROJECT_NAME=`cargo metadata --format-version 1 | jq ".resolve.root" | xargs echo | cut -d ' ' -f1` LIB_NAME=lib$PROJECT_NAME.a # /target/universal/debug UNIVERSAL_DEBUG_DIR="$TARGET_DIR/debug" FLUTTER_MACOS_DIR="$DIR/../plugin/macos" LIB_SOURCE_FILE="$UNIVERSAL_DEBUG_DIR/$LIB_NAME" LIB_TARGET_FILE="$FLUTTER_MACOS_DIR/$LIB_NAME" cargo build cp $LIB_SOURCE_FILE $LIB_TARGET_FILE ================================================ FILE: flutter/todo_cubit/src/lib.rs ================================================ use core::time; use std::{ sync::{RwLockReadGuard, RwLockWriteGuard}, thread, }; use rid::RidStore; const COMPLETED_EXPIRY_MILLIS: u64 = 7000; const EXPIRY_STEP: u64 = 10; // ----------------- // Store // ----------------- #[rid::store] #[rid::structs(Todo, Settings)] #[rid::enums(Filter)] #[derive(Debug)] pub struct Store { last_added_id: u32, todos: Vec, filter: Filter, settings: Settings, } impl RidStore for Store { fn create() -> Self { let first_todo = Todo { id: 0, title: "Learn Flutter".to_string(), completed: true, expiry_millis: COMPLETED_EXPIRY_MILLIS, }; let second_todo = Todo { id: 1, title: "Learn Rust".to_string(), completed: true, expiry_millis: COMPLETED_EXPIRY_MILLIS, }; let third_todo = Todo { id: 2, title: "Learn Rid".to_string(), completed: false, expiry_millis: COMPLETED_EXPIRY_MILLIS, }; let fourth_todo = Todo { id: 3, title: "Build Awesome Apps".to_string(), completed: false, expiry_millis: COMPLETED_EXPIRY_MILLIS, }; Self { last_added_id: 3, todos: vec![first_todo, second_todo, third_todo, fourth_todo], filter: Filter::All, settings: Settings { auto_expire_completed_todos: false, completed_expiry_millis: COMPLETED_EXPIRY_MILLIS, }, } } fn update(&mut self, req_id: u64, msg: Msg) { use Msg::*; match msg { AddTodo(title) => { self.last_added_id += 1; let todo = Todo { id: self.last_added_id, title, completed: false, expiry_millis: COMPLETED_EXPIRY_MILLIS, }; self.todos.push(todo); rid::post(Reply::AddedTodo(req_id, self.last_added_id.to_string())); } RemoveTodo(id) => { self.remove_todo(id); rid::post(Reply::RemovedTodo(req_id, self.last_added_id.to_string())); } RemoveCompleted => { self.todos.retain(|todo| !todo.completed); rid::post(Reply::RemovedCompleted(req_id)); } CompleteTodo(id) => { self.update_todo(id, |todo| todo.set_completed(true)); rid::post(Reply::CompletedTodo(req_id, id.to_string())); } RestartTodo(id) => { self.update_todo(id, |todo| todo.set_completed(false)); rid::post(Reply::RestartedTodo(req_id, id.to_string())); } ToggleTodo(id) => { self.update_todo(id, |todo| todo.set_completed(!todo.completed)); rid::post(Reply::ToggledTodo(req_id, id.to_string())); } CompleteAll => { self.todos.iter_mut().for_each(|x| x.set_completed(true)); rid::post(Reply::CompletedAll(req_id)); } RestartAll => { self.todos.iter_mut().for_each(|x| x.set_completed(false)); rid::post(Reply::RestartedAll(req_id)); } SetFilter(filter) => { self.filter = filter; rid::post(Reply::SetFilter(req_id)); } SetAutoExpireCompletedTodos(expire) => { self.set_auto_expire_completed_todos(expire); rid::post(Reply::SetAutoExpireCompletedTodos(req_id)); } }; } } #[rid::export] #[rid::structs(Todo)] impl Store { fn remove_todo(&mut self, id: u32) { let mut enumerated = self.todos.iter().enumerate(); let idx = match enumerated.find(|(_, todo)| todo.id == id) { Some((idx, _)) => idx, None => return eprintln!("Could not find Todo with id '{}'", id), }; self.todos.remove(idx); } fn update_todo(&mut self, id: u32, update: F) { match self.todos.iter_mut().find(|x| x.id == id) { Some(todo) => update(todo), None => eprintln!("Could not find Todo with id '{}'", id), }; } #[rid::export] fn filtered_todos(&self) -> Vec<&Todo> { let mut vec: Vec<&Todo> = match self.filter { Filter::Completed => self.todos.iter().filter(|x| x.completed).collect(), Filter::Pending => self.todos.iter().filter(|x| !x.completed).collect(), Filter::All => self.todos.iter().collect(), }; vec.sort(); vec } #[rid::export] fn todo_by_id(&self, id: u32) -> Option<&Todo> { self.todos.iter().find(|x| x.id == id) } // The below read/write wrappers help with auto complete since procmacros // aren't very well supported by the rust analyzer yet fn read() -> RwLockReadGuard<'static, Store> { store::read() } fn write() -> RwLockWriteGuard<'static, Store> { store::write() } pub fn set_auto_expire_completed_todos(&mut self, expire: bool) { self.settings.auto_expire_completed_todos = expire; if expire { thread::spawn(move || { eprintln!( "rust: thread {:?} started auto expiring", thread::current().id() ); while Store::read().settings.auto_expire_completed_todos { thread::sleep(time::Duration::from_millis(EXPIRY_STEP)); { let ids_to_update: Vec<(u32, bool)> = Store::write() .todos .iter_mut() .filter(|x| x.completed) .map(|x: &mut Todo| { let next_value = x.expiry_millis - EXPIRY_STEP; if next_value <= 0 { (x.id, true) } else { x.expiry_millis = next_value; (x.id, false) } }) .collect(); for (id, remove) in ids_to_update { if remove { Store::write().remove_todo(id); rid::post(Reply::CompletedTodoExpired); } else { rid::post(Reply::Tick(id.to_string())); } } } } eprintln!( "rust: thread {:?} stopped auto expiring", thread::current().id() ); }); } } } // ----------------- // Settings // ----------------- #[rid::model] #[derive(Debug)] pub struct Settings { auto_expire_completed_todos: bool, completed_expiry_millis: u64, } // ----------------- // Todo Model // ----------------- #[rid::model] #[derive(PartialEq, Eq, PartialOrd, Debug)] pub struct Todo { id: u32, title: String, completed: bool, expiry_millis: u64, } impl Todo { fn set_completed(&mut self, completed: bool) { self.completed = completed; self.expiry_millis = COMPLETED_EXPIRY_MILLIS; } } impl Ord for Todo { fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.id.cmp(&other.id) } } // ----------------- // Filter // ----------------- #[rid::model] #[derive(Clone, Debug)] pub enum Filter { Completed, Pending, All, } // ----------------- // Msg // ----------------- #[rid::message(Reply)] #[rid::enums(Filter)] #[derive(Debug)] pub enum Msg { AddTodo(String), RemoveTodo(u32), RemoveCompleted, CompleteTodo(u32), RestartTodo(u32), ToggleTodo(u32), CompleteAll, RestartAll, SetFilter(Filter), SetAutoExpireCompletedTodos(bool), } // ----------------- // Reply // ----------------- #[rid::reply] pub enum Reply { // Message Replies AddedTodo(u64, String), RemovedTodo(u64, String), RemovedCompleted(u64), CompletedTodo(u64, String), RestartedTodo(u64, String), ToggledTodo(u64, String), CompletedAll(u64), RestartedAll(u64), SetFilter(u64), SetAutoExpireCompletedTodos(u64), // Application Events CompletedTodoExpired, Tick(String), } ================================================ FILE: flutter/todo_cubit/test/widget_test.dart ================================================ // This is a basic Flutter widget test. // // To perform an interaction with a widget in your test, use the WidgetTester // utility that Flutter provides. For example, you can send tap and scroll // gestures. You can also use WidgetTester to find child widgets in the widget // tree, read text, and verify that the values of widget properties are correct. import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:todo_cubit/main.dart'; void main() { testWidgets('Counter increments smoke test', (WidgetTester tester) async { // Build our app and trigger a frame. await tester.pumpWidget(MyApp()); // Verify that our counter starts at 0. expect(find.text('0'), findsOneWidget); expect(find.text('1'), findsNothing); // Tap the '+' icon and trigger a frame. await tester.tap(find.byIcon(Icons.add)); await tester.pump(); // Verify that our counter has incremented. expect(find.text('0'), findsNothing); expect(find.text('1'), findsOneWidget); }); } ================================================ FILE: flutter/todo_cubit/todo_cubit.iml ================================================ ================================================ FILE: flutter/todo_cubit/web/index.html ================================================ todo_cubit ================================================ FILE: flutter/todo_cubit/web/manifest.json ================================================ { "name": "todo_cubit", "short_name": "todo_cubit", "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" } ] } ================================================ FILE: flutter/todo_cubit/windows/.gitignore ================================================ 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/ ================================================ FILE: flutter/todo_cubit/windows/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.15) project(todo_cubit LANGUAGES CXX) set(BINARY_NAME "todo_cubit") 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) ================================================ FILE: flutter/todo_cubit/windows/flutter/CMakeLists.txt ================================================ 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} ) ================================================ FILE: flutter/todo_cubit/windows/flutter/generated_plugin_registrant.cc ================================================ // // Generated file. Do not edit. // #include "generated_plugin_registrant.h" void RegisterPlugins(flutter::PluginRegistry* registry) { } ================================================ FILE: flutter/todo_cubit/windows/flutter/generated_plugin_registrant.h ================================================ // // Generated file. Do not edit. // #ifndef GENERATED_PLUGIN_REGISTRANT_ #define GENERATED_PLUGIN_REGISTRANT_ #include // Registers Flutter plugins. void RegisterPlugins(flutter::PluginRegistry* registry); #endif // GENERATED_PLUGIN_REGISTRANT_ ================================================ FILE: flutter/todo_cubit/windows/flutter/generated_plugins.cmake ================================================ # # 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) ================================================ FILE: flutter/todo_cubit/windows/runner/CMakeLists.txt ================================================ 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) ================================================ FILE: flutter/todo_cubit/windows/runner/Runner.rc ================================================ // 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", "todo_cubit" "\0" VALUE "LegalCopyright", "Copyright (C) 2021 com.example. All rights reserved." "\0" VALUE "OriginalFilename", "todo_cubit.exe" "\0" VALUE "ProductName", "todo_cubit" "\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 ================================================ FILE: flutter/todo_cubit/windows/runner/flutter_window.cpp ================================================ #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); } ================================================ FILE: flutter/todo_cubit/windows/runner/flutter_window.h ================================================ #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_ ================================================ FILE: flutter/todo_cubit/windows/runner/main.cpp ================================================ #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"todo_cubit", origin, size)) { return EXIT_FAILURE; } window.SetQuitOnClose(true); run_loop.Run(); ::CoUninitialize(); return EXIT_SUCCESS; } ================================================ FILE: flutter/todo_cubit/windows/runner/resource.h ================================================ //{{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 ================================================ FILE: flutter/todo_cubit/windows/runner/run_loop.cpp ================================================ #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; } ================================================ FILE: flutter/todo_cubit/windows/runner/run_loop.h ================================================ #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_ ================================================ FILE: flutter/todo_cubit/windows/runner/runner.exe.manifest ================================================ PerMonitorV2 ================================================ FILE: flutter/todo_cubit/windows/runner/utils.cpp ================================================ #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; } ================================================ FILE: flutter/todo_cubit/windows/runner/utils.h ================================================ #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_ ================================================ FILE: flutter/todo_cubit/windows/runner/win32_window.cpp ================================================ #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. } ================================================ FILE: flutter/todo_cubit/windows/runner/win32_window.h ================================================ #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_ ================================================ FILE: flutter/todo_cubit_wasm/.gitignore ================================================ .DS_Store .idea/ .metadata ## Flutter ### # Flutter/Dart/Pub related **/doc/api/ .dart_tool/ .flutter-plugins .flutter-plugins-dependencies .fvm/ .packages .pub-cache/ .pub/ build/ coverage/ lib/generated_plugin_registrant.dart # For library packages, don’t commit the pubspec.lock file. # Regenerating the pubspec.lock file lets you test your package against the latest compatible versions of its dependencies. # See https://dart.dev/guides/libraries/private-files#pubspeclock #pubspec.lock # Android related **/android/**/gradle-wrapper.jar **/android/.gradle **/android/captures/ **/android/gradlew **/android/gradlew.bat **/android/key.properties **/android/local.properties **/android/**/GeneratedPluginRegistrant.java # iOS/XCode related **/ios/**/*.mode1v3 **/ios/**/*.mode2v3 **/ios/**/*.moved-aside **/ios/**/*.pbxuser **/ios/**/*.perspectivev3 **/ios/**/*sync/ **/ios/**/.sconsign.dblite **/ios/**/.tags* **/ios/**/.vagrant/ **/ios/**/DerivedData/ **/ios/**/Icon? **/ios/**/Pods/ **/ios/**/.symlinks/ **/ios/**/profile **/ios/**/xcuserdata **/ios/.generated/ **/ios/Flutter/.last_build_id **/ios/Flutter/App.framework **/ios/Flutter/Flutter.framework **/ios/Flutter/Flutter.podspec **/ios/Flutter/Generated.xcconfig **/ios/Flutter/app.flx **/ios/Flutter/app.zip **/ios/Flutter/flutter_assets/ **/ios/Flutter/flutter_export_environment.sh **/ios/ServiceDefinitions.json **/ios/Runner/GeneratedPluginRegistrant.* # Exceptions to above rules. !**/ios/**/default.mode1v3 !**/ios/**/default.mode2v3 !**/ios/**/default.pbxuser !**/ios/**/default.perspectivev3 !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages ### Rust ### **/target/** **/target_wasm/** **/Cargo.lock ### Rid ### **/generated/** **/Classes/bindings.h **/macos/*.a **/ios/*.a **/android/src/main/jniLibs/* ================================================ FILE: flutter/todo_cubit_wasm/Cargo.toml ================================================ [package] name = "todo_cubit" version = "0.1.0" authors = ["Thorsten Lorenz "] edition = "2018" [lib] crate-type = ["cdylib", "staticlib" ] [[bin]] name = "rid_build" path = "rid_build.rs" [dependencies] cbindgen = "0.18.0" rid_build = { path = "../../../rid/rid-build" } rid = { path = "../../../rid" } serde = "1.0.123" serde_json = "1.0.64" # https://rustwasm.github.io/docs/book/reference/code-size.html [profile.release] opt-level = 'z' ================================================ FILE: flutter/todo_cubit_wasm/README.md ================================================ # todo_cubit Rust integrated Dart Flutter Project ## Getting Started Use the below scripts to get the app ready to run with Flutter. ### 1. Generate Glue Code ```sh ./sh/bindgen ``` ### 2. Build For Desired Target/Device Run any of the below three to build the binary for the specific device and have it placed into the devices specific plugin folder. ```sh ./sh/macos ``` ### 3. Run with Flutter Run on the device. ```sh flutter run -d macos ``` ### 4. Develop Run step `1` whenever a function exposed to Flutter changes. Run step `2` whenever any of your Rust code changes. **Note** that to apply changes from Rust you need to restart the app to reload the compiled binary. A hot restart/reload does not achieve this. ## Folder Structure ``` ├── android ├── ios ├── macos ├── lib ├── plugin │ ├── android │ ├── ios │ ├── macos │ └── lib └── src ``` ### `./plugin` Provides connection from Flutter to Rust. Rust binaries are placed into the respective plugin folders `./ios, ./macos, ./android` when they are built. Generated Dart glue code is placed inside `./plugin/lib/generated` while `./plugin/lib/plugin.dart` just exposes the API to the app. ### `./src` Contains the starter Rust code inside `./src/lib.rs`. Keep developing the Rust part of your app here. ### `./lib` Contains the starter Flutter app inside `./lib/main.dart`. ### `./sh` Provides scripts to run build and code generation tasks. In the future a tool will provide the functionality currently provided by these scripts. - `bindgen` generates the `binding.h` header file for the extern Rust functions found inside `./src`. These are then placed inside the `./plugin` device folders were needed as well as `./plugin/lib/generated/binding.h` where they are used to generate Dart glue code - as part of this script `ffigen` generates Dart glue code inside `./plugin/lib/generated/ffigen_binding.dart` using `./plugin/lib/generated/binding.h` as input - `./android` builds the Rust binary to run on Android devices/emulators and places it inside `./plugin/lib/android` - `./ios` builds the Rust binary to run on IOS devices/emulators and places it inside `./plugin/lib/ios` - `./macos` builds the Rust binary to run on MacOs directly and places it inside `./plugin/lib/macos`, this is the same format as running `cargo build` on your Mac - `clean` cleans both the Flutter plugin and application, run this to reset Flutter when things aren't working ================================================ FILE: flutter/todo_cubit_wasm/analysis_options.yaml ================================================ # This file configures the analyzer, which statically analyzes Dart code to # check for errors, warnings, and lints. # # The issues identified by the analyzer are surfaced in the UI of Dart-enabled # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be # invoked from the command line by running `flutter analyze`. # The following line activates a set of recommended lints for Flutter apps, # packages, and plugins designed to encourage good coding practices. include: package:flutter_lints/flutter.yaml linter: # The lint rules applied to this project can be customized in the # section below to disable rules from the `package:flutter_lints/flutter.yaml` # included above or to enable additional rules. A list of all available lints # and their documentation is published at # https://dart-lang.github.io/linter/lints/index.html. # # Instead of disabling a lint rule for the entire project in the # section below, it can also be suppressed for a single line of code # or a specific dart file by using the `// ignore: name_of_lint` and # `// ignore_for_file: name_of_lint` syntax on the line or in the file # producing the lint. rules: # avoid_print: false # Uncomment to disable the `avoid_print` rule # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options ================================================ FILE: flutter/todo_cubit_wasm/lib/blocs/cubit/filter_cubit.dart ================================================ import 'package:bloc/bloc.dart'; import 'package:plugin/generated/rid_api.dart'; class FilterCubit extends Cubit { final Store _store = Store.instance; FilterCubit() : super(Store.instance.filter); Future setFilter(Filter filter) async { await _store.msgSetFilter(filter); emit(_store.filter); } } ================================================ FILE: flutter/todo_cubit_wasm/lib/blocs/cubit/settings_cubit.dart ================================================ import 'package:bloc/bloc.dart'; import 'package:plugin/generated/rid_api.dart'; class SettingsCubit extends Cubit { final Store _store = Store.instance; SettingsCubit() : super(Store.instance.settings); Future setAutoExpireCompleted(bool val) async { await _store.msgSetAutoExpireCompletedTodos(val); emit(_store.settings); } } ================================================ FILE: flutter/todo_cubit_wasm/lib/blocs/cubit/todo_cubit.dart ================================================ import 'dart:async'; import 'package:bloc/bloc.dart'; import 'package:flutter/foundation.dart'; import 'package:meta/meta.dart'; import 'package:plugin/generated/rid_api.dart'; part 'todo_state.dart'; class TodoCubit extends Cubit { final Store _store = Store.instance; late final StreamSubscription tickSub; TodoCubit(Todo todo) : super(ExistingTodo(todo)) { _subscribe(); } // Reply.Tick is a reply that includes data, in this case the id // of the completed todo whose life is ticking away bool _tickIsForThisTodo(PostedReply reply) { // We make sure that the data is a parseable int id assert( reply.data != null, 'Reply.Tick should include data containing the id of the ticked todo', ); final id = int.tryParse(reply.data!); assert(id != null, 'Reply.Tick included invalid id ${reply.data}'); return id == state.id; } void _subscribe() { tickSub = rid.replyChannel.stream .where((x) => x.type == Reply.Tick && _tickIsForThisTodo(x)) .listen(_refreshState); } @override Future close() { tickSub.cancel(); return super.close(); } void _refreshState(PostedReply _reply) async { final todo = _store.todoById(state.id); if (todo == null) { emit(MissingTodo(state.id)); } else { emit(ExistingTodo(todo)); } } Future toggleCompleted() => _store.msgToggleTodo(state.id).then(_refreshState); Future removeTodo(int id) => _store.msgRemoveTodo(id).then(_refreshState); } ================================================ FILE: flutter/todo_cubit_wasm/lib/blocs/cubit/todo_state.dart ================================================ part of 'todo_cubit.dart'; @immutable abstract class TodoState { final int id; TodoState(this.id); @override bool operator ==(Object other) => identical(this, other) || other is TodoState && other.id == id; @override int get hashCode => id; } class ExistingTodo extends TodoState { final Todo todo; ExistingTodo(this.todo) : super(todo.id); @override bool operator ==(Object other) => identical(this, other) || other is ExistingTodo && other.todo == todo; @override int get hashCode => todo.hashCode; } class MissingTodo extends TodoState { MissingTodo(int id) : super(id); @override bool operator ==(Object other) => identical(this, other) || other is MissingTodo && other.id == id; @override int get hashCode => super.hashCode; } ================================================ FILE: flutter/todo_cubit_wasm/lib/blocs/cubit/todos_cubit.dart ================================================ import 'dart:async'; import 'package:bloc/bloc.dart'; import 'package:flutter/foundation.dart'; import 'package:meta/meta.dart'; import 'package:plugin/generated/rid_api.dart'; part 'todos_state.dart'; class TodosCubit extends Cubit { late final StreamSubscription removedTodosSub; final Store _store = Store.instance; TodosCubit() : super(TodosState(Store.instance.filteredTodos())) { _subscribe(); } void _subscribe() { removedTodosSub = rid.replyChannel.stream .where((x) => x.type == Reply.RemovedTodo || x.type == Reply.RemovedCompleted || x.type == Reply.CompletedTodoExpired || x.type == Reply.SetFilter) .listen(_refreshList); } @override Future close() async { await removedTodosSub.cancel(); return super.close(); } void _refreshList(PostedReply _reply) async { final todos = _store.filteredTodos(); emit(TodosState(todos)); debugPrint('${_store.raw.debug(true)}'); } Future addTodo(String title) => _store.msgAddTodo(title).then(_refreshList); Future restartAll() => _store.msgRestartAll().then(_refreshList); Future completeAll() => _store.msgCompleteAll().then(_refreshList); Future removeCompleted() => _store.msgRemoveCompleted().then(_refreshList); } ================================================ FILE: flutter/todo_cubit_wasm/lib/blocs/cubit/todos_state.dart ================================================ part of 'todos_cubit.dart'; @immutable class TodosState { final List todos; const TodosState(this.todos); @override bool operator ==(Object other) => identical(this, other) || other is TodosState && other.todos == todos; @override int get hashCode => todos.hashCode; } ================================================ FILE: flutter/todo_cubit_wasm/lib/main.dart ================================================ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:plugin/generated/rid_api.dart'; import 'package:todo_cubit/blocs/cubit/filter_cubit.dart'; import 'package:todo_cubit/blocs/cubit/settings_cubit.dart'; import 'package:todo_cubit/blocs/cubit/todos_cubit.dart'; import 'package:todo_cubit/views/menu.dart'; import 'package:todo_cubit/views/todos.dart'; const Color FILTER_SELECTED_COLOR = Colors.blue; const Color FILTER_UNSELECTED_COLOR = Colors.black; void configRid() { rid.debugReply = (reply) => debugPrint('$reply'); } const WASM_FILE = 'todo_cubit.wasm'; void main() async { // HTTP_HOST = 'localhost:8080'; await initWasm(WASM_FILE); debugPrint(rid_ffi.toString()); configRid(); // await diagnose(); runApp(TodoApp()); } class TodoApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Rust/Flutter Cubit Todo App', theme: ThemeData( primarySwatch: Colors.blue, ), home: MultiBlocProvider( providers: [ BlocProvider(create: (_) => SettingsCubit()), BlocProvider(create: (_) => TodosCubit()), BlocProvider(create: (_) => FilterCubit()) ], child: TodosPage(title: 'Cubit Todo App'), ), debugShowCheckedModeBanner: false, ); } } class TodosPage extends StatefulWidget { final String title; TodosPage({Key? key, required this.title}) : super(key: key); @override _TodosPageState createState() => _TodosPageState(); } class _TodosPageState extends State { final _textFieldController = TextEditingController(); String? addTodoTitle; @override Widget build(BuildContext context) { final filter = context.watch().state; return SafeArea( child: Scaffold( appBar: AppBar( title: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(widget.title), Row( children: [ Image.asset( "assets/dash.png", height: 40.0, width: 40.0, ), Icon(Icons.favorite, color: Colors.red), Image.asset( "assets/ferris.png", height: 50.0, width: 50.0, ), ], ) ], ), ), drawer: Drawer(child: Menu()), bottomNavigationBar: BottomAppBar( child: Row( children: [ IconButton( icon: Icon( Icons.calendar_today_rounded, color: filter == Filter.Pending ? FILTER_SELECTED_COLOR : FILTER_UNSELECTED_COLOR, ), onPressed: () => context.read().setFilter(Filter.Pending)), Spacer(), IconButton( icon: Icon( Icons.check, color: filter == Filter.Completed ? FILTER_SELECTED_COLOR : FILTER_UNSELECTED_COLOR, ), onPressed: () => context.read().setFilter(Filter.Completed)), IconButton( icon: Icon( Icons.all_inclusive, color: filter == Filter.All ? FILTER_SELECTED_COLOR : FILTER_UNSELECTED_COLOR, ), onPressed: () => context.read().setFilter(Filter.All)), ], ), ), body: TodosView(), floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked, floatingActionButton: FloatingActionButton( onPressed: () async { _textFieldController.clear(); await _addTodoDialog(context); final title = addTodoTitle; if (title != null && title.trim().isNotEmpty) { context.read().addTodo(title); } }, tooltip: 'Add Todo', child: Icon(Icons.add), ), ), ); } Future _addTodoDialog(BuildContext context) async { return showDialog( context: context, builder: (context) { return AlertDialog( title: Text('Enter Todo Title'), content: TextField( controller: _textFieldController, decoration: InputDecoration(hintText: "Todo title"), autofocus: true, ), actions: [ TextButton( child: Text('Done'), onPressed: () { addTodoTitle = _textFieldController.value.text; Navigator.pop(context); }, ), ], ); }); } } ================================================ FILE: flutter/todo_cubit_wasm/lib/views/expiry.dart ================================================ import 'package:flutter/material.dart'; Color expiryColor(double completedExpiryMillis, double remaining) { return remaining > completedExpiryMillis * 0.80 ? Colors.greenAccent : remaining > completedExpiryMillis * 0.60 ? Colors.green : remaining > completedExpiryMillis * 0.4 ? Colors.orange : remaining > completedExpiryMillis * 0.2 ? Colors.redAccent : Colors.red; } class ExpiryWidget extends StatelessWidget { final double completedExpiryMillis; final double remainingMillis; const ExpiryWidget({ required this.completedExpiryMillis, required this.remainingMillis, }) : super(); Widget build(BuildContext context) { final totalWidth = MediaQuery.of(context).size.width * 0.8; final expiryWidth = (remainingMillis / completedExpiryMillis) * totalWidth; return Container( height: 10, width: totalWidth, decoration: BoxDecoration( border: Border.all( color: Colors.blueGrey, width: 1.0, style: BorderStyle.solid, ), borderRadius: BorderRadius.all(Radius.circular(2.0)), ), child: Container( margin: EdgeInsets.only(right: totalWidth - expiryWidth), color: expiryColor(completedExpiryMillis, remainingMillis), ), ); } } ================================================ FILE: flutter/todo_cubit_wasm/lib/views/menu.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:todo_cubit/blocs/cubit/settings_cubit.dart'; import 'package:todo_cubit/blocs/cubit/todos_cubit.dart'; class Menu extends StatelessWidget { @override Widget build(BuildContext context) { return ListView( padding: EdgeInsets.zero, children: [ ListTile(title: Text('Todo Actions')), Divider(), ListTile( title: Text('Restart All'), onTap: () => context.read().restartAll(), ), ListTile( title: Text('Complete All'), onTap: () => context.read().completeAll(), ), ListTile( title: Text('Remove Completed'), onTap: () => context.read().removeCompleted(), ), ], ); } } class AutoRemoveCompletedWidget extends StatelessWidget { @override Widget build(BuildContext context) { return Checkbox( value: context.select( (x) => x.state.autoExpireCompletedTodos, ), onChanged: (val) { if (val != null) context.read().setAutoExpireCompleted(val); }, ); } } ================================================ FILE: flutter/todo_cubit_wasm/lib/views/todo.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:plugin/generated/rid_api.dart'; import 'package:todo_cubit/blocs/cubit/settings_cubit.dart'; import 'package:todo_cubit/blocs/cubit/todo_cubit.dart'; import 'package:todo_cubit/views/expiry.dart'; class TodoView extends StatelessWidget { @override Widget build(BuildContext context) { return BlocBuilder(builder: (context, state) { if (state is MissingTodo) { return Text('Todo ${state.id} missing, most likely removed'); } if (state is ExistingTodo) { final todo = state.todo; return Dismissible( key: Key("Todo Dismissible ${todo.id}"), child: Card( child: InkWell( onTap: () => context.read().toggleCompleted(), child: ListTile( leading: todo.completed ? Icon(Icons.check, color: Colors.green) : Icon(Icons.calendar_today_rounded), title: Text('${todo.title}'), subtitle: BlocBuilder( builder: (context, settings) { return settings.autoExpireCompletedTodos && todo.completed ? ExpiryWidget( completedExpiryMillis: settings.completedExpiryMillis.toDouble(), remainingMillis: todo.expiryMillis.toDouble(), ) : Container(); }, ), ), ), ), direction: DismissDirection.endToStart, // Make sure we removed the Todo and got the reply before updating the UI confirmDismiss: (_) => context.read().removeTodo(todo.id).then((_) => true), background: Padding( padding: EdgeInsets.all(5.0), child: Container(color: Colors.red), ), ); } else { return Text('Unkown TodoState type $state'); } }); } } ================================================ FILE: flutter/todo_cubit_wasm/lib/views/todos.dart ================================================ import 'package:flutter/material.dart'; import 'package:todo_cubit/blocs/cubit/todo_cubit.dart'; import 'package:todo_cubit/blocs/cubit/todos_cubit.dart'; import 'package:todo_cubit/views/todo.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class TodosView extends StatelessWidget { @override Widget build(BuildContext context) { return Center( child: BlocBuilder(builder: (context, state) { final todos = state.todos; return ListView.builder( itemCount: todos.length, itemBuilder: (context, index) { final todo = todos[index]; return BlocProvider( create: (_) => TodoCubit(todo), child: TodoView(), key: Key(todo.hashCode.toString()), ); }); }), ); } } ================================================ FILE: flutter/todo_cubit_wasm/plugin/.gitignore ================================================ .DS_Store .dart_tool/ .packages .pub/ build/ ================================================ FILE: flutter/todo_cubit_wasm/plugin/LICENSE ================================================ TODO: Add your license here. ================================================ FILE: flutter/todo_cubit_wasm/plugin/analysis_options.yaml ================================================ include: package:flutter_lints/flutter.yaml # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options ================================================ FILE: flutter/todo_cubit_wasm/plugin/lib/wasm/reply_channel.dart ================================================ import 'dart:async'; import '../generated/rid_api.dart'; const String RESPONSE_SEPARATOR = '^'; abstract class IReply { int? get reqId; String? get data; } typedef Decode = TReply Function(ReplyStruct reply); // TODO: error handling (could be part of Post data) class ReplyChannel { final _zone = Zone.current; final StreamController _sink; final Decode _decode; final NativeLibrary _dl; late final _zonedAdd; late final Timer _pollTimer; int _lastReqId = 0; ReplyChannel._(this._dl, this._decode, bool isDebugMode) : _sink = StreamController.broadcast() { _zonedAdd = _zone.registerUnaryCallback(_add); _pollReplies(); } void _pollReplies() { _pollTimer = Timer.periodic(Duration(milliseconds: 100), (_) { // TODO: ugly hack to prevent printing polling logs for now final save = rid.debugLock; rid.debugLock = null; // TODO: need to Readlock replies final ptr = _dl.rid_poll_reply(); final castPtr = Pointer.fromAddress(RawReplyStruct(ptr.address)); final reply = ptr.address == 0x0 ? null : castPtr.toDart(); rid.debugLock = save; if (reply != null) { _onReceivedReply(reply); _dl.rid_handled_reply(reply.reqId); } }); } void _onReceivedReply(ReplyStruct reply) { _zone.runUnary(_zonedAdd, reply); } void _add(ReplyStruct reply) { if (!_sink.isClosed) { _sink.add(_decode(reply)); } } Stream get stream => _sink.stream; Future reply(int reqId) { assert(reqId != 0, "Invalid requestID "); return stream.firstWhere((reply) { final replyId = reply.reqId; if (replyId == null) return false; return reqId == replyId; }).onError((error, stackTrace) { print( "The responseChannel was disposed while a message was waiting for a reply.\n" "Did you forget to `await` the reply to the message with reqId: '$reqId'?\n" "Ignore the message further down about type 'Null'.\n" "The real problem is that no reply for the message was posted yet, but the reply \n" "stream is being disposed most likely via `store.dispose()` causing the following:.\n"); print(error); print(stackTrace); return null as TReply; }); } int get reqId { _lastReqId++; return _lastReqId; } Future dispose() { if (_pollTimer.isActive) _pollTimer.cancel(); return _sink.close(); } static bool _initialized = false; static ReplyChannel instance( NativeLibrary dl, Decode decode, bool isDebugMode, ) { if (_initialized && !isDebugMode) { throw Exception( "The reply channel can only be initialized once unless running in debug mode"); } _initialized = true; return ReplyChannel._(dl, decode, isDebugMode); } } ================================================ FILE: flutter/todo_cubit_wasm/plugin/lib/wasm/utils.dart ================================================ import 'dart:html'; import 'dart:typed_data'; import 'package:flutter/services.dart'; import 'package:universal_io/io.dart'; String HTTP_ROOT = window.location.href.replaceFirst(RegExp('#/\$'), ''); Future loadWasmFromNetwork(String wasmFile) async { final path = '$HTTP_ROOT/$wasmFile'; try { // http-server --cors final httpClient = HttpClient(); final request = await httpClient.getUrl(Uri.parse(path)); if (request is BrowserHttpClientRequest) { request.browserResponseType = 'arraybuffer'; } final response = await request.close(); final list = await response.toList().then((List> lists) { return lists.fold>([], (List acc, List list) { acc.addAll(list); return acc; }); }); return Uint8List.fromList(list); } catch (e) { print(e); print("Couldn't open $path"); return Uint8List.fromList([]); } } // This is currently not working for web apps // It may be useful once we support Wasm for non-web apps Future loadWasmAsset(String wasmAsset) async { return rootBundle.load(wasmAsset).then((bytes) => bytes.buffer.asUint8List()); } ================================================ FILE: flutter/todo_cubit_wasm/plugin/plugin.iml ================================================ ================================================ FILE: flutter/todo_cubit_wasm/plugin/pubspec.yaml ================================================ name: plugin description: Plugin to provide a bridge to Rust. version: 0.0.1 environment: sdk: ">=2.13.0 <=3.0.0" flutter: ">=2.0.0" wasmjsgen: comments: false allocate: 'rid_malloc' deallocate: 'rid_free' reallocate: 'rid_realloc' output: 'lib/generated/ffigen_binding.dart' headers: entry-points: - 'lib/generated/bindings.h' ffigen: name: plugin description: Plugin to provide a bridge to Rust. output: 'lib/generated/nativ_binding.dart' headers: entry-points: - 'lib/generated/bindings.h' dependencies: ffi: ^1.0.0 wasm_interop: ^2.0.0 flutter: sdk: flutter universal_io: ^2.0.4 dev_dependencies: wasmjsgen: ^0.0.4 flutter: # This section identifies this Flutter project as a plugin project. # The 'pluginClass' and Android 'package' identifiers should not ordinarily # be modified. They are used by the tooling to maintain consistency when # adding or updating assets for this project. plugin: platforms: android: package: com.example.plugin pluginClass: Plugin ios: pluginClass: Plugin macos: pluginClass: Plugin ================================================ FILE: flutter/todo_cubit_wasm/pubspec.yaml ================================================ name: todo_cubit description: A new Flutter project. publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 1.0.0+1 environment: sdk: ">=2.13.0 <3.0.0" dependencies: flutter: sdk: flutter plugin: path: plugin bloc: ^7.0.0 flutter_bloc: ^7.0.1 provider: ^5.0.0 dev_dependencies: flutter_test: sdk: flutter flutter: uses-material-design: true assets: - assets/dash.png - assets/ferris.png ================================================ FILE: flutter/todo_cubit_wasm/rid_build.rs ================================================ use rid_build::{build, BuildConfig, BuildTarget, FlutterConfig, Project}; use std::env; fn main() { let crate_dir = env::var("CARGO_MANIFEST_DIR") .expect("Missing CARGO_MANIFEST_DIR, please run this via 'cargo run'"); let workspace_dir = &crate_dir; let crate_name = &env::var("CARGO_PKG_NAME") .expect("Missing CARGO_PKG_NAME, please run this via 'cargo run'"); let lib_name = &format!("lib{}", &crate_name); let build_config = BuildConfig { target: BuildTarget::Debug, project: Project::Flutter(FlutterConfig { plugin_name: "plugin".to_string(), // TODO: once rid itself supports WASM we need to add web architecture here platforms: vec![], }), lib_name, crate_name, project_root: &crate_dir, workspace_root: Some(&workspace_dir), }; build(&build_config).expect("Build failed"); } ================================================ FILE: flutter/todo_cubit_wasm/sh/build-web ================================================ #!/usr/bin/env bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" flutter build web --web-renderer html --base-href=/rid-examples/todo_cubit/ $DIR/wasm-release ================================================ FILE: flutter/todo_cubit_wasm/sh/clean ================================================ #!/usr/bin/env bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" (cd $DIR/../plugin && flutter clean && flutter pub get) (cd $DIR/.. && flutter clean && flutter pub get) ================================================ FILE: flutter/todo_cubit_wasm/sh/debug-web ================================================ #!/usr/bin/env bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" flutter build web --web-renderer html --profile --dart-define=Dart2jsOptimization=O0 $DIR/wasm-release (cd $DIR/../build/web && http-server) ================================================ FILE: flutter/todo_cubit_wasm/sh/wasm ================================================ #!/usr/bin/env bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" ROOT=$DIR/.. cargo build --target wasm32-unknown-unknown --lib --target-dir target_wasm mv $ROOT/target_wasm/wasm32-unknown-unknown/debug/todo_cubit.wasm $ROOT/todo_cubit.wasm ================================================ FILE: flutter/todo_cubit_wasm/sh/wasm-release ================================================ #!/usr/bin/env bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" ROOT=$DIR/.. cargo build --release --target wasm32-unknown-unknown --lib --target-dir target_wasm # https://rustwasm.github.io/docs/book/reference/code-size.html#use-the-wasm-opt-tool WASM_INTERFACE_TYPES=1 wasm-opt -Oz -o \ $ROOT/todo_cubit.wasm \ $ROOT/target_wasm/wasm32-unknown-unknown/release/todo_cubit.wasm cp $ROOT/todo_cubit.wasm $ROOT/build/web/todo_cubit.wasm ================================================ FILE: flutter/todo_cubit_wasm/sh/wasmgen ================================================ #!/usr/bin/env bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" ROOT=$DIR/.. cd $ROOT && cargo run rid_build && \ cd $ROOT/plugin && flutter pub run wasmjsgen --verbose severe cp $ROOT/plugin/lib/generated/rid_api.sav.dart $ROOT/plugin/lib/generated/rid_api.dart ================================================ FILE: flutter/todo_cubit_wasm/src/alloc.rs ================================================ // https://radu-matei.com/blog/practical-guide-to-wasm-memory/ // https://github.com/rustwasm/wasm-bindgen/blob/eb855e3fd48188bef6bbea8180102f5fe550a0e5/src/lib.rs#L969-L1026 // TODO: look into this hack to get it included in the binary (possibly also into bindings?) // https://github.com/rustwasm/wasm-bindgen/blob/eb855e3fd48188bef6bbea8180102f5fe550a0e5/src/lib.rs#L1028-L1063 mod __rid_malloc_methods { use std::alloc::{alloc, dealloc, realloc, Layout}; use std::mem; #[no_mangle] pub extern "C" fn rid_malloc(size: usize) -> *mut u8 { let align = mem::align_of::(); if let Ok(layout) = Layout::from_size_align(size, align) { unsafe { if layout.size() > 0 { let ptr = alloc(layout); if !ptr.is_null() { return ptr; } } else { return align as *mut u8; } } } malloc_failure(); } #[no_mangle] pub unsafe extern "C" fn rid_realloc( ptr: *mut u8, old_size: usize, new_size: usize, ) -> *mut u8 { let align = mem::align_of::(); debug_assert!(old_size > 0); debug_assert!(new_size > 0); if let Ok(layout) = Layout::from_size_align(old_size, align) { let ptr = realloc(ptr, layout, new_size); if !ptr.is_null() { return ptr; } } malloc_failure(); } #[cold] fn malloc_failure() -> ! { std::process::abort(); } #[no_mangle] pub unsafe extern "C" fn rid_free(ptr: *mut u8, size: usize) { // This happens for zero-length slices, and in that case `ptr` is // likely bogus so don't actually send this to the system allocator if size == 0 { return; } let align = mem::align_of::(); let layout = Layout::from_size_align_unchecked(size, align); dealloc(ptr, layout); } } ================================================ FILE: flutter/todo_cubit_wasm/src/lib.rs ================================================ mod alloc; mod replies; use rid::RidStore; use serde::Serialize; const COMPLETED_EXPIRY_MILLIS: u64 = 7000; // ----------------- // Store // ----------------- #[rid::store] #[rid::structs(Todo, Settings)] #[rid::enums(Filter)] #[derive(Debug)] pub struct Store { last_added_id: u32, todos: Vec, filter: Filter, settings: Settings, } impl RidStore for Store { fn create() -> Self { let first_todo = Todo { id: 0, title: "Learn Flutter".to_string(), completed: true, expiry_millis: COMPLETED_EXPIRY_MILLIS, }; let second_todo = Todo { id: 1, title: "Learn Rust".to_string(), completed: true, expiry_millis: COMPLETED_EXPIRY_MILLIS, }; let third_todo = Todo { id: 2, title: "Learn Rid".to_string(), completed: false, expiry_millis: COMPLETED_EXPIRY_MILLIS, }; let fourth_todo = Todo { id: 3, title: "Build Awesome Apps".to_string(), completed: false, expiry_millis: COMPLETED_EXPIRY_MILLIS, }; Self { last_added_id: 3, todos: vec![first_todo, second_todo, third_todo, fourth_todo], filter: Filter::All, settings: Settings { auto_expire_completed_todos: false, completed_expiry_millis: COMPLETED_EXPIRY_MILLIS, }, } } fn update(&mut self, req_id: u64, msg: Msg) { use Msg::*; match msg { AddTodo(title) => { self.last_added_id += 1; let todo = Todo { id: self.last_added_id, title, completed: false, expiry_millis: COMPLETED_EXPIRY_MILLIS, }; self.todos.push(todo); replies::post(Reply::AddedTodo(req_id, self.last_added_id.to_string())); } RemoveTodo(id) => { self.remove_todo(id); replies::post(Reply::RemovedTodo(req_id, self.last_added_id.to_string())); } RemoveCompleted => { self.todos.retain(|todo| !todo.completed); replies::post(Reply::RemovedCompleted(req_id)); } CompleteTodo(id) => { self.update_todo(id, |todo| todo.set_completed(true)); replies::post(Reply::CompletedTodo(req_id, id.to_string())); } RestartTodo(id) => { self.update_todo(id, |todo| todo.set_completed(false)); replies::post(Reply::RestartedTodo(req_id, id.to_string())); } ToggleTodo(id) => { self.update_todo(id, |todo| todo.set_completed(!todo.completed)); replies::post(Reply::ToggledTodo(req_id, id.to_string())); } CompleteAll => { self.todos.iter_mut().for_each(|x| x.set_completed(true)); replies::post(Reply::CompletedAll(req_id)); } RestartAll => { self.todos.iter_mut().for_each(|x| x.set_completed(false)); replies::post(Reply::RestartedAll(req_id)); } SetFilter(filter) => { self.filter = filter; replies::post(Reply::SetFilter(req_id)); } SetAutoExpireCompletedTodos(_) => { panic!("The Auto Expire Todos feature has been disabled in the WASM version") } }; } } #[rid::export] #[rid::structs(Todo)] impl Store { fn remove_todo(&mut self, id: u32) { let mut enumerated = self.todos.iter().enumerate(); let idx = match enumerated.find(|(_, todo)| todo.id == id) { Some((idx, _)) => idx, None => return eprintln!("Could not find Todo with id '{}'", id), }; self.todos.remove(idx); } fn update_todo(&mut self, id: u32, update: F) { match self.todos.iter_mut().find(|x| x.id == id) { Some(todo) => update(todo), None => eprintln!("Could not find Todo with id '{}'", id), }; } fn filtered_todos(&self) -> Vec<&Todo> { let mut vec: Vec<&Todo> = match self.filter { Filter::Completed => self.todos.iter().filter(|x| x.completed).collect(), Filter::Pending => self.todos.iter().filter(|x| !x.completed).collect(), Filter::All => self.todos.iter().collect(), }; vec.sort(); vec } // At this point sending a Vec is not supported in WASM, therefore we work around this // by sending serializing here and deserializing on the Dart side #[rid::export] fn filtered_todos_string(&self) -> String { serde_json::to_string(&self.filtered_todos()).expect("Unable JSON stringify filtred todos") } #[rid::export] fn todo_by_id(&self, id: u32) -> Option<&Todo> { self.todos.iter().find(|x| x.id == id) } } // ----------------- // Settings // ----------------- #[rid::model] #[derive(Debug)] pub struct Settings { auto_expire_completed_todos: bool, completed_expiry_millis: u64, } // ----------------- // Todo Model // ----------------- #[rid::model] #[derive(PartialEq, Eq, PartialOrd, Debug, Serialize)] pub struct Todo { id: u32, title: String, completed: bool, expiry_millis: u64, } impl Todo { fn set_completed(&mut self, completed: bool) { self.completed = completed; self.expiry_millis = COMPLETED_EXPIRY_MILLIS; } } impl Ord for Todo { fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.id.cmp(&other.id) } } // ----------------- // Filter // ----------------- #[rid::model] #[derive(Clone, Debug)] pub enum Filter { Completed, Pending, All, } // ----------------- // Msg // ----------------- #[rid::message(Reply)] #[rid::enums(Filter)] #[derive(Debug)] pub enum Msg { AddTodo(String), RemoveTodo(u32), RemoveCompleted, CompleteTodo(u32), RestartTodo(u32), ToggleTodo(u32), CompleteAll, RestartAll, SetFilter(Filter), SetAutoExpireCompletedTodos(bool), } // ----------------- // Reply // ----------------- #[rid::reply] pub enum Reply { // Message Replies AddedTodo(u64, String), RemovedTodo(u64, String), RemovedCompleted(u64), CompletedTodo(u64, String), RestartedTodo(u64, String), ToggledTodo(u64, String), CompletedAll(u64), RestartedAll(u64), SetFilter(u64), // Application Events CompletedTodoExpired, Tick(String), } ================================================ FILE: flutter/todo_cubit_wasm/src/replies.rs ================================================ use crate::Reply; /// cbindgen:ignore static mut REPLIES_LOCK: Option<::std::sync::RwLock>> = None; /// cbindgen:ignore static mut REPLIES_ACCESS: Option = None; /// cbindgen:ignore static INIT_REPLIES: ::std::sync::Once = ::std::sync::Once::new(); struct RidRepliesAccess { lock: &'static ::std::sync::RwLock>, } impl RidRepliesAccess { fn instance() -> &'static RidRepliesAccess { unsafe { INIT_REPLIES.call_once(|| { REPLIES_LOCK = Some(::std::sync::RwLock::new(vec![])); REPLIES_ACCESS = Some(RidRepliesAccess { lock: REPLIES_LOCK.as_ref().unwrap(), }); }); REPLIES_ACCESS.as_ref().unwrap() } } } // ----------------- // API used by rid internally when we poll replies for wasm support // ----------------- pub fn post(reply: Reply) { replies_write().push(reply.into()) } pub fn replies_read() -> ::std::sync::RwLockReadGuard<'static, Vec> { RidRepliesAccess::instance().lock.read().unwrap() } pub fn replies_write() -> ::std::sync::RwLockWriteGuard<'static, Vec> { RidRepliesAccess::instance().lock.write().unwrap() } #[no_mangle] pub extern "C" fn rid_poll_reply() -> *const ReplyStruct { rid::_option_ref_to_pointer(replies_read().iter().next()) } #[no_mangle] pub extern "C" fn rid_handled_reply(req_id: u64) { replies_write().retain(|x| x.req_id != req_id) } #[rid::model] #[derive(Debug, Clone)] pub struct ReplyStruct { ty: u8, req_id: u64, data: String, } impl ReplyStruct { fn with_req_id(ty: u8, req_id: u64) -> Self { Self { ty, req_id, data: "".to_string(), } } fn with_data(ty: u8, req_id: u64, data: String) -> Self { Self { ty, req_id, data } } } impl From for ReplyStruct { fn from(reply: Reply) -> Self { match reply { Reply::AddedTodo(req_id, data) => ReplyStruct::with_data(0, req_id, data), Reply::RemovedTodo(req_id, data) => ReplyStruct::with_data(1, req_id, data), Reply::RemovedCompleted(req_id) => ReplyStruct::with_req_id(2, req_id), Reply::CompletedTodo(req_id, data) => ReplyStruct::with_data(3, req_id, data), Reply::RestartedTodo(req_id, data) => ReplyStruct::with_data(4, req_id, data), Reply::ToggledTodo(req_id, data) => ReplyStruct::with_data(5, req_id, data), Reply::CompletedAll(req_id) => ReplyStruct::with_req_id(6, req_id), Reply::RestartedAll(req_id) => ReplyStruct::with_req_id(7, req_id), Reply::SetFilter(req_id) => ReplyStruct::with_req_id(8, req_id), Reply::CompletedTodoExpired => Self { ty: 10, req_id: 0, data: "".to_string(), }, Reply::Tick(data) => Self { ty: 11, req_id: 0, data, }, } } } ================================================ FILE: flutter/todo_cubit_wasm/todo_cubit.iml ================================================ ================================================ FILE: flutter/todo_cubit_wasm/twitch.md ================================================ # Twitch Cubit Todo ## Intro - show app - we'll most likely build parts of it focusing on multi threading and leveraging the higher level API with cubits - remaining parts at github ### Miro - walk through [miro](https://miro.com/app/board/o9J_l9TpJQI=/) - explain how multi threaded apps work while providing memory safety #### 1 - app starting up - get Todos - `TodosCubit` has list of `Todo` which is a Dart list of Dart class instances - converted for us behind the scenes to guarantee memory safety - **Rust provides that safety**, but once we leave it and re-enter from Dart we lost the guarantees - rid locks the store for reading for us when retrieving Todos to ensure no other thread can write to it while we perform this step #### 2 - render the Todos #### 3 - toggle Todo - **we never directly mutate** the store from Dart - send messages to it instead - rid receives the message and locks store for writing - no other thread can read from nor write to it - store update receives `mut Store` and while it runs, the Store remains locked - **Flutter is `await` ing** the completion of the action triggered by sending the message - when Todo toggled Rust posts a _toggled_ reply with the `req_id` included in the message - Flutter `TodoCubit` now knows that that message has been handled and retrieves the updated Todo state - while that is going on the Store is read locked and we get a Dart Todo instance back #### 4 - Todo is now re-rendered with completed checked #### 5 - user configures to auto expire completed Todos - SettingsCubit sends message to Store which updates the settings - activates ticker thread - and posts reply causing Todo to re-render with expire progress bar -> (6) - expiry ticker thread is now active and starts ticking down the life of completed todos - everytime it updates a Todo it aquires a write lock on the Store to make sure no other thread is reading todos while they're modified #### 7 - TodoCubit listening on reply channel stream for tick updates for its todo - receives todo tick - gets updated todo state from store (with locked store) #### 8 - another tick update .. Todo about to expire #### 9 - another Todo tick and Todo expired - TodoCubit tries to retrieve state of Todo - ticker thread wants to remove expired Todo and aquired write lock, causing resolution of Todo state to wait until that step is complete and it can aquire a read lock - now the Todo isn't found anymore and we return `null`, could show an empty container also #### 10 - TodosCubit receives TodoExpired reply - gets latest state of filtered todos which no longer includes our Todo - renders todos without it If we would not read-lock the store and allow to use a pointer of a Todo that might have been removed in the meantime making the pointer invalid we'd run into all kinds of trouble most likely causing the app to crash. ## Counter Start - show app and point out logs - point out debug feature since we won't use that most likely in the todo ================================================ FILE: flutter/todo_cubit_wasm/web/index.html ================================================ todo_cubit_wasm ================================================ FILE: flutter/todo_cubit_wasm/web/manifest.json ================================================ { "name": "todo_cubit_wasm", "short_name": "todo_cubit", "start_url": ".", "display": "standalone", "background_color": "#0175C2", "theme_color": "#0175C2", "description": "Todo Cubit Wasm Version.", "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" }, { "src": "icons/Icon-maskable-192.png", "sizes": "192x192", "type": "image/png", "purpose": "maskable" }, { "src": "icons/Icon-maskable-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" } ] }