Repository: imaNNeo/fl_chart Branch: main Commit: 9f74754de6bf Files: 345 Total size: 2.8 MB Directory structure: gitextract_lew4f113/ ├── .codecov.yml ├── .github/ │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ └── feature_request.md │ ├── dependabot.yml │ ├── pull_request_template.md │ └── workflows/ │ ├── claude.yml │ ├── codecov.yml │ ├── gh-pages.yml │ ├── pr-title-checker.yml │ ├── publish.yml │ └── verification.yml ├── .gitignore ├── .metadata ├── .pubignore ├── .vscode/ │ └── settings.json ├── CHANGELOG.md ├── CLAUDE.md ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── SOURCES.md ├── _config.yml ├── analysis_options.yaml ├── example/ │ ├── .gitignore │ ├── .metadata │ ├── README.md │ ├── analysis_options.yaml │ ├── android/ │ │ ├── .gitignore │ │ ├── app/ │ │ │ ├── build.gradle.kts │ │ │ └── src/ │ │ │ ├── debug/ │ │ │ │ └── AndroidManifest.xml │ │ │ ├── main/ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ ├── kotlin/ │ │ │ │ │ └── dev/ │ │ │ │ │ └── flchart/ │ │ │ │ │ └── app/ │ │ │ │ │ └── MainActivity.kt │ │ │ │ └── res/ │ │ │ │ ├── drawable/ │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable-v21/ │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── mipmap-anydpi-v26/ │ │ │ │ │ ├── ic_launcher.xml │ │ │ │ │ └── ic_launcher_round.xml │ │ │ │ ├── values/ │ │ │ │ │ ├── ic_launcher_background.xml │ │ │ │ │ └── styles.xml │ │ │ │ └── values-night/ │ │ │ │ └── styles.xml │ │ │ └── profile/ │ │ │ └── AndroidManifest.xml │ │ ├── build.gradle.kts │ │ ├── gradle/ │ │ │ └── wrapper/ │ │ │ └── gradle-wrapper.properties │ │ ├── gradle.properties │ │ └── settings.gradle.kts │ ├── assets/ │ │ └── data/ │ │ ├── amsterdam_2024_weather.csv │ │ ├── bitcoin_2023-01-01_2023-12-31.csv │ │ └── btc_last_year_price.json │ ├── devtools_options.yaml │ ├── ios/ │ │ ├── .gitignore │ │ ├── Flutter/ │ │ │ ├── AppFrameworkInfo.plist │ │ │ ├── Debug.xcconfig │ │ │ └── Release.xcconfig │ │ ├── Podfile │ │ ├── Runner/ │ │ │ ├── AppDelegate.swift │ │ │ ├── Assets.xcassets/ │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── 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 │ │ └── RunnerTests/ │ │ └── RunnerTests.swift │ ├── lib/ │ │ ├── cubits/ │ │ │ └── app/ │ │ │ ├── app_cubit.dart │ │ │ └── app_state.dart │ │ ├── main.dart │ │ ├── presentation/ │ │ │ ├── menu/ │ │ │ │ ├── app_menu.dart │ │ │ │ ├── fl_chart_banner.dart │ │ │ │ └── menu_row.dart │ │ │ ├── pages/ │ │ │ │ ├── chart_samples_page.dart │ │ │ │ └── home_page.dart │ │ │ ├── presentation_utils.dart │ │ │ ├── resources/ │ │ │ │ ├── app_assets.dart │ │ │ │ ├── app_colors.dart │ │ │ │ ├── app_dimens.dart │ │ │ │ ├── app_resources.dart │ │ │ │ └── app_texts.dart │ │ │ ├── router/ │ │ │ │ └── app_router.dart │ │ │ ├── samples/ │ │ │ │ ├── bar/ │ │ │ │ │ ├── bar_chart_sample1.dart │ │ │ │ │ ├── bar_chart_sample2.dart │ │ │ │ │ ├── bar_chart_sample3.dart │ │ │ │ │ ├── bar_chart_sample4.dart │ │ │ │ │ ├── bar_chart_sample5.dart │ │ │ │ │ ├── bar_chart_sample6.dart │ │ │ │ │ ├── bar_chart_sample7.dart │ │ │ │ │ └── bar_chart_sample8.dart │ │ │ │ ├── candlestick/ │ │ │ │ │ └── candlestick_chart_sample1.dart │ │ │ │ ├── chart_sample.dart │ │ │ │ ├── chart_samples.dart │ │ │ │ ├── line/ │ │ │ │ │ ├── line_chart_sample1.dart │ │ │ │ │ ├── line_chart_sample10.dart │ │ │ │ │ ├── line_chart_sample11.dart │ │ │ │ │ ├── line_chart_sample12.dart │ │ │ │ │ ├── line_chart_sample13.dart │ │ │ │ │ ├── line_chart_sample2.dart │ │ │ │ │ ├── line_chart_sample3.dart │ │ │ │ │ ├── line_chart_sample4.dart │ │ │ │ │ ├── line_chart_sample5.dart │ │ │ │ │ ├── line_chart_sample6.dart │ │ │ │ │ ├── line_chart_sample7.dart │ │ │ │ │ ├── line_chart_sample8.dart │ │ │ │ │ └── line_chart_sample9.dart │ │ │ │ ├── pie/ │ │ │ │ │ ├── pie_chart_sample1.dart │ │ │ │ │ ├── pie_chart_sample2.dart │ │ │ │ │ └── pie_chart_sample3.dart │ │ │ │ ├── radar/ │ │ │ │ │ └── radar_chart_sample1.dart │ │ │ │ └── scatter/ │ │ │ │ ├── scatter_chart_sample1.dart │ │ │ │ └── scatter_chart_sample2.dart │ │ │ └── widgets/ │ │ │ ├── chart_holder.dart │ │ │ ├── download_native_app_button.dart │ │ │ ├── indicator.dart │ │ │ └── legend_widget.dart │ │ ├── urls.dart │ │ └── util/ │ │ ├── app_helper.dart │ │ ├── app_utils.dart │ │ ├── csv_parser.dart │ │ ├── device_info.dart │ │ └── extensions/ │ │ ├── color_extensions.dart │ │ ├── iterable_extensions.dart │ │ └── list_extensions.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 │ │ └── runner/ │ │ ├── CMakeLists.txt │ │ ├── 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 │ │ └── RunnerTests/ │ │ └── RunnerTests.swift │ ├── pubspec.yaml │ ├── web/ │ │ ├── CNAME │ │ ├── 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 │ ├── runner.exe.manifest │ ├── utils.cpp │ ├── utils.h │ ├── win32_window.cpp │ └── win32_window.h ├── fl_chart.iml ├── lib/ │ ├── fl_chart.dart │ └── src/ │ ├── chart/ │ │ ├── bar_chart/ │ │ │ ├── bar_chart.dart │ │ │ ├── bar_chart_data.dart │ │ │ ├── bar_chart_helper.dart │ │ │ ├── bar_chart_painter.dart │ │ │ └── bar_chart_renderer.dart │ │ ├── base/ │ │ │ ├── axis_chart/ │ │ │ │ ├── axis_chart_data.dart │ │ │ │ ├── axis_chart_extensions.dart │ │ │ │ ├── axis_chart_helper.dart │ │ │ │ ├── axis_chart_painter.dart │ │ │ │ ├── axis_chart_scaffold_widget.dart │ │ │ │ ├── axis_chart_widgets.dart │ │ │ │ ├── scale_axis.dart │ │ │ │ ├── side_titles/ │ │ │ │ │ ├── side_titles_flex.dart │ │ │ │ │ └── side_titles_widget.dart │ │ │ │ └── transformation_config.dart │ │ │ ├── base_chart/ │ │ │ │ ├── base_chart_data.dart │ │ │ │ ├── base_chart_painter.dart │ │ │ │ ├── fl_touch_event.dart │ │ │ │ └── render_base_chart.dart │ │ │ ├── custom_interactive_viewer.dart │ │ │ └── line.dart │ │ ├── candlestick_chart/ │ │ │ ├── candlestick_chart.dart │ │ │ ├── candlestick_chart_data.dart │ │ │ ├── candlestick_chart_helper.dart │ │ │ ├── candlestick_chart_painter.dart │ │ │ └── candlestick_chart_renderer.dart │ │ ├── line_chart/ │ │ │ ├── line_chart.dart │ │ │ ├── line_chart_data.dart │ │ │ ├── line_chart_helper.dart │ │ │ ├── line_chart_painter.dart │ │ │ └── line_chart_renderer.dart │ │ ├── pie_chart/ │ │ │ ├── pie_chart.dart │ │ │ ├── pie_chart_data.dart │ │ │ ├── pie_chart_helper.dart │ │ │ ├── pie_chart_painter.dart │ │ │ └── pie_chart_renderer.dart │ │ ├── radar_chart/ │ │ │ ├── radar_chart.dart │ │ │ ├── radar_chart_data.dart │ │ │ ├── radar_chart_painter.dart │ │ │ ├── radar_chart_renderer.dart │ │ │ └── radar_extension.dart │ │ └── scatter_chart/ │ │ ├── scatter_chart.dart │ │ ├── scatter_chart_data.dart │ │ ├── scatter_chart_helper.dart │ │ ├── scatter_chart_painter.dart │ │ └── scatter_chart_renderer.dart │ ├── extensions/ │ │ ├── bar_chart_data_extension.dart │ │ ├── border_extension.dart │ │ ├── color_extension.dart │ │ ├── edge_insets_extension.dart │ │ ├── fl_border_data_extension.dart │ │ ├── fl_titles_data_extension.dart │ │ ├── gradient_extension.dart │ │ ├── paint_extension.dart │ │ ├── path_extension.dart │ │ ├── rrect_extension.dart │ │ ├── side_titles_extension.dart │ │ ├── size_extension.dart │ │ └── text_align_extension.dart │ └── utils/ │ ├── canvas_wrapper.dart │ ├── lerp.dart │ ├── path_drawing/ │ │ └── dash_path.dart │ └── utils.dart ├── pubspec.yaml ├── repo_files/ │ ├── documentations/ │ │ ├── bar_chart.md │ │ ├── base_chart.md │ │ ├── candlestick_chart.md │ │ ├── handle_animations.md │ │ ├── handle_touches.md │ │ ├── handle_transformations.md │ │ ├── index.md │ │ ├── line_chart.md │ │ ├── migration_guides/ │ │ │ ├── 0.50.0/ │ │ │ │ └── MIGRATION_00_50_00.md │ │ │ ├── 0.55.0/ │ │ │ │ └── MIGRATION_00_55_00.md │ │ │ ├── 0.67.0/ │ │ │ │ └── MIGRATION_00_67_00.md │ │ │ ├── 0.70.0/ │ │ │ │ └── MIGRATION_00_70_00.md │ │ │ └── INDEX.md │ │ ├── pie_chart.md │ │ ├── radar_chart.md │ │ └── scatter_chart.md │ ├── drawio/ │ │ └── flchart.drawio │ └── images/ │ └── architecture/ │ └── fl_chart_architecture.txt ├── scripts/ │ └── makefile_scripts.sh └── test/ ├── chart/ │ ├── bar_chart/ │ │ ├── bar_chart_data_test.dart │ │ ├── bar_chart_helper_test.dart │ │ ├── bar_chart_painter_test.dart │ │ ├── bar_chart_painter_test.mocks.dart │ │ ├── bar_chart_renderer_test.dart │ │ ├── bar_chart_renderer_test.mocks.dart │ │ └── bar_chart_test.dart │ ├── base/ │ │ ├── axis_chart/ │ │ │ ├── axis_chart_data_test.dart │ │ │ ├── axis_chart_data_test.mocks.dart │ │ │ ├── axis_chart_extensions_test.dart │ │ │ ├── axis_chart_helper_test.dart │ │ │ ├── axis_chart_scaffold_widget_test.dart │ │ │ ├── axis_chart_widgets_test.dart │ │ │ ├── base_chart_data_test.dart │ │ │ ├── side_titles/ │ │ │ │ ├── side_titles_flex_test.dart │ │ │ │ ├── side_titles_test.dart │ │ │ │ └── side_titles_widget_test.dart │ │ │ └── transformation_config_test.dart │ │ ├── line_test.dart │ │ ├── render_base_chart_test.dart │ │ └── render_base_chart_test.mocks.dart │ ├── candlestick_chart/ │ │ ├── candlestick_chart_data_test.dart │ │ ├── candlestick_chart_helper_test.dart │ │ ├── candlestick_chart_painter_test.dart │ │ ├── candlestick_chart_painter_test.mocks.dart │ │ ├── candlestick_chart_renderer_test.dart │ │ ├── candlestick_chart_renderer_test.mocks.dart │ │ └── candlestick_chart_test.dart │ ├── data_pool.dart │ ├── line_chart/ │ │ ├── line_chart_data_test.dart │ │ ├── line_chart_helper_test.dart │ │ ├── line_chart_painter_test.dart │ │ ├── line_chart_painter_test.mocks.dart │ │ ├── line_chart_renderer_test.dart │ │ ├── line_chart_renderer_test.mocks.dart │ │ └── line_chart_test.dart │ ├── pie_chart/ │ │ ├── pie_chart_data_test.dart │ │ ├── pie_chart_helper_test.dart │ │ ├── pie_chart_painter_test.dart │ │ ├── pie_chart_painter_test.mocks.dart │ │ ├── pie_chart_renderer_test.dart │ │ └── pie_chart_renderer_test.mocks.dart │ ├── radar_chart/ │ │ ├── radar_chart_data_test.dart │ │ ├── radar_chart_painter_test.dart │ │ ├── radar_chart_painter_test.mocks.dart │ │ ├── radar_chart_renderer_test.dart │ │ └── radar_chart_renderer_test.mocks.dart │ └── scatter_chart/ │ ├── scatter_chart_data_test.dart │ ├── scatter_chart_helper_test.dart │ ├── scatter_chart_painter_test.dart │ ├── scatter_chart_painter_test.mocks.dart │ ├── scatter_chart_renderer_test.dart │ ├── scatter_chart_renderer_test.mocks.dart │ └── scatter_chart_test.dart ├── extensions/ │ ├── bar_chart_data_extensions_test.dart │ ├── border_extension_test.dart │ ├── color_extensions_test.dart │ ├── edge_insets_extension_test.dart │ ├── fl_border_data_extension_test.dart │ ├── fl_titles_data_extension_test.dart │ ├── gradient_extension_test.dart │ ├── paint_extension_test.dart │ ├── path_extension_test.dart │ ├── rrect_extension_test.dart │ ├── side_titles_extension_test.dart │ ├── size_extension_test.dart │ └── text_align_extension_test.dart ├── helper_methods.dart ├── matchers.dart └── utils/ ├── canvas_wrapper_test.dart ├── canvas_wrapper_test.mocks.dart ├── lerp_test.dart ├── utils_test.dart └── utils_test.mocks.dart ================================================ FILE CONTENTS ================================================ ================================================ FILE: .codecov.yml ================================================ comment: require_changes: true coverage: status: project: default: target: auto threshold: 1% patch: default: target: 50% threshold: 10% precision: 1 range: "80...100" # Ignore all the file inside the example and # end eventually also the autogenerate file ignore: - '**/example/' - '**/*.g.dart' ================================================ FILE: .github/FUNDING.yml ================================================ github: [imaNNeo] custom: ["https://www.buymeacoffee.com/fl_chart"] ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report to help us improve title: '' labels: '' assignees: '' --- ** Don't make a duplicate issue. You can search in issues to make sure there isn't any already opened issue with your concern. **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Provide us a completely reproducible code (contains the main function) in a `main.dart` file, it helps us to find the bug immediately. **Screenshots** If applicable, add screenshots, or videoshots to help explain your problem. **Versions** - which version of the Flutter are you using? - which version of the FlChart are you using? ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for this project title: '' labels: '' assignees: '' --- ** Don't make a duplicate issue. You can search in issues to make sure there isn't any already opened issue with your concern. **Is your feature request relasted to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. ================================================ FILE: .github/dependabot.yml ================================================ # To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "daily" assignees: - "dependabot" commit-message: prefix: "chore: " - package-ecosystem: "pub" directory: "/" schedule: interval: "daily" assignees: - "dependabot" commit-message: prefix: "chore: " ================================================ FILE: .github/pull_request_template.md ================================================ # Description Replace this text. ## Checklist - [ ] I have followed the [Contributor Guide] when preparing my PR. - [ ] I have updated/added tests for ALL new/updated/fixed functionality. - [ ] I have updated/added relevant documentation and added dartdoc comments with `///`. - [ ] I have updated/added relevant examples in `example`. ## Breaking Change? - [ ] Yes, this PR is a breaking change. - [ ] No, this PR is not a breaking change. ## Related Issues [Contributor Guide]: https://github.com/imaNNeo/fl_chart/blob/main/CONTRIBUTING.md [Conventional Commit]: https://conventionalcommits.org [CHANGELOG]: https://github.com/imaNNeo/fl_chart/blob/main/CHANGELOG.md ================================================ FILE: .github/workflows/claude.yml ================================================ name: Claude Code on: issue_comment: types: [created] pull_request_review_comment: types: [created] issues: types: [opened, assigned] pull_request_review: types: [submitted] jobs: claude: if: | (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) runs-on: ubuntu-latest permissions: contents: read pull-requests: read issues: read id-token: write actions: read # Required for Claude to read CI results on PRs steps: - name: Checkout repository uses: actions/checkout@v4 with: fetch-depth: 1 - name: Run Claude Code id: claude uses: anthropics/claude-code-action@v1 with: claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} # This is an optional setting that allows Claude to read CI results on PRs additional_permissions: | actions: read # Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it. # prompt: 'Update the pull request description to include a summary of changes.' # Optional: Add claude_args to customize behavior and configuration # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md # or https://code.claude.com/docs/en/cli-reference for available options # claude_args: '--allowed-tools Bash(gh pr:*)' ================================================ FILE: .github/workflows/codecov.yml ================================================ name: Code Coverage on: [ push, pull_request ] jobs: upload: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Flutter uses: subosito/flutter-action@v2 with: channel: "stable" - name: Get packages run: flutter pub get - name: Generate coverage file run: flutter test --coverage - name: Upload coverage to Codecov uses: codecov/codecov-action@v5 with: fail_ci_if_error: true files: ./coverage/lcov.info flags: flutter ================================================ FILE: .github/workflows/gh-pages.yml ================================================ name: Gh-Pages on: push: branches: [ main ] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install Flutter uses: subosito/flutter-action@v2 - name: Set fl_chart Version run: | VERSION=$(grep "version:" pubspec.yaml | awk '{ print $2 }') echo "USING_FL_CHART_VERSION=$VERSION" >> $GITHUB_ENV - run: flutter config --enable-web working-directory: example - run: flutter build web --release --wasm --base-href=/ --dart-define="USING_FL_CHART_VERSION=${{ env.USING_FL_CHART_VERSION }}" working-directory: example - run: git config user.name github-actions working-directory: example - run: git config user.email github-actions@github.com working-directory: example - run: git --work-tree build/web add --all working-directory: example - run: git commit -m "Automatic deployment by github-actions" working-directory: example - run: git push origin HEAD:gh-pages --force working-directory: example ================================================ FILE: .github/workflows/pr-title-checker.yml ================================================ name: "Lint PR Title" on: pull_request_target: types: - opened - edited - synchronize jobs: main: name: Validate PR title runs-on: ubuntu-latest steps: - uses: amannn/action-semantic-pull-request@v5 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: # Types allowed (Standard Conventional Commits) types: | feat fix docs style refactor perf test build ci chore revert # If the PR only contains one commit, it's often useful to check if that commit # message also follows the convention. checkLatestCommitMessage: false # You can also add a regex to validate the subject line (the part after the colon) # Flame enforces that it starts with a capital letter. We can do the same if you like. subjectPattern: ^[^a-z].+$ subjectPatternError: | The subject of the PR can't begin with a lowercase letter. ================================================ FILE: .github/workflows/publish.yml ================================================ name: Publish plugin on: release: types: [ published ] jobs: publish: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Publish uses: k-paxian/dart-package-publisher@master with: credentialJson: ${{ secrets.CREDENTIAL_JSON }} ================================================ FILE: .github/workflows/verification.yml ================================================ name: Code Verification on: [ push, pull_request ] jobs: verify: runs-on: ubuntu-latest steps: - name: Clone repository uses: actions/checkout@v4 - name: Setup Flutter uses: subosito/flutter-action@v2 with: channel: stable - name: Print Flutter version run: flutter --version - name: Get packages run: flutter pub get - name: Check formatting run: make checkFormat - name: Analyze the source code run: make analyze - name: Run tests run: make runTests ================================================ FILE: .gitignore ================================================ .DS_Store .dart_tool/ .idea/ .packages .pub/ build/ ios/.generated/ ios/Flutter/Generated.xcconfig ios/Runner/GeneratedPluginRegistrant.* pubspec.lock .vscode/launch.json example/android/.project example/android/.settings/org.eclipse.buildship.core.prefs example/android/app/.classpath example/android/app/.project example/android/app/.settings/org.eclipse.buildship.core.prefs example/macos/Flutter/ephemeral/ coverage/ .fvm/ # Files generated by dart tools .dart_tool ================================================ FILE: .metadata ================================================ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # # This file should be version controlled and should not be manually edited. version: revision: 7a4c33425ddd78c54aba07d86f3f9a4a0051769b channel: beta project_type: package ================================================ FILE: .pubignore ================================================ /docs/* /repo_files/* ================================================ FILE: .vscode/settings.json ================================================ { "dart.lineLength": 80, "editor.formatOnSave": true, "editor.formatOnType": true, "editor.suggest.snippetsPreventQuickSuggestions": false, "editor.tabCompletion": "onlySnippets", "editor.wordBasedSuggestions": "off", "files.insertFinalNewline": true, "editor.defaultFormatter": "Dart-Code.dart-code" } ================================================ FILE: CHANGELOG.md ================================================ ## 1.2.0 * **BUGFIX** (by @imaNNeo) Consider the `enabled` property in [LineTouchData](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/line_chart.md#linetouchdata-read-about-touch-handling), [BarTouchData](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/bar_chart.md#bartouchdata-read-about-touch-handling), [PieTouchData](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/pie_chart.md#pietouchdata-read-about-touch-handling), [ScatterTouchData](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/scatter_chart.md#scattertouchdata-read-about-touch-handling), [RadarTouchData](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/radar_chart.md#radartouchdata-read-about-touch-handling) and [CandlestickTouchData](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/candlestick_chart.md#candlesticktouchdata-read-about-touch-handling), #1676 * **BUGFIX** (by @artshooter) Fix wrong bar chart color with small value, #1757 * **FEATURE** (by @3ph) Add `horizontalMirrored` and `verticalMirrored` properties in our `LabelDirection` enum which is used in ([HorizontalLineLabel](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/base_chart.md#HorizontalLineLabel) and [VerticalLineLabel](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/base_chart.md#VerticalLineLabel)), #1890 * **FEATURE** (by @Vizten18) Add `cornerRadius` property in the [PieChartSectionData](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/pie_chart.md#piechartsectiondata), #1175 * **IMPROVEMENT** (by @imaNNeo) Add convention and linter (checker) for PRs title and updated contributing guideline. We're gonna have automated changelog generation from the next release * **BUGFIX** (by @diymelvin) Fix inverted lowerBy/upperBy in error range calculation, #2031 * **FEATURE** (by @imaNNeo) Add `label` property in the [BarChartRodData](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/bar_chart.md#barchartroddata) to allow you to show a label on top of each bar rod, #2071, #84 ## 1.1.1 * **IMPROVEMENT** (by @imaNNeo) Upgrade `vector_math` dependency to `2.2.0`, #1985 * **IMPROVEMENT** (by @imaNNeo) Upgrade `build_runner` (dev) dependency to `2.8.0` * **IMPROVEMENT** (by @imaNNeo) Upgrade `mockito` (dev) dependency to `5.5.1` * **IMPROVEMENT** (by @imaNNeo) Upgrade `very_good_analysis` (dev) dependency to `9.0.0` ## 1.1.0 * **FEATURE** (by @kamilJ96) Add `gradient` property inside our `BarChartRodStackItem` to be able to render gradient (along with the possibility to render a solid color), #919 * **FEATURE** (by @alettsy) Add `sideTitleAlignment` property in our `AxisTitles` to allow you to control the alignment of side titles (for example show them inside the chart), #1946 * **FEATURE** (by @huanghui1998hhh) Add `gradientArea` property to `LineChartBarData` to allow you to control the scope of gradient effects, #1925 * **FEATURE** (by @alikhavarii13) Add `label` and `labelStyle` properties in our `BarChartRodStackItem` to allow you to show labels on each stack item, #598 * **BREAKING** ⚠️ (by @alikhavarii13) `borderSide` now is a [named parameter](https://dart.dev/language/functions#named-parameters) (instead of a [optional positional parameter](https://dart.dev/language/functions#optional-positional-parameters) in `BarChartRodStackItem` constructor. As it is a very minor breaking change, we decided to include it in this minor release (instead of doing a major release based on the [semantic versioning](https://semver.org/). Sorry about that! So you just need to change this: ```dart BarChartRodStackItem( 0, 10, Colors.green, BorderSide(color: Colors.white), ), ``` to this: ```dart BarChartRodStackItem( 0, 10, Colors.green, borderSide: BorderSide(color: Colors.white), ), ``` ## 1.0.0 Image * **FEATURE** (by @imaNNeo) Implement a new chart type called CandlestickChart. You can take a look at the documentation [here](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/candlestick_chart.md). And I just implemented a basic example to show the Bitcoin price in 2024, you can take a look at it in our sample app [here](https://app.flchart.dev/#/candlestick). #433, #1143 Image * **BREAKING** (by @imaNNeo) Remove the deprecated `tooltipRoundedRadius` property -> you should use `tooltipBorderRadius` instead. * **BUGFIX** (by @imaNNeo) Fix the BarChartData mismatch issue when changing the data, #1911 * **FEATURE** (by @frybitsinc) Add fillGradient property in [RadarDataSet](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/radar_chart.md#radardataset) * **BREAKING** (by @imaNNeo) Upgrade the min flutter version to `3.27.4`. So please make sure that your project is not using an old flutter version, #1846 * **IMPORTANT** (by @imaNNeo) You can read more about this release and the history of fl_chart here in my [blog post](https://flutter4fun.com/fl-chart-1-0-0) ## 0.71.0 * **IMPROVEMENT** (by @MattiaPispisa) Add a new property called `BorderRadius tooltipBorderRadius` instead of (deprecated) `double tooltipRoundedRadius` in `BarTouchTooltipData`, `LineTouchTooltipData` and `ScatterTouchTooltipData` #1715 * **FEATURE** (by @frybitsinc) Add `children` property in our [RadarChartTitle](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/radar_chart.md#radarcharttitle), #1840 * **BUGFIX** (by @morvagergely) Fix the initial zoom issue in our scrollable LineChart, #1863 ## 0.70.2 * **FEATURE** (by @imaNNeo) Add error range feature in our axis-based charts. You can set `xError` and `yError` in the [FlSpot](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/base_chart.md#flspot) or `toYErrorRange` in [BarChartRodData](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/bar_chart.md#barchartroddata). Also we have `errorIndicatorData` property in our [LineChartData](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/line_chart.md#linechartdata), [BarChartData](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/bar_chart.md#barchartdata) and [ScatterChartData](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/scatter_chart.md#scatterchartdata) that is responsible to render the error bars. You can take a look at the [LineChartSample 13](https://github.com/imaNNeo/fl_chart/blob/main/example/lib/presentation/samples/line/line_chart_sample13.dart) and [BarChartSample 8](https://github.com/imaNNeo/fl_chart/blob/main/example/lib/presentation/samples/bar/bar_chart_sample8.dart) in our [sample app](https://app.flchart.dev), #1483 ## 0.70.1 * **FEATURE** (by @Peetee06) Add `panEnabled` and `scaleEnabled` properties in the TransformationController, #1818 * **FEATURE** (by @mitulagr2) Add `renderPriority` feature in our [ScatterSpot](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/scatter_chart.md#scatterspot), #1545 * **FEATURE** (by @imaNNeo) Add `rotationQuarterTurns` property in our Axis-Based charts (such as [LineChart](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/line_chart.md), [BarChart](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/bar_chart.md) and [ScatterChart](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/scatter_chart.md)). It allows you to rotate the chart 90 degrees (clockwise) in each turn. For example you can have Horizontal Bar Charts by setting `rotationQuarterTurns` to 1 (which rotates the chart 90 degrees clockwise). It works exactly like [RotatesBox](https://api.flutter.dev/flutter/widgets/RotatedBox-class.html) widget, #113 * **FEATURE** (by @soraef) Add `isMinValueAtCenter` property in the [RadarChart](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/radar_chart.md) to allow the user to set the minimum value at the center of the chart, #1351, #1442 * **BREAKING** (by @imaNNeo) Add `TitleMeta` parameter in the `SideTitleWidget` instead of `axisSide` ## 0.70.0 * **FEATURE** (by @Peetee06) Implemented a 5 years-old feature request about scroll and zoom support in our axis-based charts. Special thanks to @Peetee06 who made it happen, #71 * **IMPROVEMENT** (by @Peetee06) Added functionality to control the transformation of axis-based charts using `FlTransformationConfig` class. You can now enable scaling and panning for `LineChart`, `BarChart` and `ScatterChart` using this class * **IMPROVEMENT** (by @Peetee06) Added some new unit tests in `bar_chart_data_extensions_test.dart`, `gradient_extension_test.dart` and fixed a typo in `bar_chart_data.dart` * **BREAKING** (by @Peetee06) Fixed the equatable functionality in our BarChart. We hope it will not affect anything in our chart, but because the behaviour is changed, we marked it as a breaking change. (read more [here](https://github.com/imaNNeo/fl_chart/pull/1789#discussion_r1858371718)) * **BREAKING** (by @Peetee06) `BarChart` is not const anymore due to adding an assert to check if transformations are allowed depending on the `BarChartData.alignment` property (read more [here](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/migration_guides/0.70.0/MIGRATION_00_70_00.md)) * **IMPROVEMENT** (by @Peetee06) Upgrade to the new Flutter version ([3.27.0](https://medium.com/flutter/whats-new-in-flutter-3-27-28341129570c)), #1804 * **IMPROVEMENT** (by @AliAkberAakash) Minor typo fix in our line chart documentation, #1795 * **IMPROVEMENT** (by @imaNNeo) Fixed the code coverage API rate-limit issue * **Improvement** (by @imaNNeo) Published the example app in Google Play and App Store. Other stores (such as [snap store](https://snapcraft.io/store) and [Microsoft Store](https://apps.microsoft.com/home)) will come next. You can download the Android version here in [Google Play](https://play.google.com/store/apps/details?id=dev.flchart.app) and the iOS version here in [App Store](https://apps.apple.com/us/app/fl-chart/id6476523019) ## 0.69.2 * **IMPROVEMENT** (by @imaNNeo) Fix the analyzer warnings (to have maximum score in the [pub.dev](https://pub.dev/packages/fl_chart/score)) ## 0.69.1 * **IMPROVEMENT** (by @moshe5745) Update the docs related to line chart's `duration` and `curve` properties, #1618 * **IMPROVEMENT** (by @imaNNeo) Deprecate `swapAnimationDuration` and `swapAnimationCurve` properties to use `curve` and `duration` instead to keep the consistency over the project, #1618 * **BUGFIX** (by @aimawari) Fixed lots of issues related to the zero value in the PieChartSectionData, #697, #817 and #1632 ## 0.69.0 * **BUGFIX** (by @imaNNeo) Fix a memory leak issue in the axis-based charts, there was a logic to calculate and cache the minX, maxX, minY and maxY properties to reduce the computation cost. But it caused some memory issues, as we don't have a quick solution for this, we disabled the caching logic for now, later we can move the calculation logic to the render objects to keep and update them only when the data is changed, #1106, #1693 * **BUGFIX** (by @imaNNeo) Fix showing grid lines even when there is no line to show in the LineChart, #1691 * **IMPROVEMENT** (by @sczesla) Allow users to control minIncluded and maxIncluded using SideTitles, #906 * **IMPROVEMENT** (by @elizabethzhenliu) Reverse the touch order in ScatterChart, so now the top spots are touched first, #1675 * **IMPROVEMENT** (by @ksw2000) Remove redundant math import, #1683 * **IMPROVEMENT** (by @Neer-Pathak) Fix linux example build issue, #1668 * **IMPROVEMENT** (by @TobiasRump) Update the bar chart documentation, #1662 ## 0.68.0 * **Improvement** (by @imaNNeo) Update LineChartSample6 to implement a way to show a tooltip on a single spot, #1620 * **Feature** (by @herna) Add `titleSunbeamLayout` inside the [BarChartData](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/bar_chart.md#barchartdata) to allow the user to customize the layout of the title sunbeam * **Improvement** (by @imaNNeo) Add LineChart and BarChart explanation videos on top of the respective documentation pages ([LineChart video](https://youtu.be/F3wTxTdAFaU?si=8lwlypKjt-0aJJK0), [BarChart video](https://youtu.be/vYe0RY1nCAA?si=30q_7eNn9MDLcph4)) ## 0.67.0 * **FEATURE** (by @julien4215) Add direction property to the [HorizontalLineLabel](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/base_chart.md#horizontallinelabel) and [VerticalLineLabel](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/base_chart.md#verticallinelabel), #1574 * **FEATURE** (by @apekshamehta) Added new method called getTooltipColor for axis charts (bar,line,scatter) to change background color of tooltip dynamically, #1279. * **BREAKING** (by @apekshamehta) Removed tooltipBgColor property from Bar, Line and Scatter Charts (you can now use `getTooltipColor` which provides more customizability), checkout the [full migration guide here](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/migration_guides/0.67.0/MIGRATION_00_67_00.md). ```dart /// Migration guide: /// This is the old way: BarChartData( barTouchData: BarTouchData( touchTooltipData: BarTouchTooltipData( tooltipBgColor: Colors.blueGrey, ) ) ) /// This is the new way: BarChartData( barTouchData: BarTouchData( touchTooltipData: BarTouchTooltipData( getTooltipColor: (BarChartGroupData group) => Colors.blueGrey, ) ) ) ``` ## 0.66.2 * **BUGFIX** (by @stwarwas) Remove dart.io to fix web platform issue, #1577 ## 0.66.1 * **BUGFIX** (by @imaNNeo) Fix PieChart blackout issue, #1538 * **BUGFIX** (by @imaNNeo) Fix memory leak in LineChart and BarChart, #1106 ## 0.66.0 * **IMPROVEMENT** (by @imaNNeo) Add Flutter sdk constraints to the pubspec.yaml to force the user/developer to upgrade the Flutter version to 3.16.0 (latest), #1509 * **IMPROVEMENT** (by @imaNNeo) Add `dotPainter` property to ScatterSpot to allow customizing the dot painter, #568 * **BREAKING** (by @imaNNeo) Remove `color` and `radius` properties from ScatterSpot (use `dotPainter` instead), #568 * **BREAKING** (by @imaNNeo) Change the default value of FlDotCirclePainter.`strokeWidth` to 0.0 ```dart /// Migration guide: /// This is the old way: ScatterSpot( 2, 5, color: Colors.red, radius: 12, ) /// This is the new way: ScatterSpot( 2, 8, dotPainter: FlDotCirclePainter( color: Colors.red, radius: 22, ), ), ``` * **BUGFIX** (by @imaNNeo) Fix barChart tooltip for values below or above the 0 point, #1462 * **BUGFIX** (by @imaNNeo) Fix pieChart drawing single section on iPhone, #1515 * **IMPROVEMENT** (by @imaNNeo) Add gradient property to the [HorizontalLine](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/base_chart.md#horizontalline) and [VerticalLine](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/base_chart.md#verticalline), #1525 * **FEATURE** (by @raldhafiri) Add gradient property to the [PieChartSectionData](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/pie_chart.md#piechartsectiondata), #1511 * **IMPROVEMENT** (by @imaNNeo) Rename default branch `master` to `main` * **IMPROVEMENT** (by @imaNNeo) Update flutter sdk constraints to remove the upper bound limit (Read more [here](https://dart.dev/go/flutter-upper-bound-deprecation)). ## 0.65.0 * **FEATURE** (by @Dartek12) Added gradient to [FlLine](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/base_chart.md#FlLine), #1197 * **BUGFIX** (by @imaNNeo) Fix bar line shadow crash when we have only one (or zero) spot, #1466 * **BUGFIX** (by @imaNNeo) Fix having negative `toY` (or positive `fromY`) in BarChart's `minY` and `maxY` calculations, #1470 * **BUGFIX** (by @bobatsar) Fix bars drawn outside of diagram * **FEATURE** (by @k0psutin) Add dashed border to BarChartRodData, #1144 * **FEATURE** (by @imaNNeo) Allow to show single point line in LineChart, #1438 ## 0.64.0 * **BUGFIX** (by @Anas35) Fix Tooltip not displaying when value from BackgroundBarChartRodData is less than zero. #1345. * **BUGFIX** (by @imaNNeo) Fix Negative BarChartRodStackItem are not drawn correctly bug, #1347 * **BUGFIX** (by @imaNNeo) Fix bar_chart_helper minY calculation bug, #1388 * **IMPROVEMENT** (by @imaNNeo) Consider fraction digits when formatting chart side titles, #1267 ## 0.63.0 * **BUGFIX** (by @imaNNeo) Fix PieChart crash on web-renderer html by ignoring `sectionsSpace` when `Path.combine()` does not work (it's flutter engine [issue](https://github.com/flutter/flutter/issues/44572)), #955 * **BUGFIX** (by @imaNNeo) Fix ScatterChart long-press interaction bug (disappears when long-pressing on the chart), #1318 * **FEATURE** (by @imaNNeo) Upgrade dart version to [3.0](https://dart.dev/resources/dart-3-migration) ## 0.62.0 * **BUGFIX** (by @JoshMart) Fix extra lines not painting when at chart min or max, #1255. * **BUGFIX** (by @imaNNeo) Check if mounted before calling setState in _handleBuiltInTouch methods in bar, line and scatter charts, #1101 * **FEATURE** (by @MagdyYacoub1): Added gradient color to [RangeAnnotations](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/base_chart.md#rangeannotations) by adding gradient attribute to [horizontalRangeAnnotations](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/base_chart.md#horizontalrangeannotation) and [VerticalRangeAnnotation](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/base_chart.md#verticalrangeannotation), #1195. * **BUGFIX** (by @Motionz-Von)Fix windows build for example app * **FEATURE** (by @Motionz-Von)BarChart groupSpace also takes effect when alignment is BarChartAlignment.end or BarChartAlignment.start. * **FEATURE** (by @Motionz-Von) supports setting line StrokeCap on HorizontalLine/VerticalLine * **BUGFIX** (by @nav-28) Fix radar chart tick and graph point not matching #1078 * **IMPROVEMENT** (by @imaNNeo) Update LineChartSample5 to demonstrate click to toggle show/hide tooltip, #118 ## 0.61.0 * **IMPROVEMENT** (by @imaNNeo) Remove assertion to check to provide only one of `color` or `gradient` property in the [BarChartRodData](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/bar_chart.md#barchartroddata) and [BackgroundBarChartRodData](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/bar_chart.md#backgroundbarchartroddata), #1121. * **IMPROVEMENT** (by @imaNNeo) Make `drawBehindEverything` property default to `true` in [AxisTitles](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/base_chart.md#axistitle) class, #1097. * **BUGFIX** (by @imaNNeo) Show `0` instead of `-0` in some edge-cases in the default titles * **FEATURE** (by @tamasapps): Add `tooltipHorizontalAlignment` and `tooltipHorizontalOffset` property in [LineTouchTooltipData], [BarTouchTooltipData], [ScatterTouchTooltipData]. * **FEATURE** (by @dhiyaaulauliyaa) Add ability to force SideTitle to be placed inside its corresponding axis bounding box, #603. ## 0.60.0 * **IMPROVEMENT** (by @lsaudon) Replace flutter_lints by very_good_analysis * **BREAKING** (by @lsaudon) Update dart sdk to 2.17.0 (flutter 3.0.0) * **BUGFIX** (by @imaNNeo) Fix indicator out of range error in line chart, #1187 * **FEATURE** (by @HTsuruo): Add `longPressDuration` optional property that allows to control the duration LongPress gesture occurs, #1114 #1127. * **IMPROVEMENT** (by @imaNNeo) Add some screenshots in `pubspec.yaml` to support new [pub.dev](pub.dev) feature. Read more about it [here](https://dart.dev/tools/pub/pubspec#screenshots) and [here](https://medium.com/dartlang/screenshots-and-automated-publishing-for-pub-dev-9bceb19edf79). * **IMPROVEMENT** (by @imaNNeo) Update the homepage url in `pubspec.yaml` (I just renamed my username) * **FEATURE** (by @JoshMart) Add ability to draw extra horizontal lines on BarChart, #476 * **FEATURE** (by @soraef) Add a `positionPercentageOffset` optional property to RadarChartTitle to allow individual title positioning * **BUGFIX** (by @imaNNeo) Allow to draw empty radarChart (with all zero values), #1217 * **IMPORTANT** **IRAN NEEDS YOU. SPREAD THE NEWS.** ## 0.55.2 * **BUGFIX** (by @imaNNeo): Fix inner border of pieChart with single section, #1089 * **IMPORTANT** **IRAN NEEDS HELP** As you might know, Islamic Republic of Iran is murdering people in silence right now in Iran They shut the Internet down to do that. That’s why I cannot maintain this library for a while. Now we need your help, please be our voice by spreading news in your media to support us Search these hashtags: [#MahsaAmini](https://twitter.com/search?q=%23MahsaAmini&src=typeahead_click) [مهسا_امینی](https://twitter.com/search?q=%23%D9%85%D9%87%D8%B3%D8%A7_%D8%A7%D9%85%DB%8C%D9%86%DB%8C&src=typeahead_click&f=top) [OpIran](https://twitter.com/search?q=%23OpIran&src=typeahead_click&f=top) Also, [this article](https://www.bbc.com/news/world-middle-east-62984076) might help. ## 0.55.1 * **BUGFIX** (by @ateich): Fix infinite loop in RadarChart when all values in RadarDataSet are equal, #882. * **BUGFIX** (by @ateich): Fix uneven titles in RadarChart when using titlePositionPercentageOffset, #1074. * **BUGFIX** (by @imaNNeo): Fix PieChart single section stroke issue, #1089 ## 0.55.0 * **FEATURE** (by @emelinepal): Add `tooltipBorder` property in [LineTouchTooltipData], [BarTouchTooltipData], [ScatterTouchTooltipData], #692. * **BUGFIX** (by @imaNNeo): Fix tooltip issue on negative bar charts, #978. * **IMPROVEMENT** (by @imaNNeo): Use Container to draw axis-based charts border. * **FEATURE** (by @FlorianArnould) Add the ability to select the RadarChart shape (circle or polygon), #1047. * **BUGFIX** (by @imaNNeo): Fix LineChart titles problem with single FlSpot, #1053. * **FEATURE** (by @FlorianArnould) Add the ability to rotate the RadarChar titles, #883. * **BREAKING** (by @FlorianArnould) [RadarChartData.getTitle](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/radar_chart.md#RadarChartData) have a new parameter `angle` and now returns a [RadarChartTitle](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/radar_chart.md#RadarChartTitle) instead of a simple `string`. (Read our [Migration Guide](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/migration_guides/0.55.0/MIGRATION_00_55_00.md) to learn more about it) ## 0.51.0 * **FEATURE** (by @imaNNeo): Add `SideTitleWidget` to help you use it in [SideTitles.getTitlesWidget]. It's a wrapper around your widget. It keeps your provided `child` widget close to the chart. It has `angle` and `space` properties to handle margin and rotation. There is a `axisSide` property that you should fill, it has provided to you in the MetaData object. Check the below sample: ```dart getTitlesWidget: (double value, TitleMeta meta) { return SideTitleWidget( axisSide: meta.axisSide, space: 8.0, angle: 0.0, child: const Text("This is your widget"), ); }, ``` * **IMPROVEMENT** (by @imaNNeo): Fix default LineChart interval issue on small view sizes, #909. ## 0.50.6 * **IMPROVEMENT** Fix a backward compatibility issue with Flutter 3.0, #1016 ## 0.50.5 * **IMPROVEMENT** Fix test coverage problem again :/ ## 0.50.4 * **IMPROVEMENT** Fix test coverage problem ## 0.50.3 * **IMPROVEMENT** Fix order of drawing lineChart bar indicator problem, #198. * **FEATURE** Add `isStrokeJoinRound` property in [LineChartBarData](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/line_chart.md#linechartbardata). * **IMPROVEMENT** Upgrade to Flutter 3, #997. * **FEATURE** Add `chartRendererKey` property to the [LineChart](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/line_chart.md), [BarChart](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/bar_chart.md), and [ScatterChart](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/scatter_chart.md). We pass it directly to our chart renderers that are responsible to render the chart itself (without anything around it like titles), #987. ## 0.50.1 * **BUGFIX** Allow to show axisTitle without sideTitles, #963 ## 0.50.0 **This release has some breaking changes. So please check out the migration guide [here](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/migration_guides/0.50.0/MIGRATION_00_50_00.md)** * **IMPROVEMENT** Allow to return a Widget in [SideTitles.getTitlesWidget](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/base_chart.md#sidetitles) instead of a `String`. For example, you can pass an [Icon](https://api.flutter.dev/flutter/widgets/Icon-class.html) widget as a title, #183. Check below samples: > **LineChartSample 8** ([Source Code](https://github.com/imaNNeo/fl_chart/blob/main/example/lib/presentation/samples/line/line_chart_sample8.dart)) > > > **BarChartSample 7** ([Source Code](https://github.com/imaNNeo/fl_chart/blob/main/example/lib/presentation/samples/bar/bar_chart_sample7.dart)) > > * **BREAKING** Structure of `FlTitlesData`, `AxisTitles`, and `SideTitles` are changed. Because we are using a new system which allows you to pass any [Flutter Widget](https://docs.flutter.dev/development/ui/widgets) as a title instead of passing `string`, `textStyle`, `textAlign`, `rotation`, ... (Read our [Migration Guide](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/migration_guides/0.50.0/MIGRATION_00_50_00.md)) * **FEATURE** Now we can use any [Gradient](https://api.flutter.dev/flutter/dart-ui/Gradient-class.html) such as [LinearGradient](https://api.flutter.dev/flutter/painting/LinearGradient-class.html) and [RadialGradient](https://api.flutter.dev/flutter/painting/RadialGradient-class.html) everywhere we have gradient. * **BUGFIX** Fix BarChart rods gradient problem, #703. * **BREAKING** `colors` property renamed to `color` to keep only one solid color. And now we have a `gradient` field instead of `colorStops`, `gradientFrom` and `gradientTo` in following classes: [BarChartRodData](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/bar_chart.md#barchartroddata), [BackgroundBarChartRodData](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/bar_chart.md#backgroundbarchartroddata), [BarAreaData](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/line_chart.md#BarAreaData), [BetweenBarsData](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/line_chart.md#betweenbarsdata), [LineChartBarData](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/line_chart.md#linechartbardata). (Read our [Migration Guide](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/migration_guides/0.50.0/MIGRATION_00_50_00.md) to learn more about it) ## 0.46.0 * **BUGFIX** Fix drawing BetweenBarsArea problem when there are `nullSpots` in fromLine and toLine, #912. * **FEATURE** Allow to have vertically grouped BarChart using `fromY` and `toY` properties in [BarChartRodData](https://github.com/imaNNeo/fl_chart/blob/feature/multi-rods-on-bar-chart/repo_files/documentations/bar_chart.md#BarChartRodData) It means you can have a negative and a positive bar chart at the same X location. #334, #875. Check [BarChartSample5](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/bar_chart.md#sample-5-source-code) and [BarChartSample6](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/bar_chart.md#sample-6-source-code. * **BREAKING** Renamed `y` property to `toY` in [BarChartRodData](https://github.com/imaNNeo/fl_chart/blob/feature/multi-rods-on-bar-chart/repo_files/documentations/bar_chart.md#BarChartRodData) and [BackgroundBarChartRodData](https://github.com/imaNNeo/fl_chart/blob/feature/multi-rods-on-bar-chart/repo_files/documentations/bar_chart.md#backgroundbarchartroddata) due to the above feature. * **BUGFIX** Fix smaller radius bubble hiding behind bigger radius bubble in ScatterChart, #930. * **BUGFIX** Fix tooltip text alignment and direction in line chart, #927. ## 0.45.1 * **IMPORTANT** **Fuck Vladimir Putin** * **BUGFIX** Fix `FlSpot.nullSpot` at the first of list bug, #912. * **FEATURE** Add `scatterLabelSettings` property in [ScatterChart](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/scatter_chart.md) which lets you to add titles on the spots, #902. ## 0.45.0 * **BUGFIX** Fix `clipData` implementation in ScatterChart and LineChart, #897. * **BUGFIX** Fix PieChart changing sections issue (we have disabled semantics for pieChart badgeWidgets), #861. * **BUGFIX** Fix LineChart width smaller width or height lower than 40, #869, #857. * **BUGFIX** Allow to show title when axis diff is zero, #842, #879. * **IMPROVEMENT** Improve iteration over axis values logic (it solves some minor problems on showing titles when min, max values are below than 1.0). * **IMPROVEMENT** Add `baselineX` and `baselineY` property in our axis-based charts, It fixes a problem about `interval` which mentioned in #893 (check [this sample](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/line_chart.md#gist---baselinex-baseliney-sample-source-code). * **IMPROVEMENT** Added `distanceCalculator` to `LineTouchData` which is used to calculate the distance between spots and touch events, #716, #261, #892 * **BREAKING** `LineTouchResponse` response now contains a list of `TouchLineBarSpot` instead of `LineBarSpot`. They are ordered based on their distance to the touch event and also contain that distance. ## 0.41.0 * **BUGFIX** Fix getNearestTouchedSpot. Previously it returned the first occurrence of a spot within the threshold, and not the nearest, #641, #645. * **FEATURE** Add `textAlign` property in the [SideTitles](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/base_chart.md#sidetitles), #784. * **IMPROVEMENT** Write some unit-tests and enable code coverage reports in our CI ## 0.40.6 * **IMPROVEMENT** Fix showing zero value in side titles and grid lines when we add negative value. Now we always go through the zero value in each axis, #739. * **BUGFIX** Fix example app unsupported operation problem on web, #844. ## 0.40.5 * **BUGFIX** Fix BarChart empty groups state error, #797. * **BUGFIX** Fix drawTooltipOnTop direction minor bug, #815. * **BUGFIX** Fix section with zero value problem in PieChart (disabled animation on changing value to zero and from zero), #817 * **BUGFIX** Fix pie chart stroke problem when adding space between sections (using new approach), #818. * **IMPROVEMENT** Fix interval below one, #811 ## 0.40.2 * **IMPROVEMENT** Use 80 characters for code format line-length instead of 100 (because pub.dev works with 80 and decreased our score). ## 0.40.1 * **IMPROVEMENT** Fix pub.dev determining web support, #780. * **IMPROVEMENT** Implement flutter_lints in the code. * **BUGFIX** Fix below/above area data transparency issue, #770. ## 0.40.0 * **BUGFIX** Fixed pieChart `centerRadius = double.infinity` problem, #747.c * **BREAKING** Charts touchCallback signature has changed to `(FlTouchEvent event, BaseTouchResponse? response)` which [FlTouchEvent](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/base_chart.md#fltouchevent) determines which touch/pointer event happened (such as `FlTapUpEvent`, `FlPanUpdateEvent`, ...), and BaseTouchResponse gives us the chart response. * **BREAKING** Chart touchResponse classes don't have `touchInput` and `clickHappened` properties anymore. Use [FlTouchEvent](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/base_chart.md#fltouchevent) provided in the callback instead of `touchInput`. Check `event is FlTapUpEvent` to detect touch events instead of checking `clickHappened`; * **IMPROVEMENT** Again we support `longPress` touch events. check [FlTouchEvent](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/base_chart.md#fltouchevent) to see all kind of supported touch/pointer events (which can be `FlLongPressStart`, `FlLongPressMoveUpdate`, `FlLongPressEnd`, ...). Also you can check out [touch handling doc](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/handle_touches.md), #649. * **IMPROVEMENT** Added `mouseCursorResolver` callback in touchData classes such as [LineTouchData](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/line_chart.md#linetouchdata-read-about-touch-handling) and [BarTouchData](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/bar_chart.md#bartouchdata-read-about-touch-handling). You can change the [MouseCursor](https://api.flutter.dev/flutter/services/MouseCursor-class.html) based on the provided [FlTouchEvent](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/base_chart.md#fltouchevent) and touchResponse using this callback. (We have used this feature in [PieChartSample2](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/scatter_chart.md#sample-2-source-code)) * **BUGFIX** Fixed `ScatterChart` default touchHandling crash * **BUGFIX** Fix text styles when updating the theme. Check this [theme-aware-sample](https://gist.github.com/imaNNeo/bf95e720621d799ab980a7a3287c56e2). * **IMPROVEMENT** Show narrow horizontal and vertical grid lines by default. * **IMPROVEMENT** Show all left, top (except BarChart), right, bottom titles in Axis based charts by default. * **IMPROVEMENT** Set `BarChartAlignment.spaceEvenly` as `alignment` property of [BarChartData](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/bar_chart.md#barchartdata) by default * **IMPROVEMENT** Allow [BarChart](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/bar_chart.md) and [LineChart](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md) have empty values instead of throwing exception (we don't show anything if there is nothing provided) * **BREAKING** `textStyle` of [ScatterTooltipItem](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/scatter_chart.md#ScatterTooltipItem) is now nullable and optional. `bottomMargin` is also optional (default is zero). So both are named parameters now. * **IMPROVEMENT** We improved touch precision of `ScatterChart`. * **BUGFIX** Fix overlapping last gridlines on border lines problem. * **NEWS** Your donation **motivates** me to work more on the `fl_chart` and resolve more issues. Now you can [buy me a coffee](https://www.buymeacoffee.com/fl_chart)! ## 0.36.4 * **IMPROVEMENT** Added `borderSide` property in [BarChartRodData](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/bar_chart.md#BarChartRodData) and [BarChartRodStackItem](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/bar_chart.md#BarChartRodStackItem) to draw strokes around each bar and rod stack items, #714. * **IMPROVEMENT** Now all textStyles are nullable and theme-aware by default, #269. * **BREAKING** All `getTextStyles` callback now give you a `context` and `value` (previously it was only a `value`). * **BUGFIX** Fixed `colorStops` calculation which used in gradient colors, #732. ## 0.36.3 * **IMPROVEMENT** Show proper error message when there is less than 3 [RadarEntry](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/radar_chart.md#radarentry) in [RadarChart](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/radar_chart.md), #694. * **IMPROVEMENT** Added `borderSide` property in [PieChartSectionData](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/pie_chart.md#piechartsectiondata) to draw strokes around each section, #606. ## 0.36.2 * **IMPROVEMENT** Support `onMouseExit` event in all charts. * **IMPROVEMENT** Add `rotateAngle` property in [LineTouchTooltipData](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md#linetouchtooltipdata), [BarTouchTooltipData](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/bar_chart.md#bartouchtooltipdata), [ScatterTouchTooltipData](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/scatter_chart.md#scattertouchtooltipdata), #260, #679. * **BUGFIX** Fix PieChart section index problem, when there is a section with 0 value, #697. ## 0.36.1 * **IMPROVEMENT** Allow to set zero value on PieChartSectionData (we remove zero sections instead of crashing), #640. * **BUGFIX** Fix NPE crash in our renderers touchCallback, #651. * **BUGFIX** Fix line index problem in LineChart, #665. (It has appeared in `0.36.0`, we had to revert 2nd change of `0.36.0`) * **BREAKING** Remove unused `lineIndex` property from (ShowingTooltipIndicators)[https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md#showingtooltipindicators]. ## 0.36.0 * **BUGFIX** Fixed bug of lerping FlSpot.nullSpot, #487. * **BUGFIX** Fixed showing tooltip problem when animating chart, #647. * **BUGFIX** Fixed RadarChart drawing problem, #627. * **IMPROVEMENT** Now [SideTitles](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/base_chart.md#SideTitles).`interval` is working correctly in bottomTitles in the BarChart, #648. * **BREAKING** You should provide `spotsIndices` instead of `showingSpots` in [ShowingTooltipIndicators](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md#showingtooltipindicators). ## 0.35.0 * **IMPROVEMENT** Added `children` property in the [LineTooltipItem](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md#linetooltipitem), [BarTooltipItem](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/bar_chart.md#bartooltipitem) and [ScatterTooltipItem](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/scatter_chart.md#scattertooltipitem) which accepts a list of [TextSpan](https://api.flutter.dev/flutter/painting/TextSpan-class.html). It allows you to have more customized texts inside the tooltip. See [BarChartSample1](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/bar_chart.md#sample-1-source-code) and [ScatterSample2](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/scatter_chart.md#sample-2-source-code), #72, #294. * **IMPROVEMENT** Added `getTouchLineStart` and `getTouchLineEnd` in [LineTouchData](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md#linetouchdata-read-about-touch-handling) to give more customizability over showing the touch lines. see [SampleLineChart9](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md#sample-8-source-code). * **IMPROVEMENT** Enabled `sectionsSpace` in PieChart for the web. * **IMPROVEMENT** Added [Makefile](https://makefiletutorial.com) commands which makes it comfortable for verifying your code before push (It is related to contributors, red more about it in [CONTRIBUTING.md](https://github.com/imaNNeoFighT/fl_chart/blob/main/CONTRIBUTING.md)). * **IMPROVEMENT** Added `FlDotCrossPainter` which extends `FlDotPainter` to paint X marks on line chart spots. * **IMPROVEMENT** Added `textDirection` property in [LineTooltipItem](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md#linetooltipitem), [BarTooltipItem](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/bar_chart.md#bartooltipitem) and [ScatterTooltipItem](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/scatter_chart.md#scattertooltipitem). It allows you to support rtl languages in tooltips. * **IMPROVEMENT** Added `textDirection` property in [SideTitles](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/base_chart.md#sidetitles) class, #531. It allows you to support rtl languages in side titles. * **IMPROVEMENT** Added `textDirection` property in [AxisTitles](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/base_chart.md#AxisTitle) class. It allows you to support rtl languages in axis titles. * **BUGFIX** Fixed some bugs on drawing PieChart (for example when we have only one section), #582, * **BREAKING** Border of pieChart now is hide by default (you can show it using `borderData: FlBorderData(show: true)`. * **BREAKING** You cannot set `0` value on [PieChartSectionData](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/pie_chart.md#piechartsectiondata).value anymore, instead remove it from list. * **BREAKING** Removed `fullHeightTouchLine` property from [LineTouchData](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md#linetouchdata-read-about-touch-handling). Now you can have a full line with following snippet: ```dart LineTouchData( ... getTouchLineStart: (barData, index) => -double.infinity // default: from bottom, getTouchLineEnd: (barData, index) => double.infinity //to top, ... ) ``` ## 0.30.0 * [IMPROVEMENT] We now use [RenderObject](https://api.flutter.dev/flutter/rendering/RenderObject-class.html) as our default drawing system. It brings a lot of stability. Such as size handling, hitTest handling (touches), and It makes us possible to paint Widgets inside our chart (It might fix #383, #556, #582, #584, #591). * [IMPROVEMENT] Added [Radar Chart Documentations](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/radar_chart.md) * [IMPROVEMENT] Added `textAlign` property in the [BarTooltipItem](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/bar_chart.md#bartooltipitem), [LineTooltipItem](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md#linetooltipitem), and [ScatterTooltipItem](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/scatter_chart.md#scattertooltipitem), default is `TextAlign.center`. * [IMPROVEMENT] Added `direction` property in the [BarTouchTooltipData](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/bar_chart.md#bartouchtooltipdata), and [LineTouchTooltipData](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md#linetouchtooltipdata) to specify the position of the tooltip (can be `auto`, `top`, `bottom`), default is `auto`. * [IMPROVEMENT] Updated touch flow, we now use [hitTest](https://api.flutter.dev/flutter/rendering/RenderProxyBoxWithHitTestBehavior/hitTest.html) for handling touch and interactions. * [IMPROVEMENT] Added 'clickHappened' property in all of our TouchResponses (such as [LineTouchResponse](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md#LineTouchResponse), [BarTouchResponse](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/bar_chart.md#bartouchresponse), ...), #210. * [IMPROVEMENT] Added `swapAnimationCurve` property to all chart widgets which handles the built-in animation [Curve](https://api.flutter.dev/flutter/animation/Curves-class.html), #436. * [BREAKING] Some properties in [ScatterTouchResponse](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/scatter_chart.md#scattertouchresponse), and [PieTouchResponse](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/pie_chart.md#pietouchresponse) moved to a wrapper class, you need to access them through that wrapper class. * [BREAKING] Renamed `tooltipBottomMargin` to `tooltipMargin` property in the [BarTouchTooltipData](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/bar_chart.md#bartouchtooltipdata), and [LineTouchTooltipData](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md#linetouchtooltipdata) * [Bugfix] Fixed `double.infinity` in [PieChartData](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/pie_chart.md#piechartdata) .centerSpaceRadius, #584. ## 0.20.1 * [BREAKING] We now support flutter version 2.0 (null-safety), check out the [migration guide](https://dart.dev/null-safety/migration-guide). * [NEW_CHART] We have added [RadarChart](https://github.com/payam-zahedi/fl_chart/blob/main/repo_files/documentations/radar_chart.md). Thanks to [Payam Zahedi](https://github.com/payam-zahedi)! ## 0.20.0-nullsafety1 * [BREAKING] **We have migrated our project to null-safety. You may need to change your source-code to compile**. check [migration guide](https://dart.dev/null-safety/migration-guide). * [BREAKING] You cannot set null value on FlSpot any more (use FlSpot.nullSpot instead). ## 0.12.3 * [Bugfix] Fixed PieChart exception bug on sections tap, #514. * [Bugfix] Fixed PieChart badges problem, #538. * [Bugfix] Fixed Bug of drawing lines with strokeWidth zero, #558. * [Improvement] Updated example app to support web. * [Improvement] Show tooltips on mouse hover on Web, and Desktop. ## 0.12.2 * [Bugfix] Fixed PieChart badges draw in first frame problem, #513. * [Improvement] Use CanvasWrapper to proxy draw functions (It does not have any effect on the result, it makes the code testable) ## 0.12.1 * [Bugfix] Fixed PieChart badges bug with re-implementing the solution, #507 * [Bugfix] Fix the setState issue using PieChart in the ListView, #467 * [Bugfix] Fixed formatNumber bug for negative numbers, #486. * [Improvement] Added applyCutOffY property in [BarAreaSpotsLine](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md#barareaspotsline) to inherit cutOffY property of its parent, #478. ## 0.12.0 * [Improvement] [BREAKING] Replaced `color` property with `colors` in [BarChartRodData](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/bar_chart.md#barchartroddata), and [BackgroundBarChartRodData](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/bar_chart.md#backgroundbarchartroddata) to support gradient in BarChart, instead of solid color, #166. Check [BarChartSample3](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/bar_chart.md#sample-3-source-code) * [Improvement] Improved gradient stops calculating algorithm. * [Improvement] [BREAKING] Changed SideTitle's `textStyle` property to `getTextStyles` getter (it gives you the axis value, and you must return a TextStyle based on it), It helps you to have a different style for specific text, #439. Check it here [LineChartSample3](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md#sample-3-source-code) * [Improvement] Added `badgeWidget`, and `badgePositionPercentageOffset` in each [PieChartSectionData](https://github.com/imaNNeoFighT/fl_chart/blob/dev/repo_files/documentations/pie_chart.md#piechartsectiondata) to provide a widget to show in the chart, see [this sample](https://github.com/imaNNeoFighT/fl_chart/blob/dev/repo_files/documentations/pie_chart.md#sample-3-source-code), #443. Providing a widget is an important step in our library, if it works perfectly, we will aplly this solution on other parts. Then I appreciate any feedback. * [Bugfix] Fixed aboveBarArea flickers after setState, #440. ## 0.11.1 * [Bugfix] Fixed drawing BarChart rods with providing minY (for positive), maxY (for negative) values bug, #404. * [Bugfix] Fixed example app build fail error, by upgrading flutter_svg package to `0.18.1` ## 0.11.0 * [Bugfix] Prevent show ScatterSpot if show is false, #385. * [Improvement] Set default centerSpaceRadius to double.infinity in [PieChartData](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/pie_chart.md#piechartdata), #384. * [Improvement] Allowed to have topTitles in the [BarChart](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/bar_chart.md), see [BarChartSample5](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/bar_chart.md#sample-5-source-code), #394. * [Improvement] Added `touchedStackItem` and `touchedStackItemIndex` properties in the [BarTouchedSpot](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/bar_chart.md#bartouchedspot) to determine in which [BarChartRodStackItem](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/bar_chart.md#barchartrodstackitem) click happened, #393. * [Improvement] [BREAKING] Renamed `rodStackItem` to `rodStackItems` in [BarChartRodData](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/bar_chart.md#barchartroddata). ## 0.10.1 * [Improvement] Show barGroups `x` value instead of `index` in bottom titles, #342. * [Improvement] [BREAKING] Use `double.infinity` instead of `double.nan` for letting `enterSpaceRadius` be as large as possible in the (PieChartData)[https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/pie_chart.md#piechartdata], #377. * [Bugfix] Fixed PieChart bug with 1 section, #368. ## 0.10.0 * [IMPORTANT] **BLACK LIVES MATTER** * [Improvement] Auto calculate interval in [SideTitles](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/base_chart.md#sidetitles) and [FlGridData](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/base_chart.md#flgriddata), instead of hard coding 1, to prevent some performance issues like #101, #322. see [BarChartSample4](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/bar_chart.md#sample-4-source-code). * [Bugfix] drawing dot on null spots * [Bugfix] Fixed LineChart have multiple NULL spot bug. * [Feature] Added `checkToShowTitle` property to the [SideTitles](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/base_chart.md#sidetitles), for checking show or not show titles in the provided value, #331. see [LineChartSample8](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md#sample-8-source-code). * [Feature] Added compatibily to have customized shapes for [FlDotData](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md#fldotdata), just override `FlDotData.etDotPainter` and pass your own painter or use built-in ones, see this [sample](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md#sample-3-source-code). * [Improvement] [BREAKING] Replaced `clipToBorder` with `clipData` in [LineChartData](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md#linechartdata) to support clipping 4 sides of a chart separately. ## 0.9.4 * [Bugfix] Fixed showing PieChart on web (we've ignored `groupSpace` on web, because some BlendModes are [not working](https://github.com/flutter/flutter/issues/56071) yet) ## 0.9.3 * [BugFix] Fixed groupBarsPosition exception, #313. * [Improvement] Shadows default off, #316. ## 0.9.2 * [Feature] Added `shadow` property in [LineChartData](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md#linechartdata) to have shadow effect in our [LineChart](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md), take a look at [LineChartSampl5](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md#sample-5-source-code), #304. * [Feature] Added `isStepLineChart`, and `lineChartStepData` in the [LineChartData](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md#linechartdata) to support Step Line Chart, take a look at [lineChartSample3](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md#sample-3-source-code), #303. * [Improvement] Added `barData` parameter to checkToShowDot Function in the [FlDotData](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md#fldotdata). ## 0.9.0 * Added `strokeWidth`, `getStrokeColor`, `getDotColor` in the [FlDotData](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md#fldotdata), also removed `dotColor` from it (you should use `getDotColor` instead, it gives you more customizability), now we have more customizability on [FlDotData](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md#fldotdata), check [line_chart_sample3](https://github.com/imaNNeoFighT/fl_chart/blob/dev/repo_files/documentations/line_chart.md#sample-3-source-code), and [line_chart_sample5](https://github.com/imaNNeoFighT/fl_chart/blob/dev/repo_files/documentations/line_chart.md#sample-5-source-code), #233, #99, #274. * Added `equatable` library to solve some equation issues. * Implemented negative values feature for the BarChart, #106, #103. * add Equatable for all models, it leads to have a better performance. * Fixed a minor touch bug in the [BarChart](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/bar_chart.md). * Fixed ScatterChart built-in touch behaviour. * Fixed drawing grid lines bug, #280. * Implemented [FlDotData](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md#fldotdata).`getDotColor` in a proper way, it returns a color based on the [LineChartBarData](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md#linechartbardata) color, #274, #282. * Updated [LineChartData](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md#linechartdata).`showingTooltipIndicators` field type to list of [ShowingTooltipIndicators](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md#showingtoltipindicators) to have a clean naming. ## 0.8.7 * Added `show` property in the `VerticalLineLabel` and set default to `false`, #256. * Fixed bug, when the screen size is square, #258. ## 0.8.6 * Fixed exception on extraLinesData, #251. * Show extra lines value with 1 floating-point. * Implemented multi-section lines in LineChart, check this issue (#26) and this merge request (#252) ## 0.8.5 * Added `fitInsideHorizontally` and `fitInsideVertically` in [ScatterTouchTooltipData](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/scatter_chart.md#scattertouchtooltipdata) * Fixed `clipToBorder` functionality basdd on the border sides. ## 0.8.4-test1 * Improved documentations ## 0.8.4 * Added `preventCurveOvershootingThreshold` in `LineChartBarData` for applying prevent overshooting algorithm, #193. * Fixed `clipToBorder` bug in the [LineChartData](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md#linechartdata), #228, #214. * Removed unused `enableNormalTouch` property from all charts TouchData. * Implemented ImageAnnotations feature (added `image`, and `sizedPicture` in the [VerticalLine](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md#verticalline), and the [HorizontalLine](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md#horizontalline), check [this sample](https://github.com/imaNNeoFighT/fl_chart/blob/dev/repo_files/documentations/line_chart.md#sample-8-source-code) for more information. * Enable 'fitInsideTheChart' to support vertical tooltip overflow as well, #225. * BREAKING CHANGE-> changed `fitInsideTheChart` to `fitInsideHorizontally` and added `fitInsideVertically` to support both sides, #225. ## 0.8.3 * prevent to set BorderRadius with numbers larger than (width / 2), fixed #200. * added `fitInsideTheChart` property inside `BarTouchTooltipData` and `LineTouchTooltipData` to force tooltip draw inside the chart (shift it to the chart), fixed #159. ## 0.8.2 * added `fullHeightTouchLine` in [LineTouchData](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md#linetouchdata-read-about-touch-handling) to show a full height touch line, see sample in merge request #208. * added `label` ([HorizontalLineLabel](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md#horizontallinelabel)) inside [HorizontalLine](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md#horizontalline) and [VerticalLine](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md#verticalline) to show a lable text on the lines. ## 0.8.1 * yaaay, added some basic unit tests * skipped the first and the last grid lines from drawing, #174. * prevent to draw touchedSpotDot if `show` is false, #180. * improved paint order, more details in #175. * added possibility to set `double.nan` in `centerSpaceRadius` for the PieChart to let it to be calculated according to the view size, fixed #179. ## 0.8.0 * added functionallity to have dashed lines, in everywhere we draw line, there should be a property called `dashArray` (for example check [LineChartBarData](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md#linechartbardata), and see [LineChartSample8](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md#sample-8-source-code)) * BREAKING CHANGE: * swapped [HorizontalExtraLines](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md#horizontalline), and [VerticalExtraLines](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md#verticalline) functionalities (now it has a well definition) * and also removed `showVerticalLines`, and `showHorizontalLines` from [ExtraLinesData](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md#ExtraLinesData), if the `horizontalLines`, or `verticalLines` is empty we don't show them ## 0.7.0 * added rangeAnnotations in the [LineChartData](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md#linechartdata) to show range annotations, #163. * removed `isRound` fiend in the [BarChartRodData](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/bar_chart.md#barchartroddata) to add more customizability, and fixed #147 bug. * fixed sever bug of click on pie chart, #146. ## 0.6.3 * Fixed drawing borddr bug, #143. * Respect text scale factor when drawing text. ## 0.6.2 * added `axisTitleData` field to all axis base charts (Line, Bar, Scatter) to show the axes titles, see [LineChartSample4](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md#sample-4-source-code) and [LineChartSample5](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md#sample-5-source-code). ## 0.6.1 * added `betweenBarsData` property in [LineChartData](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md#linechartdata), fixed #93. ## 0.6.0 * fixed calculating size for handling touches bug, #126 * added `rotateAngle` property to rotate the [SideTitles](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/base_chart.md#sidetitles), fixed issue [#75](https://github.com/imaNNeoFighT/fl_chart/issues/75) , see in this [sample](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/bar_chart.md#sample-5-source-code) * BREAKING CHANGES: * some property names updated in the [FlGridData](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/base_chart.md#flgriddata): `drawHorizontalGrid` -> `drawHorizontalLine`, `getDrawingHorizontalGridLine` -> `getDrawingHorizontalLine`, `checkToShowHorizontalGrid` -> `checkToShowHorizontalLine` (and same for vertical properties), fixed issue [#92](https://github.com/imaNNeoFighT/fl_chart/issues/92) ## 0.5.2 * drawing titles using targetData instead of animating data, fixed issue #130. ## 0.5.1 * prevent to show touch indicators if barData.show is false in LineChart, [#125](https://github.com/imaNNeoFighT/fl_chart/issues/125). ## 0.5.0 * 💥 Added ScatterChart ([read about it](https://jbt.github.io/markdown-editor/repo_files/documentations/scatter_chart.md)) 💥 * Added Velocity to in [FlPanEnd](https://github.com/imaNNeoFighT/fl_chart/blob/feature/scatter-chart/repo_files/documentations/base_chart.md#fltouchinput) to determine the Tap event. ## 0.4.3 * fixed a size bug, #100. * direction support for gradient on the LineChart (added `gradientFrom` and `gradientTo` in the [LineChartBarData](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md#linechartbardata)). ## 0.4.2 * implemented stacked bar chart, check the [samples](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/bar_chart.md#sample-5-source-code) * added `groupSpace in [BarChartData](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/bar_chart.md#barchartdata) to apply space between bar groups * fixed drawing left and right titles of the BarChart * fixed showing gridLines bug (the grid line of exact max value of each direction doesn't show) ## 0.4.1 * fixed handling disabled `handleBuiltInTouches` state bug ## 0.4.0 * BIG BREAKING CHANGES * There is no `FlChart` class anymore, instead use [LineChart](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md), [BarChart](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/bar_chart.md), and [PieChart](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/pie_chart.md) directly as a widget. * Touch handling system is improved and for sure we have some changes, there is no `touchedResultSink` anymore and use `touchCallback` function which is added to each TouchData like ([LineTouchData](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md#linetouchdata-read-about-touch-handling)), [read more](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/handle_touches.md). * `TouchTooltipData` class inside `LineTouchData` and `BarTouchData` renamed to `LineTouchTooltipData` and `BarTouchTooltipData` respectively, and also `TooltipItem` class renamed to `LineTooltipItem` and `BarTooltipItem`. * `spots` inside `LineTouchResponse` renamed to `lineBarSpots` and type changed from `LineTouchedSpot` to `LineBarSpot`. * `FlTouchNormapInput` renamed to `FlTouchNormalInput` (fixed typo) * added `showingTooltipIndicators` in [LineChartData](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md#linechartdata) to show manually tooltips in `LineChart`. * added `showingIndicators` in [LineChartBarData](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md#linechartbardata) to show manually indicators in `LineChart`. * added `showingTooltipIndicators` in [BarChartGroupData](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/bar_chart.md#barchartgroupdata) to show manually tooltips in `BarChart`. ## 0.3.4 * BREAKING CHANGES * swapped horizontal and vertical semantics in [FlGridData](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/base_chart.md#FlGridData), fixed this [issue](https://github.com/imaNNeoFighT/fl_chart/issues/85). ## 0.3.3 * BREAKING CHANGES * added support for drawing below and above areas separately in LineChart * added cutOffY feature in LineChart, see this [issue](https://github.com/imaNNeoFighT/fl_chart/issues/62) * added `aboveBarData` in [LineChartBarData](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md#linechartbardata) * `BelowBarData` class renamed to [BarAreaData](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md#barareadata) to reuse for both above and below areas * `belowSpotsLine` renamed to `spotsLine` in [BarAreaData](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md#barareadata) * `cutOffY` and `applyCutOffY` fields are added in [BarAreaData](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md#barareadata) to handle cutting of drawing below or above area * `BelowSpotsLine` renamed to [BarAreaSpotsLine](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md#barareaspotsline), and inside it `checkToShowSpotBelowLine` renamed to `checkToShowSpotLine` ## 0.3.2 * provided default size (square with 30% smaller than screen) for the FLChart, fixed this [issue](https://github.com/imaNNeoFighT/fl_chart/issues/74). ## 0.3.1 * added `interval` field in [SideTitles](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/base_chart.md#sidetitles), fixed this [issue](https://github.com/imaNNeoFighT/fl_chart/issues/67) ## 0.3.0 * 💥 Added Animations 💥, [read about it](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/handle_animations.md). ## 0.2.2 * fixed a typo on CHANGELOG * reformatted dart files with `flutter format` command ## 0.2.1 * fixed #64, added a technical debt :( ## 0.2.0 * fixed a critical got stuck in draw loop bug, * set `BarChartGroupData` x as required property to keep consistency and prevent unpredictable bugs ## 0.1.6 * added `enableNormalTouch` property to chart's TouchData to handle normal taps, and enabled by default. ## 0.1.5 * reverted getPixelY() on axis_chart_painter to solve the regression bug (fixed issue #48) * (fix) BelowBar considers its own color stops refs #46 ## 0.1.4 * bugfix -> fixed draw bug on BarChart when y value is very low in high scale y values (#43). ## 0.1.3 * added `SideTitles` class to hold titles representation data, and used in `FlTitlesData` to show left, top, right, bottom titles, instead of legacy direct parameters, and implemented a reversed chart [sample](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/line_chart.md#sample-6-source-code) using this update. ## 0.1.2 * added `preventCurveOverShooting` on BarData, check this [issue](https://github.com/imaNNeoFighT/fl_chart/issues/25) ## 0.1.1 * nothing important ## 0.1.0 * added **Touch Interactivity**, read more about it [here](https://github.com/imaNNeoFighT/fl_chart/blob/main/repo_files/documentations/handle_touches.md) ## 0.0.8 * added backgroundColor to axis based charts (LineChart, BarChart) to draw a solid background color behind the chart * added getDrawingHorizontalGridLine, getDrawingVerticalGridLine on FlGridData to determine how(color, strokeWidth) the grid lines should be drawn with the given value on FlGridLine ## 0.0.7 * added ExtraLinesData in the LineChartData to draw extra horizontal and vertical lines on LineChart * added BelowSpotsLine in the BlowBarData to draw lines from spot to the bottom of chart on LineChart ## 0.0.6 * fixed charts repainting bug, #16 ## 0.0.5 * added clipToBorder to the LineChartData to clip the drawing to the border, #3 ## 0.0.4 * fixed bug of adding bar with y = 0 on bar chart #13 ## 0.0.3 * renamed `FlChartWidget` to `FlChart` (our main widget) and now you have to import `package:fl_chart/fl_chart.dart` instead of `package:fl_chart/fl_chart_widget.dart` * renamed `FlChart*` to `BaseChart*` (parent class of our charts like `PieChart`) * renamed `FlAxisChart*` to `AxisChart*` ## 0.0.2 * fixed `minX`, `maxX` functionality on LineChart * restricted to access private classes of the library ## 0.0.1 - Released on (2019 June 4) ================================================ FILE: CLAUDE.md ================================================ # CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Overview FL Chart is a highly customizable Flutter chart library supporting 6 chart types: Line, Bar, Pie, Scatter, Radar, and Candlestick. Single-package Flutter library (not a monorepo). ## Common Commands ```bash make sure # Run tests + checkstyle (use before pushing) make runTests # flutter test make analyze # flutter analyze make checkFormat # Verify formatting (dry run) make format # Auto-format code make checkstyle # analyze + format check make codeGen # Generate mock files: dart run build_runner build --delete-conflicting-outputs flutter test test/chart/line_chart/line_chart_painter_test.dart # Run a single test file ``` ## Architecture ### Per-Chart Pattern Every chart type in `lib/src/chart/` follows a consistent structure: - `{type}_chart.dart` — Widget (extends `ImplicitlyAnimatedWidget` for built-in animations) - `{type}_chart_data.dart` — Data classes (extend `BaseChartData` or `AxisChartData`) - `{type}_chart_painter.dart` — Canvas drawing logic (extends `BaseChartPainter`) - `{type}_chart_renderer.dart` — Rendering widget that builds the custom painter - `{type}_chart_helper.dart` — Chart-specific utility functions ### Class Hierarchy ``` BaseChartData ├── AxisChartData (charts with X/Y axes) │ ├── LineChartData │ ├── BarChartData │ ├── ScatterChartData │ └── RadarChartData ├── PieChartData └── CandlestickChartData ``` Painters follow the same hierarchy: `BaseChartPainter` → `AxisChartPainter` → specific painters. ### Key Design Decisions - **CanvasWrapper** (`lib/src/utils/canvas_wrapper.dart`): All drawing goes through this proxy instead of `Canvas` directly, enabling unit testing of paint logic with Mockito. - **PaintHolder**: Holds current data, target data, text scaler, and virtual rect — passed to painters for rendering and animation interpolation. - **Implicit animations**: Charts use `ImplicitlyAnimatedWidget` with `*DataTween` classes. Default: 150ms linear. - **Equatable**: All data classes use `equatable` for value equality. - **Lerp**: Data models must implement a `lerp()` method to enable smooth implicit animations between states. See `lib/src/utils/lerp.dart` for helpers. - **Theme-aware text styles**: When rendering text in painters, always use `Utils().getThemeAwareTextStyle(context, style)` instead of hardcoded fallback `TextStyle` values. This merges user-provided styles with the app's theme. ### Touch System Each chart type defines `*TouchData` and uses `FlTouchEvent` base class. Touch callbacks are configured in the chart data classes. ## Testing Tests mirror the `lib/` structure under `test/`. Each chart has tests for data, painter, renderer, and helper. Painter tests mock `CanvasWrapper` to verify drawing calls. Key test utilities: - `test/helper_methods.dart` — Path/RRect equality helpers - `test/chart/data_pool.dart` — Shared mock data - `*.mocks.dart` files are generated by Mockito (run `make codeGen` to regenerate) ## Code Style - Uses `very_good_analysis` linter (strict, with some relaxed rules in `analysis_options.yaml`) - `public_member_api_docs` is disabled — public API docs are not enforced - `lines_longer_than_80_chars` is disabled - Generated `*.mocks.dart` files are excluded from analysis ## PR Conventions PR titles must follow Conventional Commits: `: ` (e.g., `feat: Add tooltip support`). Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert. Breaking changes use `!` (e.g., `feat!: Change API`). Subject starts with a capital letter. ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing Hello, we are glad to have a contributor like you here. Don't forget that `open-source` makes no sense without contributors. No matter how big your changes are, it helps us a lot even it is a line of change. This file is intended to be a guide for those who are interested in contributing to the FL Chart. #### Below are the people who have contributed to the FL Chart. We hope we have your picture here soon. [![](https://opencollective.com/fl_chart/contributors.svg?width=890&button=false)](https://github.com/imaNNeo/fl_chart/graphs/contributors) ## Let's get Started Make sure you have Flutter installed and on your path (follow the [installation guide](https://docs.flutter.dev/get-started/install)). Follow these steps to clone FL Chart and set up the development environment: 1. Fork the repository 2. Clone the project, you can find it in your repositories: `git clone https://github.com/your-username/fl_chart.git` 3. Go into the cloned directory: `cd fl_chart` 4. Install all packages: `flutter packages get` 5. Try to run the sample app. It should work on all platforms (Android, iOS, Web, Linux, MacOS, Windows) 6. Create a new branch for your changes: `git checkout -b your-branch-name`. The branch name doesn't matter for our project history (as we use **Squash and Merge**), but it's good practice to use descriptive names. You can [read more about naming conventions here](https://www.geeksforgeeks.org/git/how-to-naming-conventions-for-git-branches/). ## Before Modifying the Code If the work you intend to do is non-trivial, it is necessary to open an issue before starting to write your code. This helps us and the community to discuss the issue and choose what is deemed to be the best solution. ### Mention the related issues: If you are going to fix or improve something, please find and mention the related issues in commit message and Pull Request description. In case you couldn't find any issue, it's better to create an issue to explain what's the issue that you are going to fix. ## Let's start by our drawing architecture We have a *_chart_painter.dart class per each chart type. It draws elements into the Canvas. We made the CanvasWrapper class, because we wanted to test draw functions. CanvasWrapper class holds a canvas and all draw functions proxies through it. You should use it for drawing on the canvas, Instead of directly accessing the canvas. It makes draw functions testable. (made with [draw.io](https://drive.google.com/file/d/1bj-2TqTRUh80dRKJk10drPNeA3fp3EA8/view)) ## Keep your branch updated If your branch falls behind the `main` branch, you can update it using the "Update branch" button on GitHub or by merging `main` into your branch. We use **Squash and Merge**, which combines all your PR commits into a single, clean commit in the `main` branch. ## Checking Your Code's Quality After you have made your changes, you have to make sure your code works correctly and meets our guidelines. Our guidelines are: You can simply run `make checkstyle`, and if you faced any formatting problem, run `make format`. ##### Run `make checkstyle` to ensure that your code is formatted correctly - It runs `flutter analyze` to verify that there are no warnings or errors. - It runs `dart format --set-exit-if-changed --dry-run .` to verify that code has formatted correctly. #### Run `make format` to reformat the code - It runs `dart format .` to format your code. #### Run `make runTests` to ensure that all tests are passing. - It runs `flutter test` under the hood. #### Run `make sure` before pushing your code. - It runs both `make runTests` and then `make checkstyle` sequentially with a single command. ## Test coverage (unit tests) We should write unit tests for our written code. If you are not familiar with unit-tests, please start from [here](https://docs.flutter.dev/cookbook/testing/unit/introduction). [Mockito](https://pub.dev/packages/mockito) is the library that we use to mock our classes. Please read more about it in their docs [here](https://github.com/dart-lang/mockito#lets-create-mocks). Our code coverage is calculated by [Codecov](https://app.codecov.io/gh/imaNNeo/fl_chart) (Our coverage is [![codecov](https://codecov.io/gh/imaNNeo/fl_chart/branch/main/graph/badge.svg?token=XBhsIZBbZG)](https://codecov.io/gh/imaNNeo/fl_chart) at the moment) When you push something in your PR (after approving your PR by one of us), you see a coverage report which describes how much coverage is increased or decreased by your code (You can check the details to see which part of your code made the change). Please make sure that your code is **not decreasing** the coverage. ## Creating a Pull Request Congratulations! Your code meets all of our guidelines :100:. Now you have to submit a pull request (PR for short) to us. These are the steps you should follow when creating a PR: ### PR Title Convention We use [Conventional Commits](https://www.conventionalcommits.org/) for our PR titles. This title will be used to automatically generate the **CHANGELOG**. The title must follow this format: `: ` - **Type**: Must be one of `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `build`, `ci`, `chore`, `revert`. - **Subject**: Must start with a **Capital Letter** and adequately summarize the changes. - **Breaking Changes**: If your PR contains a breaking change, add a `!` after the type (e.g., `feat!: Change PieChart API`). **Examples:** - `feat: Add rounded corners to PieChart` - `fix: Resolve memory leak in BarChart` - `docs: Update BarChart documentation` A GitHub Action will validate your PR title and will fail if it doesn't follow this convention. ### PR Description - Use the provided PR template. - Provide a concise description of the changes. This text will become the permanent commit body in our Git history, so please keep it clear and brief. (Tip: Feel free to use AI assistants to help you draft this!) - Mention the issues that you are fixing (e.g., `Closes #1234`). - If it's a breaking change, provide migration instructions in the "Migration instructions" section of the template. After you follow the above steps, your PR will hopefully be merged. Thanks for contributing! ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2022 Flutter 4 Fun Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Makefile ================================================ ifeq ($(OS),Windows_NT) FIND_CMD=dir /S /B lib\*.dart test\*.dart | findstr /V .mocks.dart else FIND_CMD=find lib test -name '*.dart' -not -name '*.mocks.dart' endif analyze: flutter analyze checkFormat: dart format -o none --set-exit-if-changed $$( $(FIND_CMD) ) checkstyle: make analyze && make checkFormat format: dart format $$( $(FIND_CMD) ) runTests: flutter test checkoutToPR: git fetch origin pull/$(id)/head:pr-$(id) --force; \ git checkout pr-$(id) # Tells you in which version this commit has landed findVersion: git describe --contains $(commit) | sed 's/~.*//' # Runs both `make runTests` and `make checkstyle`. Use this before pushing your code. sure: make runTests && make checkstyle # To create generated files (for example mock files in unit_tests) codeGen: dart run build_runner build --delete-conflicting-outputs showTestCoverage: flutter test --coverage genhtml coverage/lcov.info -o coverage/html source ./scripts/makefile_scripts.sh && open_link "coverage/html/index.html" buildRunner: flutter packages pub run build_runner build --delete-conflicting-outputs ================================================ FILE: README.md ================================================ ![FL Chart Logo](https://github.com/imaNNeo/fl_chart/raw/main/repo_files/images/landing_logo.png) [![pub package](https://img.shields.io/pub/v/fl_chart.svg)](https://pub.dartlang.org/packages/fl_chart) [![codecov](https://codecov.io/gh/imaNNeo/fl_chart/branch/main/graph/badge.svg?token=XBhsIZBbZG)](https://codecov.io/gh/imaNNeo/fl_chart) Awesome Flutter GitHub Repo stars GitHub contributors GitHub closed issues ![GitHub Sponsors](https://img.shields.io/github/sponsors/imaNNeo) Buy Me A Coffee donate button ### Our Financial Heroes Your financial support acts as fuel for fl_chart's development. [Support here](https://github.com/sponsors/imaNNeo).
Become a sponsor
### Overview FL Chart is a highly customizable Flutter chart library that supports **[Line Chart](https://app.flchart.dev/#/line)**, **[Bar Chart](https://app.flchart.dev/#/bar)**, **[Pie Chart](https://app.flchart.dev/#/pie)**, **[Scatter Chart](https://app.flchart.dev/#/scatter)**, and **[Radar Chart](https://app.flchart.dev/#/radar)**.

### Chart Types |LineChart |BarChart |PieChart | |:------------:|:------------:|:-------------:| | [![](https://github.com/imaNNeo/fl_chart/raw/main/repo_files/images/line_chart/line_chart_sample_1.gif)](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/line_chart.md#sample-1-source-code) [![](https://github.com/imaNNeo/fl_chart/raw/main/repo_files/images/line_chart/line_chart_sample_2.gif)](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/line_chart.md#sample-2-source-code) | [![](https://github.com/imaNNeo/fl_chart/raw/main/repo_files/images/bar_chart/bar_chart_sample_1.gif)](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/bar_chart.md#sample-1-source-code) [![](https://github.com/imaNNeo/fl_chart/raw/main/repo_files/images/bar_chart/bar_chart_sample_2.gif)](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/bar_chart.md#sample-2-source-code) | [![](https://github.com/imaNNeo/fl_chart/raw/main/repo_files/images/pie_chart/pie_chart_sample_1.gif)](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/pie_chart.md#sample-1-source-code) [![](https://github.com/imaNNeo/fl_chart/raw/main/repo_files/images/pie_chart/pie_chart_sample_2.gif)](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/pie_chart.md#sample-2-source-code) | |[Read More](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/line_chart.md)|[Read More](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/bar_chart.md)|[Read More](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/pie_chart.md)| |ScatterChart |RadarChart| CandlestickChart| |:------------:|:------------:|:-------------:| | [![](https://github.com/imaNNeo/fl_chart/raw/main/repo_files/images/scatter_chart/scatter_chart_sample_1.gif)](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/scatter_chart.md#sample-1-source-code) [![](https://github.com/imaNNeo/fl_chart/raw/main/repo_files/images/scatter_chart/scatter_chart_sample_2.gif)](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/scatter_chart.md#sample-2-source-code) | ![](https://github.com/imaNNeo/fl_chart/raw/main/repo_files/images/radar_chart/radar_chart_sample_1.jpg) ![](https://github.com/imaNNeo/fl_chart/raw/main/repo_files/images/blank.png)|![](https://github.com/imaNNeo/fl_chart/raw/main/repo_files/images/candlestick_chart/candlestick_chart_sample_1.gif) ![](https://github.com/imaNNeo/fl_chart/raw/main/repo_files/images/blank.png)| |[Read More](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/scatter_chart.md)|[Read More](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/radar_chart.md)|[Read More](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/candlestick_chart.md)| Banner designed by [Soheil Saffar](https://www.linkedin.com/in/soheilsaffar), and samples inspired from [David Kovalev](https://dribbble.com/shots/5560237-Live-Graphs-XD), [Ricardo Salazar](https://dribbble.com/shots/1956890-Data-Stats), [Dmitro Petrenko](https://dribbble.com/shots/5425378-Mobile-Application-Dashboard-for-Stock-Platform), [Ghani Pradita](https://dribbble.com/shots/6379476-Calories-Management-App), [MONUiXD](https://www.uplabs.com/posts/chart-pie-chart-bar-chart). Thank you all! # Let's get started First of all, you need to add the `fl_chart` in your project. To do that, follow [this guide](https://pub.dev/packages/fl_chart/install). Then you need to read the docs. Start from [here](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/index.md). We suggest that you check the sample source code. ##### - You can read about the animation handling [here](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/handle_animations.md) |Sample1 |Sample2 |Sample3 | |:------------:|:------------:|:-------------:| | [![](https://github.com/imaNNeo/fl_chart/raw/main/repo_files/images/line_chart/line_chart_sample_1_anim.gif)](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/line_chart.md#sample-1-source-code) | [![](https://github.com/imaNNeo/fl_chart/raw/main/repo_files/images/line_chart/line_chart_sample_2_anim.gif)](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/line_chart.md#sample-2-source-code) | [![](https://github.com/imaNNeo/fl_chart/raw/main/repo_files/images/bar_chart/bar_chart_sample_1_anim.gif)](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/bar_chart.md#sample-1-source-code) | ### Try it out You can try the FL Chart sample app on the platforms that are available below: [![Get it on Google Play](https://img.shields.io/badge/Get%20it%20on-Google%20Play-green?style=for-the-badge&logo=google-play&logoColor=white)](https://play.google.com/store/apps/details?id=dev.flchart.app) [![Download on the App Store](https://img.shields.io/badge/Download-on%20the%20App%20Store-blue?style=for-the-badge&logo=app-store&logoColor=white)](https://apps.apple.com/us/app/fl-chart/id6476523019) [![Try it on Web](https://img.shields.io/badge/Try%20it%20on-Web-yellow?style=for-the-badge&logo=google-chrome&logoColor=white)](https://app.flchart.dev) [//]: # ([![Download for macOS](https://img.shields.io/badge/Download-for%20macOS-darkblue?style=for-the-badge&logo=apple&logoColor=white)](https://apps.apple.com/app/your-macos-app-id)) [//]: # ([![Download for Linux](https://img.shields.io/badge/Download-for%20Linux-orange?style=for-the-badge&logo=linux&logoColor=white)](https://your-linux-distribution-link.com)) [//]: # ([![Download for Windows](https://img.shields.io/badge/Download-for%20Windows-blue?style=for-the-badge&logo=windows&logoColor=white)](https://your-windows-app-link.com)) ### Donation Your donation motivates me to work more on the fl_chart and resolve more issues. There are multiple ways to donate to me: 1. You can be my sponsor on [GitHub](https://github.com/sponsors/imaNNeo) (This is the most reliable way to donate to me) 2. You can buy me a coffee! 3. Or if you are a fan of crypto, you can donate me Bitcoins here: `1L7ghKdcmgydmUJAnmYmMaiVjT1LoP4a45` ### Contributing ##### :beer: Pull requests are welcome! Remember that open-source projects thrive on contributions. Every improvement—no matter how small—makes a meaningful difference, even if it’s just a single line. Our documentation may contain grammatical issues, and if you’re fluent in English, your help in correcting them would be greatly appreciated. Check out [CONTRIBUTING.md](https://github.com/imaNNeo/fl_chart/blob/main/CONTRIBUTING.md), which contains a guide for those who want to contribute to the FL Chart. Reporting bugs and issues is also a contribution, yes, it is. #### Below are the people who have contributed to the FL Chart. We hope we have your picture here soon. [![](https://opencollective.com/fl_chart/contributors.svg?width=890&button=false)](https://github.com/imaNNeo/fl_chart/graphs/contributors) ================================================ FILE: SOURCES.md ================================================ ### Sources to learn more about the fl_chart: All sources are sorted by date. Did you find any new article or source? please contribute to have them all here. #### Blog post: * [Design Stunning Charts with fl_charts in Flutter](https://www.atatus.com/blog/design-stunning-charts-with-fl-charts-in-flutter/) * [Build beautiful charts in Flutter with FL Chart](https://blog.logrocket.com/build-beautiful-charts-flutter-fl-chart) * [Flutter4Fun UI Challenge 7](https://flutter4fun.com/ui-challenge-7/) * [Stock charts](https://dev.to/kamilpowalowski/stock-charts-with-flchart-library-1gd2) #### Video: * [how to create line chart in flutter | fl_chart](https://www.youtube.com/watch?v=Iv3F2HO5Jvc) * [line chart in flutter - flutter tutorial](https://www.youtube.com/watch?v=xHzDAewbSGY) * [Portfolio Dashboard Flutter UI Desktop & Web](https://www.youtube.com/watch?v=H9vXUine7Zo) * [Flutter UI | Stocks App UI Design - Day 55](https://www.youtube.com/watch?v=oILraFu8OE8) * [Implementing Chart in Flutter - Pair Programming with Fl_Chart Author](https://www.youtube.com/watch?v=msMxuUERtg8) * [how to create line chart in flutter | fl_chart](https://www.youtube.com/watch?v=Iv3F2HO5Jvc) * [Responsive Admin Dashboard or Panel using Flutter - Flutter Web UI - Part 1](https://www.youtube.com/watch?v=MRiZpwdy1CM) * [Admin Panel Dashboard - Flutter Responsive UI Design](https://www.youtube.com/watch?v=n7O3pXfENPU) * [How to build Flutter UI - 3 Steps](https://www.youtube.com/watch?v=I0NBtFS_ibc) * [Flutter Web - Dashboard Website Template (Responsive)](https://www.youtube.com/watch?v=3SMdJE_dSxU) * [How to create charts in Flutter](https://www.youtube.com/watch?v=JBJ6o4blgPA) * [Flutter Charts 📊📈](https://www.youtube.com/watch?v=ibkcwCv9Lyw) * [Flutter Library for Customizable](https://www.youtube.com/watch?v=1pjAItIDNz8) * [Pie Chart - FLChart](https://www.youtube.com/watch?v=rZx_isqXrhg&t=77s) * [Flutter Tutorial - Bar Chart](https://www.youtube.com/watch?v=7wUmzYOPQ8w) * [wallet-app-ui-piechart](https://www.youtube.com/watch?v=M4w-dighmMU) * [Flutter UI Tutorial - Fitness App](https://www.youtube.com/watch?v=hTg4DDl8Ixo) * [Gradient Chart](https://www.youtube.com/watch?v=OR2DMRnEXkA) * [Flutter charts tutorial for beginners](https://www.youtube.com/watch?v=nCmihMrWS38) * [The easy way with fl-Chart](https://www.youtube.com/watch?v=R_vpnW5QZEw) * [Get the data form COVID-19 API](https://www.youtube.com/watch?v=QXMWzbdGDkA) * [Flutter COVID-19 Dashboard UI](https://www.youtube.com/watch?v=krU-ASLb8lM) * [Flutter UI](https://www.youtube.com/watch?v=axWBN1aotQk) * [Flutter](https://www.youtube.com/watch?v=rwHFslLo6ho) * [Setup Pie Charts](https://www.youtube.com/watch?v=zRZiJdbp3_E) ================================================ FILE: _config.yml ================================================ theme: jekyll-theme-architect ================================================ FILE: analysis_options.yaml ================================================ include: package:very_good_analysis/analysis_options.yaml analyzer: exclude: - "**.mocks.dart" linter: rules: always_put_required_named_parameters_first: false avoid_bool_literals_in_conditional_expressions: false avoid_positional_boolean_parameters: false comment_references: false library_private_types_in_public_api: false lines_longer_than_80_chars: false no_default_cases: false parameter_assignments: false prefer_asserts_with_message: false prefer_constructors_over_static_methods: false public_member_api_docs: false use_if_null_to_convert_nulls_to_bools: false use_setters_to_change_properties: false avoid_catches_without_on_clauses: false ================================================ FILE: example/.gitignore ================================================ # Miscellaneous .flutter-plugins *.class *.log *.pyc *.swp .DS_Store .atom/ .build/ .buildlog/ .history .svn/ .swiftpm/ migrate_working_dir/ # 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 **/doc/api/ **/ios/Flutter/.last_build_id .dart_tool/ .flutter-plugins-dependencies .pub-cache/ .pub/ /build/ /coverage/ # Symbolication related app.*.symbols # Obfuscation related app.*.map.json # Android Studio will place build artifacts here /android/app/debug /android/app/profile /android/app/release ================================================ FILE: example/.metadata ================================================ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # # This file should be version controlled and should not be manually edited. version: revision: "20f82749394e68bcfbbeee96bad384abaae09c13" channel: "stable" project_type: app # Tracks metadata for the flutter migrate command migration: platforms: - platform: root create_revision: 20f82749394e68bcfbbeee96bad384abaae09c13 base_revision: 20f82749394e68bcfbbeee96bad384abaae09c13 - platform: ios create_revision: 20f82749394e68bcfbbeee96bad384abaae09c13 base_revision: 20f82749394e68bcfbbeee96bad384abaae09c13 # User provided section # List of Local paths (relative to this file) that should be # ignored by the migrate tool. # # Files that are not part of the templates will be ignored by default. unmanaged_files: - 'lib/main.dart' - 'ios/Runner.xcodeproj/project.pbxproj' ================================================ FILE: example/README.md ================================================ This is the FL Chart App. Check it out live here: [app.flchart.dev](https://app.flchart.dev) ================================================ FILE: example/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.dev/lints. # # 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: example/android/.gitignore ================================================ gradle-wrapper.jar /.gradle /captures/ /gradlew /gradlew.bat /local.properties GeneratedPluginRegistrant.java .cxx/ # Remember to never publicly share your keystore. # See https://flutter.dev/to/reference-keystore key.properties **/*.keystore **/*.jks ================================================ FILE: example/android/app/build.gradle.kts ================================================ import java.util.Properties import java.io.FileInputStream plugins { id("com.android.application") id("kotlin-android") // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. id("dev.flutter.flutter-gradle-plugin") } val keystoreProperties = Properties() val keystorePropertiesFile = rootProject.file("key.properties") if (keystorePropertiesFile.exists()) { keystoreProperties.load(FileInputStream(keystorePropertiesFile)) } android { namespace = "dev.flchart.app" compileSdk = flutter.compileSdkVersion ndkVersion = flutter.ndkVersion compileOptions { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 } kotlinOptions { jvmTarget = JavaVersion.VERSION_11.toString() } defaultConfig { applicationId = "dev.flchart.app" minSdk = flutter.minSdkVersion targetSdk = flutter.targetSdkVersion versionCode = flutter.versionCode versionName = flutter.versionName } signingConfigs { create("release") { keyAlias = keystoreProperties["keyAlias"] as String keyPassword = keystoreProperties["keyPassword"] as String storeFile = keystoreProperties["storeFile"]?.let { file(it) } storePassword = keystoreProperties["storePassword"] as String } } buildTypes { release { signingConfig = signingConfigs.getByName("release") } } } flutter { source = "../.." } ================================================ FILE: example/android/app/src/debug/AndroidManifest.xml ================================================ ================================================ FILE: example/android/app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: example/android/app/src/main/kotlin/dev/flchart/app/MainActivity.kt ================================================ package dev.flchart.app import io.flutter.embedding.android.FlutterActivity class MainActivity : FlutterActivity() ================================================ FILE: example/android/app/src/main/res/drawable/launch_background.xml ================================================ ================================================ FILE: example/android/app/src/main/res/drawable-v21/launch_background.xml ================================================ ================================================ FILE: example/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml ================================================ ================================================ FILE: example/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml ================================================ ================================================ FILE: example/android/app/src/main/res/values/ic_launcher_background.xml ================================================ #282E45 ================================================ FILE: example/android/app/src/main/res/values/styles.xml ================================================ ================================================ FILE: example/android/app/src/main/res/values-night/styles.xml ================================================ ================================================ FILE: example/android/app/src/profile/AndroidManifest.xml ================================================ ================================================ FILE: example/android/build.gradle.kts ================================================ allprojects { repositories { google() mavenCentral() } } val newBuildDir: Directory = rootProject.layout.buildDirectory .dir("../../build") .get() rootProject.layout.buildDirectory.value(newBuildDir) subprojects { val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) project.layout.buildDirectory.value(newSubprojectBuildDir) } subprojects { project.evaluationDependsOn(":app") } tasks.register("clean") { delete(rootProject.layout.buildDirectory) } ================================================ FILE: example/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-8.12-all.zip ================================================ FILE: example/android/gradle.properties ================================================ org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true ================================================ FILE: example/android/settings.gradle.kts ================================================ pluginManagement { val flutterSdkPath = run { val properties = java.util.Properties() file("local.properties").inputStream().use { properties.load(it) } val flutterSdkPath = properties.getProperty("flutter.sdk") require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } flutterSdkPath } includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") repositories { google() mavenCentral() gradlePluginPortal() } } plugins { id("dev.flutter.flutter-plugin-loader") version "1.0.0" id("com.android.application") version "8.9.1" apply false id("org.jetbrains.kotlin.android") version "2.1.0" apply false } include(":app") ================================================ FILE: example/assets/data/amsterdam_2024_weather.csv ================================================ name,datetime,tempmax,tempmin,temp,feelslikemax,feelslikemin,feelslike,dew,humidity,precip,precipprob,precipcover,preciptype,snow,snowdepth,windgust,windspeed,winddir,sealevelpressure,cloudcover,visibility,solarradiation,solarenergy,uvindex,severerisk,sunrise,sunset,moonphase,conditions,description,icon,stations "Amsterdam,Netherlands",2024-01-01,9.1,6.4,8,5.3,2.5,4.1,5.1,82.4,14.26,100,37.5,rain,0,0,53.9,40.2,225.9,1000.1,88.7,20.5,20.6,1.8,2,,2024-01-01T08:50:34,2024-01-01T16:37:06,0.68,"Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-01-02,12.2,6.6,10.2,12.2,2.7,9,8.8,91.4,17.935,100,66.67,rain,0,0,91,62.5,205.1,987.8,100,12.9,7.4,0.6,1,,2024-01-02T08:50:27,2024-01-02T16:38:10,0.71,"Rain, Overcast",Cloudy skies throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-01-03,10.5,8.1,9.6,10.5,4.5,7.5,7.7,88,9.238,100,62.5,rain,0,0,106.8,58.1,248.3,987.8,96.3,15.4,10.2,1,1,,2024-01-03T08:50:16,2024-01-03T16:39:18,0.74,"Rain, Overcast",Cloudy skies throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-01-04,8.5,6.7,7.4,7.1,4,5.6,6.6,94.6,8.205,100,62.5,rain,0,0,28.1,17.7,235.3,1000.4,99.6,22.1,14.3,1.3,1,,2024-01-04T08:50:01,2024-01-04T16:40:29,0.75,"Rain, Overcast",Cloudy skies throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-01-05,8.5,5.5,7.1,5.7,2.3,4.3,6.1,93.7,8.285,100,58.33,rain,0,0,45,29.9,118.1,996.5,99.9,21.5,5.2,0.6,0,,2024-01-05T08:49:44,2024-01-05T16:41:42,0.8,"Rain, Overcast",Cloudy skies throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-01-06,5,1.4,3.7,1.2,-3.6,-0.2,1.8,87.9,0.098,100,8.33,rain,0,0,35.2,21.8,11,1011.1,99.3,29.8,9.3,0.8,1,,2024-01-06T08:49:22,2024-01-06T16:42:57,0.84,"Rain, Overcast",Cloudy skies throughout the day with rain in the morning and afternoon.,rain,"06260099999,D3248,06249099999,C0449,06240099999,06269099999,06257099999" "Amsterdam,Netherlands",2024-01-07,1.3,-0.7,0.5,-2.9,-5.9,-4.9,-2.7,79.1,0.015,100,4.17,"rain,snow",0,0,48.8,31.7,52.5,1024.9,67.5,39.1,22,1.9,2,,2024-01-07T08:48:58,2024-01-07T16:44:15,0.87,"Snow, Rain, Partially cloudy",Partly cloudy throughout the day with morning rain or snow.,rain,"06260099999,D3248,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-01-08,0.1,-2,-0.8,-5.9,-9.1,-7.2,-5.4,71.7,0,0,0,,0,0,49.5,37.2,63,1032.8,57.5,44.3,22.9,1.8,2,,2024-01-08T08:48:30,2024-01-08T16:45:36,0.9,Partially cloudy,Partly cloudy throughout the day.,partly-cloudy-day,"06260099999,D3248,06249099999,C0449,06240099999,06269099999,06257099999" "Amsterdam,Netherlands",2024-01-09,-0.3,-4.1,-2.3,-6.6,-11.5,-9.1,-8.4,64,0,0,0,,0,0,46.1,30.7,64.6,1034,0,41.4,40,3.4,3,,2024-01-09T08:47:58,2024-01-09T16:46:59,0.93,Clear,Clear conditions throughout the day.,clear-day,"06260099999,D3248,06249099999,C0449,06240099999,06269099999,06257099999" "Amsterdam,Netherlands",2024-01-10,0.3,-3.6,-2,-5.4,-9.6,-7.7,-8.4,62.2,0,0,0,,0,0,33.9,24.2,60.6,1031.1,0.1,41.8,39,3.4,2,,2024-01-10T08:47:24,2024-01-10T16:48:24,0.97,Clear,Clear conditions throughout the day.,clear-day,"06260099999,D3248,06249099999,C0449,06240099999,06269099999,06257099999" "Amsterdam,Netherlands",2024-01-11,4.7,-5.3,-0.3,3.2,-8.7,-2.6,-2.4,86,0,0,0,,0,0,27.3,10.6,9.4,1034.4,59.7,14.8,13.4,1.1,1,,2024-01-11T08:46:46,2024-01-11T16:49:51,0,Partially cloudy,Partly cloudy throughout the day.,partly-cloudy-day,"06260099999,D3248,06249099999,C0449,06240099999,06269099999,06257099999" "Amsterdam,Netherlands",2024-01-12,5.6,2.3,4,3.9,1,2.4,2.6,90.9,0.01,100,4.17,rain,0,0,31.8,13.7,328.2,1033.1,100,18.5,20.2,1.7,2,,2024-01-12T08:46:05,2024-01-12T16:51:20,0.03,"Rain, Overcast",Cloudy skies throughout the day with late afternoon rain.,rain,"06260099999,D3248,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-01-13,5.9,3.2,4.6,2.1,-0.9,1,3.4,92.3,1.002,100,41.67,rain,0,0,38.6,26.7,265.8,1022.5,99.6,17,10,0.8,1,,2024-01-13T08:45:21,2024-01-13T16:52:51,0.07,"Rain, Overcast",Cloudy skies throughout the day with rain.,rain,"06260099999,D3248,06249099999,C0449,06240099999,06257099999" "Amsterdam,Netherlands",2024-01-14,5.7,2.7,4.3,2.1,-1.6,0.5,2.5,88,2.747,100,62.5,rain,0,0,42,27.2,275.1,1009.3,95.4,25.6,11.8,1,1,,2024-01-14T08:44:33,2024-01-14T16:54:24,0.1,"Rain, Overcast",Cloudy skies throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06249099999,C0449,06240099999,06269099999,06257099999" "Amsterdam,Netherlands",2024-01-15,3.9,1.4,2.5,0.2,-4.4,-2.1,-0.3,81.9,3.366,100,70.83,"rain,snow",0.1,0,60.6,34.4,301,1003.2,69.4,28.2,25,2.1,3,,2024-01-15T08:43:43,2024-01-15T16:55:59,0.14,"Snow, Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain or snow throughout the day.,snow,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999" "Amsterdam,Netherlands",2024-01-16,3,-0.2,1.3,-1.3,-5.6,-3,-1.8,80.5,2.814,100,41.67,"rain,snow",0.1,0.5,49.7,30.3,231,1006.7,88.4,34.2,45.8,3.9,3,,2024-01-16T08:42:50,2024-01-16T16:57:36,0.17,"Snow, Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain or snow throughout the day.,snow,"06260099999,D3248,06249099999,C0449,06240099999,06269099999,06257099999" "Amsterdam,Netherlands",2024-01-17,0.5,-2.1,-0.5,0.2,-7.3,-4,-2.8,84.5,0.129,100,8.33,"rain,snow",0,0.5,31.6,24.3,184.6,993.6,99.8,25.1,18.3,1.6,1,,2024-01-17T08:41:53,2024-01-17T16:59:14,0.21,"Snow, Rain, Overcast",Cloudy skies throughout the day with rain or snow clearing later.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999" "Amsterdam,Netherlands",2024-01-18,3.5,-3.4,-0.3,2.9,-6.3,-2.3,-2.5,85.7,0.337,100,8.33,"rain,snow",0,0,24.5,12.9,292.9,1001.5,40.9,34.2,22.9,2.1,2,,2024-01-18T08:40:54,2024-01-18T17:00:53,0.25,"Snow, Rain, Partially cloudy",Partly cloudy throughout the day with morning rain or snow.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999" "Amsterdam,Netherlands",2024-01-19,4.8,-1.3,2,0.9,-4.8,-2.1,-1.1,80.5,0.751,100,33.33,"rain,snow",0.2,0,38.5,23.9,245.2,1018.5,33.8,32.3,47.6,4,3,,2024-01-19T08:39:52,2024-01-19T17:02:34,0.28,"Snow, Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain or snow throughout the day.,snow,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-01-20,2.2,-0.2,0.9,-3.1,-6.1,-4.5,-2.1,80.8,0,0,0,,0,0,38.9,29.8,201,1025.9,89.7,21.2,30.4,2.6,1,,2024-01-20T08:38:47,2024-01-20T17:04:17,0.31,Partially cloudy,Partly cloudy throughout the day.,partly-cloudy-day,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-01-21,7.4,-0.1,3.4,2.2,-5.7,-2,-0.6,76,0.201,100,4.17,rain,0,0,66.7,45.1,189.5,1016.4,99.6,34.2,9.9,0.7,0,,2024-01-21T08:37:40,2024-01-21T17:06:00,0.35,"Rain, Overcast",Cloudy skies throughout the day with late afternoon rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-01-22,11.5,7.6,9.5,11.5,3.2,6.8,6.6,82.3,1.635,100,50,rain,0,0,83.1,55.8,228,1005.8,64.2,16.6,38.5,3.3,3,,2024-01-22T08:36:29,2024-01-22T17:07:45,0.38,"Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-01-23,11.5,6.2,8.1,11.5,2.1,4.4,5.3,83,4.604,100,33.33,rain,0,0,82,57.5,230.6,1018.5,71.9,14.7,26.8,2.3,2,,2024-01-23T08:35:16,2024-01-23T17:09:31,0.42,"Rain, Partially cloudy",Partly cloudy throughout the day with rain.,rain,"06260099999,C0449,06257099999,EHAM,D3248,EHRD,06348099999,06249099999,06240099999,EHLE,EHKD,06269099999,06344099999" "Amsterdam,Netherlands",2024-01-24,12.2,8.6,10,12.2,4.9,8,6.6,79.2,1.409,100,16.67,rain,0,0,94.2,61.6,247.1,1018.3,78.8,14.1,31.8,2.7,2,,2024-01-24T08:34:01,2024-01-24T17:11:17,0.45,"Rain, Partially cloudy",Partly cloudy throughout the day with rain clearing later.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-01-25,9.6,3.4,6.9,6.8,0.9,4.2,5.7,92.3,1.425,100,33.33,rain,0,0,38.4,26.8,222.3,1026.6,80.5,8.7,20.9,1.8,2,,2024-01-25T08:32:43,2024-01-25T17:13:05,0.5,"Rain, Partially cloudy",Partly cloudy throughout the day with rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-01-26,11.4,3.9,8.5,11.4,1.1,5.7,5.8,84,4.101,100,29.17,rain,0,0,68.2,48.4,256.5,1024.2,59.2,18.1,44.4,3.8,4,,2024-01-26T08:31:22,2024-01-26T17:14:54,0.52,"Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-01-27,7.9,2,4.2,5.2,-1.7,0.9,2.5,89.1,0.268,100,4.17,rain,0,0,28.1,20.2,207.5,1034.4,56.5,16.7,43.9,3.9,2,,2024-01-27T08:30:00,2024-01-27T17:16:43,0.56,"Rain, Partially cloudy",Partly cloudy throughout the day with morning rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-01-28,8.6,0.8,4.2,5.6,-3.3,0.9,0,74.9,0,0,0,,0,0,30.7,20.7,150.7,1027.6,88.2,33,62,5.3,3,,2024-01-28T08:28:34,2024-01-28T17:18:33,0.59,Partially cloudy,Partly cloudy throughout the day.,partly-cloudy-day,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-01-29,10.5,3,6.4,10.5,-0.2,3.9,4.1,85.4,0,0,0,,0,0,27.8,16.6,171.7,1025.1,99.7,28.6,39.1,3.4,2,,2024-01-29T08:27:07,2024-01-29T17:20:24,0.62,Overcast,Cloudy skies throughout the day.,cloudy,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-01-30,10.5,4.7,7.8,10.5,2.8,5,6.7,92.3,0.644,100,20.83,rain,0,0,58.9,34.3,227.3,1026.7,99.9,17.4,13.5,1.2,1,,2024-01-30T08:25:37,2024-01-30T17:22:15,0.65,"Rain, Overcast",Cloudy skies throughout the day with rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-01-31,8.3,5.1,6.5,5.3,2.5,3.4,3.4,81,0.075,100,8.33,rain,0,0,56.2,37.5,221.6,1030.4,100,34.6,12.7,1.1,1,,2024-01-31T08:24:05,2024-01-31T17:24:07,0.69,"Rain, Overcast",Cloudy skies throughout the day with rain clearing later.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-02-01,9.1,4.4,6.6,5.3,1.3,3.3,4.6,87.4,4.35,100,20.83,rain,0,0,59.4,37,272.7,1029.2,64.5,12.1,56.7,4.9,4,,2024-02-01T08:22:31,2024-02-01T17:25:59,0.72,"Rain, Partially cloudy",Partly cloudy throughout the day with rain clearing later.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06257099999" "Amsterdam,Netherlands",2024-02-02,9.1,5.9,7.6,5.5,2.4,3.8,6.2,90.6,0,0,0,,0,0,49.7,34.1,237.3,1026.9,99.6,12.7,25,2.1,2,,2024-02-02T08:20:55,2024-02-02T17:27:52,0.75,Overcast,Cloudy skies throughout the day.,cloudy,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06257099999" "Amsterdam,Netherlands",2024-02-03,10.9,9.1,10,10.9,6,7.7,8.4,89.9,0,0,0,,0,0,54.2,34.1,249.4,1024.4,99.5,23.2,37,3.1,3,,2024-02-03T08:19:17,2024-02-03T17:29:45,0.75,Overcast,Cloudy skies throughout the day.,cloudy,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-02-04,10.5,8.3,9.7,10.5,5.4,8,8.3,91.1,2.126,100,20.83,rain,0,0,61,38.5,252.3,1020.7,99.9,18.6,13.2,1.1,1,,2024-02-04T08:17:37,2024-02-04T17:31:38,0.82,"Rain, Overcast",Cloudy skies throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-02-05,10.4,8.7,9.4,10.4,4.6,5.6,7.1,85.8,0.051,100,12.5,rain,0,0,72.5,50.4,240.1,1017.4,99.8,16.7,22.2,2,1,,2024-02-05T08:15:56,2024-02-05T17:33:32,0.85,"Rain, Overcast",Cloudy skies throughout the day with rain.,rain,"06260099999,06348099999,06249099999,C0449,06240099999,06257099999" "Amsterdam,Netherlands",2024-02-06,12,7,10.5,12,3.5,10,7.9,84,4.573,100,25,rain,0,0,82.3,57.4,237.3,1007.6,100,14.3,24.6,2.1,2,,2024-02-06T08:14:12,2024-02-06T17:35:25,0.88,"Rain, Overcast",Cloudy skies throughout the day with rain.,rain,"D3248,06260099999,06348099999,06249099999,06240099999,C0449,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-02-07,6.7,1.4,4.8,6.7,1.4,3.2,1.8,82,3.834,100,29.17,rain,0,0,27.8,15.6,345.1,1005.3,100,22.2,46.1,4,3,,2024-02-07T08:12:27,2024-02-07T17:37:19,0.92,"Rain, Overcast",Cloudy skies throughout the day with rain clearing later.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06257099999" "Amsterdam,Netherlands",2024-02-08,4,0.5,2.4,1.7,-2.7,-0.3,1.6,94.4,8.38,100,45.83,rain,0.4,0,42.8,24.9,92.6,997.7,99.6,11.9,13.1,1.2,1,,2024-02-08T08:10:39,2024-02-08T17:39:13,0.95,"Rain, Overcast",Cloudy skies throughout the day with rain or snow.,snow,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06257099999" "Amsterdam,Netherlands",2024-02-09,12.2,4.9,10.3,12.2,2,9.1,8.7,90.6,12.645,100,54.17,rain,0,0,44.9,27.6,176.5,983.4,99.8,25.9,20.9,1.7,1,,2024-02-09T08:08:51,2024-02-09T17:41:07,0,"Rain, Overcast",Cloudy skies throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-02-10,11.9,9.4,10.4,11.9,7.3,9.6,8.9,90.6,0.454,100,12.5,rain,0,0,24.3,17.2,137.6,985.9,99.4,31.1,37.3,3.3,2,,2024-02-10T08:07:00,2024-02-10T17:43:01,0.02,"Rain, Overcast",Cloudy skies throughout the day with rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-02-11,10,7.9,8.9,8.7,5.2,6.9,8.1,94.4,3.279,100,54.17,rain,0,0,31.9,20.8,211.1,989.7,99.9,10.5,22.8,2,2,,2024-02-11T08:05:08,2024-02-11T17:44:55,0.05,"Rain, Overcast",Cloudy skies throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-02-12,8.9,3.7,7,7,0.5,4.2,5.4,90.2,0.591,100,20.83,rain,0,0,38,26.6,246,1003.4,76.5,21.8,57.4,4.9,4,,2024-02-12T08:03:15,2024-02-12T17:46:49,0.09,"Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-02-13,9.1,3.8,6.6,6,0.5,3.1,4.6,87,0.083,100,16.67,rain,0,0,39.1,27.7,200.4,1014.3,95.5,24.5,41.9,3.5,3,,2024-02-13T08:01:20,2024-02-13T17:48:43,0.12,"Rain, Overcast",Cloudy skies throughout the day with late afternoon rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-02-14,11.7,8,10.6,11.7,4.2,9.9,10.1,96.6,11.723,100,70.83,rain,0,0,49.8,34.4,224.9,1013.4,100,8.9,13.6,1.2,1,,2024-02-14T07:59:23,2024-02-14T17:50:36,0.15,"Rain, Overcast",Cloudy skies throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-02-15,15.6,11.2,12.8,15.6,11.2,12.8,11.3,90.8,6.248,100,37.5,rain,0,0,31.9,23.2,180.4,1012.6,99.2,23.2,31.5,2.8,2,,2024-02-15T07:57:26,2024-02-15T17:52:30,0.19,"Rain, Overcast",Cloudy skies throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-02-16,12,9.2,10.7,12,7.2,10.3,9,89.9,2.067,100,20.83,rain,0,0,33,23.6,228.8,1012.9,97.4,28.2,21,1.8,1,,2024-02-16T07:55:26,2024-02-16T17:54:23,0.25,"Rain, Overcast",Cloudy skies throughout the day with rain in the morning and afternoon.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-02-17,11.4,7.9,9.8,11.4,6.2,9.1,8.4,91.4,0.415,100,12.5,rain,0,0,23.8,16.8,228.3,1029.2,99.2,20.9,35.3,3,2,,2024-02-17T07:53:26,2024-02-17T17:56:17,0.26,"Rain, Overcast",Cloudy skies throughout the day with rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-02-18,10.4,8.4,9.4,10.4,4.6,6.9,8.6,94.5,16.3,100,79.17,rain,0,0,56.4,35,225.4,1023.8,100,12.1,10.5,0.8,1,,2024-02-18T07:51:24,2024-02-18T17:58:10,0.29,"Rain, Overcast",Cloudy skies throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-02-19,10.4,5.7,8.7,10.4,3.4,6.2,7.4,92,4.968,100,25,rain,0,0,44.6,33.1,269.7,1026,95,14.2,23.8,2,1,,2024-02-19T07:49:22,2024-02-19T18:00:03,0.33,"Rain, Overcast",Cloudy skies throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-02-20,10.4,5.1,8.3,10.4,2.6,5.6,6.7,90,0.207,100,4.17,rain,0,0,51.2,33.9,231.7,1025.6,99.7,13.7,43.3,3.7,2,,2024-02-20T07:47:18,2024-02-20T18:01:55,0.36,"Rain, Overcast",Cloudy skies throughout the day with morning rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-02-21,10.1,8.7,9.3,10.1,4.8,5.9,7.7,90,5.415,100,37.5,rain,0,0,49.7,37.4,203.7,1011.3,100,15.3,23,1.9,2,,2024-02-21T07:45:13,2024-02-21T18:03:48,0.39,"Rain, Overcast",Cloudy skies throughout the day with rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-02-22,12.5,5.2,9.9,12.5,-0.5,8.1,8.9,93.8,6.403,100,58.33,rain,0,0,67.7,49.2,208.2,986.4,98.1,13.7,27,2.3,1,,2024-02-22T07:43:07,2024-02-22T18:05:40,0.43,"Rain, Overcast",Cloudy skies throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06257099999" "Amsterdam,Netherlands",2024-02-23,8.2,4.9,6.2,4.6,-0.8,1.8,3.4,82.3,6.364,100,66.67,rain,0,0,69.8,41.3,209.9,988.7,94.9,32.8,34.5,3,3,,2024-02-23T07:40:59,2024-02-23T18:07:32,0.46,"Rain, Overcast",Cloudy skies throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-02-24,6.2,2.7,4.7,3.3,-0.8,1.4,3.2,89.8,4.341,100,29.17,rain,0,0,38.4,27.8,184.4,996.8,96.7,29.3,34.1,3,1,,2024-02-24T07:38:51,2024-02-24T18:09:23,0.5,"Rain, Overcast",Cloudy skies throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-02-25,8.9,4.1,6.2,8.9,1.4,3.6,4.2,87.5,4.063,100,29.17,rain,0,0,36.7,17.6,164.3,999.5,94.9,26.3,44.7,4,3,,2024-02-25T07:36:42,2024-02-25T18:11:15,0.53,"Rain, Overcast",Cloudy skies throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-02-26,6.5,4.6,5.5,2.5,-0.1,1,3,83.9,0.122,100,29.17,rain,0,0,56.3,34.6,41,1006.5,91.3,36.3,37.9,3.3,2,,2024-02-26T07:34:32,2024-02-26T18:13:06,0.57,"Rain, Overcast",Cloudy skies throughout the day with rain in the morning and afternoon.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-02-27,8.1,1.7,4.7,7,-0.8,2.3,1.8,82.1,0.05,100,4.17,rain,0,0,44.8,20.9,22.1,1018.7,55.9,33.1,85.5,7.3,5,,2024-02-27T07:32:22,2024-02-27T18:14:56,0.6,"Rain, Partially cloudy",Partly cloudy throughout the day with morning rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-02-28,9.4,2.8,6.9,6.6,0,4.1,5.4,90.2,0,0,0,,0,0,37.1,23.9,190.2,1019.2,91.5,15.6,29.1,2.5,2,,2024-02-28T07:30:10,2024-02-28T18:16:47,0.63,Overcast,Cloudy skies throughout the day.,cloudy,"06260099999,C0449,06257099999,EHAM,D3248,EHRD,06348099999,06249099999,06240099999,EHLE,EHKD,06269099999,06344099999" "Amsterdam,Netherlands",2024-02-29,9.1,7,8.3,5.9,3.4,4.9,7.6,95.7,4.966,100,79.17,rain,0,0,35.8,25.3,174.3,1008,100,10,15.8,1.5,1,,2024-02-29T07:27:58,2024-02-29T18:18:37,0.67,"Rain, Overcast",Cloudy skies throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-03-01,9.2,5.1,7.6,6.5,2,4.2,5.4,86,2.291,100,33.33,rain,0,0,57.5,37.6,171.4,999.9,80.7,28.5,55.7,4.8,3,,2024-03-01T07:25:45,2024-03-01T18:20:27,0.7,"Rain, Partially cloudy",Partly cloudy throughout the day with rain.,rain,"06260099999,C0449,06257099999,EHAM,D3248,EHRD,06348099999,06249099999,06240099999,EHLE,EHKD,06269099999,06344099999" "Amsterdam,Netherlands",2024-03-02,11.5,5.4,8.9,11.5,1.9,6.8,4.2,73.6,0.755,100,20.83,rain,0,0,49.4,37.1,150.7,998.6,72.6,39.8,98.6,8.4,5,,2024-03-02T07:23:31,2024-03-02T18:22:17,0.73,"Rain, Partially cloudy",Partly cloudy throughout the day with rain clearing later.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-03-03,14.3,7.8,10.6,14.3,5,9.6,6.6,77,0,0,0,,0,0,36.1,21.5,143.2,1001.1,99.5,32,76.7,6.6,4,,2024-03-03T07:21:16,2024-03-03T18:24:06,0.75,Overcast,Cloudy skies throughout the day.,cloudy,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-03-04,10.4,4.2,7.5,10.4,1.9,5.6,4.2,81,0,0,0,,0,0,33.7,23.8,306.9,1011.1,98.3,35.7,88.3,7.7,5,,2024-03-04T07:19:01,2024-03-04T18:25:55,0.8,Overcast,Cloudy skies throughout the day.,cloudy,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-03-05,8.2,2.9,6,8.1,1,5.1,5.2,94.6,0.466,100,33.33,rain,0,0,27.8,7.4,77.8,1014.2,91.2,8.8,21.5,1.9,1,,2024-03-05T07:16:45,2024-03-05T18:27:44,0.83,"Rain, Overcast",Cloudy skies throughout the day with rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-03-06,11.3,5.4,7.5,11.3,3.4,6.4,5.4,87.9,0.426,100,8.33,rain,0,0,23.4,13.9,108.3,1022.1,53.8,11.7,96.5,8.5,5,,2024-03-06T07:14:29,2024-03-06T18:29:32,0.87,"Rain, Partially cloudy",Partly cloudy throughout the day with rain clearing later.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-03-07,8.9,3.2,5.6,5.5,-0.3,2.3,2.9,83.5,0,0,0,,0,0,39.5,27.8,75.9,1023.6,27,13.2,117.4,10.1,5,,2024-03-07T07:12:12,2024-03-07T18:31:20,0.9,Partially cloudy,Partly cloudy throughout the day.,partly-cloudy-day,"06260099999,C0449,06257099999,EHAM,D3248,EHRD,06348099999,06249099999,06240099999,EHLE,EHKD,06269099999,06344099999" "Amsterdam,Netherlands",2024-03-08,9.7,1.8,5.5,6,-3.2,1.3,0.3,70.1,0,0,0,,0,0,47.2,33.1,90.1,1012.6,2.6,17.7,118.2,10.3,5,,2024-03-08T07:09:55,2024-03-08T18:33:08,0.94,Clear,Clear conditions throughout the day.,clear-day,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-03-09,12.5,3.6,7.9,12.5,-0.4,5.6,2.7,70.3,0,0,0,,0,0,36.3,26,97.8,1001.4,78.8,18.6,89.4,7.7,5,,2024-03-09T07:07:37,2024-03-09T18:34:56,0.97,Partially cloudy,Partly cloudy throughout the day.,partly-cloudy-day,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-03-10,11.3,6.2,8.7,11.3,2.5,6.5,4.2,73.5,0,0,0,,0,0,38.8,27.3,71.8,997.1,99.7,18.2,59.8,5.3,3,,2024-03-10T07:05:19,2024-03-10T18:36:44,0,Overcast,Cloudy skies throughout the day.,cloudy,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-03-11,8.3,6.7,7.1,7.2,4.1,5.5,6.1,93.3,3.569,100,58.33,rain,0,0,28.9,13.9,18.7,1002.1,99,5.9,18.2,1.6,1,,2024-03-11T07:03:01,2024-03-11T18:38:31,0.04,"Rain, Overcast",Cloudy skies throughout the day with rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-03-12,10.7,7,8.4,10.7,4,6.3,6.8,90.4,1.414,100,33.33,rain,0,0,31.6,21.5,215.3,1011.9,97.7,6.9,41,3.5,2,,2024-03-12T07:00:42,2024-03-12T18:40:18,0.07,"Rain, Overcast",Cloudy skies throughout the day with rain.,rain,"06260099999,06356099999,C0449,06257099999,EHAM,D3248,06348099999,06249099999,06240099999,06330099999,EHLE,06269099999,06344099999" "Amsterdam,Netherlands",2024-03-13,12.2,8.8,10.9,12.2,6.6,10.7,9.3,90.2,1.099,100,37.5,rain,0,0,51.2,32.5,225.8,1013.3,99.6,21.2,27,2.2,1,,2024-03-13T06:58:23,2024-03-13T18:42:04,0.1,"Rain, Overcast",Cloudy skies throughout the day with rain clearing later.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-03-14,16.5,8.3,12.5,16.5,6,12,8.6,78.1,0.005,100,4.17,rain,0,0,41.3,27.7,194.2,1010.4,94.5,32.4,116.8,10.2,5,,2024-03-14T06:56:03,2024-03-14T18:43:51,0.14,"Rain, Overcast",Cloudy skies throughout the day with morning rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-03-15,14.2,10.5,12.5,14.2,10.5,12.5,9.8,83.9,0.701,100,25,rain,0,0,57.7,41.3,222.4,1006.2,90.3,21.7,56.8,4.8,2,,2024-03-15T06:53:43,2024-03-15T18:45:37,0.17,"Rain, Overcast",Cloudy skies throughout the day with rain clearing later.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-03-16,11,3.2,7.8,11,1.4,5.8,4.4,79.7,0.87,100,25,rain,0,0,41.1,27.2,296.5,1018.3,99.1,29.7,55.3,4.7,2,,2024-03-16T06:51:23,2024-03-16T18:47:23,0.2,"Rain, Overcast",Cloudy skies throughout the day with rain clearing later.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-03-17,12.8,4.6,9.3,12.8,2.9,8.1,6.6,83.4,2.806,100,25,rain,0,0,35.1,24.2,142.9,1019.3,99.7,26.2,64.8,5.4,3,,2024-03-17T06:49:03,2024-03-17T18:49:09,0.25,"Rain, Overcast",Cloudy skies throughout the day with late afternoon rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-03-18,13.3,7.2,10.7,13.3,6,10.4,8.4,86.6,0.944,100,12.5,rain,0,0,28.1,19.2,236.5,1015.6,87.9,15.5,129.7,11.2,6,,2024-03-18T06:46:43,2024-03-18T18:50:55,0.27,"Rain, Partially cloudy",Partly cloudy throughout the day with rain clearing later.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-03-19,15.2,7.9,11.4,15.2,6,11,8.2,81.6,0,0,0,,0,0,38.1,27.7,211.3,1017.5,94.3,28.8,91.5,8,6,,2024-03-19T06:44:22,2024-03-19T18:52:41,0.3,Overcast,Cloudy skies throughout the day.,cloudy,"06260099999,C0449,06257099999,EHAM,D3248,EHRD,06348099999,06249099999,06240099999,EHLE,EHKD,06269099999,06344099999" "Amsterdam,Netherlands",2024-03-20,15,8,11.2,15,6.8,10.9,8.8,85.7,0.021,100,8.33,rain,0,0,17.8,9.9,236.3,1019,99.7,21.7,76.6,6.7,3,,2024-03-20T06:42:01,2024-03-20T18:54:26,0.34,"Rain, Overcast",Cloudy skies throughout the day with rain in the morning and afternoon.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-03-21,10.5,7.5,8.9,10.5,4.5,7.1,7.1,88.3,0.014,100,4.17,rain,0,0,29.2,20.9,267.2,1023.4,97.9,24.7,48,4,2,,2024-03-21T06:39:41,2024-03-21T18:56:12,0.37,"Rain, Overcast",Cloudy skies throughout the day with morning rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-03-22,10.8,7.6,9.7,10.8,6.2,8.7,7.9,88.8,0.634,100,37.5,rain,0,0,41.6,27.1,254.5,1016.2,100,10.5,27.6,2.4,1,,2024-03-22T06:37:20,2024-03-22T18:57:57,0.41,"Rain, Overcast",Cloudy skies throughout the day with rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-03-23,8.4,5,6.9,5.9,2.1,3.2,3.1,77.2,2.822,100,54.17,rain,0,0,56.3,40.9,259,1008.1,64,26.7,65.5,5.7,5,,2024-03-23T06:34:59,2024-03-23T18:59:42,0.44,"Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-03-24,8.1,5.3,6.9,3.9,1.1,2.6,4.5,84.8,7.443,100,91.67,rain,0,0,63,41.3,281.8,1004,93.2,15.7,55.7,4.9,3,,2024-03-24T06:32:38,2024-03-24T19:01:27,0.47,"Rain, Overcast",Cloudy skies throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-03-25,11.4,2.8,7.4,11.4,-0.1,5.8,2.5,73.1,0.855,100,8.33,rain,0,0,28.2,20.2,150.4,1004.7,69.7,26.1,132,11.4,6,,2024-03-25T06:30:18,2024-03-25T19:03:12,0.5,"Rain, Partially cloudy",Partly cloudy throughout the day with rain clearing later.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-03-26,11,4.8,8.3,11,1.1,6.2,3.2,70.2,0.1,100,4.17,rain,0,0,36,21.6,119.1,991.7,96.9,29.6,96.5,8.3,3,,2024-03-26T06:27:57,2024-03-26T19:04:56,0.54,"Rain, Overcast",Cloudy skies throughout the day with late afternoon rain.,rain,"06260099999,06356099999,C0449,06257099999,EHAM,D3248,EHRD,06348099999,06249099999,06240099999,EHLE,EHKD,06275099999,06269099999,06344099999" "Amsterdam,Netherlands",2024-03-27,12.3,7.1,10,12.3,4.6,9,6,76.7,0.014,100,8.33,rain,0,0,37.4,23.2,175.6,985.5,86.8,24.3,84.4,7.2,4,,2024-03-27T06:25:36,2024-03-27T19:06:41,0.57,"Rain, Partially cloudy",Partly cloudy throughout the day with rain in the morning and afternoon.,rain,"06356099999,06260099999,C0449,EHAM,06257099999,D3248,EHRD,06348099999,06249099999,06240099999,EHLE,EHKD,06269099999,06344099999" "Amsterdam,Netherlands",2024-03-28,11.8,7,8.8,11.8,3.4,6.8,4.4,74.5,1.6,100,33.33,rain,0,0,63,40.9,178.9,984.6,76.3,10,84.5,7.3,7,,2024-03-28T06:23:16,2024-03-28T19:08:26,0.61,"Rain, Partially cloudy",Partly cloudy throughout the day with rain.,rain,"D3248,EHRD,C0449,EHLE,EHKD,EHAM" "Amsterdam,Netherlands",2024-03-29,13.9,8,10.7,13.9,4.5,9.1,5.5,70.6,2.4,100,41.67,rain,0,0,51.8,29.8,186,991.4,59.5,10,117.6,10,4,,2024-03-29T06:20:56,2024-03-29T19:10:10,0.64,"Rain, Partially cloudy",Partly cloudy throughout the day with rain.,rain,"D3248,EHRD,C0449,EHLE,EHKD,EHAM" "Amsterdam,Netherlands",2024-03-30,11.8,5,9,11.8,3.3,7.8,7.9,93.3,4.8,100,50,rain,0,0,25.6,22.6,200.1,996.3,78,8.8,27.3,2.3,1,,2024-03-30T06:18:35,2024-03-30T19:11:55,0.68,"Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain throughout the day.,rain,"D3248,EHRD,C0449,EHLE,EHKD,EHAM" "Amsterdam,Netherlands",2024-03-31,14.9,3.8,9.3,14.9,1.4,8,7.9,91.9,5.8,100,54.17,rain,0,0,38.5,21.9,82.6,996.8,69.4,8.5,69.3,5.7,5,,2024-03-31T07:16:16,2024-03-31T20:13:39,0.71,"Rain, Partially cloudy",Partly cloudy throughout the day with rain.,rain,"D3248,EHRD,C0449,EHLE,EHKD,EHAM" "Amsterdam,Netherlands",2024-04-01,12.8,7.1,10.1,12.8,5.6,9.3,7.7,86.1,0.1,100,4.17,rain,0,0,40.3,30.6,218.9,994.9,50.9,14.7,64,5.8,3,,2024-04-01T07:13:56,2024-04-01T20:15:24,0.75,"Rain, Partially cloudy",Partly cloudy throughout the day with afternoon rain.,rain,"D3248,06260099999,EHRD,06348099999,06249099999,C0449,06240099999,EHLE,EHKD,EHAM,06257099999" "Amsterdam,Netherlands",2024-04-02,12,7.5,9.7,12,4.6,8.3,7.5,86.3,1.6,100,33.33,rain,0,0,55.4,29.3,218.6,1003.4,48.4,18.4,90.5,7.8,5,,2024-04-02T07:11:37,2024-04-02T20:17:08,0.75,"Rain, Partially cloudy",Partly cloudy throughout the day with rain in the morning and afternoon.,rain,"06260099999,06356099999,C0449,06257099999,EHAM,D3248,EHRD,06348099999,06249099999,06240099999,EHLE,EHKD,06269099999,06344099999" "Amsterdam,Netherlands",2024-04-03,13.6,9.3,10.9,13.6,7.3,10.1,9.2,89.2,3.521,100,62.5,rain,0,0,62.8,37.6,202.4,1003.4,99.1,21.8,53.3,4.6,3,,2024-04-03T07:09:18,2024-04-03T20:18:52,0.82,"Rain, Overcast",Cloudy skies throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-04-04,13.9,9.1,11.5,13.9,6.2,11,9.1,85.4,4.072,100,45.83,rain,0,0,70.6,47.5,227.4,1004.1,98.1,15.7,111.5,9.5,7,,2024-04-04T07:06:59,2024-04-04T20:20:37,0.85,"Rain, Overcast",Cloudy skies throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-04-05,16.2,11.2,13.2,16.2,11.2,13.2,10.3,83.4,2.471,100,33.33,rain,0,0,66.6,46.8,213.1,1007.4,89.7,20.9,104.5,9.1,6,,2024-04-05T07:04:41,2024-04-05T20:22:21,0.88,"Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-04-06,23.2,11.3,16.8,23.2,11.3,16.8,10.9,70.7,0.495,100,4.17,rain,0,0,54.6,34.1,174.1,1008.4,97.1,34,143.6,12.5,6,,2024-04-06T07:02:23,2024-04-06T20:24:05,0.92,"Rain, Overcast",Cloudy skies throughout the day with morning rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-04-07,18.4,13.1,15.5,18.4,13.1,15.5,9.3,67.2,0.556,100,20.83,rain,0,0,53.1,37.6,221.2,1011.2,79.3,35.4,183.9,15.9,8,,2024-04-07T07:00:05,2024-04-07T20:25:50,0.95,"Rain, Partially cloudy",Partly cloudy throughout the day with rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-04-08,18.5,10.8,14.6,18.5,10.8,14.6,10.9,79.2,0.068,100,20.83,rain,0,0,25,17.2,140.9,1008.3,89.5,30,113.5,9.6,5,,2024-04-08T06:57:48,2024-04-08T20:27:34,0,"Rain, Partially cloudy",Partly cloudy throughout the day with rain clearing later.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-04-09,15.8,10.3,11.8,15.8,10.3,11.8,8.1,78.2,4.532,100,41.67,rain,0,0,69.8,48,216.7,1007.4,99.9,30.2,54.2,4.8,2,,2024-04-09T06:55:32,2024-04-09T20:29:18,0.02,"Rain, Overcast",Cloudy skies throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06257099999" "Amsterdam,Netherlands",2024-04-10,14.1,8.3,11.2,14.1,5.9,10.2,5,67.3,0.447,100,25,rain,0,0,61.9,36.1,250,1025.2,76.9,29.4,173.5,15,7,,2024-04-10T06:53:16,2024-04-10T20:31:02,0.05,"Rain, Partially cloudy",Partly cloudy throughout the day with rain clearing later.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-04-11,14.4,11.9,13,14.4,11.9,13,10.3,84.5,0.15,100,20.83,rain,0,0,48.9,33.3,221.4,1029.3,100,25.7,46.4,4,3,,2024-04-11T06:51:00,2024-04-11T20:32:47,0.09,"Rain, Overcast",Cloudy skies throughout the day with rain in the morning and afternoon.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-04-12,17.3,12.9,14.9,17.3,12.9,14.9,11,78.4,0.023,100,4.17,rain,0,0,55.2,35.3,230.2,1029.1,100,26.6,150.5,12.9,6,,2024-04-12T06:48:45,2024-04-12T20:34:31,0.12,"Rain, Overcast",Cloudy skies throughout the day with morning rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-04-13,20.9,11.5,16.3,20.9,11.5,16.3,11,71.6,0,0,0,,0,0,53.2,35,230.4,1022.7,99.9,38.9,181.3,15.7,6,,2024-04-13T06:46:31,2024-04-13T20:36:15,0.15,Overcast,Cloudy skies throughout the day.,cloudy,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-04-14,13.7,8.8,11.3,13.7,7.8,11.1,5,65.5,0,0,0,,0,0,53.1,27.5,279.6,1021.2,99.8,31.5,190.3,16.8,7,,2024-04-14T06:44:17,2024-04-14T20:37:59,0.19,Overcast,Cloudy skies throughout the day.,cloudy,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-04-15,10.2,5.1,8.3,10.2,0.4,5.2,4.9,79.2,7.703,100,50,rain,0,0,87,45.7,238,1006.1,93.5,26.6,65.5,5.7,4,,2024-04-15T06:42:05,2024-04-15T20:39:44,0.25,"Rain, Overcast",Cloudy skies throughout the day with rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-04-16,10.6,5.6,8.4,10.6,0,4.9,5.1,80.2,11.676,100,58.33,rain,0,0,66.1,44,303.1,1003.4,86.1,21.8,91.6,7.9,4,,2024-04-16T06:39:53,2024-04-16T20:41:28,0.25,"Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-04-17,8.6,4,6.1,7.8,0.3,4.2,3.2,82,10.732,100,62.5,rain,0,0,39.2,23,317.8,1011.9,70.5,32.3,116,10.1,5,,2024-04-17T06:37:41,2024-04-17T20:43:12,0.28,"Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,06240099999,C0449,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-04-18,10.3,1.8,6.9,10.3,-1,4.8,2.9,77.7,0.122,100,16.67,rain,0,0,34.7,23.7,295.4,1018.5,65,34.9,151.2,13.1,6,,2024-04-18T06:35:31,2024-04-18T20:44:56,0.32,"Rain, Partially cloudy",Partly cloudy throughout the day with rain clearing later.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-04-19,10.3,6.7,8.6,10.3,2.4,4.9,6.4,86,9.469,100,95.83,rain,0,0,66.4,44.9,302,1010.9,98.5,14.6,114.1,9.7,8,,2024-04-19T06:33:21,2024-04-19T20:46:40,0.35,"Rain, Overcast",Cloudy skies throughout the day with a chance of rain throughout the day.,rain,"D3248,06260099999,06348099999,06249099999,06240099999,C0449,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-04-20,9.7,5.9,7.4,6.1,1.8,3.8,3.5,76.1,2.218,100,54.17,rain,0,0,58.2,34.2,334.4,1021.1,91.6,24.4,172,14.9,8,,2024-04-20T06:31:12,2024-04-20T20:48:24,0.38,"Rain, Overcast",Cloudy skies throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-04-21,10.5,3.2,6.8,10.5,-1.6,3.6,2,72.1,1.675,100,20.83,rain,0,0,54.3,26.2,19.7,1025.1,48.6,34.8,234.3,20.3,8,,2024-04-21T06:29:04,2024-04-21T20:50:08,0.42,"Rain, Partially cloudy",Partly cloudy throughout the day with rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-04-22,9.8,1.9,6,7.1,-0.9,3.5,0.2,68.6,0.269,100,12.5,rain,0,0,36.4,24.5,18.9,1025.9,75.8,38.5,195.3,16.8,8,,2024-04-22T06:26:57,2024-04-22T20:51:52,0.45,"Rain, Partially cloudy",Partly cloudy throughout the day with rain in the morning and afternoon.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-04-23,9.5,-0.3,5.4,9.3,-1.4,3.5,0.1,70.6,1.639,100,20.83,rain,0,0,39.1,29.7,304.8,1020.5,68.7,36.9,172.4,14.9,8,,2024-04-23T06:24:52,2024-04-23T20:53:35,0.48,"Rain, Partially cloudy",Partly cloudy throughout the day with rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-04-24,9.4,4.6,6.4,5.5,0.8,2.9,2.9,78.6,2.51,100,70.83,rain,0,0,56.1,34.2,323.9,1010.6,86,31,117.7,10.2,7,,2024-04-24T06:22:47,2024-04-24T20:55:19,0.5,"Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-04-25,9.8,3.6,6.2,7,1.6,3.6,2.9,80.1,5.41,100,54.17,rain,0,0,41.3,29.3,225.5,1004.9,91.7,29.7,83.8,7.3,3,,2024-04-25T06:20:43,2024-04-25T20:57:02,0.55,"Rain, Overcast",Cloudy skies throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-04-26,11,4.3,7.8,11,1.3,6.1,3.6,75.5,1.107,100,33.33,rain,0,0,31,20.1,256.5,1003.2,87.7,34.3,176,15.2,7,,2024-04-26T06:18:40,2024-04-26T20:58:46,0.59,"Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-04-27,15,5.7,10.6,15,4.4,9.8,8,85,2.916,100,37.5,rain,0,0,31,19.2,109.8,1005,99.3,29.7,103,8.9,4,,2024-04-27T06:16:39,2024-04-27T21:00:29,0.62,"Rain, Overcast",Cloudy skies throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-04-28,15.3,7.3,12.8,15.3,4.9,12.6,7.8,72.1,0.788,100,12.5,rain,0,0,69.5,47.2,184.3,1006.3,90.7,41.7,88.7,7.7,4,,2024-04-28T06:14:38,2024-04-28T21:02:12,0.66,"Rain, Overcast",Cloudy skies throughout the day with rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-04-29,18.7,5.6,12.4,18.7,2.9,11.6,7.1,72.4,0.023,100,8.33,rain,0,0,39.4,26.4,172.5,1018.1,35.8,33.7,262.1,22.8,9,,2024-04-29T06:12:39,2024-04-29T21:03:54,0.69,"Rain, Partially cloudy",Becoming cloudy in the afternoon with rain clearing later.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-04-30,20.4,11.9,15.7,20.4,11.9,15.7,11.3,76,0,0,0,,0,0,25.9,19.9,138.3,1015.5,91.5,36.7,180.8,15.4,8,,2024-04-30T06:10:42,2024-04-30T21:05:36,0.73,Overcast,Cloudy skies throughout the day.,cloudy,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-05-01,22.3,13.9,18.1,22.3,13.9,18.1,14.6,80.4,0.7,100,8.33,rain,0,0,30.2,19,5.6,1007.3,53.9,10,237.8,20.6,7,,2024-05-01T06:08:45,2024-05-01T21:07:18,0.75,"Rain, Partially cloudy",Partly cloudy throughout the day with morning rain.,rain,"D3248,EHRD,C0449,EHLE,EHKD,EHAM" "Amsterdam,Netherlands",2024-05-02,24.4,13.1,17.6,24.4,13.1,17.6,13.3,77.1,0.85,100,29.17,rain,0,0,36.3,24.6,37.4,1000.1,75.7,24.9,228.7,19.8,7,,2024-05-02T06:06:50,2024-05-02T21:09:00,0.8,"Rain, Partially cloudy",Partly cloudy throughout the day with rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-05-03,13.6,9.3,12,13.6,7.7,11.9,10.7,91.5,3.878,100,58.33,rain,0,0,48,33.5,227.4,1007.2,86.7,15.2,33,2.7,1,,2024-05-03T06:04:56,2024-05-03T21:10:42,0.83,"Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain throughout the day.,rain,"D3248,06260099999,06348099999,06249099999,06240099999,C0449,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-05-04,16.6,6.9,11.5,16.6,5.1,11,8.5,83,4.036,100,25,rain,0,0,47.5,22.2,148,1013.2,92.1,26.6,158.7,13.9,6,,2024-05-04T06:03:04,2024-05-04T21:12:22,0.87,"Rain, Overcast",Cloudy skies throughout the day with rain.,rain,"D3248,06260099999,06348099999,06249099999,06240099999,C0449,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-05-05,15.7,9.3,12.5,15.7,7.3,12.1,8.9,80.5,6.25,100,33.33,rain,0,0,31.2,18,295.4,1009,75.2,33,255.7,22.2,8,,2024-05-05T06:01:14,2024-05-05T21:14:03,0.9,"Rain, Partially cloudy",Partly cloudy throughout the day with rain clearing later.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-05-06,18.1,8.5,13.9,18.1,7.4,13.6,9.3,75.5,0,0,0,,0,0,25.6,17.6,69.7,1008.7,83.8,32.1,165,14.3,7,,2024-05-06T05:59:25,2024-05-06T21:15:43,0.94,Partially cloudy,Partly cloudy throughout the day.,partly-cloudy-day,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-05-07,18,10.6,14.1,18,10.6,14.1,9.5,74.8,0,0,0,,0,0,35.2,26.1,7.3,1018.8,71.5,28.5,194.7,16.7,8,,2024-05-07T05:57:37,2024-05-07T21:17:22,0.97,Partially cloudy,Partly cloudy throughout the day.,partly-cloudy-day,"06260099999,C0449,06257099999,EHAM,D3248,EHRD,06348099999,06249099999,06240099999,EHLE,EHKD,06269099999,06344099999" "Amsterdam,Netherlands",2024-05-08,15,10.2,12.5,15,10.2,12.5,9.9,84.7,0.058,100,8.33,rain,0,0,23,15.6,340.7,1027.2,89.6,25.1,101.7,8.7,4,,2024-05-08T05:55:52,2024-05-08T21:19:01,0,"Rain, Partially cloudy",Partly cloudy throughout the day with rain.,rain,"06260099999,06356099999,C0449,06257099999,EHAM,06267099999,D3248,06273099999,EHRD,06348099999,06249099999,06240099999,EHLE,EHKD,06269099999,06235099999,06344099999" "Amsterdam,Netherlands",2024-05-09,17.2,6.3,12.3,17.2,6.3,12.3,9.2,82.3,0,0,0,,0,0,21.3,13.6,301.7,1027.6,50.8,15.5,215.2,18.6,8,,2024-05-09T05:54:08,2024-05-09T21:20:40,0.04,Partially cloudy,Partly cloudy throughout the day.,partly-cloudy-day,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-05-10,19.2,8.5,14.4,19.2,8.5,14.4,10.5,79,0,0,0,,0,0,21.7,17,38,1024.5,80,15.9,237.7,20.5,8,,2024-05-10T05:52:25,2024-05-10T21:22:17,0.07,Partially cloudy,Partly cloudy throughout the day.,partly-cloudy-day,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-05-11,21.7,11.1,16.1,21.7,11.1,16.1,11.4,75.9,0,0,0,,0,0,27.4,18.7,57.5,1022.5,93.5,23.2,247.8,21.3,8,,2024-05-11T05:50:45,2024-05-11T21:23:54,0.11,Overcast,Cloudy skies throughout the day.,cloudy,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-05-12,25,14,19.5,25,14,19.5,11,59.2,0.065,100,4.17,rain,0,0,38.3,24,95.7,1016.1,99.4,37.3,271.5,23.4,7,,2024-05-12T05:49:06,2024-05-12T21:25:30,0.14,"Rain, Overcast",Cloudy skies throughout the day with early morning rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-05-13,22.4,16.8,19.6,22.4,16.8,19.6,14.8,74.4,0.461,100,4.17,rain,0,0,34.6,20.8,147.4,1010,74.1,33.4,170,14.7,7,,2024-05-13T05:47:30,2024-05-13T21:27:05,0.17,"Rain, Partially cloudy",Partly cloudy throughout the day with late afternoon rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-05-14,26.6,15.6,20.2,26.6,15.6,20.2,14.4,72.2,0.962,100,12.5,rain,0,0,48.3,31.2,131.1,1004.9,41.3,31.2,249.6,21.7,8,,2024-05-14T05:45:55,2024-05-14T21:28:40,0.2,"Rain, Partially cloudy",Becoming cloudy in the afternoon with rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-05-15,19.8,12.8,16,19.8,12.8,16,14.3,90.6,6.274,100,37.5,rain,0,0,32.8,19,302.6,1006.1,81.5,12.4,131,11.4,7,,2024-05-15T05:44:22,2024-05-15T21:30:13,0.25,"Rain, Partially cloudy",Partly cloudy throughout the day with rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-05-16,20.2,13.7,16,20.2,13.7,16,14.1,88.9,3.508,100,20.83,rain,0,0,31.5,26.6,246.5,1004.8,99.7,13.7,155,13.3,8,,2024-05-16T05:42:52,2024-05-16T21:31:46,0.27,"Rain, Overcast",Cloudy skies throughout the day with rain clearing later.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-05-17,17.5,13.2,14.7,17.5,13.2,14.7,13.5,92.8,1.518,100,16.67,rain,0,0,30.7,22.7,318,1008.4,98.6,8.7,143.7,12.4,8,,2024-05-17T05:41:23,2024-05-17T21:33:17,0.3,"Rain, Overcast",Cloudy skies throughout the day with rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-05-18,22.3,13.3,17.5,22.3,13.3,17.5,12.9,77.2,0.214,100,8.33,rain,0,0,27.8,16.6,321.3,1010.3,67.6,18.3,273.8,23.6,8,,2024-05-18T05:39:57,2024-05-18T21:34:47,0.33,"Rain, Partially cloudy",Partly cloudy throughout the day with rain clearing later.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-05-19,21.5,11.3,17,21.5,11.3,17,11.8,74.2,0.717,100,4.17,rain,0,0,41.8,27.9,3.5,1011.2,22.3,22.8,268.6,23.2,8,,2024-05-19T05:38:33,2024-05-19T21:36:16,0.37,"Rain, Partially cloudy",Becoming cloudy in the afternoon with morning rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-05-20,19,13.1,15.7,19,13.1,15.7,13.7,88.2,2.89,100,20.83,rain,0,0,30.8,17.4,353.5,1011.5,89.7,18.8,109.6,9.4,6,,2024-05-20T05:37:11,2024-05-20T21:37:44,0.4,"Rain, Partially cloudy",Partly cloudy throughout the day with rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-05-21,23.1,12.4,17.4,23.1,12.4,17.4,14.1,83.1,8.497,100,29.17,rain,0,0,53.1,25.9,52.3,1008.1,78.8,20.9,207.5,17.8,7,,2024-05-21T05:35:52,2024-05-21T21:39:11,0.43,"Rain, Partially cloudy",Partly cloudy throughout the day with rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-05-22,18.1,14,15.5,18.1,14,15.5,13,85.7,23.183,100,37.5,rain,0,0,46.3,30,223.5,1006.8,95.3,23.1,85.3,7.3,7,,2024-05-22T05:34:35,2024-05-22T21:40:36,0.46,"Rain, Overcast",Cloudy skies throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-05-23,18.1,13.2,15.5,18.1,13.2,15.5,11.6,78.1,0,0,0,,0,0,41.7,26.9,225.1,1013.5,58.9,28.8,183.6,15.7,8,,2024-05-23T05:33:21,2024-05-23T21:41:59,0.5,Partially cloudy,Partly cloudy throughout the day.,partly-cloudy-day,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-05-24,18.7,11.4,14.9,18.7,11.4,14.9,12.8,87.9,5.086,100,37.5,rain,0,0,27.1,17.2,22.9,1018.3,96.7,14.4,113.4,10,4,,2024-05-24T05:32:09,2024-05-24T21:43:21,0.53,"Rain, Overcast",Cloudy skies throughout the day with rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-05-25,17.7,11.6,15.2,17.7,11.6,15.2,13.8,91.7,31.269,100,45.83,rain,0,0,32.5,21.4,261.1,1016.3,85.4,17.5,98.3,8.3,3,,2024-05-25T05:30:59,2024-05-25T21:44:42,0.57,"Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-05-26,20.4,11.1,15.6,20.4,11.1,15.6,12.5,82.5,3.309,100,20.83,rain,0,0,48.1,21.3,154.4,1014.7,74.6,24.1,174.7,15.1,7,,2024-05-26T05:29:52,2024-05-26T21:46:01,0.6,"Rain, Partially cloudy",Partly cloudy throughout the day with rain.,rain,"06260099999,D3248,06356099999,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-05-27,17.3,12.7,14.7,17.3,12.7,14.7,10.1,74.9,1.316,100,20.83,rain,0,0,34.5,23.9,232.8,1016,84.4,30.4,219.9,19.2,9,,2024-05-27T05:28:48,2024-05-27T21:47:18,0.64,"Rain, Partially cloudy",Partly cloudy throughout the day with rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-05-28,18.7,10.8,14.2,18.7,10.8,14.2,11.1,82.4,6.213,100,37.5,rain,0,0,44.4,26,198.9,1015.5,84.6,23.1,189,16.4,7,,2024-05-28T05:27:46,2024-05-28T21:48:33,0.67,"Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-05-29,17,13.9,15.2,17,13.9,15.2,13,87.1,22.194,100,66.67,rain,0,0,45.2,29.9,242,1008.1,88.9,19.3,151.7,13.2,6,,2024-05-29T05:26:48,2024-05-29T21:49:46,0.71,"Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-05-30,16.6,12.9,14.3,16.6,12.9,14.3,12,86.3,0.621,100,8.33,rain,0,0,32.1,23.9,261.7,1006.6,97.5,24.4,121.6,10.5,5,,2024-05-30T05:25:52,2024-05-30T21:50:57,0.75,"Rain, Overcast",Cloudy skies throughout the day with rain in the morning and afternoon.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-05-31,18.5,12.9,15.4,18.5,12.9,15.4,12.5,83.7,1.511,100,33.33,rain,0,0,42.2,27.7,339.3,1011.5,90.3,24.8,216.6,18.7,9,,2024-05-31T05:24:59,2024-05-31T21:52:07,0.78,"Rain, Overcast",Cloudy skies throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06257099999" "Amsterdam,Netherlands",2024-06-01,15.7,13.8,14.8,15.7,13.8,14.8,13.6,92.9,2.332,100,33.33,rain,0,0,42.1,26.4,350.1,1018,100,9.3,51,4.5,2,,2024-06-01T05:24:08,2024-06-01T21:53:14,0.82,"Rain, Overcast",Cloudy skies throughout the day with rain clearing later.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-06-02,16.4,12.8,14,16.4,12.8,14,9.5,74.5,0.559,100,16.67,rain,0,0,38.4,23.5,342.8,1023.5,94.8,20.9,198.6,17.4,9,,2024-06-02T05:23:21,2024-06-02T21:54:19,0.85,"Rain, Overcast",Cloudy skies throughout the day with rain in the morning and afternoon.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-06-03,16.3,9.7,13.7,16.3,8.8,13.7,10.6,81.9,0,0,0,,0,0,24.9,14.1,288.4,1020.7,92.4,27.4,141.3,12.2,5,,2024-06-03T05:22:37,2024-06-03T21:55:21,0.89,Overcast,Cloudy skies throughout the day.,cloudy,"06260099999,C0449,06257099999,EHAM,D3248,EHRD,06348099999,06249099999,06240099999,EHLE,EHKD,06269099999,06344099999" "Amsterdam,Netherlands",2024-06-04,20.4,11.8,16,20.4,11.8,16,13,83.4,0,0,0,,0,0,46.4,27,214.9,1012.6,99.3,16.8,111.2,9.6,6,,2024-06-04T05:21:55,2024-06-04T21:56:22,0.92,Overcast,Cloudy skies throughout the day.,cloudy,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-06-05,14.8,10.8,13.1,14.8,10.8,13.1,7.4,69.6,3.409,100,12.5,rain,0,0,47.7,34.9,268.9,1012,54.7,26.5,302,26.2,9,,2024-06-05T05:21:17,2024-06-05T21:57:19,0.96,"Rain, Partially cloudy",Partly cloudy throughout the day with rain clearing later.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-06-06,16.8,9.7,13.2,16.8,8.3,13,7.3,68.9,0,0,0,,0,0,37.6,20.6,254.7,1016.8,79.7,25.1,153.3,13.1,7,,2024-06-06T05:20:42,2024-06-06T21:58:15,0,Partially cloudy,Partly cloudy throughout the day.,partly-cloudy-day,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-06-07,17.8,10,14.3,17.8,10,14.3,8.3,69.3,0,0,0,,0,0,41.2,26.3,241.7,1017.9,45.9,22.3,257.1,22.3,10,,2024-06-07T05:20:09,2024-06-07T21:59:08,0.03,Partially cloudy,Partly cloudy throughout the day.,partly-cloudy-day,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-06-08,17.6,10.2,13.7,17.6,10.2,13.7,10.3,80.3,0.373,100,8.33,rain,0,0,45.4,33.5,243.3,1012.5,65,24.7,189.6,16.4,9,,2024-06-08T05:19:40,2024-06-08T21:59:58,0.06,"Rain, Partially cloudy",Partly cloudy throughout the day with late afternoon rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-06-09,15.3,9,12.8,15.3,7.4,12.5,7.9,73.3,0.62,100,12.5,rain,0,0,46.6,33.8,261.7,1010.9,53.2,32.5,169.3,14.5,7,,2024-06-09T05:19:14,2024-06-09T22:00:45,0.09,"Rain, Partially cloudy",Partly cloudy throughout the day with rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-06-10,14.1,9.4,11.6,14.1,5.7,11.2,10.1,90.4,24.004,100,62.5,rain,0,0,63.1,32.2,274,1005.8,99.9,20.3,72.5,6.4,6,,2024-06-10T05:18:51,2024-06-10T22:01:30,0.13,"Rain, Overcast",Cloudy skies throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-06-11,14.9,9.5,11.9,14.9,7.6,11.5,8.1,78.2,8.244,100,58.33,rain,0,0,41.9,30.4,289.6,1014.7,74.3,30.2,200,17.3,9,,2024-06-11T05:18:32,2024-06-11T22:02:12,0.16,"Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-06-12,14.5,9.5,11.5,14.5,8.5,11.4,7.8,78.4,2.397,100,37.5,rain,0,0,39.7,25,297.8,1019.4,98.2,33.7,150.4,13,4,,2024-06-12T05:18:15,2024-06-12T22:02:51,0.19,"Rain, Overcast",Cloudy skies throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-06-13,17.5,10.4,14,17.5,10.4,14,8.8,71.8,0.346,100,4.17,rain,0,0,38.6,24.6,228.5,1015.8,89.6,31.8,152.1,13.4,7,,2024-06-13T05:18:02,2024-06-13T22:03:28,0.22,"Rain, Partially cloudy",Partly cloudy throughout the day with morning rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-06-14,17.9,13,15,17.9,13,15,11.9,82.1,1.596,100,25,rain,0,0,39.7,27.3,175.7,1005.8,95.6,29.6,97.8,8.4,3,,2024-06-14T05:17:52,2024-06-14T22:04:01,0.25,"Rain, Overcast",Cloudy skies throughout the day with rain.,rain,"06260099999,D3248,06356099999,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-06-15,17.2,11.9,14.2,17.2,11.9,14.2,11.2,82.9,9.259,100,62.5,rain,0,0,65.6,41.1,208.8,1001.5,77.5,23.7,159.1,13.7,6,,2024-06-15T05:17:45,2024-06-15T22:04:31,0.29,"Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-06-16,17.2,12.6,14.9,17.2,12.6,14.9,11.3,79.7,2.728,100,37.5,rain,0,0,47.4,30.3,207,1004.5,85.4,34.7,180,15.5,9,,2024-06-16T05:17:42,2024-06-16T22:04:58,0.32,"Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain throughout the day.,rain,"D3248,06260099999,06348099999,06249099999,06240099999,C0449,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-06-17,19.1,12.3,16,19.1,12.3,16,10.8,72.5,0.555,100,4.17,rain,0,0,38.5,25.3,230.9,1010.4,69.9,29.8,219.5,18.8,9,,2024-06-17T05:17:41,2024-06-17T22:05:23,0.35,"Rain, Partially cloudy",Partly cloudy throughout the day with morning rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-06-18,18.4,10.6,15.3,18.4,10.6,15.3,12.3,82.8,0.608,100,29.17,rain,0,0,22.6,10.5,77.9,1013.9,99.6,29,92.8,8.2,3,,2024-06-18T05:17:44,2024-06-18T22:05:44,0.38,"Rain, Overcast",Cloudy skies throughout the day with rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-06-19,18.8,12.3,15.7,18.8,12.3,15.7,11.6,78.2,0.178,100,4.17,rain,0,0,34,21,4.7,1019,50,26.7,207.2,17.8,9,,2024-06-19T05:17:50,2024-06-19T22:06:02,0.42,"Rain, Partially cloudy",Partly cloudy throughout the day with morning rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-06-20,20.4,9.7,15.3,20.4,8.9,15.3,11.7,79.9,0,0,0,,0,0,27.4,15.3,47.3,1020.4,78.4,34.7,182,15.8,7,,2024-06-20T05:17:59,2024-06-20T22:06:16,0.45,Partially cloudy,Partly cloudy throughout the day.,partly-cloudy-day,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999,EHAM" "Amsterdam,Netherlands",2024-06-21,18.7,13.4,15.9,18.7,13.4,15.9,13.9,88.1,0.941,100,20.83,rain,0,0,27.3,15.9,328,1013.2,98,21.5,80.2,6.8,4,,2024-06-21T05:18:12,2024-06-21T22:06:28,0.48,"Rain, Overcast",Cloudy skies throughout the day with rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-06-22,19.3,13.2,16.3,19.3,13.2,16.3,13.1,81.8,0.335,100,4.17,rain,0,0,35.6,23.7,249.4,1012.6,67.1,23.2,268.5,23.1,10,,2024-06-22T05:18:27,2024-06-22T22:06:36,0.5,"Rain, Partially cloudy",Partly cloudy throughout the day with morning rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-06-23,22.5,12.8,17.9,22.5,12.8,17.9,13.4,76.7,0,0,0,,0,0,28.1,17.7,265.5,1018.9,26.8,27.8,279.5,24.3,9,,2024-06-23T05:18:46,2024-06-23T22:06:41,0.55,Partially cloudy,Partly cloudy throughout the day.,partly-cloudy-day,"D3248,06260099999,06348099999,06249099999,06240099999,C0449,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-06-24,24.6,11.8,19.1,24.6,11.8,19.1,14,74.7,0,0,0,,0,0,21.3,13.7,76.8,1020.3,10.9,28.5,308.9,26.5,10,,2024-06-24T05:19:07,2024-06-24T22:06:43,0.59,Clear,Clear conditions throughout the day.,clear-day,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-06-25,27.1,15.9,22.1,27.6,15.9,22.2,16,70.2,0,0,0,,0,0,31.1,20.3,51.6,1016.2,4.1,34.8,288,25,8,,2024-06-25T05:19:32,2024-06-25T22:06:42,0.62,Clear,Clear conditions throughout the day.,clear-day,"06260099999,D3248,06348099999,06249099999,06240099999,C0449,06269099999,06257099999" "Amsterdam,Netherlands",2024-06-26,28.8,18.6,24.1,28.8,18.6,24.2,16.8,66.1,0,0,0,,0,0,24.9,14.5,54.1,1011.5,0.6,32.5,293.2,25.4,8,,2024-06-26T05:20:00,2024-06-26T22:06:37,0.66,Clear,Clear conditions throughout the day.,clear-day,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-06-27,26.6,18.5,22.1,26.6,18.5,22.1,16.3,71.4,0,0,0,,0,0,41.5,32.1,248.3,1008.3,47.3,27,257,22.2,8,,2024-06-27T05:20:30,2024-06-27T22:06:29,0.69,Partially cloudy,Partly cloudy throughout the day.,partly-cloudy-day,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-06-28,19.3,15.4,17.5,19.3,15.4,17.5,11.7,69,0,0,0,,0,0,45,30.4,250,1014.5,66,30.1,212.2,18.4,10,,2024-06-28T05:21:04,2024-06-28T22:06:18,0.75,Partially cloudy,Partly cloudy throughout the day.,partly-cloudy-day,"D3248,06260099999,06348099999,06249099999,06240099999,C0449,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-06-29,22.9,11.5,17.8,22.9,11.5,17.8,12.4,72.5,0.066,100,8.33,rain,0,0,35.4,19.2,340.9,1014.6,83.1,27.2,292.3,25.1,9,,2024-06-29T05:21:40,2024-06-29T22:06:04,0.76,"Rain, Partially cloudy",Partly cloudy throughout the day with late afternoon rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-06-30,19.9,14.7,17.6,19.9,14.7,17.6,13.6,78.3,0.507,100,12.5,rain,0,0,35,25,315.4,1009.9,99.6,36.1,187.2,16.2,8,,2024-06-30T05:22:19,2024-06-30T22:05:46,0.8,"Rain, Overcast",Cloudy skies throughout the day with rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-07-01,18,12.9,15.8,18,12.9,15.8,11.4,75.4,0.203,100,8.33,rain,0,0,40.8,24.6,285.5,1015.2,87.5,37.3,172.7,14.8,9,,2024-07-01T05:23:01,2024-07-01T22:05:25,0.84,"Rain, Partially cloudy",Partly cloudy throughout the day with rain clearing later.,rain,"D3248,06260099999,06348099999,06249099999,06240099999,C0449,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-07-02,17.3,12.8,15.3,17.3,12.8,15.3,11.6,79.8,3.898,100,37.5,rain,0,0,38,26.8,308.4,1014.9,96.4,25.7,147.6,12.8,6,,2024-07-02T05:23:46,2024-07-02T22:05:01,0.87,"Rain, Overcast",Cloudy skies throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-07-03,16.5,10.6,13.7,16.5,10.6,13.7,10.5,81.5,2.363,100,45.83,rain,0,0,31.1,23,234.1,1009.9,92.6,30,133.4,11.5,7,,2024-07-03T05:24:33,2024-07-03T22:04:34,0.91,"Rain, Overcast",Cloudy skies throughout the day with rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-07-04,16.9,14.2,15.8,16.9,14.2,15.8,10.3,71.2,14.628,100,33.33,rain,0,0,54.6,33.5,256.6,1006.5,72.6,27.1,228.9,19.9,10,,2024-07-04T05:25:23,2024-07-04T22:04:03,0.94,"Rain, Partially cloudy",Partly cloudy throughout the day with rain clearing later.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-07-05,17.2,15.2,16.1,17.2,15.2,16.1,13,82.2,1.173,100,33.33,rain,0,0,46.7,29.3,221.6,1007.9,98.9,21,76.2,6.8,4,,2024-07-05T05:26:15,2024-07-05T22:03:30,0.98,"Rain, Overcast",Cloudy skies throughout the day with rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-07-06,19.5,13.5,16.5,19.5,13.5,16.5,13.2,81.5,9.967,100,45.83,rain,0,0,79.6,54.8,219.8,1001.3,84.2,26.6,206.9,17.7,9,,2024-07-06T05:27:10,2024-07-06T22:02:53,0,"Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-07-07,17.3,12.4,14.8,17.3,12.4,14.8,11.4,81,4.117,100,50,rain,0,0,46.9,29.9,206.9,1011,84.7,34.9,145.3,12.5,4,,2024-07-07T05:28:07,2024-07-07T22:02:13,0.05,"Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-07-08,20,12.2,16.5,20,12.2,16.5,12.2,76.8,1.382,100,12.5,rain,0,0,37,17.7,215.1,1016.4,61.9,34.3,160.7,14,8,,2024-07-08T05:29:07,2024-07-08T22:01:30,0.08,"Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-07-09,26,16.6,20.1,26,16.6,20.1,15.1,74.4,2.33,100,20.83,rain,0,0,76,24.1,86.5,1013.7,99.7,32.7,176.1,15.3,7,,2024-07-09T05:30:08,2024-07-09T22:00:44,0.11,"Rain, Overcast",Cloudy skies throughout the day with rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-07-10,21.1,16.5,18.9,21.1,16.5,18.9,15.5,81.6,16.692,100,20.83,rain,0,0,51,30.5,222.7,1013.6,80.1,24,210.9,18.4,8,,2024-07-10T05:31:12,2024-07-10T21:59:56,0.14,"Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-07-11,19.5,15,17.1,19.5,15,17.1,13,77.2,0,0,0,,0,0,42,24.2,251.2,1016.9,92.8,36.2,199.9,17.5,10,,2024-07-11T05:32:18,2024-07-11T21:59:04,0.18,Overcast,Cloudy skies throughout the day.,cloudy,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-07-12,15.2,13.8,14.7,15.2,13.8,14.7,12.8,88.5,8.476,100,41.67,rain,0,0,32.1,20.7,342.7,1013.8,100,29.2,52,4.5,2,,2024-07-12T05:33:26,2024-07-12T21:58:09,0.21,"Rain, Overcast",Cloudy skies throughout the day with rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-07-13,16,14,15,16,14,15,12.8,87.1,5.368,100,54.17,rain,0,0,54,31,247.7,1010.8,93.8,25.8,106.4,9.4,6,,2024-07-13T05:34:36,2024-07-13T21:57:12,0.24,"Rain, Overcast",Cloudy skies throughout the day with a chance of rain throughout the day.,rain,"06260099999,06356099999,C0449,06257099999,EHAM,D3248,EHRD,06348099999,06249099999,06240099999,EHLE,06269099999,06235099999,06344099999" "Amsterdam,Netherlands",2024-07-14,19.9,13.1,16.8,19.9,13.1,16.8,11.9,73.8,0.092,100,12.5,rain,0,0,46.2,34,227.6,1011.6,56.1,22.2,272,23.6,9,,2024-07-14T05:35:48,2024-07-14T21:56:12,0.25,"Rain, Partially cloudy",Partly cloudy throughout the day with rain clearing later.,rain,"06260099999,06356099999,C0449,06257099999,EHAM,D3248,EHRD,06348099999,06249099999,06240099999,EHLE,EHKD,06330099999,06275099999,06269099999,06235099999,06344099999" "Amsterdam,Netherlands",2024-07-15,24.1,12.1,18.4,24.1,12.1,18.4,13.1,73,2.35,100,12.5,rain,0,0,26,14.1,153.3,1010.9,54.7,30.4,235.8,20.1,8,,2024-07-15T05:37:02,2024-07-15T21:55:09,0.31,"Rain, Partially cloudy",Partly cloudy throughout the day with late afternoon rain.,rain,"D3248,06260099999,EHRD,06348099999,06249099999,C0449,06240099999,EHLE,06269099999,06344099999,EHAM,06257099999" "Amsterdam,Netherlands",2024-07-16,20.3,16.3,18.1,20.3,16.3,18.1,15,82.5,9.575,100,41.67,rain,0,0,43,30.3,223.4,1008.7,84.5,29,207.3,17.7,10,,2024-07-16T05:38:17,2024-07-16T21:54:03,0.34,"Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain throughout the day.,rain,"D3248,06260099999,06348099999,06249099999,06240099999,C0449,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-07-17,20.6,15,17.7,20.6,15,17.7,14.2,80.8,0.545,100,8.33,rain,0,0,36.7,24.1,272.5,1019.1,72.7,33.2,235.4,20.2,9,,2024-07-17T05:39:34,2024-07-17T21:52:54,0.37,"Rain, Partially cloudy",Partly cloudy throughout the day with rain clearing later.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-07-18,25.3,12.5,19.3,25.3,12.5,19.3,14.6,76.9,0,0,0,,0,0,18,11.2,61.8,1022.3,73.1,19.7,287.3,24.9,10,,2024-07-18T05:40:53,2024-07-18T21:51:44,0.4,Partially cloudy,Partly cloudy throughout the day.,partly-cloudy-day,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-07-19,27.6,17.3,22.3,28.4,17.3,22.4,16.8,72.4,0.005,100,4.17,rain,0,0,18,13.5,100.9,1018.5,87.7,32.6,181.1,15.4,7,,2024-07-19T05:42:13,2024-07-19T21:50:30,0.43,"Rain, Partially cloudy",Partly cloudy throughout the day with morning rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-07-20,29.3,18.2,23.6,29.4,18.2,23.7,16.6,66.1,0.146,100,8.33,rain,0,0,24,19.2,128,1009.7,44.1,28.3,277.4,24,9,,2024-07-20T05:43:34,2024-07-20T21:49:14,0.47,"Rain, Partially cloudy",Becoming cloudy in the afternoon with rain.,rain,"06260099999,06356099999,C0449,06257099999,EHAM,06267099999,D3248,EHRD,06348099999,06249099999,06240099999,06330099999,EHLE,06275099999,06269099999,06235099999,06344099999" "Amsterdam,Netherlands",2024-07-21,22.2,16.4,19.3,22.2,16.4,19.3,16.6,84.6,0.988,100,16.67,rain,0,0,29,20.8,306.6,1008.3,89.5,30.4,126.5,11.1,6,,2024-07-21T05:44:57,2024-07-21T21:47:56,0.5,"Rain, Partially cloudy",Partly cloudy throughout the day with rain in the morning and afternoon.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-07-22,21.9,13.2,18.1,21.9,13.2,18.1,14.2,79.1,1.494,100,12.5,rain,0,0,39.9,27.5,242.3,1015.7,61.5,31.7,195.3,16.9,9,,2024-07-22T05:46:21,2024-07-22T21:46:35,0.54,"Rain, Partially cloudy",Partly cloudy throughout the day with rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-07-23,21.6,17,19.1,21.6,17,19.1,15.8,81.4,0.346,100,16.67,rain,0,0,38.4,25.1,272.1,1016,90,28.1,165.7,14.4,7,,2024-07-23T05:47:46,2024-07-23T21:45:12,0.57,"Rain, Partially cloudy",Partly cloudy throughout the day with rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,EHLE,06269099999,06257099999,06344099999,EHAM" "Amsterdam,Netherlands",2024-07-24,20.2,14.1,17.8,20.2,14.1,17.8,12.8,73.6,0,0,0,,0,0,33.7,16.3,321.1,1020.8,57.2,32.2,267.5,23.2,8,,2024-07-24T05:49:13,2024-07-24T21:43:46,0.61,Partially cloudy,Partly cloudy throughout the day.,partly-cloudy-day,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-07-25,22.3,13.1,18.3,22.3,13.1,18.3,14.4,78.3,0.363,100,20.83,rain,0,0,39.1,30,192.7,1013.8,94.5,33.4,128.6,11.1,7,,2024-07-25T05:50:41,2024-07-25T21:42:18,0.64,"Rain, Overcast",Cloudy skies throughout the day with rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-07-26,21,15.7,18.8,21,15.7,18.8,15.6,82.2,3.884,100,41.67,rain,0,0,34.6,23.2,250.1,1010.2,92.1,25.5,179.9,15.8,8,,2024-07-26T05:52:09,2024-07-26T21:40:49,0.68,"Rain, Overcast",Cloudy skies throughout the day with rain clearing later.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-07-27,22.3,12.8,17.8,22.3,12.8,17.8,13.5,77.9,0.014,100,4.17,rain,0,0,27.4,17,234.7,1013.9,57.1,23.2,194.4,16.8,8,,2024-07-27T05:53:39,2024-07-27T21:39:17,0.71,"Rain, Partially cloudy",Partly cloudy throughout the day with morning rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-07-28,22.1,14,18.4,22.1,14,18.4,13.1,73,0,0,0,,0,0,23,14.4,311.5,1023.9,35.4,30.5,217.8,18.6,9,,2024-07-28T05:55:09,2024-07-28T21:37:42,0.75,Partially cloudy,Partly cloudy throughout the day.,partly-cloudy-day,"D3248,06260099999,06348099999,06249099999,06240099999,C0449,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-07-29,25.6,13.5,19.8,25.6,13.5,19.8,13.6,69.6,0,0,0,,0,0,24.8,14.4,99.4,1023.5,41.9,37.3,303,26.1,9,,2024-07-29T05:56:41,2024-07-29T21:36:06,0.78,Partially cloudy,Partly cloudy throughout the day.,partly-cloudy-day,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-07-30,27.9,16.4,22.6,28.6,16.4,22.7,15.4,65.8,0,0,0,,0,0,20.4,13.5,51.6,1017,56.1,35.8,294.4,25.5,9,,2024-07-30T05:58:13,2024-07-30T21:34:28,0.82,Partially cloudy,Partly cloudy throughout the day.,partly-cloudy-day,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-07-31,25.2,16.9,21.3,25.2,16.9,21.3,14.7,66.9,0,0,0,,0,0,36.4,17.5,47.1,1015.5,79,36.5,225,19.3,8,,2024-07-31T05:59:46,2024-07-31T21:32:48,0.86,Partially cloudy,Partly cloudy throughout the day.,partly-cloudy-day,"06260099999,C0449,06257099999,EHAM,D3248,EHRD,06348099999,06249099999,06240099999,EHLE,EHKD,06269099999,06344099999" "Amsterdam,Netherlands",2024-08-01,22.3,16.4,19.4,22.3,16.4,19.4,15.6,78.7,0,0,0,,0,0,31.1,19.9,52.1,1013,90.4,32.6,111.9,9.5,4,,2024-08-01T06:01:20,2024-08-01T21:31:07,0.89,Overcast,Cloudy skies throughout the day.,cloudy,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-08-02,25,14.9,19.8,25,14.9,19.8,14.7,74,0,0,0,,0,0,18.2,13.2,87.5,1012.6,52.2,29.2,211.1,18.2,7,,2024-08-02T06:02:54,2024-08-02T21:29:23,0.93,Partially cloudy,Partly cloudy throughout the day.,partly-cloudy-day,"06260099999,C0449,06257099999,EHAM,D3248,EHRD,06348099999,06249099999,06240099999,EHLE,EHKD,06269099999,06344099999" "Amsterdam,Netherlands",2024-08-03,22.9,18,20.3,22.9,18,20.3,16.7,80.1,0.029,100,8.33,rain,0,0,43.4,30.2,242.8,1011.1,94.8,24.2,167.9,14.4,6,,2024-08-03T06:04:29,2024-08-03T21:27:37,0.96,"Rain, Overcast",Cloudy skies throughout the day with late afternoon rain.,rain,"06260099999,C0449,06257099999,EHAM,D3248,EHRD,06348099999,06249099999,06240099999,EHLE,EHKD,06269099999,06344099999" "Amsterdam,Netherlands",2024-08-04,19.8,14.4,17.6,19.8,14.4,17.6,12.4,72.7,0.049,100,12.5,rain,0,0,39.9,17,293.4,1014.5,99.5,28.5,142.7,12.1,5,,2024-08-04T06:06:04,2024-08-04T21:25:50,0,"Rain, Overcast",Cloudy skies throughout the day with rain clearing later.,rain,"D3248,06260099999,06348099999,06249099999,06240099999,C0449,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-08-05,23.1,14.8,19.1,23.1,14.8,19.1,13.5,70.3,0,0,0,,0,0,34,19.5,218.9,1014.1,52.6,29.9,233.5,20.2,8,,2024-08-05T06:07:40,2024-08-05T21:24:02,0.03,Partially cloudy,Partly cloudy throughout the day.,partly-cloudy-day,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-08-06,27.5,16.1,21.4,27.8,16.1,21.5,15.2,69,0,0,0,,0,0,31.3,20.4,196.4,1010.6,58,28.4,225.7,19.6,9,,2024-08-06T06:09:16,2024-08-06T21:22:11,0.06,Partially cloudy,Partly cloudy throughout the day.,partly-cloudy-day,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-08-07,22.5,17.6,20,22.5,17.6,20,13.9,69.8,0.34,100,16.67,rain,0,0,39.1,23.9,262,1011.8,62.4,33.7,249.9,21.6,9,,2024-08-07T06:10:53,2024-08-07T21:20:19,0.1,"Rain, Partially cloudy",Clearing in the afternoon with rain clearing later.,rain,"D3248,06260099999,06348099999,06249099999,06240099999,C0449,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-08-08,23.6,17.5,20.3,23.6,17.5,20.3,13.5,66.4,0,0,0,,0,0,37.9,23.9,221.4,1014.9,80.1,28,261.7,22.6,9,,2024-08-08T06:12:30,2024-08-08T21:18:26,0.13,Partially cloudy,Partly cloudy throughout the day.,partly-cloudy-day,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-08-09,22.8,17.1,20.3,22.8,17.1,20.3,16.6,79.8,0.448,100,29.17,rain,0,0,61.7,36.1,240.7,1013.1,85.2,27.6,132.9,11.5,5,,2024-08-09T06:14:08,2024-08-09T21:16:31,0.16,"Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-08-10,23.1,16.9,19.8,23.1,16.9,19.8,14.6,73.6,0.037,100,4.17,rain,0,0,45.2,30.5,237.9,1019,64.1,23.7,211.8,18.4,7,,2024-08-10T06:15:45,2024-08-10T21:14:35,0.19,"Rain, Partially cloudy",Partly cloudy throughout the day with morning rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-08-11,25.9,15.5,21.2,25.9,15.5,21.2,16.2,74.7,0.027,100,8.33,rain,0,0,29.9,18.1,52.3,1021.1,34,24.8,249.3,21.5,8,,2024-08-11T06:17:23,2024-08-11T21:12:37,0.23,"Rain, Partially cloudy",Partly cloudy throughout the day with rain.,rain,"06260099999,C0449,06257099999,EHAM,D3248,EHRD,06348099999,06249099999,06240099999,EHLE,EHKD,06269099999,06344099999" "Amsterdam,Netherlands",2024-08-12,30.8,16.8,23.9,32.2,16.8,24.5,16.6,64.8,0,0,0,,0,0,34.6,21.8,103.2,1012.2,5.9,10,268.5,23.1,9,,2024-08-12T06:19:02,2024-08-12T21:10:38,0.25,Clear,Clear conditions throughout the day.,clear-day,"D3248,EHRD,C0449,EHLE,EHKD,EHAM" "Amsterdam,Netherlands",2024-08-13,26.2,20.3,23.5,26.2,20.3,23.5,19.2,77.3,0.088,100,8.33,rain,0,0,27.1,19,249.7,1008.9,77.5,27.1,165.2,14.4,7,,2024-08-13T06:20:40,2024-08-13T21:08:37,0.29,"Rain, Partially cloudy",Partly cloudy throughout the day with rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-08-14,22.5,18.2,20.7,22.5,18.2,20.7,17.7,83.8,4.624,100,41.67,rain,0,0,24.3,13.7,298.4,1011.6,100,19.9,79.6,7,5,,2024-08-14T06:22:19,2024-08-14T21:06:36,0.33,"Rain, Overcast",Cloudy skies throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-08-15,23.9,16.5,20.5,23.9,16.5,20.5,16.6,79.2,0.417,100,4.17,rain,0,0,42.8,30.5,230.5,1015.1,61.6,19.8,165,14.4,6,,2024-08-15T06:23:58,2024-08-15T21:04:33,0.36,"Rain, Partially cloudy",Partly cloudy throughout the day with morning rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-08-16,21.3,17.2,19.7,21.3,17.2,19.7,17.1,85.1,7.05,100,66.67,rain,0,0,43.8,25.8,257.9,1014.1,99.4,24.8,78.9,6.8,5,,2024-08-16T06:25:37,2024-08-16T21:02:29,0.39,"Rain, Overcast",Cloudy skies throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-08-17,22.7,12.4,18.2,22.7,12.4,18.2,12.5,71.9,0.636,100,8.33,rain,0,0,20.7,10.7,52.6,1013.4,57.3,36.3,237.7,20.5,8,,2024-08-17T06:27:16,2024-08-17T21:00:24,0.42,"Rain, Partially cloudy",Becoming cloudy in the afternoon with rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-08-18,21.9,13.7,17.7,21.9,13.7,17.7,12.7,73.9,0,0,0,,0,0,34,20.6,315.2,1012.6,80.9,32.3,194.5,16.9,8,,2024-08-18T06:28:55,2024-08-18T20:58:18,0.46,Partially cloudy,Partly cloudy throughout the day.,partly-cloudy-day,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-08-19,22.8,11.7,18,22.8,11.7,18,11.9,69,0,0,0,,0,0,31.4,20.4,172.6,1017.3,49.1,28.7,204.4,17.7,6,,2024-08-19T06:30:34,2024-08-19T20:56:11,0.5,Partially cloudy,Partly cloudy throughout the day.,partly-cloudy-day,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-08-20,23,16,18.3,23,16,18.3,15,81.7,9.017,100,37.5,rain,0,0,47.3,29.7,189.3,1010.8,93.5,17,90.5,7.8,4,,2024-08-20T06:32:14,2024-08-20T20:54:03,0.52,"Rain, Overcast",Cloudy skies throughout the day with rain.,rain,"06260099999,06356099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,EHAM,06344099999" "Amsterdam,Netherlands",2024-08-21,19,13.4,16.4,19,13.4,16.4,10.4,68.5,3.655,100,25,rain,0,0,48.8,31.3,257.1,1014.9,68.5,32.4,208.2,18.1,9,,2024-08-21T06:33:53,2024-08-21T20:51:53,0.56,"Rain, Partially cloudy",Partly cloudy throughout the day with rain clearing later.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-08-22,21.8,16,18.7,21.8,16,18.7,12.1,66.6,0,0,0,,0,0,53.9,37.2,211.5,1010,98.8,31.8,121.6,10.6,5,,2024-08-22T06:35:33,2024-08-22T20:49:43,0.59,Overcast,Cloudy skies throughout the day.,cloudy,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-08-23,21.9,15.8,19.6,21.9,15.8,19.6,14.3,72.1,0.848,100,8.33,rain,0,0,68.9,44.3,219.2,1005.2,91.6,33.4,117.1,10.2,6,,2024-08-23T06:37:12,2024-08-23T20:47:33,0.63,"Rain, Overcast",Cloudy skies throughout the day with afternoon rain.,rain,"D3248,06260099999,06348099999,06249099999,06240099999,C0449,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-08-24,25.5,15.2,18.3,25.5,15.2,18.3,15.7,85.6,10.326,100,41.67,rain,0,0,80.1,36.7,191,1006,97.5,26.5,89.4,8,6,,2024-08-24T06:38:51,2024-08-24T20:45:21,0.66,"Rain, Overcast",Cloudy skies throughout the day with rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-08-25,19.9,13.6,16.8,19.9,13.6,16.8,11.4,71.4,4.947,100,20.83,rain,0,0,42.6,30.2,238.1,1016.9,60.2,32.9,160,13.8,7,,2024-08-25T06:40:31,2024-08-25T20:43:08,0.7,"Rain, Partially cloudy",Partly cloudy throughout the day with rain clearing later.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-08-26,20.7,14.4,17.2,20.7,14.4,17.2,13,76.9,0.97,100,20.83,rain,0,0,42.2,27.8,211.8,1020.6,78.1,30.2,186.5,16.1,7,,2024-08-26T06:42:10,2024-08-26T20:40:55,0.75,"Rain, Partially cloudy",Partly cloudy throughout the day with rain in the morning and afternoon.,rain,"D3248,06260099999,06348099999,06249099999,06240099999,C0449,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-08-27,23.6,12.8,18.2,23.6,12.8,18.2,13,74.1,0.721,100,8.33,rain,0,0,27.7,16.6,161.8,1020.8,63.1,25.2,213,18.7,7,,2024-08-27T06:43:50,2024-08-27T20:38:41,0.77,"Rain, Partially cloudy",Partly cloudy throughout the day with rain in the morning and afternoon.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-08-28,28.2,13.9,21,29,13.9,21.2,15.4,72.2,0.009,100,4.17,rain,0,0,30.3,19.5,126.1,1015.3,16.9,26.7,220.1,19,8,,2024-08-28T06:45:29,2024-08-28T20:36:26,0.8,Rain,Clear conditions throughout the day with afternoon rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-08-29,22.8,15.1,19.7,22.8,15.1,19.7,16.2,81.1,0.009,100,4.17,rain,0,0,30,20.8,275.1,1016.1,81.3,23.4,174.9,15,8,,2024-08-29T06:47:09,2024-08-29T20:34:11,0.84,"Rain, Partially cloudy",Partly cloudy throughout the day with afternoon rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-08-30,21.6,12.5,17,21.6,12.5,17,12.7,78,0.009,100,4.17,rain,0,0,29.4,20.2,33,1022.6,97.2,24.3,196.9,16.9,7,,2024-08-30T06:48:48,2024-08-30T20:31:55,0.87,"Rain, Overcast",Cloudy skies throughout the day with afternoon rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-08-31,21.6,15.4,18.2,21.6,15.4,18.2,12.7,71,0.009,100,4.17,rain,0,0,45.4,30.8,60,1023.5,59.2,27.2,195.5,16.8,6,,2024-08-31T06:50:28,2024-08-31T20:29:38,0.91,"Rain, Partially cloudy",Clearing in the afternoon with afternoon rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-09-01,27.8,15.7,21,29,15.7,21.2,15.8,72.9,0,0,0,,0,0,33.6,22.5,74.3,1016.2,67.3,28.9,173,15,7,,2024-09-01T06:52:07,2024-09-01T20:27:21,0.94,Partially cloudy,Partly cloudy throughout the day.,partly-cloudy-day,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-09-02,27,18.3,22.2,28.1,18.3,22.3,18.3,79.5,0.656,100,12.5,rain,0,0,27.1,15.8,240.5,1011.9,84.8,24.7,178.2,15.3,7,,2024-09-02T06:53:46,2024-09-02T20:25:03,0.98,"Rain, Partially cloudy",Partly cloudy throughout the day with rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-09-03,23.1,17.3,19.8,23.1,17.3,19.8,17.5,86.6,8.288,100,29.17,rain,0,0,28.1,20.4,218.2,1013.5,98.3,16.8,72,6.2,3,,2024-09-03T06:55:26,2024-09-03T20:22:45,0,"Rain, Overcast",Cloudy skies throughout the day with rain.,rain,"D3248,06260099999,06348099999,06249099999,06240099999,C0449,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-09-04,20.4,16.5,18.3,20.4,16.5,18.3,16.6,90.5,1.983,100,25,rain,0,0,20.2,12.8,188,1015.7,99.8,17.9,64.5,5.6,5,,2024-09-04T06:57:05,2024-09-04T20:20:26,0.04,"Rain, Overcast",Cloudy skies throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-09-05,27.6,15.7,21.1,28.8,15.7,21.3,17.4,81,0.009,100,4.17,rain,0,0,45,28.1,49.3,1011.7,71.9,18.7,138.6,12.1,7,,2024-09-05T06:58:44,2024-09-05T20:18:07,0.08,"Rain, Partially cloudy",Partly cloudy throughout the day with afternoon rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-09-06,26.3,18.6,22,26.3,18.6,22,15.8,69.1,5.645,100,20.83,rain,0,0,32.5,21.3,89.4,1010.1,31.2,22.2,165.3,14.4,7,,2024-09-06T07:00:23,2024-09-06T20:15:47,0.11,"Rain, Partially cloudy",Becoming cloudy in the afternoon with late afternoon rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-09-07,24.6,16.8,20.6,24.6,16.8,20.6,17.5,83.6,1.292,100,12.5,rain,0,0,21.7,13.5,119.9,1011.1,94,20.6,156.4,13.4,7,,2024-09-07T07:02:03,2024-09-07T20:13:27,0.14,"Rain, Overcast",Cloudy skies throughout the day with rain clearing later.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-09-08,22.3,15.5,19.7,22.3,15.5,19.7,15,75.4,0.777,100,20.83,rain,0,0,39.5,27.3,172.2,1007.5,80.5,33.8,142,12.3,7,,2024-09-08T07:03:42,2024-09-08T20:11:07,0.18,"Rain, Partially cloudy",Partly cloudy throughout the day with rain clearing later.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-09-09,20.5,14.4,16.7,20.5,14.4,16.7,13.1,79.6,0.836,100,25,rain,0,0,45.5,29.2,305,1005,88.2,34.9,109.1,9.6,6,,2024-09-09T07:05:21,2024-09-09T20:08:46,0.21,"Rain, Partially cloudy",Partly cloudy throughout the day with rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999,EHAM" "Amsterdam,Netherlands",2024-09-10,17.2,13.1,15.1,17.2,13.1,15.1,11.9,81.7,12.151,100,50,rain,0,0,55.1,40.4,231.1,1007.3,98.8,20.5,40,3.3,2,,2024-09-10T07:07:01,2024-09-10T20:06:25,0.24,"Rain, Overcast",Cloudy skies throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-09-11,13.8,9,11.8,13.8,7,11.6,8.2,79.6,18.236,100,79.17,rain,0,0,54.3,35.6,266.8,1004.6,74.5,27.6,101,8.7,5,,2024-09-11T07:08:40,2024-09-11T20:04:04,0.25,"Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-09-12,13.9,8,10.5,13.9,6.1,9.7,7.6,83.1,8.291,100,41.67,rain,0,0,27.5,19,272.2,1012.1,73.9,33.4,112.2,9.6,6,,2024-09-12T07:10:19,2024-09-12T20:01:43,0.31,"Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-09-13,16.8,6.9,11.7,16.8,5.7,11.5,7.8,78.6,0.388,100,16.67,rain,0,0,36.7,23.7,341.8,1023.8,42.7,25,128.5,11,7,,2024-09-13T07:11:59,2024-09-13T19:59:21,0.34,"Rain, Partially cloudy",Partly cloudy throughout the day with rain in the morning and afternoon.,rain,"06260099999,D3248,EHRD,06348099999,06249099999,C0449,06240099999,EHLE,EHKD,06257099999,EHAM" "Amsterdam,Netherlands",2024-09-14,17.2,5.7,11.8,17.2,5.7,11.8,7.2,75.3,0.055,100,4.17,rain,0,0,26.8,17,270.6,1030.4,58.4,35.2,106.6,9.1,5,,2024-09-14T07:13:38,2024-09-14T19:56:59,0.38,"Rain, Partially cloudy",Partly cloudy throughout the day with morning rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-09-15,18.7,8.9,13.9,18.7,7.8,13.6,10.3,79.5,0.018,100,4.17,rain,0,0,25,17,227.3,1027,57.9,28.1,96.5,8.2,4,,2024-09-15T07:15:17,2024-09-15T19:54:37,0.41,"Rain, Partially cloudy",Partly cloudy throughout the day with early morning rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-09-16,19,12.2,15.5,19,12.2,15.5,12.6,83.9,0.343,100,20.83,rain,0,0,32.9,21.1,357.5,1026,74.8,24.9,91.4,8,4,,2024-09-16T07:16:57,2024-09-16T19:52:15,0.44,"Rain, Partially cloudy",Partly cloudy throughout the day with rain in the morning and afternoon.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-09-17,19,12.5,16.3,19,12.5,16.3,13.3,82.7,0.105,100,4.17,rain,0,0,42.9,27.7,35.4,1029.6,85.7,31.8,131.6,11.2,6,,2024-09-17T07:18:37,2024-09-17T19:49:53,0.48,"Rain, Partially cloudy",Partly cloudy throughout the day with morning rain.,rain,"06260099999,D3248,E5029,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-09-18,21,14.3,17,21,14.3,17,14.4,85.4,0,0,0,,0,0,37.6,23.5,50.4,1028.3,56.1,15.7,144.6,12.5,6,,2024-09-18T07:20:16,2024-09-18T19:47:31,0.5,Partially cloudy,Partly cloudy throughout the day.,partly-cloudy-day,"06260099999,D3248,06348099999,06249099999,06240099999,C0449,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-09-19,21,15.1,17.2,21,15.1,17.2,15.1,87.8,0.014,100,4.17,rain,0,0,31.9,22.3,52.7,1025.5,21.1,7.3,104.3,9,6,,2024-09-19T07:21:56,2024-09-19T19:45:08,0.54,"Rain, Partially cloudy",Partly cloudy throughout the day with morning rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-09-20,22.4,15.1,17.8,22.4,15.1,17.8,13.4,77,0.009,100,4.17,rain,0,0,39.6,24.8,73.4,1022.2,11.6,20.4,149.9,13,6,,2024-09-20T07:23:36,2024-09-20T19:42:46,0.58,Rain,Clear conditions throughout the day with late afternoon rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999" "Amsterdam,Netherlands",2024-09-21,23.2,12.7,17.5,23.2,12.7,17.5,13.4,77.8,0,0,0,,0,0,21.3,16.6,80.1,1019.9,2.2,24.8,157.3,13.5,6,,2024-09-21T07:25:16,2024-09-21T19:40:24,0.61,Clear,Clear conditions throughout the day.,clear-day,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-09-22,23.2,13.6,17.9,23.2,13.6,17.9,14.5,81.4,0.13,100,8.33,rain,0,0,23.2,17,97.5,1014.9,88,23.1,151.1,13.1,8,,2024-09-22T07:26:56,2024-09-22T19:38:02,0.65,"Rain, Partially cloudy",Partly cloudy throughout the day with rain.,rain,"D3248,06260099999,06348099999,06249099999,06240099999,C0449,06269099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-09-23,20.6,14.8,17.4,20.6,14.8,17.4,14.4,83,0.369,100,20.83,rain,0,0,29.6,23.4,176.5,1006.8,50.4,12.6,77.8,6.5,6,,2024-09-23T07:28:36,2024-09-23T19:35:40,0.68,"Rain, Partially cloudy",Partly cloudy throughout the day with rain.,rain,"D3248,06260099999,E5029,06348099999,06249099999,06240099999,C0449,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-09-24,17.3,11.9,14.3,17.3,11.9,14.3,12.4,88.8,1.494,100,20.83,rain,0,0,32,23.4,209.8,1002,49.3,10.7,54.9,4.8,3,,2024-09-24T07:30:17,2024-09-24T19:33:18,0.75,"Rain, Partially cloudy",Partly cloudy throughout the day with rain.,rain,"06260099999,E5029,C0449,06257099999,EHAM,D3248,EHRD,06348099999,06249099999,06240099999,EHLE,EHKD,06269099999" "Amsterdam,Netherlands",2024-09-25,16.8,14.1,14.9,16.8,14.1,14.9,11.9,82,2.543,100,12.5,rain,0,0,37.1,18.9,199.1,1000.9,70.9,13.3,48.1,4.2,2,,2024-09-25T07:31:57,2024-09-25T19:30:56,0.75,"Rain, Partially cloudy",Partly cloudy throughout the day with rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-09-26,18.1,14,15.9,18.1,14,15.9,13.7,87.4,12.356,100,50,rain,0,0,46.9,31.2,194.9,988.8,55.6,13.8,86.1,7.4,5,,2024-09-26T07:33:38,2024-09-26T19:28:34,0.78,"Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-09-27,15.1,11.4,12.8,15.1,11.4,12.8,10,83,25.278,100,54.17,rain,0,0,66.4,45.7,228.5,994.4,78.1,12.2,26.5,2.4,2,,2024-09-27T07:35:19,2024-09-27T19:26:13,0.82,"Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-09-28,14,8.3,10.8,14,7.1,10.4,6.9,77.1,10.955,100,41.67,rain,0,0,42.4,26.5,315.8,1018.2,37.8,14.9,101.3,8.9,5,,2024-09-28T07:37:00,2024-09-28T19:23:52,0.85,"Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain throughout the day.,rain,"06260099999,C0449,06257099999,EHAM,D3248,EHRD,06348099999,06249099999,06240099999,06330099999,EHLE,EHKD,06275099999,06269099999,06235099999,06344099999" "Amsterdam,Netherlands",2024-09-29,14.1,6.1,10.3,14.1,4.5,9.7,6.7,79.8,0.572,100,4.17,rain,0,0,28.6,22,151.7,1025.1,30.5,14.2,119.3,10.3,5,,2024-09-29T07:38:42,2024-09-29T19:21:31,0.89,"Rain, Partially cloudy",Partly cloudy throughout the day with morning rain.,rain,"06260099999,06356099999,C0449,06257099999,EHAM,D3248,EHRD,06348099999,06249099999,06240099999,EHLE,EHKD,06269099999,06235099999" "Amsterdam,Netherlands",2024-09-30,14.9,9,11.5,14.9,5.8,10.5,9.1,85.7,8.691,100,37.5,rain,0,0,48.2,30.1,135.9,1009.1,74.5,12.9,35.1,3.1,2,,2024-09-30T07:40:23,2024-09-30T19:19:10,0.92,"Rain, Partially cloudy",Partly cloudy throughout the day with rain.,rain,"06260099999,06356099999,C0449,06257099999,EHAM,D3248,EHRD,06348099999,06249099999,06240099999,EHLE,06269099999,06235099999,06344099999" "Amsterdam,Netherlands",2024-10-01,14.2,12.1,13.4,14.2,12.1,13.4,12.4,93.9,13.099,100,37.5,rain,0,0,42.8,26.8,187.5,1003.6,81.2,10.1,21.2,1.9,1,,2024-10-01T07:42:05,2024-10-01T19:16:50,0.95,"Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-10-02,13.6,11,12.1,13.6,11,12.1,9.8,86.2,3.896,100,25,rain,0,0,39.6,26.2,49.6,1011.4,72.9,12.2,35.4,2.9,2,,2024-10-02T07:43:47,2024-10-02T19:14:30,0,"Rain, Partially cloudy",Partly cloudy throughout the day with rain clearing later.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-10-03,15,5.8,10.5,15,3.9,9.7,6.6,77.9,0.022,100,8.33,rain,0,0,37.3,25.8,43.1,1020,14.5,16.8,121.7,10.6,6,,2024-10-03T07:45:29,2024-10-03T19:12:10,0.02,Rain,Clear conditions throughout the day with rain.,rain,"D3248,06260099999,06348099999,06249099999,06240099999,C0449,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-10-04,15,4.5,9.3,15,4.5,9.2,6.3,83.5,0,0,0,,0,0,16.4,10.9,69.5,1021.7,9.7,12.8,110.1,9.7,5,,2024-10-04T07:47:12,2024-10-04T19:09:51,0.06,Clear,Clear conditions throughout the day.,clear-day,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-10-05,15.2,4.3,9.4,15.2,3.4,9.1,6.2,82.3,0.014,100,4.17,rain,0,0,22.2,13.3,114.8,1018.4,8,12.4,129.2,11.2,6,,2024-10-05T07:48:55,2024-10-05T19:07:32,0.09,Rain,Clear conditions throughout the day with morning rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-10-06,15.1,7.1,10.8,15.1,4.7,9.8,7.7,81.5,0,0,0,,0,0,36.4,27.8,125.5,1006.8,50.5,15.5,107.7,9.2,6,,2024-10-06T07:50:38,2024-10-06T19:05:14,0.12,Partially cloudy,Partly cloudy throughout the day.,partly-cloudy-day,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-10-07,18,12.8,15.2,18,12.8,15.2,12.2,82.8,0.202,100,12.5,rain,0,0,36.8,26.6,189.1,1000.1,62,14.6,101.9,8.9,4,,2024-10-07T07:52:21,2024-10-07T19:02:56,0.16,"Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-10-08,17.2,13.9,15.6,17.2,13.9,15.6,13.1,85.4,0.143,100,16.67,rain,0,0,35.3,25.7,180.7,996.6,60,15.2,69.5,5.8,3,,2024-10-08T07:54:05,2024-10-08T19:00:39,0.19,"Rain, Partially cloudy",Partly cloudy throughout the day with rain clearing later.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-10-09,16.1,12.1,14.3,16.1,12.1,14.3,12.3,87.8,0.334,100,20.83,rain,0,0,39.7,26.7,168.1,989.2,55.4,15.7,41.5,3.7,2,,2024-10-09T07:55:49,2024-10-09T18:58:23,0.23,"Rain, Partially cloudy",Partly cloudy throughout the day with rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-10-10,13.7,9.3,12,13.7,7.9,11.9,9,82.4,0.347,100,20.83,rain,0,0,44,26.3,323,997.3,59.3,12.6,63,5.3,3,,2024-10-10T07:57:33,2024-10-10T18:56:07,0.25,"Rain, Partially cloudy",Partly cloudy throughout the day with rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-10-11,13,5.4,8.9,13,3.7,8.3,5.2,78.6,2.067,100,16.67,rain,0,0,26.2,16.2,227.6,1014.8,33.2,15.4,100.2,8.5,6,,2024-10-11T07:59:18,2024-10-11T18:53:51,0.29,"Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-10-12,12.1,4.1,8.7,12.1,3.3,7.8,6.7,87.6,0.104,100,4.17,rain,0,0,42,32.7,164.6,1010.4,51.7,13.3,49.4,4.2,3,,2024-10-12T08:01:02,2024-10-12T18:51:37,0.33,"Rain, Partially cloudy",Partly cloudy throughout the day with morning rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-10-13,13.5,8.3,10.8,13.5,6,10.4,6.1,72.6,7.684,100,41.67,rain,0,0,55.6,38.6,282.2,1012.7,46.9,14.4,52.3,4.5,2,,2024-10-13T08:02:48,2024-10-13T18:49:23,0.36,"Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-10-14,12,5.2,8.5,12,4.2,7.8,6.3,86.4,0.116,100,8.33,rain,0,0,16.8,11.9,108.4,1017.9,40.3,12.6,44.6,3.7,3,,2024-10-14T08:04:33,2024-10-14T18:47:10,0.4,"Rain, Partially cloudy",Partly cloudy throughout the day with rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-10-15,14,4.7,9.4,14,2.3,8.2,6.7,84,0,0,0,,0,0,34.8,22.5,100.7,1018.8,44,13.6,87.4,7.5,4,,2024-10-15T08:06:19,2024-10-15T18:44:57,0.43,Partially cloudy,Partly cloudy throughout the day.,partly-cloudy-day,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999" "Amsterdam,Netherlands",2024-10-16,19.7,10.9,15,19.7,10.9,15,12,82.6,0,0,0,,0,0,40,26.2,125.9,1009.4,46.9,14.9,75.2,6.4,3,,2024-10-16T08:08:05,2024-10-16T18:42:46,0.46,Partially cloudy,Partly cloudy throughout the day.,partly-cloudy-day,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-10-17,18.6,14.4,16.4,18.6,14.4,16.4,14.4,88.2,0.25,100,25,rain,0,0,36.1,25.7,182.9,1009.4,67.9,15.1,25.5,2.3,2,,2024-10-17T08:09:51,2024-10-17T18:40:35,0.5,"Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-10-18,17.2,13.9,15.2,17.2,13.9,15.2,14.4,95.4,0.293,100,25,rain,0,0,16.2,11.1,359.3,1013.7,75.4,6.7,37.2,3.1,3,,2024-10-18T08:11:38,2024-10-18T18:38:26,0.53,"Rain, Partially cloudy",Partly cloudy throughout the day with rain clearing later.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-10-19,15.9,13.9,14.7,15.9,13.9,14.7,12.9,89.5,0.986,100,16.67,rain,0,0,31.6,22,187.3,1013.5,76.3,11.4,36.3,3.1,2,,2024-10-19T08:13:25,2024-10-19T18:36:17,0.56,"Rain, Partially cloudy",Partly cloudy throughout the day with rain in the morning and afternoon.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-10-20,17.2,13.4,15.3,17.2,13.4,15.3,13,86.5,0.808,100,20.83,rain,0,0,51.2,35.3,185.3,1015,82.7,14.1,37,3.2,2,,2024-10-20T08:15:13,2024-10-20T18:34:09,0.6,"Rain, Partially cloudy",Partly cloudy throughout the day with rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-10-21,15.6,11.3,14.2,15.6,11.3,14.2,12.4,88.8,0.973,100,25,rain,0,0,44,26.3,218,1020.2,59.6,11.1,17.9,1.4,1,,2024-10-21T08:17:00,2024-10-21T18:32:02,0.63,"Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-10-22,15.9,9.1,12.4,15.9,7.3,12.3,10,86.1,6.662,100,25,rain,0,0,36.4,24.7,224.6,1026.7,38.6,12.1,62,5.4,3,,2024-10-22T08:18:48,2024-10-22T18:29:57,0.66,"Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-10-23,14.6,9.3,11.3,14.6,8.1,11.2,10.8,96.4,0,0,0,,0,0,20.9,12.1,157.3,1032.8,53.6,4.9,29.5,2.6,1,,2024-10-23T08:20:36,2024-10-23T18:27:52,0.7,Partially cloudy,Partly cloudy throughout the day.,partly-cloudy-day,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-10-24,13.9,7,10.4,13.9,5.2,9.6,8.9,90.7,0.128,100,4.17,rain,0,0,31.2,17.4,121.1,1024.7,22,7.9,70.8,6.2,4,,2024-10-24T08:22:25,2024-10-24T18:25:49,0.75,"Rain, Partially cloudy",Partly cloudy throughout the day with morning rain.,rain,"06260099999,06356099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-10-25,18.7,10.3,13.7,18.7,10.3,13.7,12.1,90.5,0.009,100,4.17,rain,0,0,22,14.7,145.3,1016.8,38.7,10,84.1,7.3,4,,2024-10-25T08:24:13,2024-10-25T18:23:47,0.76,"Rain, Partially cloudy",Clearing in the afternoon with morning rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-10-26,19.7,10.4,14.4,19.7,10.4,14.4,13,91.8,0,0,0,,0,0,19.6,14.2,129.5,1016,31.1,8,81,7.1,4,,2024-10-26T08:26:02,2024-10-26T18:21:46,0.8,Partially cloudy,Partly cloudy throughout the day.,partly-cloudy-day,"06260099999,C0449,06257099999,EHAM,D3248,EHRD,06348099999,06249099999,06240099999,EHLE,EHKD,06269099999,06235099999,06344099999" "Amsterdam,Netherlands",2024-10-27,15.9,10.1,13.4,15.9,10.1,13.4,11.2,86.8,0.071,100,12.5,rain,0,0,27.2,19,257.2,1019.5,57.5,11.4,78.8,7,5,,2024-10-27T07:27:51,2024-10-27T17:19:46,0.83,"Rain, Partially cloudy",Partly cloudy throughout the day with morning rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-10-28,15.5,9.9,12.7,15.5,8.3,12.5,11.5,92.9,1.034,100,33.33,rain,0,0,38.4,28.3,212.4,1021.8,65.2,9.2,29,2.5,2,,2024-10-28T07:29:41,2024-10-28T17:17:48,0.86,"Rain, Partially cloudy",Partly cloudy throughout the day with rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-10-29,15.1,13.1,13.9,15.1,13.1,13.9,13.3,96,3.475,100,58.33,rain,0,0,29.2,19,218.6,1023.2,90,7.5,27,2.3,1,,2024-10-29T07:31:30,2024-10-29T17:15:51,0.9,"Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06356099999,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-10-30,14.8,10.6,12.9,14.8,10.6,12.9,11.9,93.9,1.222,100,29.17,rain,0,0,11.9,8.3,298.8,1027.5,91.5,11.3,21.8,1.7,1,,2024-10-30T07:33:20,2024-10-30T17:13:56,0.93,"Rain, Overcast",Cloudy skies throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-10-31,14.2,9.7,12.6,14.2,8.4,12.6,10.6,87.6,0.077,100,8.33,rain,0,0,21.6,16.2,230.2,1027,83.4,10.8,30.2,2.6,2,,2024-10-31T07:35:10,2024-10-31T17:12:02,0.96,"Rain, Partially cloudy",Partly cloudy throughout the day with rain in the morning and afternoon.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-11-01,12.8,11.2,12,12.8,11.2,12,10.5,90.6,0,0,0,,0,0,29.6,19.8,225,1023.7,91.8,6.7,11.7,0.9,1,,2024-11-01T07:36:59,2024-11-01T17:10:10,0,Overcast,Cloudy skies throughout the day.,cloudy,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-11-02,12.1,5.7,10.2,12.1,3.9,9.7,7.4,83.7,0.207,100,25,rain,0,0,23.6,16.7,58.8,1030.7,50.9,12,60.4,5.3,3,,2024-11-02T07:38:49,2024-11-02T17:08:19,0.03,"Rain, Partially cloudy",Clearing in the afternoon with rain clearing later.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-11-03,12.5,2.9,6.6,12.5,1,5.4,4.9,89.5,0,0,0,,0,0,14.8,10.1,76.5,1030.1,4.9,10.9,76.5,6.6,3,,2024-11-03T07:40:39,2024-11-03T17:06:30,0.07,Clear,Clear conditions throughout the day.,clear-day,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-11-04,12.6,3.4,7.5,12.6,1,5.9,6,90.6,0.014,100,4.17,rain,0,0,26,19.7,81.3,1027.2,31.3,10.2,71.5,6.2,3,,2024-11-04T07:42:29,2024-11-04T17:04:43,0.1,"Rain, Partially cloudy",Partly cloudy throughout the day with morning rain.,rain,"06260099999,C0449,06257099999,EHAM,D3248,EHRD,06348099999,06249099999,06240099999,EHLE,EHKD,06269099999,06235099999,06344099999" "Amsterdam,Netherlands",2024-11-05,11.6,4,7.1,11.6,1.4,5.8,5.6,90.9,0.062,100,4.17,rain,0,0,22.8,13.1,131.9,1023.6,23,8.2,58.7,5.1,3,,2024-11-05T07:44:19,2024-11-05T17:02:57,0.14,"Rain, Partially cloudy",Partly cloudy throughout the day with morning rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-11-06,11.1,7.2,9,11.1,5.7,8.4,8,93.7,0,0,0,,0,0,11.5,8.8,161.3,1029.9,92.8,4.2,22.6,1.8,1,,2024-11-06T07:46:09,2024-11-06T17:01:13,0.17,Overcast,Cloudy skies throughout the day.,cloudy,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-11-07,9.1,4.1,7,8.6,0.5,5,6,93.4,0,0,0,,0,0,23.6,16.3,105.2,1032.6,95.8,4.1,15.5,1.3,1,,2024-11-07T07:47:58,2024-11-07T16:59:31,0.21,Overcast,Cloudy skies throughout the day.,cloudy,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-11-08,5,4.1,4.6,3.2,0.3,1.5,3.5,93,0,0,0,,0,0,25.9,17.8,107.5,1028.1,93.4,6.2,8.8,0.8,0,,2024-11-08T07:49:48,2024-11-08T16:57:51,0.24,Overcast,Cloudy skies throughout the day.,cloudy,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-11-09,7.9,5.1,6.3,6.9,3.1,5,5.2,92.8,0.22,100,25,rain,0,0,17.2,12,131.9,1024.9,93.3,5.6,9.8,0.8,1,,2024-11-09T07:51:37,2024-11-09T16:56:13,0.25,"Rain, Overcast",Cloudy skies throughout the day with rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-11-10,10.1,7.9,8.9,10.1,6.1,7.8,8.2,95.2,0.404,100,8.33,rain,0,0,17,11.9,181.3,1026.8,89,5.9,6.4,0.6,0,,2024-11-10T07:53:26,2024-11-10T16:54:37,0.31,"Rain, Partially cloudy",Partly cloudy throughout the day with rain clearing later.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-11-11,11.8,9.1,10.5,11.8,7.4,10.1,8.2,85.8,2.623,100,29.17,rain,0,0,42.4,29.4,323.1,1028.5,52.6,10.3,39.2,3.4,2,,2024-11-11T07:55:14,2024-11-11T16:53:03,0.35,"Rain, Partially cloudy",Partly cloudy throughout the day with rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-11-12,10.3,7.2,8.9,10.3,5.5,7,6.6,85.9,2.552,100,8.33,rain,0,0,39.2,24.4,36,1032.6,58.8,12.5,50.7,4.4,3,,2024-11-12T07:57:02,2024-11-12T16:51:31,0.38,"Rain, Partially cloudy",Partly cloudy throughout the day with rain clearing later.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-11-13,12,5,9,12,5,9,7.6,90.8,0,0,0,,0,0,20.4,14,315.3,1032.1,55.6,10.3,22.3,2,2,,2024-11-13T07:58:50,2024-11-13T16:50:02,0.42,Partially cloudy,Partly cloudy throughout the day.,partly-cloudy-day,"06260099999,D3248,EHRD,06348099999,06249099999,C0449,06240099999,EHLE,06269099999,06257099999,06344099999,EHAM" "Amsterdam,Netherlands",2024-11-14,11.3,7.9,10.1,11.3,7.3,9.9,8.5,90.2,0.642,100,25,rain,0,0,26.7,14.7,329.1,1028.3,90,11.5,25.6,2.3,2,,2024-11-14T08:00:37,2024-11-14T16:48:34,0.45,"Rain, Partially cloudy",Partly cloudy throughout the day with rain.,rain,"06260099999,D3248,06356099999,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-11-15,10.9,7.1,9.3,10.9,6.4,8.6,8.2,92.9,0.271,100,12.5,rain,0,0,23.9,18.2,214.5,1026.2,91.2,9.2,15.1,1.4,1,,2024-11-15T08:02:23,2024-11-15T16:47:09,0.5,"Rain, Overcast",Cloudy skies throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06356099999,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-11-16,10.1,8.5,9.5,10.1,5.9,7.3,7.7,88.7,0.795,100,29.17,rain,0,0,37.2,25.7,219.7,1016.5,91.5,9.4,14.3,1.3,1,,2024-11-16T08:04:09,2024-11-16T16:45:46,0.52,"Rain, Overcast",Cloudy skies throughout the day with rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-11-17,9.9,5.9,8.2,8.4,3.2,5.2,4.3,76.8,3.553,100,54.17,rain,0,0,44.4,32.3,276.7,1009.7,58.6,14.6,30,2.7,3,,2024-11-17T08:05:54,2024-11-17T16:44:26,0.55,"Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain throughout the day.,rain,"06260099999,06356099999,C0449,06257099999,EHAM,D3248,EHRD,06348099999,06249099999,06240099999,EHLE,EHKD,06269099999,06235099999,06344099999" "Amsterdam,Netherlands",2024-11-18,8.9,3.9,5.8,5.9,1.1,3.6,2.2,78.4,2.5,100,29.17,rain,0,0,57.6,21.4,281.1,1008.6,39.6,9.8,29.1,2.6,2,,2024-11-18T08:07:39,2024-11-18T16:43:08,0.58,"Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain throughout the day.,rain,"D3248,EHRD,C0449,EHLE,EHKD,EHAM" "Amsterdam,Netherlands",2024-11-19,5,2,4,2.4,-2.1,0.7,2.9,92.5,13.5,100,62.5,rain,0.1,0,47.9,32.6,39.9,994.6,68.7,8.8,12,1.1,1,,2024-11-19T08:09:22,2024-11-19T16:41:53,0.62,"Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain or snow throughout the day.,snow,"D3248,EHRD,C0449,EHLE,EHKD,EHAM" "Amsterdam,Netherlands",2024-11-20,5.7,1.1,3.6,2.5,-2.7,-0.1,1,83.4,1,100,33.33,rain,0,0,55.4,38.7,281.9,999.2,59.2,9.3,16.3,1.5,1,,2024-11-20T08:11:05,2024-11-20T16:40:40,0.65,"Rain, Partially cloudy",Partly cloudy throughout the day with rain.,rain,"D3248,EHRD,C0449,06240099999,EHLE,EHKD,06269099999,EHAM" "Amsterdam,Netherlands",2024-11-21,4.7,0,2.1,1.3,-3.7,-1.5,-1.1,80,1.7,100,41.67,"rain,snow",0.1,0,55.4,24.5,272.8,997.8,45,9.9,31.5,2.8,4,,2024-11-21T08:12:46,2024-11-21T16:39:30,0.68,"Snow, Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain or snow throughout the day.,snow,"D3248,06260099999,EHRD,06348099999,06249099999,C0449,06240099999,EHLE,EHKD,06269099999,EHAM,06257099999" "Amsterdam,Netherlands",2024-11-22,6.5,1.4,4,2.7,-2.5,0.1,0.9,80.3,11.495,100,75,"rain,snow",0,0,59.7,40.2,265.1,1000.9,64.2,13.7,10.7,0.9,1,,2024-11-22T08:14:27,2024-11-22T16:38:23,0.71,"Snow, Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain or snow throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-11-23,8.9,2,4.3,4.9,-2.7,-0.2,2.2,86.2,3.886,100,37.5,rain,0,0,54.8,39.2,181.5,1010.2,58.4,14.1,12.9,1.2,1,,2024-11-23T08:16:06,2024-11-23T16:37:18,0.75,"Rain, Partially cloudy",Becoming cloudy in the afternoon with a chance of rain throughout the day.,rain,"06260099999,D3248,06356099999,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-11-24,16.4,9.9,14,16.4,6.2,13.8,8.4,69.5,9.312,100,12.5,rain,0,0,61.2,42.3,192.7,1002,64.9,17.1,27.9,2.6,2,,2024-11-24T08:17:44,2024-11-24T16:36:17,0.78,"Rain, Partially cloudy",Partly cloudy throughout the day with rain clearing later.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-11-25,16.3,7.3,12.2,16.3,4.5,11.7,8.7,80.2,2.626,100,37.5,rain,0,0,58.3,37.5,203,1001.9,53.3,14.4,8.9,0.8,1,,2024-11-25T08:19:20,2024-11-25T16:35:18,0.81,"Rain, Partially cloudy",Partly cloudy throughout the day with rain in the morning and afternoon.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-11-26,9.8,3.9,7.4,6.6,1,4.4,5.3,86.7,2.521,100,20.83,rain,0,0,41.6,29.7,214.8,1014.8,40.9,12.2,26.7,2.3,2,,2024-11-26T08:20:56,2024-11-26T16:34:22,0.84,"Rain, Partially cloudy",Partly cloudy throughout the day with rain.,rain,"06260099999,C0449,06257099999,EHAM,D3248,EHRD,06348099999,06249099999,06240099999,EHLE,EHKD,06269099999,06235099999,06344099999" "Amsterdam,Netherlands",2024-11-27,11,3.7,7.5,11,1.9,4.5,6.2,91.6,9.14,100,54.17,rain,0,0,84.1,56.1,218.9,1011.2,76.1,8.3,3.5,0.2,0,,2024-11-27T08:22:29,2024-11-27T16:33:29,0.88,"Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-11-28,9.7,5.4,7.3,7,3.4,5,3.8,78.2,5.758,100,25,rain,0,0,72.9,27.7,308.4,1024.3,53.3,15.9,31.2,2.6,2,,2024-11-28T08:24:01,2024-11-28T16:32:39,0.91,"Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-11-29,7.9,0.6,3.8,5.2,-1.8,1.8,1.6,85.9,0.163,100,4.17,"rain,snow",0,0,25.6,20.3,149.5,1030.7,44.3,13,42.6,3.8,2,,2024-11-29T08:25:31,2024-11-29T16:31:53,0.94,"Snow, Rain, Partially cloudy",Partly cloudy throughout the day with morning rain or snow.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-11-30,6.2,1.3,4,3.7,-3.3,0.6,1.6,84.2,0,0,0,,0,0,27.6,18,164.9,1026.7,56.6,13.5,30.8,2.8,2,,2024-11-30T08:27:00,2024-11-30T16:31:09,0.97,Partially cloudy,Partly cloudy throughout the day.,partly-cloudy-day,"06260099999,C0449,06257099999,EHAM,D3248,EHRD,06348099999,06249099999,06240099999,EHLE,EHKD,06269099999,06235099999,06344099999" "Amsterdam,Netherlands",2024-12-01,8.1,3.1,6.1,5,-0.8,2.9,3.1,81.4,1,100,12.5,rain,0,0,41,25.6,170.8,1018.7,82.2,9.4,35.3,3.1,2,,2024-12-01T08:28:26,2024-12-01T16:30:28,0,"Rain, Partially cloudy",Partly cloudy throughout the day with rain.,rain,"D3248,06260099999,EHRD,06348099999,06249099999,C0449,06240099999,EHLE,EHKD,06269099999,EHAM,06257099999" "Amsterdam,Netherlands",2024-12-02,11,7.5,9.6,11,5.1,8.3,8.6,93.7,1.266,100,50,rain,0,0,36.4,24.5,212.1,1010.9,86,10.8,13,1.2,1,,2024-12-02T08:29:51,2024-12-02T16:29:51,0.04,"Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-12-03,8.7,3,5.9,6.1,-0.2,3.1,2.9,81.1,1.775,100,25,rain,0,0,41.2,25.5,319,1018.2,51.2,13.2,18.2,1.8,1,,2024-12-03T08:31:13,2024-12-03T16:29:17,0.08,"Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain throughout the day.,rain,"06260099999,C0449,06257099999,EHAM,D3248,EHRD,06348099999,06249099999,06240099999,EHLE,EHKD,06269099999,06235099999,06344099999" "Amsterdam,Netherlands",2024-12-04,6.1,1.2,4.2,4.4,-0.3,2.1,2.8,91,0.971,100,25,rain,0,0,20,14.3,187.4,1024.3,58,13.2,10.3,0.8,1,,2024-12-04T08:32:34,2024-12-04T16:28:47,0.11,"Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain throughout the day.,rain,"06260099999,C0449,EHAM,06257099999,D3248,EHRD,06348099999,06249099999,06240099999,EHLE,EHKD,06269099999,06235099999,06344099999" "Amsterdam,Netherlands",2024-12-05,10.8,4.2,6.9,10.8,0,3.2,6.1,94.5,5.233,100,58.33,rain,0,0,50.4,36.9,182.3,1011.1,91.9,6.3,4.8,0.4,0,,2024-12-05T08:33:52,2024-12-05T16:28:20,0.15,"Rain, Overcast",Cloudy skies throughout the day with rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-12-06,11.1,5.5,8.8,11.1,2.4,5.2,5.2,78.3,14.869,100,37.5,rain,0,0,79.6,51,283.5,1007.2,75.6,11,13.9,1.1,1,,2024-12-06T08:35:08,2024-12-06T16:27:56,0.18,"Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-12-07,10,6,7.5,10,1.6,3.6,5.9,89.8,2.271,100,54.17,rain,0,0,54.7,37.1,180.5,993.5,80.7,11.2,17.3,1.5,2,,2024-12-07T08:36:21,2024-12-07T16:27:35,0.22,"Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-12-08,7.3,6.1,6.9,4.3,1.7,3.3,5.5,90.9,7.263,100,25,rain,0,0,42,26.5,43,1008.2,84.3,12.1,16.5,1.5,1,,2024-12-08T08:37:32,2024-12-08T16:27:18,0.25,"Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-12-09,7,5.9,6.4,3.1,1.3,2.2,3.5,81.7,0.153,100,8.33,rain,0,0,52.8,34.4,39.5,1027.2,74.2,15.4,8.8,0.8,1,,2024-12-09T08:38:40,2024-12-09T16:27:05,0.29,"Rain, Partially cloudy",Partly cloudy throughout the day with rain clearing later.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-12-10,5.8,4.8,5,1.6,0.2,0.7,2.4,83.1,0.014,100,4.17,rain,0,0,40.8,27.2,51.9,1031.3,92.3,14.4,6.8,0.6,0,,2024-12-10T08:39:45,2024-12-10T16:26:55,0.33,"Rain, Overcast",Cloudy skies throughout the day with morning rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-12-11,4.8,3.1,3.9,2.5,0,1,1.6,85,0,0,0,,0,0,26,18,59.8,1032,96.1,11.4,6.9,0.6,0,,2024-12-11T08:40:48,2024-12-11T16:26:49,0.36,Overcast,Cloudy skies throughout the day.,cloudy,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-12-12,5,3.1,4.1,5,0.8,2,3.2,94,0.23,100,25,rain,0,0,20.4,12.3,129.6,1031.4,94,6.5,7.9,0.6,0,,2024-12-12T08:41:48,2024-12-12T16:26:46,0.4,"Rain, Overcast",Cloudy skies throughout the day with rain in the morning and afternoon.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-12-13,3.9,0.6,2.1,1,-3.1,-1.4,0.5,89.2,0.049,100,4.17,rain,0,0,25.6,14.9,147.1,1027.1,92,8,5.5,0.4,0,,2024-12-13T08:42:45,2024-12-13T16:26:46,0.43,"Rain, Overcast",Cloudy skies throughout the day with morning rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-12-14,6.9,1.9,4.5,4.1,-2.4,0.9,3,90.1,0.716,100,50,rain,0,0,31.1,23.5,227.9,1018.6,82,7.9,8.3,0.7,0,,2024-12-14T08:43:39,2024-12-14T16:26:51,0.47,"Rain, Partially cloudy",Partly cloudy throughout the day with rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-12-15,10.8,5.1,8.6,10.8,2.1,6.4,7.2,90.8,1.237,100,25,rain,0,0,44,32.2,244.8,1023.3,82.5,11.2,7.3,0.6,0,,2024-12-15T08:44:30,2024-12-15T16:26:58,0.5,"Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-12-16,10.9,9.7,10.4,10.9,6.4,9.1,8.2,86.5,0.056,100,16.67,rain,0,0,48.2,33.1,249.2,1027.7,78.8,13.5,6.8,0.5,0,,2024-12-16T08:45:18,2024-12-16T16:27:10,0.53,"Rain, Partially cloudy",Partly cloudy throughout the day with rain in the morning and afternoon.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-12-17,9.8,7.3,8.6,6.9,4.5,5.8,7.1,90.6,0,0,0,,0,0,34.4,24,212.9,1025,79.3,11.5,12.2,1.1,1,,2024-12-17T08:46:03,2024-12-17T16:27:25,0.57,Partially cloudy,Partly cloudy throughout the day.,partly-cloudy-day,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-12-18,12.8,6.4,10.2,12.8,2,8.4,8.4,88.4,0.254,100,25,rain,0,0,71.4,48.4,210.9,1009.3,80.5,11.8,5.7,0.5,0,,2024-12-18T08:46:45,2024-12-18T16:27:43,0.6,"Rain, Partially cloudy",Partly cloudy throughout the day with rain.,rain,"06260099999,D3248,EHRD,06348099999,06249099999,C0449,06240099999,EHLE,EHKD,06269099999,06257099999,EHAM" "Amsterdam,Netherlands",2024-12-19,12.8,5.3,8.5,12.8,1.1,5.7,6,84.8,15.598,100,70.83,rain,0,0,67.2,43.4,267.6,999.9,71.3,11.6,7.5,0.6,0,,2024-12-19T08:47:23,2024-12-19T16:28:05,0.63,"Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-12-20,7.8,5,6.4,4.2,1.6,2.8,3.1,79.4,2.074,100,25,rain,0,0,48.1,27.6,248.1,1015.9,53.5,12.9,18.7,1.6,1,,2024-12-20T08:47:58,2024-12-20T16:28:30,0.66,"Rain, Partially cloudy",Becoming cloudy in the afternoon with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-12-21,10.7,7.1,8.5,10.7,3.1,5.5,7.1,91,3.895,100,54.17,rain,0,0,54.8,41.3,231,1008.6,75.3,10.2,9.5,0.9,1,,2024-12-21T08:48:30,2024-12-21T16:28:59,0.7,"Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-12-22,9.1,3.5,6.1,5.2,-1.1,1.7,2.6,78,2.824,100,37.5,rain,0,0,57.2,40.4,273.1,998.3,54.3,12.7,17.9,1.4,2,,2024-12-22T08:48:58,2024-12-22T16:29:32,0.75,"Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-12-23,8,3.2,6.4,4.4,1,2.8,3,79.1,10.719,100,37.5,rain,0,0,58.8,39,320.4,1013.9,44.9,12,18.8,1.6,1,,2024-12-23T08:49:23,2024-12-23T16:30:08,0.76,"Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-12-24,8.9,3.3,6.1,7,0.3,3.3,5.4,95.2,1.101,100,62.5,rain,0,0,27.6,19.6,207.6,1024.4,82.7,6.2,6.8,0.6,1,,2024-12-24T08:49:44,2024-12-24T16:30:47,0.79,"Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-12-25,9.8,8.9,9.3,8.5,6.6,7.5,8.9,97.6,0.606,100,29.17,rain,0,0,22.4,15.5,214.6,1032.4,95,3.6,4.4,0.5,0,,2024-12-25T08:50:02,2024-12-25T16:31:29,0.82,"Rain, Overcast",Cloudy skies throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-12-26,8.7,5.2,7.4,7.1,3.5,5.7,7,97.2,0.022,100,4.17,rain,0,0,20.4,14.3,176,1035.8,99,3.5,6.7,0.5,0,,2024-12-26T08:50:16,2024-12-26T16:32:15,0.86,"Rain, Overcast",Cloudy skies throughout the day with morning rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-12-27,5.1,2,3.3,4,0.1,2.1,3,98.3,0.083,100,8.33,rain,0,0,16.7,10.9,143.4,1033.4,92.6,2.1,18.4,1.6,1,,2024-12-27T08:50:27,2024-12-27T16:33:04,0.89,"Rain, Overcast",Cloudy skies throughout the day with rain clearing later.,rain,"D3248,06260099999,06348099999,06249099999,06240099999,C0449,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-12-28,3.1,0.9,1.8,1.3,-1.9,-0.7,1.4,97.8,0,0,0,,0,0,20.2,14,196,1029.5,91.4,2.4,11.8,0.9,1,,2024-12-28T08:50:35,2024-12-28T16:33:56,0.92,Overcast,Cloudy skies throughout the day.,cloudy,"D3248,06260099999,06348099999,06249099999,06240099999,C0449,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-12-29,7,3.3,5.4,3.9,-0.2,2.2,4.8,96,0.316,100,33.33,rain,0,0,34.8,22.9,211.2,1027.7,91.6,4.3,4.6,0.5,0,,2024-12-29T08:50:39,2024-12-29T16:34:51,0.95,"Rain, Overcast",Cloudy skies throughout the day with rain.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-12-30,8.6,6.3,7.7,4.8,2.5,4.1,5.8,88.2,0.117,100,20.83,rain,0,0,46,31.1,229.1,1026.5,86.6,10.8,8.3,0.7,0,,2024-12-30T08:50:39,2024-12-30T16:35:50,0,"Rain, Partially cloudy",Partly cloudy throughout the day with rain clearing later.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" "Amsterdam,Netherlands",2024-12-31,8,3.8,5.6,4.5,-1.5,0.9,4,89.9,0,0,0,,0,0,54.8,39.5,204.2,1021.9,94.4,9.6,7,0.6,0,,2024-12-31T08:50:36,2024-12-31T16:36:51,0.02,Overcast,Cloudy skies throughout the day.,cloudy,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06235099999,06257099999,06344099999" ================================================ FILE: example/assets/data/bitcoin_2023-01-01_2023-12-31.csv ================================================ Start,End,Open,High,Low,Close,Volume,Market Cap 2023-12-31,2024-01-01,37100.9041846051,37694.17078259951,36977.382178671105,37174.6564186542,22825350707.305206,731910439496.2489 2023-12-30,2023-12-31,37039.794687585294,37479.209036563574,36572.16308778603,37152.43478337427,30594016263.905033,726109862996.9537 2023-12-29,2023-12-30,37473.67123601244,37887.51837863061,36698.41437539069,37027.90910611624,31014082955.47395,731231275147.4174 2023-12-28,2023-12-29,38241.00437565481,38500.50623772924,37260.03891427415,37498.622152963035,31118209903.51097,740038242927.5472 2023-12-27,2023-12-28,37425.46860005459,38407.71946506079,37112.99226116585,38196.07687770177,29646539917.820133,738226351479.7245 2023-12-26,2023-12-27,38364.96658830988,38364.96658830988,36733.70134615216,37435.0034776331,28343694802.52816,736382893906.3896 2023-12-25,2023-12-26,37833.46979741687,38496.614810314924,37650.00044020673,38398.246216423235,24222883364.049984,746958065848.8574 2023-12-24,2023-12-25,38475.94270269319,38674.6960372591,37725.89207892026,37873.53741316923,18441594555.91457,751912373357.4268 2023-12-23,2023-12-24,38700.12237746846,38716.8678411382,38166.85595555673,38498.12031730101,23689451646.33951,752865941432.9445 2023-12-22,2023-12-23,38616.4919045984,39053.898910928576,38236.311772008135,38693.193523678725,31635908335.28064,754925466509.3234 2023-12-21,2023-12-22,38440.60290712519,38919.56542792496,38118.477236910454,38605.6099944534,36900311517.69093,753656559272.9637 2023-12-20,2023-12-21,37215.8861801502,38984.67200197213,37175.78334786016,38426.48107551306,32216573879.423225,744105485376.4031 2023-12-19,2023-12-20,37556.46531611245,38134.870535203336,36842.32675664493,37241.62946919874,35321133560.19876,736112600653.7316 2023-12-18,2023-12-19,36426.79802435224,37606.83376913799,35797.63697032126,37563.77274768231,29230001499.750675,713473537007.7267 2023-12-17,2023-12-18,37222.12831145507,37345.72075046442,36359.728128329065,36492.03666041573,21485318327.465748,722965909668.2908 2023-12-16,2023-12-17,36919.785531285495,37591.80511168045,36736.07846244597,37192.72250248717,26013472004.2771,728624719756.068 2023-12-15,2023-12-16,37877.340799239326,37927.057746317674,36750.499634628424,36936.354912266805,33576481436.623196,731848194136.2754 2023-12-14,2023-12-15,37740.55976686652,38172.61385946841,36923.113494096826,37861.44933660847,37815134507.75901,738173146604.9332 2023-12-13,2023-12-14,36530.431490628005,38169.70849510931,35827.42135706928,37775.78510868704,33444797308.24787,717054467420.6472 2023-12-12,2023-12-13,36324.81973534772,37017.82837220359,35833.34653953496,36548.25986283159,43182665596.03501,715787192578.017 2023-12-11,2023-12-12,38552.8468168652,38566.00019369096,35546.86000545857,36291.62814857858,38616502126.022385,722335599913.6361 2023-12-10,2023-12-11,38486.04104487468,38767.23629416374,38394.847820536525,38529.251736615515,22433123426.065327,754829380952.9645 2023-12-09,2023-12-10,38888.77736985289,39041.37062764675,38412.420872841896,38475.16793886409,30769814401.870342,758255680218.3878 2023-12-08,2023-12-09,38100.0061628941,39278.77411232315,37972.22295590009,38913.68426613138,34997013565.24524,751988730200.5594 2023-12-07,2023-12-08,38532.67654490549,38778.646452374036,37867.55060176259,38077.87256895839,36189011173.544044,750162704788.3457 2023-12-06,2023-12-07,38801.83654244033,38975.89427995387,38257.48571529191,38486.85982937588,51559637064.33903,755648237085.3834 2023-12-05,2023-12-06,36969.9338809505,39102.67381562382,36482.48417456838,38813.62527843076,43605395400.09655,730195971787.5426 2023-12-04,2023-12-05,35192.0798006744,37317.47708724017,35192.0798006744,36973.66683394523,39795773948.88418,713444181714.2103 2023-12-03,2023-12-04,34735.594235052784,35312.09776110862,34589.89461451097,35164.654921951354,23114925704.107597,681002113706.5303 2023-12-02,2023-12-03,34056.645800868086,34910.770097637855,34020.61047868079,34741.11442733508,24968442438.03418,669739373202.578 2023-12-01,2023-12-02,33202.4686792918,34300.124138295345,33125.24761628061,34035.07567153536,25657764864.129047,661256575066.8969 2023-11-30,2023-12-01,33327.751512110095,33582.437512655946,33042.7000519444,33204.810579047924,24769328951.891644,650494593059.8873 2023-11-29,2023-11-30,33308.39122051716,33760.31624450842,33123.80373823548,33286.680225033684,60203429986.63117,653269996178.6052 2023-11-28,2023-11-29,32787.53862813978,33696.67996090964,32483.8840319414,33259.28175871389,66111478083.01223,644450541405.1779 2023-11-27,2023-11-28,32965.285297975934,33057.74631767078,32358.75086940828,32811.97890529393,54203204738.04888,639679531300.7247 2023-11-26,2023-11-27,33262.61852565965,33292.52617028957,32732.662458290415,32996.231830467594,33237734083.827305,647139105881.2972 2023-11-25,2023-11-26,33201.33294595142,33339.540248100515,33102.4184957256,33267.161459021154,61553322629.37676,649919275526.9122 2023-11-24,2023-11-25,32834.35021085902,33789.24663021755,32805.97448561845,33201.09523432204,56329227390.823845,647902555273.643 2023-11-23,2023-11-24,32932.128927744474,33134.38630781015,32498.657369500717,32834.49107700977,71524721313.82204,642293254021.1082 2023-11-22,2023-11-23,31491.64927850119,33279.55768028666,31491.64927850119,32970.40050007484,93379003831.60822,630926624123.9825 2023-11-21,2023-11-22,32970.43571661252,33105.50874690755,31643.987216396825,31643.987216396825,74216217634.24104,639043650485.0344 2023-11-20,2023-11-21,32883.43326025902,33205.18915682805,32494.220085752277,32985.47317820449,53095880190.50191,641665094312.1991 2023-11-19,2023-11-20,32193.955081306183,32998.195152443586,32047.058098483052,32917.34678605074,35501561032.696236,630488133929.0431 2023-11-18,2023-11-19,32242.42184129668,32405.58006039637,31895.767852583576,32187.396001162153,57432245152.28768,627914596432.3691 2023-11-17,2023-11-18,31841.147002632435,32285.799811591525,31643.652659288804,32147.42523088843,78771004869.51784,625600863201.4542 2023-11-16,2023-11-17,33330.61285579709,33341.11618816196,31296.10064886471,31891.69153834641,89245282004.02216,635629883084.7542 2023-11-15,2023-11-16,31299.771972918486,33335.67523308947,31158.166274882686,33335.67523308947,76584212183.93465,623679337045.7006 2023-11-14,2023-11-15,32105.860912284414,32340.526311155718,30946.55890406135,31289.92895063522,64335580554.127945,623189647313.1923 2023-11-13,2023-11-14,32631.71425301322,32872.6658038615,32059.58638176488,32162.98213641126,46106918187.29965,634269491678.048 2023-11-12,2023-11-13,32692.233873026777,32756.706549395603,32358.988581037655,32606.13824251869,45702663616.12886,637760357716.0565 2023-11-11,2023-11-12,32831.60332091951,32924.19640263068,32389.19556623791,32647.640932181755,67547247019.99921,637684718060.7434 2023-11-10,2023-11-11,32302.58049179895,32967.09894966676,32037.743324265077,32858.03333245292,107693014611.56467,635362285488.1392 2023-11-09,2023-11-10,31361.110377433244,33364.218236884044,31330.965021173946,32377.40683024749,95049049236.81912,629424558285.6077 2023-11-08,2023-11-09,31169.708495109306,31606.28791280386,30956.366709806927,31413.4069358971,65338994513.963486,608786880752.1049 2023-11-07,2023-11-08,30851.148499335293,31565.55118283546,30433.902960830408,31187.096660591815,53116956739.91035,601374828234.8928 2023-11-06,2023-11-07,30824.956199431257,31057.78153420847,30583.969432045295,30831.576908516243,55593478416.07753,601906249554.1104 2023-11-05,2023-11-06,30869.1089335552,31088.30546824789,30466.196525888558,30834.226952977122,37939776675.62549,602564889724.8782 2023-11-04,2023-11-05,30565.137388517654,30998.036678024004,30456.529586293727,30831.79701187678,49263838985.33085,597828761317.721 2023-11-03,2023-11-04,30752.154811899672,30752.154811899672,30049.162286609793,30576.785258357326,69933412493.12483,593762736096.3765 2023-11-02,2023-11-03,31185.494308127098,31591.629028992018,30334.944489932474,30742.320593750832,87193998967.29948,604424394739.1691 2023-11-01,2023-11-02,30500.136464083538,31270.013998573733,30102.73544456477,31191.516336071425,61577149209.30313,594761359227.5613 2023-10-31,2023-11-01,30352.02451071023,30519.505559810892,30012.93327346522,30506.35218298513,56096644493.466354,591237341407.3635 2023-10-30,2023-10-31,30393.940994691107,30661.577876971027,30006.206914767175,30362.615884419323,46228472890.55691,592151523212.1005 2023-10-29,2023-10-30,30006.250935439286,30565.181409189758,29897.651937349783,30405.58886453079,32373537300.913456,589718265254.667 2023-10-28,2023-10-29,29838.998793833587,30277.383059084546,29813.167463440837,30011.88558146906,48381101793.12706,585989312580.1788 2023-10-27,2023-10-28,30067.554123416357,30149.75832651013,29440.303566554856,29854.837431657907,60334747804.30696,583852275406.8123 2023-10-26,2023-10-27,30369.315830714102,30638.95125150771,29731.59715802541,30065.115378181596,75784058731.1894,590172065644.0789 2023-10-25,2023-10-26,29859.40677742268,30899.22787741123,29745.648556562166,30342.771365433215,106577812777.00316,590122722188.2163 2023-10-24,2023-10-25,29109.048008945003,30802.98988404955,28947.289647218335,29807.277497512834,152605324417.8083,586042854374.1233 2023-10-23,2023-10-24,26402.753933247055,29989.70796686124,26317.468283105747,29002.104188126745,52209234683.26393,531074280079.6341 2023-10-22,2023-10-23,26332.972363822053,26555.179912486903,26190.451035806418,26381.597598232132,37718159919.575455,514055713328.8206 2023-10-21,2023-10-22,26121.056848295964,26592.90562848314,25946.4972751204,26319.220305855633,47749099551.780914,512008396415.1675 2023-10-20,2023-10-21,25283.545953179615,26432.925701909622,25175.255099794867,26117.482369720823,57020497039.10846,505686274857.8469 2023-10-19,2023-10-20,24933.326290025798,25413.362915224992,24812.753669123023,25264.317723603006,38390510017.338036,489396525224.86316 2023-10-18,2023-10-19,25000.149670285165,25350.659869874897,24819.295140998216,24935.712210454032,45873609114.43736,488019880360.7977 2023-10-17,2023-10-18,25092.50504036696,25176.707781974415,24755.236258947207,25010.27442486992,66287448095.436455,487757459872.5621 2023-10-16,2023-10-17,23906.83464955143,25788.841640034163,23880.37822561475,25093.63196957291,40863666312.60596,479477459574.1294 2023-10-15,2023-10-16,23641.275542994994,24010.080733912648,23615.514645677613,23900.24915700413,18395967060.974087,462817239100.3731 2023-10-14,2023-10-15,23649.665883098707,23743.03372863897,23601.718567039083,23646.760518739604,25956344664.72817,461824260830.513 2023-10-13,2023-10-14,23559.564371428824,23820.42207020417,23504.441685815662,23631.811098491857,25524542061.776657,460709148210.67285 2023-10-12,2023-10-13,23662.264599455906,23708.415872093537,23399.100217462124,23540.890802320773,23928365175.032307,459794710703.0548 2023-10-11,2023-10-12,24113.75822086052,24182.932305010436,23373.330516010323,23634.22343132335,23076947516.552383,464052681627.1808 2023-10-10,2023-10-11,24289.5943935272,24394.275551799128,24037.68169532413,24114.823521125523,22640158012.774193,472752475967.7028 2023-10-09,2023-10-10,24578.510868703943,24629.522023542257,24035.251754223784,24285.412429676977,20386854633.518124,475333242579.8633 2023-10-08,2023-10-09,24613.392849282027,24735.206853138236,24420.670346794857,24592.095648116356,14807557012.392187,479473397980.7412 2023-10-07,2023-10-08,24593.275402128842,24663.734889904303,24532.720565577598,24615.144872031906,22966215201.340775,479653811431.45325 2023-10-06,2023-10-07,24131.991583247494,24849.64299234921,23989.18852293037,24592.588679643963,21332203552.015804,475783478783.8519 2023-10-05,2023-10-06,24462.745305195323,24714.481920709968,24100.76331845435,24143.22565876936,17334073687.856873,474547071141.5178 2023-10-04,2023-10-05,24147.34599367863,24492.952290395573,23995.976510569366,24456.652844175627,19514701948.7279,472505565004.0212 2023-10-03,2023-10-04,24209.15982145216,24355.343669387144,23995.43065423523,24140.258665469308,27502514213.382385,471926706796.8163 2023-10-02,2023-10-03,24643.714288229756,25073.26800665593,24100.64006057245,24307.449178134055,27752533373.83541,481516356300.7094 2023-10-01,2023-10-02,23738.173846438287,24646.20585827105,23738.173846438287,24632.295325885036,16582476253.818794,466422622641.34186 2023-09-30,2023-10-01,23689.275683861146,23842.3179525105,23671.561765405037,23742.20614000335,19192072934.68763,462960269129.40625 2023-09-29,2023-09-30,23789.99498164338,23973.032936266874,23509.63612512436,23692.533213597108,29559383346.88192,462775165650.66345 2023-09-28,2023-09-29,23216.79300599562,23978.024880483877,23181.91982955196,23789.04413512586,25508406408.449966,457996126198.6249 2023-09-27,2023-09-28,23079.941540547443,23602.598980481238,23042.6208147346,23214.77685921309,25079537938.08517,451939455383.10156 2023-09-26,2023-09-27,23159.05549245926,23231.038095489643,22999.98239173116,23082.133770018405,27117977046.606644,450494273120.35095 2023-09-25,2023-09-26,23112.296734546544,23260.505533398486,22916.34311472668,23162.313022195227,26174674174.282246,449741019393.3438 2023-09-24,2023-09-25,23397.841226239845,23514.724914820003,23101.00983421815,23114.47135574866,18100283816.081142,455797753127.80634 2023-09-23,2023-09-24,23401.47733375593,23447.83110148526,23352.87851174912,23401.970365283538,24198618520.692043,456094622917.11304 2023-09-22,2023-09-23,23391.282146095808,23535.766796087446,23328.711162762036,23399.285104284976,28851926657.84297,456670057967.75085 2023-09-21,2023-09-22,23881.188205981533,23892.92411716542,23258.647861035544,23381.12217497337,30564553490.435776,459535430591.6311 2023-09-20,2023-09-21,23953.927964572165,24075.601102277633,23631.74066541648,23878.432511907595,31388357323.28207,465402214345.0409 2023-09-19,2023-09-20,23567.954711532537,24177.103968023388,23486.22593169753,23957.106257098338,31555589119.658092,464048509925.20953 2023-09-18,2023-09-19,23355.581381016527,24119.55134130988,23255.900971096027,23541.146122218997,24155391190.807037,460755193940.43945 2023-09-17,2023-09-18,23391.079651004115,23427.89854115493,23269.37129676096,23358.60119912311,18621474753.383358,455117398247.3353 2023-09-16,2023-09-17,23418.988757120347,23526.311155718726,23298.627435443686,23377.732583221084,24112777794.138798,455641189245.16595 2023-09-15,2023-09-16,23348.53807347931,23603.752322090455,23101.212329309845,23443.376209467966,31174206698.82744,454658876788.8168 2023-09-14,2023-09-15,23086.22769252441,23534.182051891574,23038.20994338942,23355.801484377065,31858953598.34757,453509735027.5423 2023-09-13,2023-09-14,22749.927365891024,23220.367484570757,22713.68074447761,23086.21888838999,33302428243.819138,447496303003.1055 2023-09-12,2023-09-13,22154.160393720893,23255.548805719165,22129.033394081864,22762.464453307275,37717367105.9563,443820194977.5929 2023-09-11,2023-09-12,22751.29200672636,22796.32515429246,22013.452717396092,22136.789836507225,26237940449.99949,437426260713.0176 2023-09-10,2023-09-11,22804.08159671782,22866.64377591717,22595.097857954097,22751.33602739847,18796303396.46387,442848670825.67865 2023-09-09,2023-09-10,22811.952492890665,22814.787424174396,22729.792310468998,22796.659711400476,21618150728.811142,443753149079.4911 2023-09-08,2023-09-09,23111.57479552398,23227.08503913438,22649.0495936892,22811.961297025082,29796500435.95445,446321289814.0323 2023-09-07,2023-09-08,22682.804645061326,23247.11444494335,22559.705237579572,23067.677381298257,28494914658.3305,443059951530.9198 2023-09-06,2023-09-07,22703.344690666738,22804.583432379848,22443.182518510694,22689.689478178956,27257283002.285652,441197002289.6528 2023-09-05,2023-09-06,22737.566361163204,22749.522375707635,22515.10349260013,22704.718135636496,27041739259.51238,441364803457.4081 2023-09-04,2023-09-05,22865.402392963737,22935.967530352256,22607.001047691996,22735.9287921608,22869495931.358814,444115648353.52606 2023-09-03,2023-09-04,22775.846737627995,22941.170773795377,22741.140839738342,22873.669475185547,21893229754.66974,444319628243.0855 2023-09-02,2023-09-03,22720.85611403115,22843.198365952656,22679.467878115567,22764.788744794558,31390071976.39734,442763597712.851 2023-09-01,2023-09-02,22837.299595890232,23006.840812445527,22363.293802769786,22729.255258269284,39071061119.61872,444017520548.75806 2023-08-31,2023-09-01,24035.28697076147,24160.772298671458,22738.834156519904,22834.79922171452,32844170485.05034,460528503233.1136 2023-08-30,2023-08-31,24403.114902758338,24423.065071357512,23845.24972927287,24049.576080927607,45220944141.72896,468768582595.0272 2023-08-29,2023-08-30,22987.260417492056,24656.34822112464,22822.975269186412,24403.502284672883,35520389861.394196,457624962436.01416 2023-08-28,2023-08-29,22980.393192643267,23053.546745551714,22800.982541401445,23003.34557108018,24304377968.014336,446133914377.1852 2023-08-27,2023-08-28,22906.051081587917,23040.446193532483,22871.12508033773,22968.56043598074,18955229866.543385,446700125105.1025 2023-08-26,2023-08-27,22943.583106626873,22976.792301664864,22890.94318692058,22909.25578651735,24865956559.17797,446225808983.5095 2023-08-25,2023-08-26,23049.276740357276,23100.217462120214,22744.222286785876,22942.00716656542,30037425915.061382,446408112637.7328 2023-08-24,2023-08-25,23275.43734537739,23380.822834403036,22859.917417219127,23015.35441043114,42977907165.9204,450521201963.97314 2023-08-23,2023-08-24,22939.16343114727,23575.719958092322,22719.121699550113,23258.234066717734,43383092390.47352,448500598651.46344 2023-08-22,2023-08-23,23002.482765906872,23008.76891788384,22533.116751626567,22933.08857839642,37643078943.280106,445245386861.9693 2023-08-21,2023-08-22,23058.045658241113,23068.4697533962,22784.642067915094,22999.436535397024,34314443969.299377,446683015899.0043 2023-08-20,2023-08-21,22978.755623640864,23126.11042145392,22892.536735250877,23049.223915550745,31910613041.00184,447634312271.3188 2023-08-19,2023-08-20,22932.07610293794,23113.72300432283,22727.21269908349,22970.54136622558,42014144416.708374,445536766622.16 2023-08-18,2023-08-19,23448.306524744025,23581.196129702512,22645.580764727118,22942.042383103108,61897504430.57366,450366064187.7128 2023-08-17,2023-08-18,25294.700791491687,25328.12128575579,22413.891163290282,23427.836912213977,42505575972.25768,482804368735.17365 2023-08-16,2023-08-17,25708.06370671668,25758.634654833913,25299.992076279024,25299.992076279024,34846631160.32317,498614353877.3042 2023-08-15,2023-08-16,25911.377582912937,25931.94404092162,25638.66951920622,25708.77684160482,38178497714.62096,502344658978.5694 2023-08-14,2023-08-15,25799.45942614652,26135.979856140446,25674.678428990257,25911.403995316203,34236979779.630074,503449625186.10657 2023-08-13,2023-08-14,25910.75248936901,25942.711497319142,25780.019897343795,25783.00449891269,26565685412.909626,503182685149.8617 2023-08-12,2023-08-13,25907.05475291197,25950.44152734124,25843.612160270466,25910.62923148711,26805714951.75579,503615627774.48846 2023-08-11,2023-08-12,25936.997614079573,26009.18271220165,25788.225350624656,25907.05475291197,30873053717.781155,503522268480.82275 2023-08-10,2023-08-11,26044.39924988775,26157.04814981115,25866.177156792834,25933.229444547163,37613754767.60387,505248823426.1632 2023-08-09,2023-08-10,26210.304358926955,26475.467279434426,25897.343792645028,26057.5086060414,42095444658.87709,508685549050.27856 2023-08-08,2023-08-09,25717.92433726878,26524.259792398512,25645.395877904266,26207.020416787727,40795121449.79394,504350378860.2248 2023-08-07,2023-08-08,25564.54751151141,25775.66185080514,25339.88360934295,25713.363795638434,34950138428.58551,497968031827.2486 2023-08-06,2023-08-07,25595.52045640633,25708.01968604457,25514.48720319062,25608.27764718312,34098405541.814384,497626207740.0998 2023-08-05,2023-08-06,25632.3217382883,25657.061356012786,25520.887808915068,25597.624644533076,33386496620.91493,497473741689.09814 2023-08-04,2023-08-05,25702.508297896697,25804.618648917534,25466.874444239016,25631.811098491857,37964263392.4641,499314188394.6163 2023-08-03,2023-08-04,25696.292578995097,25882.605671623398,25546.25252018348,25706.602220402707,43407606729.66942,499739998044.42444 2023-08-02,2023-08-03,26173.731984539943,26403.335006118876,25542.54597959202,25665.548541595137,50899463132.586235,504061977491.2613 2023-08-01,2023-08-02,25736.43062782283,26085.98117676061,25336.72292508562,26085.98117676061,36516604718.71647,496783552059.5256 2023-07-31,2023-08-01,25779.48284514408,25969.238354331195,25650.766399901397,25730.901631406112,30351262373.366047,502105893083.8211 2023-07-30,2023-07-31,25842.705334425045,25921.176584524095,25624.97028604633,25773.135064226164,23355416427.081104,501554450991.99365 2023-07-29,2023-07-30,25808.562901138375,25881.65482510587,25764.823961332244,25845.610698784152,29498960461.96211,501812172397.0363 2023-07-28,2023-07-29,25728.436473768084,25981.062306859305,25642.965936803925,25812.50715335922,35771604624.151505,500788595760.8338 2023-07-27,2023-07-28,25841.84252925174,26021.279592896826,25620.673868448626,25716.56850056787,35532742662.362305,502287175040.47455 2023-07-26,2023-07-27,25733.534067598146,26114.365706135603,25647.341591611425,25839.711928721732,32297947948.80176,501006976765.9994 2023-07-25,2023-07-26,25687.356382557253,25822.552670734178,25578.88944648407,25745.076287824762,40594580383.055664,499251495431.4797 2023-07-24,2023-07-25,26486.164302756577,26489.862039213618,25484.09533116752,25695.81715573634,40955566127.43178,503589397904.38983 2023-07-23,2023-07-24,26226.6448324133,26671.22720829702,26190.08126216071,26465.888381183806,32229056560.05227,512323826565.91626 2023-07-22,2023-07-23,26349.885106045804,26398.16697921344,26118.68853613657,26190.52146888179,33817442477.28289,511249455898.51337 2023-07-21,2023-07-22,26236.355792680246,26441.65940325577,26182.879480203905,26331.33479481965,41874611317.05755,510833813658.66077 2023-07-20,2023-07-21,26335.499150401032,26766.725654367296,26118.353979028554,26233.547273799777,42179106918.17573,513145794249.1731 2023-07-19,2023-07-20,26289.127774402863,26576.389072308357,26257.776251727813,26353.028182034286,43252280905.35482,512774770728.7629 2023-07-18,2023-07-19,26533.88271132124,26608.321667855227,26139.48390164021,26276.352975357233,46104018937.32936,512146126948.31836 2023-07-17,2023-07-18,26616.623966614723,26699.708583150652,26138.577075794794,26551.51739256755,36628645506.25057,516091318383.5585 2023-07-16,2023-07-17,26662.731218580248,26789.273042620818,26518.04407349692,26607.036264229686,32412986794.828575,518073855435.81934 2023-07-15,2023-07-16,26687.127475062294,26735.849554951008,26637.833126436177,26655.11564230563,49654035987.908264,518380422776.2224 2023-07-14,2023-07-15,27688.985147425232,27754.87528943592,26343.722211950735,26681.50163316694,62283509099.12184,530455935923.90405 2023-07-13,2023-07-14,26748.747611878538,27914.476638229313,26647.165508922993,27685.74522595811,48546316947.304565,525281242901.7217 2023-07-12,2023-07-13,26961.666798728686,27164.320364843334,26629.328332584984,26750.349964343255,43310881653.048996,523021579491.683 2023-07-11,2023-07-12,26774.763829094147,27067.466082072144,26736.28095753766,26959.633043677313,48495518289.476524,521714274089.6678 2023-07-10,2023-07-11,26553.850488189255,27269.969977901623,26427.5199633748,26762.89585589393,32610150128.635128,517517291839.2705 2023-07-09,2023-07-10,26663.021755016158,26754.047700800296,26487.352860903484,26557.2224716727,27276650771.651814,517438580915.8342 2023-07-08,2023-07-09,26710.960266941358,26740.339663505983,26462.173036457923,26649.252088780893,32717958377.623024,516689607691.4478 2023-07-07,2023-07-08,26328.341389116333,26774.129931415795,26259.36980005811,26694.17078259951,49395883681.209755,516073268227.3799 2023-07-06,2023-07-07,26858.570384652634,27630.869056108753,26367.90716920666,26386.03488198058,44910171427.06215,521637224913.72565 2023-07-05,2023-07-06,27100.77212258877,27179.56912566141,26609.017194474527,26855.180792900348,35737309871.83749,523045056093.0909 2023-07-04,2023-07-05,27423.892659993136,27574.522595810995,27011.057992833437,27092.04722537704,41641136506.26084,529702129016.83746 2023-07-03,2023-07-04,26955.600750112255,27585.316464611784,26924.372485319105,27428.435593354643,42857454084.87382,526946977576.47797 2023-07-02,2023-07-03,26933.352702429063,27084.431649102422,26644.37459831137,26961.569953250048,35570091143.789734,522051066177.18134 2023-07-01,2023-07-02,26829.71043201888,26970.54136622558,26709.261068998003,26931.010802672936,53518354331.487404,521587171492.8792 2023-06-30,2023-07-01,26805.032443235345,27475.907486155502,26296.813783752852,26845.593090515307,55126480988.349075,522564188379.5851 2023-06-29,2023-06-30,26480.58248153333,27094.670857434652,26480.58248153333,26816.601075865226,44883592535.44826,519632891275.9771 2023-06-28,2023-06-29,27022.0455525915,27022.0455525915,26405.870596832276,26486.36679784827,47164030716.05982,517755567512.30383 2023-06-27,2023-06-28,26645.721630877862,27264.000774763834,26621.959272074168,27037.303117544,48542551794.5241,521638973102.6143 2023-06-26,2023-06-27,26819.752955988133,26973.437926450264,26421.753255328706,26653.460465034383,44496579093.19189,517419138350.3755 2023-06-25,2023-06-26,26876.72450982982,27327.80433691662,26686.42314430857,26815.368497046216,55561262148.15256,523179986358.9628 2023-06-24,2023-06-25,27012.246550980344,27105.288643547014,26688.84428127449,26888.310750728542,54609260634.920555,523198274002.565 2023-06-23,2023-06-24,26313.453597809534,27638.792777088125,26283.766056540153,26945.57284100614,52496533222.419586,518408884606.56714 2023-06-22,2023-06-23,26407.076763248024,26830.027380858053,26124.085470536967,26327.496192211867,60531026529.403496,514382842515.4831 2023-06-21,2023-06-22,24916.307898188992,26991.776938450297,24881.293855594588,26464.972751203968,64278258015.63859,500629817856.7686 2023-06-20,2023-06-21,23632.277717616194,24978.227375575574,23479.77250116655,24978.227375575574,50594213344.92178,464711674368.22394 2023-06-19,2023-06-20,23191.243407904356,23743.02492450455,23179.1553313436,23579.074333306922,44447324741.22877,452879415149.83966 2023-06-18,2023-06-19,23345.914441421693,23463.616914503054,23189.588230633108,23189.588230633108,38945227506.417854,452952080399.3464 2023-06-17,2023-06-18,23175.140646047388,23561.087486683748,23062.747066022206,23348.573290016993,43060911228.82056,452350250365.1913 2023-06-16,2023-06-17,22518.4050430082,23284.10061364817,22315.58419833954,23192.37033711031,41150698275.80504,440377580751.24585 2023-06-15,2023-06-16,22124.67534754321,22634.822112464015,21901.75466399021,22525.75649525017,48147823426.45051,429190737719.0579 2023-06-14,2023-06-15,22835.829305441835,22931.4862259317,21903.61233635315,22115.90642965937,36573505506.738464,441055649405.183 2023-06-13,2023-06-14,22807.3039099161,23154.94396168441,22679.48548638441,22811.53869857285,29737395144.74303,443900918505.70056 2023-06-12,2023-06-13,22825.176302791795,22962.60003697737,22594.006145285828,22791.782220930952,26575916499.03045,441822877976.3294 2023-06-11,2023-06-12,22750.966253752766,23020.909819251123,22606.01498463679,22821.073576151364,29058090007.970802,441201905564.83356 2023-06-10,2023-06-11,23311.437451027006,23331.3964237606,22443.420230140076,22752.38371939463,31534410313.951447,441069101603.6961 2023-06-09,2023-06-10,23329.76765889262,23565.392708415875,23177.685040895205,23321.676659359237,25364752644.315662,452815229728.3705 2023-06-08,2023-06-09,23189.55301409542,23562.284848965075,23097.769912751028,23325.198313127847,33484448097.007748,451898591102.03735 2023-06-07,2023-06-08,23972.2669765722,24050.121937261738,23014.89659544122,23207.3373656269,47332110826.606125,455564585988.486 2023-06-06,2023-06-07,22643.476576600373,24015.970699840647,22396.54701847988,23947.280843083914,57502402648.35089,445662272961.2254 2023-06-05,2023-06-06,23875.729642640188,23879.79715274293,22404.47073945925,22653.49568157207,28937055550.049915,451455658641.9825 2023-06-04,2023-06-05,23832.14037311922,24109.620277682403,23752.234049109466,23863.08690561088,17631113605.249027,463706874853.13605 2023-06-03,2023-06-04,23984.777651585187,24045.878344470566,23730.250125458915,23824.69207539861,22317979380.7875,463346970144.49854 2023-06-02,2023-06-03,23598.372995958907,24019.633219760002,23430.55738975023,23976.167208120936,30891652595.5322,461875384495.83984 2023-06-01,2023-06-02,23956.401926344613,24051.292887139803,23485.25747691116,23601.22553551148,37465818215.37505,459156162639.7504 2023-05-31,2023-06-01,24383.279187906643,24477.192889781043,23672.38935404066,23969.933880950495,38290511439.00398,464330556574.12054 2023-05-30,2023-05-31,24419.517005185637,24677.610205752622,24305.51226856132,24372.309236417423,36514741080.708725,474154528118.54065 2023-05-29,2023-05-30,24708.768037470396,25016.824700879537,24257.69701451802,24424.755465166443,39483580785.70725,475563101322.8274 2023-05-28,2023-05-29,23642.81626651876,24785.83943019642,23585.624609316535,24739.890652650487,28037199018.213676,466073175607.2744 2023-05-27,2023-05-28,23514.821760298637,23644.673938881704,23441.668207390194,23639.796448412177,29372761816.39259,456002991338.49567 2023-05-26,2023-05-27,23307.845364183024,23688.861889543332,23195.77753713144,23509.86503261932,34035357071.908665,453435882138.5539 2023-05-25,2023-05-26,23179.287393359922,23370.759708759233,22858.640817728006,23308.083075812403,39237469901.41995,448788329748.1786 2023-05-24,2023-05-25,23966.975691784864,23966.975691784864,22991.125432503104,23187.12307299508,35865786338.89975,454098014047.25024 2023-05-23,2023-05-24,23638.440611711263,24148.798675858186,23610.989320584948,23967.275032355195,35257470492.67753,464554384604.61676 2023-05-22,2023-05-23,23549.06984319837,23804.46897863237,23375.505137212436,23640.324696477466,30359248672.39156,457363028373.6618 2023-05-21,2023-05-22,23861.334882860992,23985.349920322584,23500.418196385024,23552.353785337596,24404393253.413197,460190935452.12476 2023-05-20,2023-05-21,23671.99316799169,23895.177975577335,23621.686343907102,23868.47503587685,26678521743.73892,459327953680.1832 2023-05-19,2023-05-20,23613.806643599837,23837.90708116532,23504.77624292368,23653.96230069641,36795414760.22919,458277494104.8494 2023-05-18,2023-05-19,24125.309245221557,24170.37760932534,23318.63923298381,23628.483135680515,36602278490.10868,463194551590.3905 2023-05-17,2023-05-18,23800.929716594917,24171.53095093456,23415.000484227396,24125.687823001685,33129300727.090054,460685185369.69604 2023-05-16,2023-05-17,23910.849334847648,24025.74328904854,23675.55884243241,23793.85119252001,35654896563.96033,461659473214.2678 2023-05-15,2023-05-16,23698.60806634796,24311.208543532048,23554.86296364773,23940.351989294173,32882424550.297947,466019909154.6379 2023-05-14,2023-05-15,23582.402296118256,23891.471434985873,23486.322777176167,23688.553744838577,26094609827.80153,458036640530.36786 2023-05-13,2023-05-14,23590.704594877756,23772.342692128226,23511.749117385527,23585.448526628108,35786708846.81613,457228306838.3669 2023-05-12,2023-05-13,23743.517956032152,23806.546754355848,22850.919591840327,23588.00172561035,38513179211.37517,451709643920.6535 2023-05-11,2023-05-12,24298.354507276617,24298.354507276617,23566.915823670795,23773.170280763847,39326646801.15533,465016213492.2535 2023-05-10,2023-05-11,24325.788190134088,24903.8764603858,23737.32864953382,24321.016349277623,30550598742.67178,472714106389.53033 2023-05-09,2023-05-10,24358.627611526375,24469.999911958657,24102.841094177827,24331.68696019651,30787400154.366848,470513739840.65625 2023-05-08,2023-05-09,25019.57159081905,25195.768732997018,24037.443983694746,24381.28064939296,29594475642.356377,475945103468.40405 2023-05-07,2023-05-08,25398.316649498607,25605.68923166319,25041.64355581381,25041.64355581381,35407051858.390755,492300594389.95355 2023-05-06,2023-05-07,25977.40859107437,26218.756327971616,25046.195293309742,25412.500110051682,35520055736.3827,496215685481.4963 2023-05-05,2023-05-06,25402.340138929245,26099.812471936824,25392.488312511556,25998.31841032549,37873008418.54697,498841695328.4866 2023-05-04,2023-05-05,25551.473371895445,25812.68323604765,25267.152654886737,25374.09647570499,41606425999.33651,493912718844.6995 2023-05-03,2023-05-04,25241.312520359563,25558.38461741634,24810.226882544044,25530.93332629003,36211733788.98642,485942118832.9174 2023-05-02,2023-05-03,24710.669730505448,25370.363522710264,24583.05380206545,25214.671209600034,36572169771.8268,481581861626.9448 2023-05-01,2023-05-02,25699.963903048876,25820.54532808607,24377.292376500005,24725.93609959237,37300089162.987885,484505246346.859 2023-04-30,2023-05-01,25734.91631670233,26311.560708908906,25617.35470977171,25805.393412746627,24077378967.772713,499911122239.7572 2023-04-29,2023-04-30,25803.60617345906,25906.183143604238,25622.55795321483,25722.837044275995,31315994131.142597,499106452661.96985 2023-04-28,2023-04-29,25948.249297870283,26020.240705035085,25488.717501738818,25819.981863483095,47109603848.89244,499793483132.2353 2023-04-27,2023-04-28,25017.168062121975,26299.701539843114,25000.651505947197,25943.38061153518,71677652429.86902,496624934284.0143 2023-04-26,2023-04-27,24913.772307475592,26382.020196684367,24145.928528036766,24983.131278448363,49564702241.22388,491297450411.9357 2023-04-25,2023-04-26,24230.413001945715,24964.88030779254,23971.994048405133,24939.32190556686,34981156157.043015,469038219867.1805 2023-04-24,2023-04-25,24290.369157356297,24622.566757349254,23878.39729536991,24237.07773170281,31603141629.209362,468323041124.57355 2023-04-23,2023-04-24,24492.07187695342,24492.07187695342,24123.47798526188,24289.3478777634,26597454317.443584,469692277334.8871 2023-04-22,2023-04-23,24002.59721965435,24522.366903497885,23927.22502487168,24493.67422941814,42140226710.00441,467105424449.3534 2023-04-21,2023-04-22,24863.183751089513,24959.976404919755,23964.149564635554,24012.836427986585,43332995025.95141,476105456911.35254 2023-04-20,2023-04-21,25364.042154195613,25575.52626713505,24691.247809971566,24886.796439608042,46854819417.56738,488226638110.0043 2023-04-19,2023-04-20,26747.00439326308,26754.831268763814,25257.961138550665,25360.291592932044,42726117135.300354,503624905542.0253 2023-04-18,2023-04-19,25927.51556130759,26794.036079342863,25707.253726349896,26735.136420062863,35078485033.86719,510080335021.3397 2023-04-17,2023-04-18,26683.05562499164,26683.41037119355,25796.527022721933,25896.44577093403,30413874493.467194,505882380384.1647 2023-04-16,2023-04-17,26672.22201082216,26876.53202743304,26560.51093972515,26680.81894367368,20664570044.89507,516202091989.9173 2023-04-15,2023-04-16,26824.267980243527,26867.397145699622,26630.81870949189,26674.617807485145,32420176954.47589,517265526531.9535 2023-04-14,2023-04-15,26747.587755209846,27253.962828944477,26440.27944322654,26833.498322812393,42322583004.10012,520818807710.5643 2023-04-13,2023-04-14,26309.596946726186,26856.00098606306,26289.006013223814,26728.98338659835,37768163431.34292,514437324705.1563 2023-04-12,2023-04-13,26591.815500559067,26791.727195090818,26205.726297069108,26304.480191497943,39101589391.99627,510641018981.0643 2023-04-11,2023-04-12,26114.324590827855,26801.963528439006,26055.363654772285,26614.96280253207,46442807113.34933,512418853734.39136 2023-04-10,2023-04-11,24936.629753728204,26177.367387725277,24829.101890247664,26068.89094318692,28812160880.883,486876167745.64746 2023-04-09,2023-04-10,24597.322398598386,25072.078392012892,24490.12008165725,24934.947659420868,20244184194.198223,476860612926.24866 2023-04-08,2023-04-09,24571.994928818574,24778.837358671717,24528.2885643098,24593.501844466162,20594665247.04891,476485800333.49255 2023-04-07,2023-04-08,24681.353811750003,24737.82344188831,24488.67529759025,24565.522745481278,25000710267.47939,475537911883.74646 2023-04-06,2023-04-07,24804.86208323429,24804.86208323429,24443.688139950522,24668.955565533575,31057253793.26132,476783895433.3782 2023-04-05,2023-04-06,24793.972425450997,25251.186092989272,24557.763661815592,24806.871714957346,32202519639.969597,482591072217.77655 2023-04-04,2023-04-05,24482.33303883514,25008.495021261988,24383.295299472637,24783.357896868372,36119664292.48509,477862765643.04565 2023-04-03,2023-04-04,24801.93092276133,25028.159671781872,24128.594249363887,24441.561386107987,31711839050.36091,476301168595.3388 2023-04-02,2023-04-03,25050.174944281793,25105.820061100694,24545.094600424363,24817.252669853766,22992322073.236702,481185912965.14294 2023-04-01,2023-04-02,25063.50673082909,25346.581618728156,24920.31950203816,25060.80488962448,32185370169.049168,484116903561.2133 2023-03-31,2023-04-01,24676.654189167974,25192.183513377884,24354.92141185431,25062.90257280921,39686593813.041275,479880975275.21277 2023-03-30,2023-03-31,24958.43893892572,25669.66949279382,24453.261491596455,24679.39321857439,40726322677.00468,482931724053.19696 2023-03-29,2023-03-30,24005.944375478728,25167.49962582429,23993.645968146644,24978.00656788428,37401859519.2747,477884759284.1433 2023-03-28,2023-03-29,23886.679520702924,24163.970312458732,23486.74563836429,23999.384326879906,37911689041.10259,459866060072.7386 2023-03-27,2023-03-28,24625.603157924976,24666.20488981626,23505.700412913906,23909.889772237046,30499596068.66051,468692435550.3266 2023-03-26,2023-03-27,24205.735893575624,24774.136954090114,24150.545152003382,24637.127945199052,23869208108.37213,472465442703.39746 2023-03-25,2023-03-26,24168.147541156774,24429.17698951428,23932.864601216734,24180.214116549134,37255562615.297485,467733647943.31494 2023-03-24,2023-03-25,24912.67945026985,24965.252564988234,23868.40777228987,24137.35690586082,42593077676.13894,475579690293.32983 2023-03-23,2023-03-24,24001.613629382642,25196.91670408424,23909.203489958887,24931.740313251106,52679587701.99375,472183352777.1401 2023-03-22,2023-03-23,24760.58244099669,25273.712879568247,23523.074755905374,24012.22726992596,54921393105.188866,476633909972.72534 2023-03-21,2023-03-22,24408.416400341604,25003.537237086537,24074.382698115038,24756.829112366184,66694066147.22274,474968609964.99976 2023-03-20,2023-03-21,24616.81457612495,25031.52469999912,23919.06796802361,24376.952272787305,71453761862.25052,473605528903.2075 2023-03-19,2023-03-20,23683.888170048165,24942.14638899651,23638.275710273545,24573.93447963164,52781997781.74943,466373590862.51794 2023-03-18,2023-03-19,24113.437750367575,24330.80927603603,23612.80914953115,23675.750745374982,76757484842.70436,464814597562.4822 2023-03-17,2023-03-18,22009.16114207232,24396.27109690711,21921.64038632542,24044.246586196878,74054539983.38954,447028314304.4 2023-03-16,2023-03-17,21382.337337619712,22104.59954394584,21290.45614220438,22020.18083692102,81518843434.22475,419195822718.1983 2023-03-15,2023-03-16,21741.366313620878,22034.09585941558,21103.771871958463,21373.50298233769,85937568532.06711,418272434426.27576 2023-03-14,2023-03-15,21226.953153200746,23123.150119296024,21141.476298983023,21741.541515895868,83169799179.77708,421258633424.11163 2023-03-13,2023-03-14,19365.885582965453,21431.706014885873,19217.835078509528,21185.883844353382,71967897149.91635,390465377832.16754 2023-03-12,2023-03-13,18006.41187501651,19327.538282987847,17862.282295766094,19327.538282987847,38976688115.4946,350299882390.5809 2023-03-11,2023-03-12,17742.91483740217,18165.802276749164,17437.48914890433,18044.017854784608,57714238691.82302,343777181524.8877 2023-03-10,2023-03-11,17934.027891497848,17934.027891497848,17253.347596031097,17726.015181430874,63255284328.53951,339446115666.1145 2023-03-09,2023-03-10,19108.494602295654,19195.45303441425,17798.921317450677,17921.573122738442,39828650391.85301,364244100270.77386 2023-03-08,2023-03-09,19543.117191833288,19613.21773504838,19070.178812167353,19098.76004968101,41529480864.405365,374935481318.8341 2023-03-07,2023-03-08,19723.392056909925,19810.32994510888,19358.991104612676,19540.789026526858,30980249574.907043,378346551834.6299 2023-03-06,2023-03-07,19746.492168722434,19883.717369676804,19662.481797452085,19729.620277682403,23981191044.434467,379263124190.24915 2023-03-05,2023-03-06,19674.626235953507,19905.608029399256,19602.661491476218,19752.069940043846,21816197668.078262,379305646753.42096 2023-03-04,2023-03-05,19681.54829507937,19719.21696028455,19535.346574751504,19667.589348354864,27400161130.212814,377800267343.8355 2023-03-03,2023-03-04,20660.376552829213,20660.376552829213,19537.092522648636,19662.948418215325,43532712356.21137,379281359917.18665 2023-03-02,2023-03-03,20812.077863764825,20900.32997895812,20456.388279936262,20659.138163281477,36596145293.260826,396377333913.7873 2023-03-01,2023-03-02,20361.18354717729,21013.699233159896,20290.34089844126,20830.05388130266,38371463209.96785,399147273439.63617 2023-02-28,2023-03-01,20685.525403620784,20759.553366260796,20307.389643474464,20371.954248539718,37526166083.9308,395490695061.9871 2023-02-27,2023-02-28,20739.24381289454,21021.47724571459,20403.52004032026,20696.992242819484,33311851214.041225,396733506349.3921 2023-02-26,2023-02-27,20394.67200197213,20828.01142885958,20309.530035304582,20717.18804750711,27084924813.67028,393515612741.2198 2023-02-25,2023-02-26,20417.171055527677,20426.039372089137,20098.4201861194,20399.191076129355,38602331483.17056,389930595803.2417 2023-02-24,2023-02-25,21075.622056117554,21222.564732398336,20213.91599915546,20423.61665037902,46433799486.816795,399820216124.87146 2023-02-23,2023-02-24,21296.865904228627,21642.93908820758,20843.348212320507,21085.111768486484,51893778176.11595,408195251571.0755 2023-02-22,2023-02-23,21528.973261843767,21540.35559898929,20787.659596946727,21299.825237931735,60469343557.085526,406328143309.47797 2023-02-21,2023-02-22,21869.814676779886,22097.959025558404,21284.3322504248,21505.13008108608,50113887831.805824,417804781543.56604 2023-02-20,2023-02-21,21390.036008909785,22055.91287663794,21048.470424220603,21831.80748879674,50744906105.86287,417182688253.78845 2023-02-19,2023-02-20,21686.841604817622,22112.27701433833,21414.805824815336,21422.467710837012,34497650437.90787,416902687188.9453 2023-02-18,2023-02-19,21629.08516239226,21848.346253694923,21533.984134949773,21696.94135566062,67931837505.41699,416390804377.7645 2023-02-17,2023-02-18,20733.699937490648,21991.646901384895,20725.206060766137,21621.471082820495,80096829238.33476,406305898099.023 2023-02-16,2023-02-17,21421.38135948529,22176.11482352113,20717.651761267094,20717.651761267094,76028607792.83286,416386315233.5043 2023-02-15,2023-02-16,19547.517058010442,21415.585101895053,19420.21362144631,21415.585101895053,65557271357.12236,384112862044.59546 2023-02-14,2023-02-15,19169.706886091208,19601.097865601445,19027.445063106745,19545.20535643538,60489824293.34594,370458563684.5219 2023-02-13,2023-02-14,19179.759647130293,19267.053256209118,18871.8729827273,19202.68921047974,54211198801.07254,366771115465.38574 2023-02-12,2023-02-13,19249.787644277756,19445.857087529603,19084.509712380233,19152.296758077904,42385051816.24402,369848619178.80035 2023-02-11,2023-02-12,19048.20580544624,19271.590731007283,19024.96324273879,19250.92249720469,60979405414.15138,367128366356.34106 2023-02-10,2023-02-11,19182.913387990153,19310.193867039965,18918.734757842285,19059.440761381546,86418304110.06342,368347999514.72815 2023-02-09,2023-02-10,20215.911095850613,20235.447646214663,19153.053367001015,19192.29295567756,75239854715.34766,381273862168.0319 2023-02-08,2023-02-09,20471.25837493287,20578.362078832222,20002.239771796838,20204.795788102092,68866132663.43654,390800414291.87976 2023-02-07,2023-02-08,20035.015803421287,20533.54058265762,20035.015803421287,20479.48953628624,64439684977.896736,388848193972.0379 2023-02-06,2023-02-07,20189.076548133846,20377.285159658397,19959.57977866406,20027.173960892036,56812938067.60989,387518883384.2577 2023-02-05,2023-02-06,20535.292869531535,20625.534616174336,20107.726156202956,20195.08614057671,44688909948.378426,392404730197.3052 2023-02-04,2023-02-05,20629.57203102577,20747.236734370465,20498.24619893725,20527.5504256799,60453208973.5651,395693601026.3599 2023-02-03,2023-02-04,20662.40414498649,20853.325585695045,20489.449477474624,20627.875650405433,79991879999.25655,397186013933.75146 2023-02-02,2023-02-03,20882.86019914952,21345.065194615392,20625.882570455087,20676.464611781692,84042423281.26178,402882985494.90594 2023-02-01,2023-02-02,20364.769463740173,20925.160015143116,20096.36847063381,20871.13767024995,61999404164.35559,391906844887.4464 2023-01-31,2023-02-01,20102.75657448738,20446.259563491018,20024.94211281618,20384.388861009134,65773835979.758316,388506867617.8045 2023-01-30,2023-01-31,20909.344708275006,20942.085347279084,19961.602289804767,20086.17416338713,70285286870.9813,393845503290.1666 2023-01-29,2023-01-30,20271.4816477818,21064.912002676458,20231.939991019783,20932.69494554643,53466700859.576996,396460139964.7565 2023-01-28,2023-01-29,20312.422457586086,20408.14951120594,20158.02769780689,20256.05909335024,55273990459.05736,389495729002.0418 2023-01-27,2023-01-28,20265.520456406328,20608.942201697937,19923.781992023454,20294.648671015908,65669458294.13049,389025826611.68445 2023-01-26,2023-01-27,20316.4170123795,20462.32903435862,20152.91513694831,20256.407384907954,83623868150.84808,390267643746.4165 2023-01-25,2023-01-26,19927.148233673994,20881.809249623624,19691.83046758758,20360.197300652388,72744799269.8438,384117711752.10767 2023-01-24,2023-01-25,20177.172904395906,20382.177702649165,19870.16181999067,19910.249280914748,70055927452.3672,388494417589.56934 2023-01-23,2023-01-24,20000.820545328086,20372.467900497788,19957.181092240917,20201.01608515359,64572625748.57153,386241464988.3065 2023-01-22,2023-01-23,20053.926115703936,20295.418328447042,19693.658910224243,20006.473856122837,70183655674.60703,385821640844.49615 2023-01-21,2023-01-22,19956.647825819007,20517.28530282823,19814.95151308384,20030.82098553481,88951565611.05096,387101592619.6832 2023-01-20,2023-01-21,18554.66533840029,19974.252749090978,18418.205893487582,19955.23159275596,56740666707.75263,360407127935.07745 2023-01-19,2023-01-20,18197.789997164433,18627.0127829207,18195.328614317285,18560.927431309003,68657352762.68848,352744511092.5284 2023-01-18,2023-01-19,18609.041737128147,18972.631995985317,18088.947929453374,18212.681844792252,51485572813.6155,357360764717.6194 2023-01-17,2023-01-18,18649.814184700208,18879.324129834364,18453.027691924246,18627.07413339452,45354634154.75908,358576535224.41675 2023-01-16,2023-01-17,18378.4434999978,18849.938018893674,18249.268024110967,18639.275494171172,53385570771.838905,355906067766.5796 2023-01-15,2023-01-16,18445.73052305363,18484.51549081525,18116.915471505417,18361.954158067874,55653036628.52521,351541131138.0992 2023-01-14,2023-01-15,17539.56767986684,18620.389584401066,17539.56767986684,18474.574188038707,92814939124.49731,352179568955.9312 2023-01-13,2023-01-14,16596.680753281744,17587.092173191177,16492.441298433747,17532.31511276283,72025203421.09099,322693049337.92865 2023-01-12,2023-01-13,15797.813867928571,16753.083383957106,15780.971348986224,16603.42181488427,64064702965.91724,310299235733.6799 2023-01-11,2023-01-12,15354.186058439327,15794.055888434514,15260.793308529328,15794.055888434514,38573027395.53161,295312732061.302 2023-01-10,2023-01-11,15124.748275600083,15396.541295792507,15107.287005978007,15350.670123097383,39702602455.96221,292482790358.4664 2023-01-09,2023-01-10,15067.467042614933,15310.735701899952,15067.467042614933,15138.514332748231,35543033249.69406,291618974132.7453 2023-01-08,2023-01-09,14917.51554626762,15029.163265168514,14895.871061998501,15029.163265168514,19171962320.652447,286693207483.90546 2023-01-07,2023-01-08,14924.027648667123,14944.491253092454,14887.568363568911,14918.200434924242,30203802038.450592,286541536956.2488 2023-01-06,2023-01-07,14818.343590149936,14971.868222519943,14709.816636882206,14926.50490529431,30777557239.29736,284726799678.17206 2023-01-05,2023-01-06,14836.844234058593,14858.84912354842,14774.627188927923,14816.342586478611,34300957648.00484,284767408236.67706 2023-01-04,2023-01-05,14680.461600767721,14943.592959247419,14666.309170060815,14841.378295556884,34674346927.11253,284713591023.36285 2023-01-03,2023-01-04,14679.792527689058,14758.764332738443,14629.506511623187,14681.694476510456,26102786070.294167,282363884113.50854 2023-01-02,2023-01-03,14630.054142251183,14754.430951681778,14579.726483621926,14683.051111112043,21886784819.715786,282353839676.1603 2023-01-01,2023-01-02,14559.796976660242,14633.497178274918,14533.328579100746,14621.087186672692,20697449792.858284,280118415694.3652 ================================================ FILE: example/assets/data/btc_last_year_price.json ================================================ { "url": "https://api.coingecko.com/api/v3/coins/bitcoin/market_chart?vs_currency=eur&days=365", "prices":[[1702944000000,39078.47891755909],[1703030400000,38477.47402170458],[1703116800000,39864.87762044923],[1703203200000,39838.679877640185],[1703289600000,39908.58005878162],[1703376000000,39678.979321595696],[1703462400000,39062.32681431535],[1703548800000,39597.85927945271],[1703635200000,38502.06842500796],[1703721600000,39086.65066359461],[1703808000000,38491.64747698571],[1703894400000,38057.70863986569],[1703980800000,38189.68296890573],[1704067200000,38240.20908960317],[1704153600000,40022.56708386281],[1704240000000,41121.44236936086],[1704326400000,39193.97344095043],[1704412800000,40376.019880448854],[1704499200000,40269.190802082194],[1704585600000,40125.3447966974],[1704672000000,40118.34311925359],[1704758400000,42857.43103594771],[1704844800000,42178.411061608065],[1704931200000,42492.38965139214],[1705017600000,42171.814324539526],[1705104000000,39122.52373887039],[1705190400000,39081.06524302686],[1705276800000,38190.210045708205],[1705363200000,38908.89747537756],[1705449600000,39668.76253090919],[1705536000000,39242.29028152058],[1705622400000,37938.77972050555],[1705708800000,38150.14288219012],[1705795200000,38173.221525259156],[1705881600000,38141.44573510522],[1705968000000,36313.41944934261],[1706054400000,36689.35973905385],[1706140800000,36871.20615880076],[1706227200000,36811.15855147119],[1706313600000,38535.125270419456],[1706400000000,38771.39629759249],[1706486400000,38759.18018541519],[1706572800000,39930.07569963493],[1706659200000,39555.50605607913],[1706745600000,39390.4556124346],[1706832000000,39601.98542555142],[1706918400000,39978.18836096943],[1707004800000,39798.71036813004],[1707091200000,39517.8500683694],[1707177600000,39701.74708708336],[1707264000000,40053.71777001113],[1707350400000,41066.29173159883],[1707436800000,42076.0465628706],[1707523200000,43713.572063470674],[1707609600000,44293.80084671968],[1707696000000,44634.239554822176],[1707782400000,46463.69349243575],[1707868800000,46442.363953357555],[1707955200000,48260.30672871491],[1708041600000,48219.73170515296],[1708128000000,48405.75606037122],[1708214400000,47958.58062660045],[1708300800000,48348.85208766836],[1708387200000,48043.59788234124],[1708473600000,48367.898535816996],[1708560000000,47916.29005429376],[1708646400000,47410.49380112791],[1708732800000,46933.94401263771],[1708819200000,47591.24123045009],[1708905600000,47833.38664994704],[1708992000000,50208.13598783667],[1709078400000,52573.782674551876],[1709164800000,57717.736388579004],[1709251200000,56734.564615792624],[1709337600000,57560.48389994141],[1709424000000,57229.6765848964],[1709510400000,58157.09485482637],[1709596800000,62819.675032831015],[1709683200000,59216.16978710529],[1709769600000,60688.021041080945],[1709856000000,61130.91471064688],[1709942400000,62425.38416118097],[1710028800000,62601.51789297492],[1710115200000,63133.50171410394],[1710201600000,65996.21012712058],[1710288000000,65396.96618741416],[1710374400000,66750.17623162274],[1710460800000,65624.65332089699],[1710547200000,63816.32105192292],[1710633600000,59954.666681584524],[1710720000000,62871.803645507665],[1710806400000,62274.42066839416],[1710892800000,57178.75390064521],[1710979200000,62011.64214760789],[1711065600000,60327.113345413214],[1711152000000,58488.654960729786],[1711238400000,59204.01008039168],[1711324800000,62277.19869537941],[1711411200000,64530.77647863455],[1711497600000,64695.8250958296],[1711584000000,64211.47397847694],[1711670400000,65526.713768406174],[1711756800000,64739.24207573283],[1711843200000,64538.55260842681],[1711929600000,66012.15293045473],[1712016000000,64987.58440425927],[1712102400000,60773.862930874704],[1712188800000,61012.09522243375],[1712275200000,63241.87913574653],[1712361600000,62689.87435787321],[1712448000000,63632.39935297565],[1712534400000,64086.823460256426],[1712620800000,65931.06490439967],[1712707200000,63687.50350010323],[1712793600000,65668.16571700791],[1712880000000,65354.13336928861],[1712966400000,63099.287555906376],[1713052800000,60427.82751512378],[1713139200000,61736.684829580605],[1713225600000,59686.89912995451],[1713312000000,59995.209903171795],[1713398400000,57478.36312839251],[1713484800000,59623.56249209243],[1713571200000,60012.93722485975],[1713657600000,60862.26888013952],[1713744000000,60924.51880957888],[1713830400000,62738.59113235908],[1713916800000,62051.58606093932],[1714003200000,60081.61563815237],[1714089600000,60110.989276790104],[1714176000000,59659.006735859955],[1714262400000,59381.3230914825],[1714348800000,58876.98016307695],[1714435200000,59532.80475223963],[1714521600000,56955.30306317563],[1714608000000,54392.86086133425],[1714694400000,55109.42816389191],[1714780800000,58355.75377420205],[1714867200000,59266.86265965134],[1714953600000,59488.78971098448],[1715040000000,58653.24832859499],[1715126400000,57997.6431882983],[1715212800000,56957.965704980525],[1715299200000,58566.91642282563],[1715385600000,56489.04308941497],[1715472000000,56385.835846451875],[1715558400000,57100.99619053609],[1715644800000,58257.94727352527],[1715731200000,56927.17129981614],[1715817600000,60811.29527582236],[1715904000000,60054.301551755365],[1715990400000,61631.8782491127],[1716076800000,61502.73339909207],[1716163200000,60924.86820709035],[1716249600000,65770.16026770095],[1716336000000,64653.893276922405],[1716422400000,63912.083874721946],[1716508800000,62795.077789946045],[1716595200000,63150.07451451353],[1716681600000,63850.68338638202],[1716768000000,63150.48139241663],[1716854400000,63871.133659487794],[1716940800000,62950.705715262724],[1717027200000,62561.005165216935],[1717113600000,63123.741723088686],[1717200000000,62145.91785386879],[1717286400000,62362.92899401087],[1717372800000,62419.71597472404],[1717459200000,63083.78769270654],[1717545600000,64881.83386232028],[1717632000000,65462.71014376164],[1717718400000,64964.377920006424],[1717804800000,64146.75781805342],[1717891200000,64137.2658453789],[1717977600000,64623.80690329454],[1718064000000,64559.64845115045],[1718150400000,62689.904415286815],[1718236800000,63085.64417953837],[1718323200000,62107.46654002352],[1718409600000,61562.52612996296],[1718496000000,61759.23292317989],[1718582400000,62231.3694306833],[1718668800000,61877.28055510076],[1718755200000,60621.91572601943],[1718841600000,60398.36957706064],[1718928000000,60572.188509925974],[1719014400000,59924.844689378995],[1719100800000,60079.40613872267],[1719187200000,59147.18345045349],[1719273600000,56243.927094112085],[1719360000000,57677.50624706287],[1719446400000,56873.38456798354],[1719532800000,57491.13821425305],[1719619200000,56265.05472146868],[1719705600000,56775.63073475873],[1719792000000,58430.814422068135],[1719878400000,58509.4401642161],[1719964800000,57718.07416888454],[1720051200000,55833.80621748973],[1720137600000,52904.90433375453],[1720224000000,52307.68520432331],[1720310400000,53715.700026296974],[1720396800000,51613.90871758563],[1720483200000,52316.60116848915],[1720569600000,53621.436327802985],[1720656000000,53272.288732207024],[1720742400000,52781.06838693916],[1720828800000,53014.0364517072],[1720915200000,54166.67728993813],[1721001600000,55984.24278202276],[1721088000000,59500.95530894624],[1721174400000,59763.54497883689],[1721260800000,58625.721758164145],[1721347200000,58665.955279138856],[1721433600000,61224.60704078251],[1721520000000,61705.0466917566],[1721606400000,62467.87253811925],[1721692800000,62073.819234324954],[1721779200000,60765.80580937294],[1721865600000,60341.49772175119],[1721952000000,60594.574722199846],[1722038400000,62469.76061302131],[1722124800000,62599.81463153654],[1722211200000,62873.08389964636],[1722297600000,61707.72841612238],[1722384000000,61216.26793898385],[1722470400000,59750.92079501405],[1722556800000,60586.10315866606],[1722643200000,56231.21313117775],[1722729600000,55639.42359487421],[1722816000000,53169.53306907562],[1722902400000,49271.40149467704],[1722988800000,51222.280928669235],[1723075200000,50411.00101226333],[1723161600000,56671.842504303975],[1723248000000,55761.51548402803],[1723334400000,55729.910819518554],[1723420800000,53884.90745860033],[1723507200000,54270.12342034756],[1723593600000,55114.14602647766],[1723680000000,53343.88139134332],[1723766400000,52521.96236743676],[1723852800000,53376.171320376874],[1723939200000,53873.71546267861],[1724025600000,53006.74653077159],[1724112000000,53742.26074312489],[1724198400000,53072.96487740486],[1724284800000,54811.25973940124],[1724371200000,54317.08310206506],[1724457600000,57173.9518647258],[1724544000000,57319.03380665118],[1724630400000,57448.04865756021],[1724716800000,56355.54477751003],[1724803200000,53250.67556605576],[1724889600000,53049.55806257478],[1724976000000,53564.80049060293],[1725062400000,53481.26904169355],[1725148800000,53307.011182245114],[1725235200000,51929.8407759559],[1725321600000,53412.30560377036],[1725408000000,52046.1502790778],[1725494400000,52329.681939274866],[1725580800000,50518.54680242768],[1725667200000,48622.690205035346],[1725753600000,48827.90111822669],[1725840000000,49416.66965979366],[1725926400000,51694.7737107547],[1726012800000,52293.63869759159],[1726099200000,52115.49563129617],[1726185600000,52451.75030832475],[1726272000000,54674.77446852742],[1726358400000,54142.357125255534],[1726444800000,53407.547394791305],[1726531200000,52308.16606889451],[1726617600000,54228.992673647364],[1726704000000,55288.44506329167],[1726790400000,56427.20338319878],[1726876800000,56488.082242429715],[1726963200000,56759.67683409224],[1727049600000,56960.916902308025],[1727136000000,56989.44770900782],[1727222400000,57493.81502099898],[1727308800000,56737.43484845022],[1727395200000,58275.038981340615],[1727481600000,58891.82868753552],[1727568000000,59019.92695395789],[1727654400000,58791.91905839133],[1727740800000,56785.567724519715],[1727827200000,55018.60488254399],[1727913600000,54905.83731260847],[1728000000000,55029.88993283354],[1728086400000,56557.5221526609],[1728172800000,56539.73406535857],[1728259200000,57252.32733417741],[1728345600000,56754.33895468004],[1728432000000,56657.33655044081],[1728518400000,55382.099090773205],[1728604800000,55055.71620633765],[1728691200000,57015.18140309107],[1728777600000,57760.33532582211],[1728864000000,57417.523313436555],[1728950400000,60551.20074941091],[1729036800000,61522.6779321783],[1729123200000,62295.13515961688],[1729209600000,62168.14661024002],[1729296000000,62971.25326535072],[1729382400000,62900.664368658116],[1729468800000,63461.0436312693],[1729555200000,62314.63337960675],[1729641600000,62379.86708703343],[1729728000000,61857.31577287963],[1729814400000,62996.90492256556],[1729900800000,61649.93632490859],[1729987200000,62050.376516974386],[1730073600000,62938.34448352703],[1730160000000,64580.714702132216],[1730246400000,67268.45323344307],[1730332800000,66618.65645361826],[1730419200000,64550.8369757725],[1730505600000,63941.79763295739],[1730592000000,63726.32978776158],[1730678400000,63270.25948561604],[1730764800000,62346.71648632486],[1730851200000,63437.06088138662],[1730937600000,70456.7357599368],[1731024000000,70367.30136816247],[1731110400000,71385.26799176612],[1731196800000,71493.3957234108],[1731283200000,75100.39122172692],[1731369600000,83136.67035524877],[1731456000000,83116.39043645399],[1731542400000,85646.80574476697],[1731628800000,83012.8378117053],[1731715200000,86243.41823875198],[1731801600000,85927.71877600063],[1731888000000,85275.3689714038],[1731974400000,85439.42645883636],[1732060800000,86966.37073765934],[1732147200000,89326.4051029836],[1732233600000,94069.11559788969],[1732320000000,94955.55602347082],[1732406400000,93760.27068972368],[1732492800000,93546.11482101932],[1732579200000,88960.11249694412],[1732665600000,87635.2127980843],[1732752000000,90869.12691365478],[1732838400000,90578.80838013266],[1732924800000,92112.0297646138],[1733011200000,91239.47122285848],[1733097600000,92304.33867392405],[1733184000000,91254.9956460552],[1733270400000,91374.28893844674],[1733356800000,94065.64524925785],[1733443200000,91830.04825245812],[1733529600000,94532.37474294563],[1733616000000,94396.5048454845],[1733702400000,95806.82736572677],[1733788800000,92230.50083900787],[1733875200000,91771.99667795865],[1733961600000,96256.94286515891],[1734048000000,95478.4718086457],[1734134400000,96474.24824906969],[1734220800000,96521.66753667717],[1734307200000,99631.30349379999],[1734393600000,100856.74619693069],[1734396005000,100857.82408798973]],"market_caps":[[1702944000000,764045559429.9309],[1703030400000,753661901693.3868],[1703116800000,780530401149.5747],[1703203200000,779398921861.9735],[1703289600000,781618872485.1848],[1703376000000,777082690166.5908],[1703462400000,767022708939.5092],[1703548800000,775395628458.022],[1703635200000,753811358997.4457],[1703721600000,764431847914.952],[1703808000000,755499387426.541],[1703894400000,744365987728.8765],[1703980800000,747690300646.6624],[1704067200000,749793913990.623],[1704153600000,782654769238.5906],[1704240000000,802781005990.2638],[1704326400000,767255728861.6259],[1704412800000,791548580238.1727],[1704499200000,788420193391.969],[1704585600000,786156615466.8936],[1704672000000,783483447081.5107],[1704758400000,839801871053.8757],[1704844800000,825360034647.3444],[1704931200000,834081331690.4401],[1705017600000,828583454756.0026],[1705104000000,764669480252.108],[1705190400000,765413030638.2758],[1705276800000,751728074198.5833],[1705363200000,763009829957.6416],[1705449600000,777614406833.8657],[1705536000000,769011178459.9075],[1705622400000,743690861843.4908],[1705708800000,747617250694.5536],[1705795200000,749429420625.5665],[1705881600000,748057990952.723],[1705968000000,712368205942.2236],[1706054400000,717595846224.838],[1706140800000,723122348942.4839],[1706227200000,722256139684.4453],[1706313600000,755726914732.5784],[1706400000000,760573087967.9829],[1706486400000,760145533102.0594],[1706572800000,783768195098.995],[1706659200000,773940797300.2744],[1706745600000,772787325782.8993],[1706832000000,776719396542.2999],[1706918400000,783958733249.4813],[1707004800000,780353189423.5363],[1707091200000,775166129480.905],[1707177600000,778777836396.1644],[1707264000000,785970965415.3887],[1707350400000,806224953741.7788],[1707436800000,826409667638.9438],[1707523200000,858208751753.8303],[1707609600000,869955939250.7516],[1707696000000,875978364782.8907],[1707782400000,911118983788.3319],[1707868800000,909849453848.8177],[1707955200000,948242282042.772],[1708041600000,947310107637.3593],[1708128000000,950197730601.678],[1708214400000,942550827895.1658],[1708300800000,949322898904.7262],[1708387200000,942991011917.1135],[1708473600000,949540001949.272],[1708560000000,941627317726.0989],[1708646400000,930501637905.6324],[1708732800000,922355633704.3813],[1708819200000,934676374407.968],[1708905600000,939479759011.2206],[1708992000000,986997076569.4227],[1709078400000,1031955351342.0142],[1709164800000,1130411922559.5562],[1709251200000,1117043914029.6543],[1709337600000,1132012429589.901],[1709424000000,1124332376821.0593],[1709510400000,1140239593736.3315],[1709596800000,1228771711791.6702],[1709683200000,1165166845467.8345],[1709769600000,1190756605244.6602],[1709856000000,1203016564461.5864],[1709942400000,1225358206016.94],[1710028800000,1229978177507.9448],[1710115200000,1240532622108.6042],[1710201600000,1297198119272.7556],[1710288000000,1284621855311.5063],[1710374400000,1311710453331.1807],[1710460800000,1289935627507.947],[1710547200000,1255862277359.9707],[1710633600000,1177567732739.3704],[1710720000000,1235543252546.1501],[1710806400000,1224902554138.9043],[1710892800000,1126404736225.0005],[1710979200000,1218425490228.851],[1711065600000,1185130051154.7644],[1711152000000,1148762056120.8308],[1711238400000,1164746053086.3816],[1711324800000,1224549270763.7375],[1711411200000,1265735595022.6558],[1711497600000,1270001087597.4917],[1711584000000,1257948244675.7322],[1711670400000,1289062232208.1372],[1711756800000,1272808920844.3113],[1711843200000,1268735443309.547],[1711929600000,1298379310622.5427],[1712016000000,1279434155078.9397],[1712102400000,1196304599517.0796],[1712188800000,1201042014718.3618],[1712275200000,1244181203942.2173],[1712361600000,1233398772850.8083],[1712448000000,1252853847882.3098],[1712534400000,1262195994485.1626],[1712620800000,1298271561930.8398],[1712707200000,1254048562016.6165],[1712793600000,1291829474485.008],[1712880000000,1286022438247.104],[1712966400000,1240036468179.4954],[1713052800000,1191586427540.0698],[1713139200000,1214419196128.5972],[1713225600000,1174217480691.2903],[1713312000000,1180219579685.6067],[1713398400000,1131391929592.0496],[1713484800000,1174027153438.296],[1713571200000,1184813761326.875],[1713657600000,1198537261371.2727],[1713744000000,1200575636208.7861],[1713830400000,1236171486303.3884],[1713916800000,1221220335945.559],[1714003200000,1182986317158.927],[1714089600000,1183322986546.24],[1714176000000,1174004613361.7327],[1714262400000,1168134415693.9995],[1714348800000,1159037915185.4558],[1714435200000,1173144443638.821],[1714521600000,1122733421392.7615],[1714608000000,1074539282758.3136],[1714694400000,1085640313340.4697],[1714780800000,1151356095639.7876],[1714867200000,1167823420330.3193],[1714953600000,1170693158689.583],[1715040000000,1155394777129.147],[1715126400000,1143529004292.6711],[1715212800000,1120237757734.764],[1715299200000,1150877656960.4106],[1715385600000,1112737515566.075],[1715472000000,1110612255834.1973],[1715558400000,1124453150567.785],[1715644800000,1148051981903.983],[1715731200000,1121416977137.8594],[1715817600000,1200367522106.6362],[1715904000000,1183686599329.002],[1715990400000,1212577969927.9417],[1716076800000,1213212301806.6638],[1716163200000,1199871409788.1362],[1716249600000,1295016060059.1472],[1716336000000,1273106118664.916],[1716422400000,1258222473590.343],[1716508800000,1235720348593.309],[1716595200000,1244542923268.9268],[1716681600000,1257314123353.6438],[1716768000000,1243813340333.789],[1716854400000,1258601999436.9578],[1716940800000,1241636599638.2234],[1717027200000,1232802172226.9258],[1717113600000,1242748884197.98],[1717200000000,1226438800629.8735],[1717286400000,1228433941518.9446],[1717372800000,1231006583667.743],[1717459200000,1242762702466.981],[1717545600000,1278263906372.3005],[1717632000000,1287689992253.43],[1717718400000,1280663158196.4136],[1717804800000,1264299316495.0254],[1717891200000,1264020591899.3923],[1717977600000,1273609983761.1584],[1718064000000,1272210501370.2603],[1718150400000,1236256618655.0908],[1718236800000,1244029473920.9504],[1718323200000,1226474851982.018],[1718409600000,1214209737400.5469],[1718496000000,1217046635445.1626],[1718582400000,1227595179188.131],[1718668800000,1219051116802.9226],[1718755200000,1193916188008.191],[1718841600000,1189652390521.7627],[1718928000000,1195025470628.2522],[1719014400000,1181499377294.3064],[1719100800000,1184321835834.7974],[1719187200000,1167675702293.7664],[1719273600000,1107858256415.485],[1719360000000,1137194446940.2493],[1719446400000,1121746795196.9453],[1719532800000,1133540553897.4954],[1719619200000,1109655746534.5195],[1719705600000,1119192024599.859],[1719792000000,1151840688521.5325],[1719878400000,1153842325231.202],[1719964800000,1137636248797.9766],[1720051200000,1099959601411.4194],[1720137600000,1045455213793.5796],[1720224000000,1031120886833.7273],[1720310400000,1058232307989.9891],[1720396800000,1016418261442.7322],[1720483200000,1031886535204.6488],[1720569600000,1058511512538.3088],[1720656000000,1051818926340.8833],[1720742400000,1040185593841.387],[1720828800000,1045247628106.2981],[1720915200000,1069888561366.5227],[1721001600000,1106207687377.5999],[1721088000000,1172591088151.519],[1721174400000,1178656334961.6272],[1721260800000,1157075741795.815],[1721347200000,1157133857736.086],[1721433600000,1208294383639.0444],[1721520000000,1217149815021.6057],[1721606400000,1232461528971.5544],[1721692800000,1224219293321.9321],[1721779200000,1198835347285.7869],[1721865600000,1190570334458.9207],[1721952000000,1195542086259.1108],[1722038400000,1232400087482.9558],[1722124800000,1235238043388.1118],[1722211200000,1240304376596.7764],[1722297600000,1217681073144.4795],[1722384000000,1208037973353.6438],[1722470400000,1178510499183.3403],[1722556800000,1194838474557.0352],[1722643200000,1109705678810.4106],[1722729600000,1097387256676.079],[1722816000000,1052498007467.1093],[1722902400000,972427918423.3748],[1722988800000,1010553135277.3352],[1723075200000,995829528973.5793],[1723161600000,1116222608474.5273],[1723248000000,1096966222757.3721],[1723334400000,1100012902964.6958],[1723420800000,1062177229408.719],[1723507200000,1071240180223.895],[1723593600000,1087488608146.0857],[1723680000000,1053042558016.0778],[1723766400000,1036457024309.6311],[1723852800000,1053996298299.8085],[1723939200000,1063563839568.8687],[1724025600000,1048736410107.2246],[1724112000000,1061067922878.8774],[1724198400000,1047855615204.1729],[1724284800000,1082245173425.6625],[1724371200000,1072480197942.716],[1724457600000,1126449704877.065],[1724544000000,1130137190128.4998],[1724630400000,1134445401094.1704],[1724716800000,1112777064777.5403],[1724803200000,1052696200890.6747],[1724889600000,1048104543360.7959],[1724976000000,1057603318248.8383],[1725062400000,1056174914763.6614],[1725148800000,1052644976421.7173],[1725235200000,1025971504101.7284],[1725321600000,1055774373172.189],[1725408000000,1029402103522.1177],[1725494400000,1033215644314.5056],[1725580800000,998279096382.0908],[1725667200000,960727122442.3049],[1725753600000,963876478138.5984],[1725840000000,978789756851.9156],[1725926400000,1021080494896.519],[1726012800000,1032514469645.9021],[1726099200000,1030116857690.4011],[1726185600000,1036768569791.1576],[1726272000000,1080345335187.4402],[1726358400000,1069483699270.5634],[1726444800000,1054918116527.0073],[1726531200000,1033801760122.5457],[1726617600000,1071488975861.3236],[1726704000000,1090617713058.4265],[1726790400000,1114446751303.7446],[1726876800000,1115365405107.757],[1726963200000,1122693086016.0098],[1727049600000,1125222965632.3362],[1727136000000,1125923016568.3755],[1727222400000,1136025045157.7083],[1727308800000,1119165941254.2256],[1727395200000,1151723722886.6248],[1727481600000,1163414323043.4895],[1727568000000,1166555764895.319],[1727654400000,1162004270981.7502],[1727740800000,1121955923185.1003],[1727827200000,1087242811870.4403],[1727913600000,1085088202725.9215],[1728000000000,1087741100651.1527],[1728086400000,1117128576184.053],[1728172800000,1117066123002.787],[1728259200000,1131919568773.6216],[1728345600000,1121655031340.7485],[1728432000000,1120296945863.3242],[1728518400000,1094687997044.0862],[1728604800000,1088058691272.9097],[1728691200000,1127062755021.9717],[1728777600000,1141359509847.64],[1728864000000,1134323870057.25],[1728950400000,1197356598803.713],[1729036800000,1215867091585.8062],[1729123200000,1231494200521.79],[1729209600000,1228557191099.4949],[1729296000000,1244920069909.3826],[1729382400000,1243448974215.4006],[1729468800000,1253692236055.3801],[1729555200000,1231860087149.921],[1729641600000,1233160899216.1892],[1729728000000,1222780930737.8003],[1729814400000,1244798930657.2415],[1729900800000,1216575374276.9106],[1729987200000,1226960927651.153],[1730073600000,1244481292778.0234],[1730160000000,1276885375262.7925],[1730246400000,1330292730940.5366],[1730332800000,1317853770698.0745],[1730419200000,1277141006442.827],[1730505600000,1264603721992.7305],[1730592000000,1260304002327.4312],[1730678400000,1252514111441.5698],[1730764800000,1232424950327.2417],[1730851200000,1254425017928.7395],[1730937600000,1394022982578.445],[1731024000000,1391245051949.168],[1731110400000,1411897389187.6711],[1731196800000,1417614104649.186],[1731283200000,1484434748459.1074],[1731369600000,1647828731487.2427],[1731456000000,1640041552819.5366],[1731542400000,1694929711862.1062],[1731628800000,1639737492315.8826],[1731715200000,1706203252031.6326],[1731801600000,1697962280123.0564],[1731888000000,1686578526653.0322],[1731974400000,1689949263959.0898],[1732060800000,1720695713494.1255],[1732147200000,1766101580550.202],[1732233600000,1861205978707.735],[1732320000000,1879473647269.3767],[1732406400000,1853767867711.6104],[1732492800000,1850862266537.5037],[1732579200000,1757981270636.0205],[1732665600000,1734807678369.8247],[1732752000000,1798047653070.2747],[1732838400000,1791871712985.0552],[1732924800000,1821220595631.0247],[1733011200000,1805655347652.551],[1733097600000,1826712092790.8245],[1733184000000,1805973641926.7527],[1733270400000,1808870011532.4983],[1733356800000,1861950970195.8547],[1733443200000,1814513963950.151],[1733529600000,1870708184255.423],[1733616000000,1868160448106.8904],[1733702400000,1895819284477.133],[1733788800000,1824685578367.7026],[1733875200000,1816541694218.0803],[1733961600000,1905410387343.5999],[1734048000000,1889662319163.5203],[1734134400000,1908887834839.7786],[1734220800000,1911680889414.6633],[1734307200000,1974756090871.2612],[1734393600000,1994542016925.7861],[1734396005000,1997412290393.3455]],"total_volumes":[[1702944000000,24236756809.610874],[1703030400000,21312795316.66426],[1703116800000,25887016911.17018],[1703203200000,19940952368.916634],[1703289600000,18895067898.906437],[1703376000000,8953022262.130066],[1703462400000,16623374495.489248],[1703548800000,17009181022.074778],[1703635200000,18544256378.162125],[1703721600000,20925648865.060066],[1703808000000,18375029769.578327],[1703894400000,22461574030.714294],[1703980800000,13321172326.845724],[1704067200000,12850316555.324738],[1704153600000,15367058214.745464],[1704240000000,35725011405.88339],[1704326400000,39491078579.2771],[1704412800000,23866156229.01105],[1704499200000,26805544307.690857],[1704585600000,10809150081.41469],[1704672000000,13845181930.24744],[1704758400000,37274113826.374435],[1704844800000,36490066076.38239],[1704931200000,47410932764.19392],[1705017600000,44797651373.354866],[1705104000000,41876420338.82902],[1705190400000,17716846311.352158],[1705276800000,15503197379.230145],[1705363200000,20679492660.517693],[1705449600000,20247282080.206425],[1705536000000,19567983377.45631],[1705622400000,23134362082.989418],[1705708800000,22383239893.13225],[1705795200000,8742685618.842075],[1705881600000,7375172401.676388],[1705968000000,28682189093.05966],[1706054400000,27340392964.10615],[1706140800000,20416186504.057236],[1706227200000,12329524792.522268],[1706313600000,20878573788.263638],[1706400000000,9844140113.16917],[1706486400000,12607124883.848349],[1706572800000,19085104248.29378],[1706659200000,22618759527.17684],[1706745600000,20533587440.086643],[1706832000000,20636966305.840107],[1706918400000,17247488100.16845],[1707004800000,7204501674.165378],[1707091200000,10436964474.709417],[1707177600000,17391461496.537296],[1707264000000,15924809340.146698],[1707350400000,19676663584.994637],[1707436800000,25715815919.968224],[1707523200000,38938892280.48865],[1707609600000,15263610371.064165],[1707696000000,12223157201.843592],[1707782400000,34984086720.421394],[1707868800000,34861630132.88448],[1707955200000,38955768310.822464],[1708041600000,29689990497.43616],[1708128000000,23058964466.517426],[1708214400000,18420761945.184425],[1708300800000,15788371337.35062],[1708387200000,20933904079.177547],[1708473600000,31913880466.34793],[1708560000000,28304827430.508766],[1708646400000,21925852529.42366],[1708732800000,20897974088.66788],[1708819200000,14334928103.442892],[1708905600000,14284884544.825203],[1708992000000,32632631614.771385],[1709078400000,47944138521.27305],[1709164800000,80469773523.04562],[1709251200000,62652966681.79769],[1709337600000,33838491238.65334],[1709424000000,23234013063.10625],[1709510400000,25003431615.645008],[1709596800000,68683749167.89397],[1709683200000,88793359828.09514],[1709769600000,67115921901.30683],[1709856000000,44611078613.12767],[1709942400000,57885098776.72138],[1710028800000,19518157761.86789],[1710115200000,33506857287.23204],[1710201600000,60984557068.80768],[1710288000000,59112467277.932526],[1710374400000,47399187528.719086],[1710460800000,57955447999.744316],[1710547200000,74565965097.54878],[1710633600000,45237986618.562836],[1710720000000,43360819029.21664],[1710806400000,47000236982.08203],[1710892800000,73786793068.606],[1710979200000,64770977376.002335],[1711065600000,44547287696.10954],[1711152000000,39081986975.866745],[1711238400000,23474954022.615948],[1711324800000,25997014256.79051],[1711411200000,41354490140.194534],[1711497600000,33470731576.137875],[1711584000000,38323058412.463455],[1711670400000,28222733898.22986],[1711756800000,23875651073.0657],[1711843200000,15193189000.910406],[1711929600000,18273877855.502132],[1712016000000,33562044549.864765],[1712102400000,41940693527.11473],[1712188800000,32810919678.179626],[1712275200000,34615710161.11247],[1712361600000,32399476179.27087],[1712448000000,17631945851.64823],[1712534400000,16561889813.524487],[1712620800000,30480013239.47029],[1712707200000,33601878224.764893],[1712793600000,35733622227.3955],[1712880000000,28076812702.66515],[1712966400000,40787556830.5058],[1713052800000,46175487248.79834],[1713139200000,37831941525.483795],[1713225600000,40270749314.591446],[1713312000000,39692534875.41359],[1713398400000,38541431787.99773],[1713484800000,33730360102.954216],[1713571200000,48982665986.2395],[1713657600000,15570292448.765707],[1713744000000,18381678273.477943],[1713830400000,26045239342.36995],[1713916800000,21708521696.923584],[1714003200000,28919311055.77435],[1714089600000,23029161947.69737],[1714176000000,21788821603.454067],[1714262400000,17940895060.380325],[1714348800000,15114077954.721254],[1714435200000,25421790237.986824],[1714521600000,37064033196.407425],[1714608000000,47686124997.17279],[1714694400000,26331959408.546562],[1714780800000,31194900456.511097],[1714867200000,19047182119.65269],[1714953600000,16836129152.427391],[1715040000000,16635968731.550478],[1715126400000,18530378080.389984],[1715212800000,18891699679.907948],[1715299200000,23963094002.49368],[1715385600000,22254420525.397045],[1715472000000,11244904093.616146],[1715558400000,12111167668.150711],[1715644800000,25701869152.978073],[1715731200000,19954749749.200596],[1715817600000,34989448098.62592],[1715904000000,26791795060.979183],[1715990400000,23344382280.64292],[1716076800000,11972376997.253046],[1716163200000,8343132501.680276],[1716249600000,33076900352.36851],[1716336000000,37863059660.65226],[1716422400000,28693829269.986565],[1716508800000,36282391585.036026],[1716595200000,25915528544.90744],[1716681600000,14683494912.702457],[1716768000000,10364325595.831345],[1716854400000,17479681941.591335],[1716940800000,28821382088.28565],[1717027200000,22515367160.244823],[1717113600000,23398765116.409836],[1717200000000,18130599420.539665],[1717286400000,9878000585.622467],[1717372800000,14869923072.588507],[1717459200000,27377900221.38685],[1717545600000,29004797739.730236],[1717632000000,29943165536.127033],[1717718400000,21678984842.985905],[1717804800000,17208262650.83919],[1717891200000,9889885385.793055],[1717977600000,9705557901.477417],[1718064000000,17963155583.8117],[1718150400000,35859341572.46878],[1718236800000,33314086852.608078],[1718323200000,27612809188.818436],[1718409600000,26082269218.757824],[1718496000000,12570767640.817629],[1718582400000,11817587184.625206],[1718668800000,27381634087.485992],[1718755200000,38534540549.638336],[1718841600000,20174670906.88063],[1718928000000,24288361251.269745],[1719014400000,23096069748.525814],[1719100800000,5929957294.213889],[1719187200000,10055620107.878874],[1719273600000,39167894649.76987],[1719360000000,19707107706.19171],[1719446400000,21446724014.390743],[1719532800000,17608135427.23568],[1719619200000,22760288280.98671],[1719705600000,10666911569.81817],[1719792000000,16181763633.619421],[1719878400000,23818378375.692726],[1719964800000,16838886952.541449],[1720051200000,28126682562.287437],[1720137600000,39882312622.7753],[1720224000000,55388162417.01709],[1720310400000,19642932662.272545],[1720396800000,18668734939.988163],[1720483200000,38420746451.49442],[1720569600000,26274120844.467396],[1720656000000,23991700389.182217],[1720742400000,26852746247.844795],[1720828800000,23352541611.613438],[1720915200000,15352362821.061203],[1721001600000,19562980897.157185],[1721088000000,34699131677.40675],[1721174400000,37524680143.79378],[1721260800000,30584615622.361465],[1721347200000,23826232916.692142],[1721433600000,33963050379.528145],[1721520000000,15932968631.498928],[1721606400000,24843581686.185513],[1721692800000,39625015138.62209],[1721779200000,33266571810.64418],[1721865600000,26460367769.009254],[1721952000000,33026960946.45104],[1722038400000,28347985942.522514],[1722124800000,28493532219.991848],[1722211200000,15823930171.331104],[1722297600000,39304449047.5405],[1722384000000,26709586357.683525],[1722470400000,29010595429.980762],[1722556800000,36070582812.4214],[1722643200000,35716608343.00602],[1722729600000,30124639091.24687],[1722816000000,30857488073.92184],[1722902400000,109805375344.71971],[1722988800000,48165283096.90922],[1723075200000,38065296810.65033],[1723161600000,45075752485.89274],[1723248000000,30945556000.44254],[1723334400000,12282202454.06668],[1723420800000,19899321258.11229],[1723507200000,36030650038.289856],[1723593600000,29062190504.223873],[1723680000000,25500715375.97391],[1723766400000,30524957963.753498],[1723852800000,27528656719.12384],[1723939200000,11301669424.835934],[1724025600000,16014988081.814625],[1724112000000,21678542798.058605],[1724198400000,28280403717.648746],[1724284800000,29546835293.844273],[1724371200000,25338482846.447086],[1724457600000,40203764990.14742],[1724544000000,19681198155.428112],[1724630400000,15978900606.43484],[1724716800000,16027077290.044294],[1724803200000,34111499405.192535],[1724889600000,37771373954.530266],[1724976000000,29388408896.179905],[1725062400000,39813795557.68244],[1725148800000,10306119140.205893],[1725235200000,23055759148.414356],[1725321600000,25313134436.021263],[1725408000000,24283254518.8608],[1725494400000,33886402300.26096],[1725580800000,27004866037.61644],[1725667200000,45320178289.28158],[1725753600000,15372585933.106846],[1725840000000,16815561367.908846],[1725926400000,32308980055.771793],[1726012800000,26930906291.377064],[1726099200000,34299672189.86188],[1726185600000,30851533892.901806],[1726272000000,28788956794.35168],[1726358400000,14256812437.764132],[1726444800000,15593989422.240282],[1726531200000,28931645426.28441],[1726617600000,30740118553.114304],[1726704000000,36395588024.81709],[1726790400000,37601462347.57837],[1726876800000,31844524527.859264],[1726963200000,11582366076.384974],[1727049600000,18230121450.62975],[1727136000000,21597115107.60136],[1727222400000,28142705656.780155],[1727308800000,23437869613.173584],[1727395200000,33996117325.546436],[1727481600000,29239510929.881367],[1727568000000,13733976640.534172],[1727654400000,11593612614.885637],[1727740800000,31537924652.0654],[1727827200000,49034177042.745705],[1727913600000,37901679060.51742],[1728000000000,34172777744.048645],[1728086400000,27639040540.389217],[1728172800000,10103308836.609213],[1728259200000,13300847513.28617],[1728345600000,30869381700.261482],[1728432000000,26081711472.44388],[1728518400000,26075363404.124302],[1728604800000,26964801126.052986],[1728691200000,29250057048.886543],[1728777600000,16143457901.477922],[1728864000000,15338765154.550152],[1728950400000,42678099498.38608],[1729036800000,47590231395.44467],[1729123200000,37424938104.72408],[1729209600000,31867101644.482155],[1729296000000,36776163863.41151],[1729382400000,12978490558.257559],[1729468800000,15877367461.98269],[1729555200000,37433699396.538925],[1729641600000,29080874803.888737],[1729728000000,30311730450.974777],[1729814400000,33147468234.34605],[1729900800000,44837230618.6183],[1729987200000,19600259820.499084],[1730073600000,15316545074.05997],[1730160000000,38825503403.448814],[1730246400000,60527540932.56764],[1730332800000,40056440641.14229],[1730419200000,42173773194.33304],[1730505600000,50469710696.11748],[1730592000000,13526495563.210878],[1730678400000,34630826043.86812],[1730764800000,42472713673.43949],[1730851200000,36620093013.85483],[1730937600000,119717779475.74457],[1731024000000,61665436983.2205],[1731110400000,47267635583.06925],[1731196800000,29323966699.251873],[1731283200000,87239403420.88857],[1731369600000,125646393751.07103],[1731456000000,143470295938.74728],[1731542400000,128767560174.35379],[1731628800000,94639219723.79593],[1731715200000,80017548477.67146],[1731801600000,46711735711.32105],[1731888000000,46088268734.089355],[1731974400000,73036195338.83128],[1732060800000,75881616541.24971],[1732147200000,76555842681.1068],[1732233600000,112837970883.96764],[1732320000000,82303465655.24199],[1732406400000,45511800251.80984],[1732492800000,48355173230.56986],[1732579200000,85585174959.04805],[1732665600000,92837558437.63956],[1732752000000,77130214035.7825],[1732838400000,46460805592.15066],[1732924800000,70577516541.62772],[1733011200000,41198720283.14104],[1733097600000,46618694880.34837],[1733184000000,96193940544.53133],[1733270400000,83671044173.51279],[1733356800000,93446650410.37816],[1733443200000,179935267250.6917],[1733529600000,109509066272.2067],[1733616000000,57765605617.94603],[1733702400000,59316795003.98513],[1733788800000,139118734393.82437],[1733875200000,119485287617.8021],[1733961600000,112782750873.94843],[1734048000000,95565580844.76036],[1734134400000,75010198346.95139],[1734220800000,54581936836.852325],[1734307200000,67211229482.25423],[1734393600000,109519261247.90494],[1734396005000,105347250732.53983]] } ================================================ FILE: example/devtools_options.yaml ================================================ extensions: ================================================ FILE: example/ios/.gitignore ================================================ **/dgph *.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: example/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 ================================================ FILE: example/ios/Flutter/Debug.xcconfig ================================================ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" ================================================ FILE: example/ios/Flutter/Release.xcconfig ================================================ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" ================================================ FILE: example/ios/Podfile ================================================ # Uncomment this line to define a global platform for your project # platform :ios, '13.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: example/ios/Runner/AppDelegate.swift ================================================ import Flutter import UIKit @main @objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { return super.application(application, didFinishLaunchingWithOptions: launchOptions) } func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) { GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry) } } ================================================ FILE: example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "filename" : "app_logo_1024.jpg", "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: example/ios/Runner/Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: example/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: example/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: example/ios/Runner/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: example/ios/Runner/Base.lproj/Main.storyboard ================================================ ================================================ FILE: example/ios/Runner/Info.plist ================================================ CADisableMinimumFrameDurationOnPhone CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName FL Chart CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName FL Chart App CFBundlePackageType APPL CFBundleShortVersionString $(FLUTTER_BUILD_NAME) CFBundleSignature ???? CFBundleVersion $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS UIApplicationSceneManifest UIApplicationSupportsMultipleScenes UISceneConfigurations UIWindowSceneSessionRoleApplication UISceneClassName UIWindowScene UISceneConfigurationName flutter UISceneDelegateClassName FlutterSceneDelegate UISceneStoryboardFile Main UIApplicationSupportsIndirectInputEvents UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight ================================================ FILE: example/ios/Runner/Runner-Bridging-Header.h ================================================ #import "GeneratedPluginRegistrant.h" ================================================ FILE: example/ios/Runner.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 54; objects = { /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 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 */; }; E74A93EDB2B26BA3B68EB856 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 66E5FD19F03E6633F69A2755 /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 97C146E61CF9000F007C117D /* Project object */; proxyType = 1; remoteGlobalIDString = 97C146ED1CF9000F007C117D; remoteInfo = Runner; }; /* End PBXContainerItemProxy 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 = ""; }; 2533C9FF039E091768C02A3C /* 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 = ""; }; 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 66E5FD19F03E6633F69A2755 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 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 = ""; }; 92784C7839A21A123BE5F569 /* 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 = ""; }; 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 = ""; }; FDA57114F6B1B00C26C17AEA /* 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 = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( E74A93EDB2B26BA3B68EB856 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 331C8082294A63A400263BE5 /* RunnerTests */ = { isa = PBXGroup; children = ( 331C807B294A618700263BE5 /* RunnerTests.swift */, ); path = RunnerTests; sourceTree = ""; }; 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 */, 331C8082294A63A400263BE5 /* RunnerTests */, B5EDA2152F3EE166ACB0102F /* Pods */, F62F59B1CB0A3441A4333BCE /* Frameworks */, ); sourceTree = ""; }; 97C146EF1CF9000F007C117D /* Products */ = { isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, 331C8081294A63A400263BE5 /* RunnerTests.xctest */, ); 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 = ""; }; B5EDA2152F3EE166ACB0102F /* Pods */ = { isa = PBXGroup; children = ( 92784C7839A21A123BE5F569 /* Pods-Runner.debug.xcconfig */, FDA57114F6B1B00C26C17AEA /* Pods-Runner.release.xcconfig */, 2533C9FF039E091768C02A3C /* Pods-Runner.profile.xcconfig */, ); path = Pods; sourceTree = ""; }; F62F59B1CB0A3441A4333BCE /* Frameworks */ = { isa = PBXGroup; children = ( 66E5FD19F03E6633F69A2755 /* Pods_Runner.framework */, ); name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 331C8080294A63A400263BE5 /* RunnerTests */ = { isa = PBXNativeTarget; buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( 331C807D294A63A400263BE5 /* Sources */, 331C807F294A63A400263BE5 /* Resources */, ); buildRules = ( ); dependencies = ( 331C8086294A63A400263BE5 /* PBXTargetDependency */, ); name = RunnerTests; productName = RunnerTests; productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( 1474CFBDFB72840D91CF260E /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 654E70DC308C8EF6E1C50DAB /* [CP] Embed Pods Frameworks */, ); 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 = { BuildIndependentTargetsInParallel = YES; LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 331C8080294A63A400263BE5 = { CreatedOnToolsVersion = 14.0; TestTargetID = 97C146ED1CF9000F007C117D; }; 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 */, 331C8080294A63A400263BE5 /* RunnerTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 331C807F294A63A400263BE5 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 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 */ 1474CFBDFB72840D91CF260E /* [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; }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; 654E70DC308C8EF6E1C50DAB /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; 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 */ 331C807D294A63A400263BE5 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 97C146ED1CF9000F007C117D /* Runner */; targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency 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; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 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; ENABLE_USER_SCRIPT_SANDBOXING = NO; 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 = 13.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; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = YANXYADU5H; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = "FL Chart"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = dev.flchart.app; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; 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; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 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; ENABLE_USER_SCRIPT_SANDBOXING = NO; 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 = 13.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; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 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; ENABLE_USER_SCRIPT_SANDBOXING = NO; 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 = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; 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; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = YANXYADU5H; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = "FL Chart"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = dev.flchart.app; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; 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; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = YANXYADU5H; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = "FL Chart"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = dev.flchart.app; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; }; B5628A0E2E53D3C1005A9E21 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { COPY_PHASE_STRIP = NO; GCC_DYNAMIC_NO_PIC = NO; GCC_OPTIMIZATION_LEVEL = 0; PRODUCT_NAME = RunnerTests; }; name = Debug; }; B5628A0F2E53D3C1005A9E21 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { COPY_PHASE_STRIP = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; PRODUCT_NAME = RunnerTests; }; name = Release; }; B5628A102E53D3C1005A9E21 /* Profile */ = { isa = XCBuildConfiguration; buildSettings = { PRODUCT_NAME = RunnerTests; }; name = Profile; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { isa = XCConfigurationList; buildConfigurations = ( B5628A0E2E53D3C1005A9E21 /* Debug */, B5628A0F2E53D3C1005A9E21 /* Release */, B5628A102E53D3C1005A9E21 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 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: example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings ================================================ PreviewsEnabled ================================================ FILE: example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ================================================ ================================================ FILE: example/ios/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings ================================================ PreviewsEnabled ================================================ FILE: example/ios/RunnerTests/RunnerTests.swift ================================================ import Flutter import UIKit import XCTest class RunnerTests: XCTestCase { func testExample() { // If you add code to the Runner application, consider adding tests here. // See https://developer.apple.com/documentation/xctest for more information about using XCTest. } } ================================================ FILE: example/lib/cubits/app/app_cubit.dart ================================================ import 'package:fl_chart_app/urls.dart'; import 'package:fl_chart_app/util/app_utils.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:equatable/equatable.dart'; part 'app_state.dart'; class AppCubit extends Cubit { AppCubit() : super(const AppState()) { initialize(); } void initialize() async { PackageInfo packageInfo = await PackageInfo.fromPlatform(); emit(state.copyWith( currentPackageInfo: packageInfo, availableVersionToUpdate: '', usingFlChartVersion: BuildConstants.usingFlChartVersion, showDownloadNativeAppButton: kIsWeb || kIsWasm, )); } void onVersionClicked() { AppUtils().tryToLaunchUrl( Urls.getVersionReleaseUrl(state.usingFlChartVersion), ); } void hideDownloadNativeAppButton() { emit(state.copyWith( showDownloadNativeAppButton: false, )); } } class BuildConstants { static const String usingFlChartVersion = String.fromEnvironment( 'USING_FL_CHART_VERSION', defaultValue: '', ); } ================================================ FILE: example/lib/cubits/app/app_state.dart ================================================ part of 'app_cubit.dart'; class AppState extends Equatable { final PackageInfo? currentPackageInfo; final String availableVersionToUpdate; final String usingFlChartVersion; final bool showDownloadNativeAppButton; String? get appVersion => currentPackageInfo?.version; const AppState([ this.currentPackageInfo, this.availableVersionToUpdate = '', this.usingFlChartVersion = '', this.showDownloadNativeAppButton = false, ]); AppState copyWith({ PackageInfo? currentPackageInfo, String? availableVersionToUpdate, String? usingFlChartVersion, bool? showDownloadNativeAppButton, }) { return AppState( currentPackageInfo ?? this.currentPackageInfo, availableVersionToUpdate ?? this.availableVersionToUpdate, usingFlChartVersion ?? this.usingFlChartVersion, showDownloadNativeAppButton ?? this.showDownloadNativeAppButton, ); } @override List get props => [ currentPackageInfo, availableVersionToUpdate, usingFlChartVersion, showDownloadNativeAppButton, ]; } ================================================ FILE: example/lib/main.dart ================================================ import 'package:fl_chart_app/cubits/app/app_cubit.dart'; import 'package:fl_chart_app/presentation/resources/app_resources.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:google_fonts/google_fonts.dart'; import 'presentation/router/app_router.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MultiBlocProvider( providers: [ BlocProvider(create: (BuildContext context) => AppCubit()), ], child: MaterialApp.router( title: AppTexts.appName, theme: ThemeData( brightness: Brightness.dark, useMaterial3: true, textTheme: GoogleFonts.assistantTextTheme( Theme.of(context).textTheme.apply( bodyColor: AppColors.mainTextColor3, ), ), scaffoldBackgroundColor: AppColors.pageBackground, ), routerConfig: appRouterConfig, ), ); } } ================================================ FILE: example/lib/presentation/menu/app_menu.dart ================================================ import 'package:dartx/dartx.dart'; import 'package:fl_chart_app/cubits/app/app_cubit.dart'; import 'package:fl_chart_app/presentation/resources/app_resources.dart'; import 'package:fl_chart_app/urls.dart'; import 'package:fl_chart_app/util/app_helper.dart'; import 'package:fl_chart_app/util/app_utils.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:url_launcher/url_launcher.dart'; import 'fl_chart_banner.dart'; import 'menu_row.dart'; class AppMenu extends StatefulWidget { final List menuItems; final int currentSelectedIndex; final Function(int, ChartMenuItem) onItemSelected; final VoidCallback? onBannerClicked; const AppMenu({ super.key, required this.menuItems, required this.currentSelectedIndex, required this.onItemSelected, required this.onBannerClicked, }); @override AppMenuState createState() => AppMenuState(); } class AppMenuState extends State { @override Widget build(BuildContext context) { return Container( color: AppColors.itemsBackground, child: Column( children: [ SafeArea( child: AspectRatio( aspectRatio: 3, child: Center( child: InkWell( onTap: widget.onBannerClicked, child: const FlChartBanner(), ), ), ), ), Expanded( child: ListView.builder( itemBuilder: (context, position) { final menuItem = widget.menuItems[position]; return MenuRow( text: menuItem.text, svgPath: menuItem.iconPath, isSelected: widget.currentSelectedIndex == position, onTap: () { widget.onItemSelected(position, menuItem); }, onDocumentsTap: () async { final url = Uri.parse(menuItem.chartType.documentationUrl); if (await canLaunchUrl(url)) { await launchUrl(url); } }, ); }, itemCount: widget.menuItems.length, ), ), const _AppVersionRow(), ], ), ); } } class _AppVersionRow extends StatelessWidget { const _AppVersionRow(); @override Widget build(BuildContext context) { return BlocBuilder(builder: (context, state) { if (state.appVersion.isNullOrBlank) { return Container(); } return Container( margin: const EdgeInsets.all(12), child: Row( children: [ Expanded( child: Padding( padding: const EdgeInsets.all(10.0), child: RichText( text: TextSpan( text: '', style: DefaultTextStyle.of(context).style, children: [ const TextSpan(text: 'App version: '), TextSpan( text: 'v${state.appVersion!}', style: const TextStyle( fontWeight: FontWeight.bold, ), ), if (state.usingFlChartVersion.isNotBlank) ...[ TextSpan( text: '\nfl_chart: ', recognizer: TapGestureRecognizer() ..onTap = BlocProvider.of(context) .onVersionClicked, ), TextSpan( text: 'v${state.usingFlChartVersion}', style: const TextStyle( fontWeight: FontWeight.bold, ), recognizer: TapGestureRecognizer() ..onTap = BlocProvider.of(context) .onVersionClicked, ), ] ], ), ), ), ), state.availableVersionToUpdate.isNotBlank ? TextButton( onPressed: () {}, child: Text( 'Update to ${state.availableVersionToUpdate}', style: const TextStyle( color: AppColors.primary, fontSize: 12, fontWeight: FontWeight.bold, ), ), ) : TextButton( onPressed: () => AppUtils().tryToLaunchUrl(Urls.aboutUrl), child: const Text( 'About', style: TextStyle( color: AppColors.primary, fontSize: 12, fontWeight: FontWeight.bold, ), ), ), ], ), ); }); } } class ChartMenuItem { final ChartType chartType; final String text; final String iconPath; const ChartMenuItem(this.chartType, this.text, this.iconPath); } ================================================ FILE: example/lib/presentation/menu/fl_chart_banner.dart ================================================ import 'package:fl_chart_app/presentation/resources/app_resources.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; class FlChartBanner extends StatelessWidget { const FlChartBanner({super.key}); @override Widget build(BuildContext context) { return LayoutBuilder(builder: (context, constraints) { final maxWidth = constraints.maxWidth; final imageSize = maxWidth / 5.14; final space = maxWidth / 16.0; final textWidth = maxWidth / 2.8; return Row( children: [ SizedBox( width: imageSize, ), Image.asset( AppAssets.flChartLogoIcon, width: imageSize, height: imageSize, ), SizedBox( width: space, ), SvgPicture.asset( AppAssets.flChartLogoText, width: textWidth, ), ], ); }); } } ================================================ FILE: example/lib/presentation/menu/menu_row.dart ================================================ import 'package:fl_chart_app/presentation/resources/app_resources.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; class MenuRow extends StatefulWidget { final String text; final String svgPath; final bool isSelected; final VoidCallback onTap; final VoidCallback onDocumentsTap; const MenuRow({ super.key, required this.text, required this.svgPath, required this.isSelected, required this.onTap, required this.onDocumentsTap, }); @override State createState() => _MenuRowState(); } class _MenuRowState extends State { bool get _showSelectedState => widget.isSelected; bool isHovered = false; @override Widget build(BuildContext context) { return Material( color: Colors.transparent, child: InkWell( onHover: (bool hovered) { setState(() { isHovered = hovered; }); }, onTap: widget.onTap, child: SizedBox( height: AppDimens.menuRowHeight, child: Row( children: [ const SizedBox( width: 36, ), SvgPicture.asset( widget.svgPath, width: AppDimens.menuIconSize, height: AppDimens.menuIconSize, colorFilter: const ColorFilter.mode(AppColors.primary, BlendMode.srcIn), ), const SizedBox( width: 18, ), Text( widget.text, style: TextStyle( color: _showSelectedState ? AppColors.primary : Colors.white, fontSize: AppDimens.menuTextSize, ), ), Expanded(child: Container()), _DocumentationIcon(onTap: widget.onDocumentsTap), const SizedBox( width: 18, ), ], ), ), ), ); } } class _DocumentationIcon extends StatelessWidget { const _DocumentationIcon({ required this.onTap, }); final VoidCallback onTap; @override Widget build(BuildContext context) { return SizedBox( width: AppDimens.menuDocumentationIconSize, height: AppDimens.menuDocumentationIconSize, child: IconButton( onPressed: onTap, icon: const Icon( Icons.article, color: AppColors.contentColorWhite, ), tooltip: 'Documentation', ), ); } } ================================================ FILE: example/lib/presentation/pages/chart_samples_page.dart ================================================ import 'package:fl_chart_app/presentation/resources/app_resources.dart'; import 'package:fl_chart_app/presentation/samples/chart_samples.dart'; import 'package:fl_chart_app/presentation/widgets/chart_holder.dart'; import 'package:fl_chart_app/util/app_helper.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; class ChartSamplesPage extends StatelessWidget { final ChartType chartType; final samples = ChartSamples.samples; ChartSamplesPage({ super.key, required this.chartType, }); @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.transparent, body: MasonryGridView.builder( itemCount: samples[chartType]!.length, key: ValueKey(chartType), padding: const EdgeInsets.only( left: AppDimens.chartSamplesSpace, right: AppDimens.chartSamplesSpace, top: AppDimens.chartSamplesSpace, bottom: AppDimens.chartSamplesSpace + 68, ), crossAxisSpacing: AppDimens.chartSamplesSpace, mainAxisSpacing: AppDimens.chartSamplesSpace, itemBuilder: (BuildContext context, int index) { return ChartHolder(chartSample: samples[chartType]![index]); }, gridDelegate: const SliverSimpleGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 600, ), ), ); } } ================================================ FILE: example/lib/presentation/pages/home_page.dart ================================================ import 'package:dartx/dartx.dart'; import 'package:fl_chart_app/cubits/app/app_cubit.dart'; import 'package:fl_chart_app/presentation/menu/app_menu.dart'; import 'package:fl_chart_app/presentation/resources/app_resources.dart'; import 'package:fl_chart_app/presentation/widgets/download_native_app_button.dart'; import 'package:fl_chart_app/urls.dart'; import 'package:fl_chart_app/util/app_helper.dart'; import 'package:fl_chart_app/util/app_utils.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; import 'chart_samples_page.dart'; class HomePage extends StatelessWidget { HomePage({ super.key, required this.showingChartType, }) { _initMenuItems(); } void _initMenuItems() { _menuItemsIndices = {}; _menuItems = ChartType.values.mapIndexed( (int index, ChartType type) { _menuItemsIndices[type] = index; return ChartMenuItem( type, type.displayName, type.assetIcon, ); }, ).toList(); } final ChartType showingChartType; late final Map _menuItemsIndices; late final List _menuItems; @override Widget build(BuildContext context) { final selectedMenuIndex = _menuItemsIndices[showingChartType]!; return LayoutBuilder( builder: (context, constraints) { final needsDrawer = constraints.maxWidth <= AppDimens.menuMaxNeededWidth + AppDimens.chartBoxMinWidth; final appMenuWidget = AppMenu( menuItems: _menuItems, currentSelectedIndex: selectedMenuIndex, onItemSelected: (newIndex, chartMenuItem) { context.go('/${chartMenuItem.chartType.name}'); if (needsDrawer) { /// to close the drawer Navigator.of(context).pop(); } }, onBannerClicked: () => AppUtils().tryToLaunchUrl(Urls.flChartUrl), ); final samplesSectionWidget = ChartSamplesPage(chartType: showingChartType); final body = needsDrawer ? samplesSectionWidget : Row( children: [ SizedBox( width: AppDimens.menuMaxNeededWidth, child: appMenuWidget, ), Expanded( child: samplesSectionWidget, ) ], ); return BlocBuilder( builder: (context, state) { return Scaffold( body: Stack( children: [ body, if (state.showDownloadNativeAppButton) Align( alignment: Alignment.bottomCenter, child: Padding( padding: const EdgeInsets.only(bottom: 24.0), child: DownloadNativeAppButton( onClose: () => context .read() .hideDownloadNativeAppButton(), onDownload: () => AppUtils().tryToLaunchUrl(Urls.downloadUrl), ), ), ), ], ), drawer: needsDrawer ? Drawer( child: appMenuWidget, ) : null, appBar: needsDrawer ? AppBar( elevation: 0, backgroundColor: Colors.transparent, title: Text(showingChartType.displayName), ) : null, ); }, ); }, ); } } ================================================ FILE: example/lib/presentation/presentation_utils.dart ================================================ import 'package:flutter/cupertino.dart'; import 'package:intl/intl.dart'; class AppUtils { static String getFormattedCurrency( BuildContext context, double value, { bool noDecimals = true, }) { final germanFormat = NumberFormat.currency( symbol: '€', decimalDigits: noDecimals && value % 1 == 0 ? 0 : 2, ); return germanFormat.format(value); } } ================================================ FILE: example/lib/presentation/resources/app_assets.dart ================================================ import 'package:fl_chart_app/util/app_helper.dart'; class AppAssets { static String getChartIcon(ChartType type) { switch (type) { case ChartType.line: return 'assets/icons/ic_line_chart.svg'; case ChartType.bar: return 'assets/icons/ic_bar_chart.svg'; case ChartType.pie: return 'assets/icons/ic_pie_chart.svg'; case ChartType.scatter: return 'assets/icons/ic_scatter_chart.svg'; case ChartType.radar: return 'assets/icons/ic_radar_chart.svg'; case ChartType.candlestick: return 'assets/icons/ic_candle_chart.svg'; } } static const flChartLogoIcon = 'assets/icons/fl_chart_logo_icon.png'; static const flChartLogoText = 'assets/icons/fl_chart_logo_text.svg'; } ================================================ FILE: example/lib/presentation/resources/app_colors.dart ================================================ import 'package:flutter/material.dart'; class AppColors { static const Color primary = contentColorCyan; static const Color menuBackground = Color(0xFF090912); static const Color itemsBackground = Color(0xFF1B2339); static const Color pageBackground = Color(0xFF282E45); static const Color mainTextColor1 = Colors.white; static const Color mainTextColor2 = Colors.white70; static const Color mainTextColor3 = Colors.white38; static const Color mainGridLineColor = Colors.white10; static const Color borderColor = Colors.white54; static const Color gridLinesColor = Color(0x11FFFFFF); static const Color contentColorBlack = Colors.black; static const Color contentColorWhite = Colors.white; static const Color contentColorBlue = Color(0xFF2196F3); static const Color contentColorYellow = Color(0xFFFFC300); static const Color contentColorOrange = Color(0xFFFF683B); static const Color contentColorGreen = Color(0xFF3BFF49); static const Color contentColorPurple = Color(0xFF6E1BFF); static const Color contentColorPink = Color(0xFFFF3AF2); static const Color contentColorRed = Color(0xFFE80054); static const Color contentColorCyan = Color(0xFF50E4FF); } ================================================ FILE: example/lib/presentation/resources/app_dimens.dart ================================================ class AppDimens { static const double menuMaxNeededWidth = 304; static const double menuRowHeight = 74; static const double menuIconSize = 32; static const double menuDocumentationIconSize = 44; static const double menuTextSize = 20; static const double chartBoxMinWidth = 350; static const double defaultRadius = 8; static const double chartSamplesSpace = 32.0; static const double chartSamplesMinWidth = 350; } ================================================ FILE: example/lib/presentation/resources/app_resources.dart ================================================ export 'app_colors.dart'; export 'app_assets.dart'; export 'app_dimens.dart'; export 'app_texts.dart'; ================================================ FILE: example/lib/presentation/resources/app_texts.dart ================================================ class AppTexts { static const appName = 'FL Chart App'; } ================================================ FILE: example/lib/presentation/router/app_router.dart ================================================ import 'package:fl_chart_app/presentation/pages/home_page.dart'; import 'package:fl_chart_app/presentation/resources/app_colors.dart'; import 'package:fl_chart_app/util/app_helper.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; final appRouterConfig = GoRouter( routes: [ GoRoute( path: '/', builder: (context, state) => Container(color: AppColors.pageBackground), redirect: (context, state) { return '/${ChartType.values.first.name}'; }, ), ...ChartType.values.map( (ChartType chartType) => GoRoute( path: '/${chartType.name}', pageBuilder: (BuildContext context, GoRouterState state) => MaterialPage( /// We set a key for HomePage to prevent recreate it /// when user choose a new chart type to show key: const ValueKey('home_page'), child: HomePage(showingChartType: chartType), ), ), ), GoRoute( path: '/:any', builder: (context, state) => Container(color: AppColors.pageBackground), redirect: (context, state) { // Unsupported path, we redirect it to /, which redirects it to /line return '/'; }, ), ], ); ================================================ FILE: example/lib/presentation/samples/bar/bar_chart_sample1.dart ================================================ import 'dart:async'; import 'dart:math'; import 'package:fl_chart_app/presentation/resources/app_resources.dart'; import 'package:fl_chart_app/util/extensions/color_extensions.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; class BarChartSample1 extends StatefulWidget { BarChartSample1({super.key}); List get availableColors => const [ AppColors.contentColorPurple, AppColors.contentColorYellow, AppColors.contentColorBlue, AppColors.contentColorOrange, AppColors.contentColorPink, AppColors.contentColorRed, ]; final Color barBackgroundColor = AppColors.contentColorWhite.darken().withValues(alpha: 0.3); final Color barColor = AppColors.contentColorWhite; final Color touchedBarColor = AppColors.contentColorGreen; @override State createState() => BarChartSample1State(); } class BarChartSample1State extends State { final Duration animDuration = const Duration(milliseconds: 250); int touchedIndex = -1; bool isPlaying = false; @override Widget build(BuildContext context) { return AspectRatio( aspectRatio: 1, child: Stack( children: [ Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ const Text( 'Mingguan', style: TextStyle( color: AppColors.contentColorGreen, fontSize: 24, fontWeight: FontWeight.bold, ), ), const SizedBox( height: 4, ), Text( 'Grafik konsumsi kalori', style: TextStyle( color: AppColors.contentColorGreen.darken(), fontSize: 18, fontWeight: FontWeight.bold, ), ), const SizedBox( height: 38, ), Expanded( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 8), child: BarChart( isPlaying ? randomData() : mainBarData(), duration: animDuration, ), ), ), const SizedBox( height: 12, ), ], ), ), Padding( padding: const EdgeInsets.all(8), child: Align( alignment: Alignment.topRight, child: IconButton( icon: Icon( isPlaying ? Icons.pause : Icons.play_arrow, color: AppColors.contentColorGreen, ), onPressed: () { setState(() { isPlaying = !isPlaying; if (isPlaying) { refreshState(); } }); }, ), ), ) ], ), ); } BarChartGroupData makeGroupData( int x, double y, { bool isTouched = false, Color? barColor, double width = 22, List showTooltips = const [], }) { barColor ??= widget.barColor; return BarChartGroupData( x: x, barRods: [ BarChartRodData( toY: isTouched ? y + 1 : y, color: isTouched ? widget.touchedBarColor : barColor, width: width, borderSide: isTouched ? BorderSide(color: widget.touchedBarColor.darken(80)) : const BorderSide(color: Colors.white, width: 0), backDrawRodData: BackgroundBarChartRodData( show: true, toY: 20, color: widget.barBackgroundColor, ), ), ], showingTooltipIndicators: showTooltips, ); } List showingGroups() => List.generate( 7, (i) => switch (i) { 0 => makeGroupData(0, 5, isTouched: i == touchedIndex), 1 => makeGroupData(1, 6.5, isTouched: i == touchedIndex), 2 => makeGroupData(2, 5, isTouched: i == touchedIndex), 3 => makeGroupData(3, 7.5, isTouched: i == touchedIndex), 4 => makeGroupData(4, 9, isTouched: i == touchedIndex), 5 => makeGroupData(5, 11.5, isTouched: i == touchedIndex), 6 => makeGroupData(6, 6.5, isTouched: i == touchedIndex), _ => throw Error(), } ); BarChartData mainBarData() { return BarChartData( barTouchData: BarTouchData( enabled: true, touchTooltipData: BarTouchTooltipData( getTooltipColor: (_) => Colors.blueGrey, tooltipHorizontalAlignment: FLHorizontalAlignment.right, tooltipMargin: -10, getTooltipItem: (group, groupIndex, rod, rodIndex) { String weekDay = switch (group.x) { 0 => 'Monday', 1 => 'Tuesday', 2 => 'Wednesday', 3 => 'Thursday', 4 => 'Friday', 5 => 'Saturday', 6 => 'Sunday', _ => throw Error(), }; return BarTooltipItem( '$weekDay\n', const TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 18, ), children: [ TextSpan( text: ((rod.toY - 1).toStringAsFixed(1)).toString(), style: const TextStyle( color: Colors.white, //widget.touchedBarColor, fontSize: 16, fontWeight: FontWeight.w500, ), ), ], ); }, ), touchCallback: (FlTouchEvent event, barTouchResponse) { setState(() { if (!event.isInterestedForInteractions || barTouchResponse == null || barTouchResponse.spot == null) { touchedIndex = -1; return; } touchedIndex = barTouchResponse.spot!.touchedBarGroupIndex; }); }, ), titlesData: FlTitlesData( show: true, rightTitles: const AxisTitles( sideTitles: SideTitles(showTitles: false), ), topTitles: const AxisTitles( sideTitles: SideTitles(showTitles: false), ), bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, getTitlesWidget: getTitles, reservedSize: 38, ), ), leftTitles: const AxisTitles( sideTitles: SideTitles( showTitles: false, ), ), ), borderData: FlBorderData( show: false, ), barGroups: showingGroups(), gridData: const FlGridData(show: false), ); } Widget getTitles(double value, TitleMeta meta) { const style = TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 14, ); String text = switch (value.toInt()) { 0 => 'M', 1 => 'T', 2 => 'W', 3 => 'T', 4 => 'F', 5 => 'S', 6 => 'S', _ => '', }; return SideTitleWidget( meta: meta, space: 16, child: Text(text, style: style), ); } BarChartData randomData() { return BarChartData( barTouchData: const BarTouchData( enabled: false, ), titlesData: FlTitlesData( show: true, bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, getTitlesWidget: getTitles, reservedSize: 38, ), ), leftTitles: const AxisTitles( sideTitles: SideTitles( showTitles: false, ), ), topTitles: const AxisTitles( sideTitles: SideTitles( showTitles: false, ), ), rightTitles: const AxisTitles( sideTitles: SideTitles( showTitles: false, ), ), ), borderData: FlBorderData( show: false, ), barGroups: List.generate( 7, (i) => makeGroupData( i, Random().nextInt(15).toDouble() + 6, barColor: widget .availableColors[Random().nextInt(widget.availableColors.length)], ), ), gridData: const FlGridData(show: false), ); } Future refreshState() async { setState(() {}); await Future.delayed( animDuration + const Duration(milliseconds: 50), ); if (isPlaying) { await refreshState(); } } } ================================================ FILE: example/lib/presentation/samples/bar/bar_chart_sample2.dart ================================================ import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart_app/presentation/resources/app_resources.dart'; import 'package:fl_chart_app/util/extensions/color_extensions.dart'; import 'package:flutter/material.dart'; class BarChartSample2 extends StatefulWidget { BarChartSample2({super.key}); final Color leftBarColor = AppColors.contentColorRed; final Color rightBarColor = AppColors.contentColorYellow; final Color avgColor = AppColors.contentColorOrange.avg(AppColors.contentColorRed); @override State createState() => BarChartSample2State(); } class BarChartSample2State extends State { final double width = 7; late List rawBarGroups; late List showingBarGroups; int touchedGroupIndex = -1; @override void initState() { super.initState(); final barGroup1 = makeGroupData(0, 5, 12); final barGroup2 = makeGroupData(1, 16, 12); final barGroup3 = makeGroupData(2, 18, 5); final barGroup4 = makeGroupData(3, 20, 16); final barGroup5 = makeGroupData(4, 17, 6); final barGroup6 = makeGroupData(5, 19, 1.5); final barGroup7 = makeGroupData(6, 10, 1.5); final items = [ barGroup1, barGroup2, barGroup3, barGroup4, barGroup5, barGroup6, barGroup7, ]; rawBarGroups = items; showingBarGroups = rawBarGroups; } @override Widget build(BuildContext context) { return AspectRatio( aspectRatio: 1, child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Row( mainAxisSize: MainAxisSize.min, children: [ makeTransactionsIcon(), const SizedBox( width: 38, ), const Text( 'Transactions', style: TextStyle(color: Colors.white, fontSize: 22), ), const SizedBox( width: 4, ), const Text( 'state', style: TextStyle(color: Color(0xff77839a), fontSize: 16), ), ], ), const SizedBox( height: 38, ), Expanded( child: BarChart( BarChartData( maxY: 20, barTouchData: BarTouchData( touchTooltipData: BarTouchTooltipData( getTooltipColor: ((group) { return Colors.grey; }), getTooltipItem: (a, b, c, d) => null, ), touchCallback: (FlTouchEvent event, response) { if (response == null || response.spot == null) { setState(() { touchedGroupIndex = -1; showingBarGroups = List.of(rawBarGroups); }); return; } touchedGroupIndex = response.spot!.touchedBarGroupIndex; setState(() { if (!event.isInterestedForInteractions) { touchedGroupIndex = -1; showingBarGroups = List.of(rawBarGroups); return; } showingBarGroups = List.of(rawBarGroups); if (touchedGroupIndex != -1) { var sum = 0.0; for (final rod in showingBarGroups[touchedGroupIndex].barRods) { sum += rod.toY; } final avg = sum / showingBarGroups[touchedGroupIndex] .barRods .length; showingBarGroups[touchedGroupIndex] = showingBarGroups[touchedGroupIndex].copyWith( barRods: showingBarGroups[touchedGroupIndex] .barRods .map((rod) { return rod.copyWith( toY: avg, color: widget.avgColor, label: makeLabel(avg, widget.avgColor, true), ); }).toList(), ); } }); }, ), titlesData: FlTitlesData( show: true, rightTitles: const AxisTitles( sideTitles: SideTitles(showTitles: false), ), topTitles: const AxisTitles( sideTitles: SideTitles(showTitles: false), ), bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, getTitlesWidget: bottomTitles, reservedSize: 42, ), ), leftTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: 28, interval: 1, getTitlesWidget: leftTitles, ), ), ), borderData: FlBorderData( show: false, ), barGroups: showingBarGroups, gridData: const FlGridData(show: false), ), ), ), const SizedBox( height: 12, ), ], ), ), ); } Widget leftTitles(double value, TitleMeta meta) { const style = TextStyle( color: Color(0xff7589a2), fontWeight: FontWeight.bold, fontSize: 14, ); String text; if (value == 0) { text = '1K'; } else if (value == 10) { text = '5K'; } else if (value == 19) { text = '10K'; } else { return Container(); } return SideTitleWidget( meta: meta, space: 0, child: Text(text, style: style), ); } Widget bottomTitles(double value, TitleMeta meta) { final titles = ['Mn', 'Te', 'Wd', 'Tu', 'Fr', 'St', 'Su']; final Widget text = Text( titles[value.toInt()], style: const TextStyle( color: Color(0xff7589a2), fontWeight: FontWeight.bold, fontSize: 14, ), ); return SideTitleWidget( meta: meta, space: 16, //margin top child: text, ); } BarChartRodLabel makeLabel( double value, Color color, bool avg, ) => BarChartRodLabel( text: value.toString(), angle: avg ? -90 : 0, style: TextStyle( color: color, fontSize: 12, fontWeight: FontWeight.bold, shadows: [ Shadow(color: Colors.black54, blurRadius: 4), ], ), ); BarChartGroupData makeGroupData(int x, double y1, double y2) { return BarChartGroupData( barsSpace: 4, x: x, barRods: [ BarChartRodData( toY: y1, color: widget.leftBarColor, width: width, label: makeLabel(y1, widget.leftBarColor, false), ), BarChartRodData( toY: y2, color: widget.rightBarColor, width: width, label: makeLabel(y2, widget.rightBarColor, false), ), ], ); } Widget makeTransactionsIcon() { const width = 4.5; const space = 3.5; return Row( mainAxisSize: MainAxisSize.min, children: [ Container( width: width, height: 10, color: Colors.white.withValues(alpha: 0.4), ), const SizedBox( width: space, ), Container( width: width, height: 28, color: Colors.white.withValues(alpha: 0.8), ), const SizedBox( width: space, ), Container( width: width, height: 42, color: Colors.white.withValues(alpha: 1), ), const SizedBox( width: space, ), Container( width: width, height: 28, color: Colors.white.withValues(alpha: 0.8), ), const SizedBox( width: space, ), Container( width: width, height: 10, color: Colors.white.withValues(alpha: 0.4), ), ], ); } } ================================================ FILE: example/lib/presentation/samples/bar/bar_chart_sample3.dart ================================================ import 'package:fl_chart_app/presentation/resources/app_resources.dart'; import 'package:fl_chart_app/util/extensions/color_extensions.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; class BarChartSample3 extends StatefulWidget { const BarChartSample3({super.key}); @override State createState() => BarChartSample3State(); } class BarChartSample3State extends State { int? touchedGroupIndex; BarTouchData get barTouchData => BarTouchData( enabled: true, handleBuiltInTouches: false, touchCallback: (event, response) { setState(() { final groupI = response?.spot?.touchedBarGroupIndex; if (event.isInterestedForInteractions && groupI != null) { touchedGroupIndex = groupI; } else { touchedGroupIndex = null; } }); }); Widget getTitles(double value, TitleMeta meta) { final style = TextStyle( color: AppColors.contentColorBlue.darken(20), fontWeight: FontWeight.bold, fontSize: 14, ); String text = switch (value.toInt()) { 0 => 'Mn', 1 => 'Te', 2 => 'Wd', 3 => 'Tu', 4 => 'Fr', 5 => 'St', 6 => 'Sn', _ => '', }; return SideTitleWidget( meta: meta, space: 4, child: Text(text, style: style), ); } FlTitlesData get titlesData => FlTitlesData( show: true, bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: 30, getTitlesWidget: getTitles, ), ), leftTitles: const AxisTitles( sideTitles: SideTitles(showTitles: false), ), topTitles: const AxisTitles( sideTitles: SideTitles(showTitles: false), ), rightTitles: const AxisTitles( sideTitles: SideTitles(showTitles: false), ), ); FlBorderData get borderData => FlBorderData( show: false, ); LinearGradient get _barsGradient => LinearGradient( colors: [ AppColors.contentColorBlue.darken(20), AppColors.contentColorCyan, ], begin: Alignment.bottomCenter, end: Alignment.topCenter, ); List get barGroups => [8, 10, 14, 15, 13, 10, 16].asMap().entries.map((entry) { int i = entry.key; int value = entry.value; final isTouched = i == touchedGroupIndex; return BarChartGroupData( x: i, barRods: [ BarChartRodData( toY: value.toDouble(), gradient: _barsGradient, label: BarChartRodLabel( text: value.toString(), style: TextStyle( color: AppColors.contentColorCyan, fontWeight: FontWeight.bold, fontSize: isTouched ? 40 : 18, ), ), ), ], ); }).toList(); @override Widget build(BuildContext context) { return AspectRatio( aspectRatio: 1.6, child: BarChart( duration: Duration(milliseconds: 100), curve: Curves.easeOutQuad, BarChartData( barTouchData: barTouchData, titlesData: titlesData, borderData: borderData, barGroups: barGroups, gridData: const FlGridData(show: false), alignment: BarChartAlignment.spaceAround, maxY: 20, ), ), ); } } ================================================ FILE: example/lib/presentation/samples/bar/bar_chart_sample4.dart ================================================ import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart_app/presentation/resources/app_resources.dart'; import 'package:fl_chart_app/util/extensions/color_extensions.dart'; import 'package:flutter/material.dart'; class BarChartSample4 extends StatefulWidget { BarChartSample4({super.key}); final Color dark = AppColors.contentColorCyan.darken(60); final Color normal = AppColors.contentColorCyan.darken(30); final Color light = AppColors.contentColorCyan; @override State createState() => BarChartSample4State(); } class BarChartSample4State extends State { Widget bottomTitles(double value, TitleMeta meta) { const style = TextStyle(fontSize: 10); String text = switch (value.toInt()) { 0 => 'Apr', 1 => 'May', 2 => 'Jun', 3 => 'Jul', 4 => 'Aug', _ => '', }; return SideTitleWidget( meta: meta, child: Text(text, style: style), ); } Widget leftTitles(double value, TitleMeta meta) { if (value == meta.max) { return Container(); } const style = TextStyle( fontSize: 10, ); return SideTitleWidget( meta: meta, child: Text( meta.formattedValue, style: style, ), ); } @override Widget build(BuildContext context) { return AspectRatio( aspectRatio: 1.66, child: Padding( padding: const EdgeInsets.only(top: 16), child: LayoutBuilder( builder: (context, constraints) { final barsSpace = 4.0 * constraints.maxWidth / 400; final barsWidth = 8.0 * constraints.maxWidth / 400; return BarChart( BarChartData( alignment: BarChartAlignment.center, barTouchData: const BarTouchData( enabled: false, ), titlesData: FlTitlesData( show: true, bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: 28, getTitlesWidget: bottomTitles, ), ), leftTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: 40, getTitlesWidget: leftTitles, ), ), topTitles: const AxisTitles( sideTitles: SideTitles(showTitles: false), ), rightTitles: const AxisTitles( sideTitles: SideTitles(showTitles: false), ), ), gridData: FlGridData( show: true, checkToShowHorizontalLine: (value) => value % 10 == 0, getDrawingHorizontalLine: (value) => FlLine( color: AppColors.borderColor.withValues(alpha: 0.1), strokeWidth: 1, ), drawVerticalLine: false, ), borderData: FlBorderData( show: false, ), groupsSpace: barsSpace, barGroups: getData(barsWidth, barsSpace), ), ); }, ), ), ); } List getData(double barsWidth, double barsSpace) { return [ BarChartGroupData( x: 0, barsSpace: barsSpace, barRods: [ BarChartRodData( toY: 17000000000, rodStackItems: [ BarChartRodStackItem(0, 2000000000, widget.dark), BarChartRodStackItem(2000000000, 12000000000, widget.normal), BarChartRodStackItem(12000000000, 17000000000, widget.light), ], borderRadius: BorderRadius.zero, width: barsWidth, ), BarChartRodData( toY: 24000000000, rodStackItems: [ BarChartRodStackItem(0, 13000000000, widget.dark), BarChartRodStackItem(13000000000, 14000000000, widget.normal), BarChartRodStackItem(14000000000, 24000000000, widget.light), ], borderRadius: BorderRadius.zero, width: barsWidth, ), BarChartRodData( toY: 23000000000.5, rodStackItems: [ BarChartRodStackItem(0, 6000000000.5, widget.dark), BarChartRodStackItem(6000000000.5, 18000000000, widget.normal), BarChartRodStackItem(18000000000, 23000000000.5, widget.light), ], borderRadius: BorderRadius.zero, width: barsWidth, ), BarChartRodData( toY: 29000000000, rodStackItems: [ BarChartRodStackItem(0, 9000000000, widget.dark), BarChartRodStackItem(9000000000, 15000000000, widget.normal), BarChartRodStackItem(15000000000, 29000000000, widget.light), ], borderRadius: BorderRadius.zero, width: barsWidth, ), BarChartRodData( toY: 32000000000, rodStackItems: [ BarChartRodStackItem(0, 2000000000.5, widget.dark), BarChartRodStackItem(2000000000.5, 17000000000.5, widget.normal), BarChartRodStackItem(17000000000.5, 32000000000, widget.light), ], borderRadius: BorderRadius.zero, width: barsWidth, ), ], ), BarChartGroupData( x: 1, barsSpace: barsSpace, barRods: [ BarChartRodData( toY: 31000000000, rodStackItems: [ BarChartRodStackItem(0, 11000000000, widget.dark), BarChartRodStackItem(11000000000, 18000000000, widget.normal), BarChartRodStackItem(18000000000, 31000000000, widget.light), ], borderRadius: BorderRadius.zero, width: barsWidth, ), BarChartRodData( toY: 35000000000, rodStackItems: [ BarChartRodStackItem(0, 14000000000, widget.dark), BarChartRodStackItem(14000000000, 27000000000, widget.normal), BarChartRodStackItem(27000000000, 35000000000, widget.light), ], borderRadius: BorderRadius.zero, width: barsWidth, ), BarChartRodData( toY: 31000000000, rodStackItems: [ BarChartRodStackItem(0, 8000000000, widget.dark), BarChartRodStackItem(8000000000, 24000000000, widget.normal), BarChartRodStackItem(24000000000, 31000000000, widget.light), ], borderRadius: BorderRadius.zero, width: barsWidth, ), BarChartRodData( toY: 15000000000, rodStackItems: [ BarChartRodStackItem(0, 6000000000.5, widget.dark), BarChartRodStackItem(6000000000.5, 12000000000.5, widget.normal), BarChartRodStackItem(12000000000.5, 15000000000, widget.light), ], borderRadius: BorderRadius.zero, width: barsWidth, ), BarChartRodData( toY: 17000000000, rodStackItems: [ BarChartRodStackItem(0, 9000000000, widget.dark), BarChartRodStackItem(9000000000, 15000000000, widget.normal), BarChartRodStackItem(15000000000, 17000000000, widget.light), ], borderRadius: BorderRadius.zero, width: barsWidth, ), ], ), BarChartGroupData( x: 2, barsSpace: barsSpace, barRods: [ BarChartRodData( toY: 34000000000, rodStackItems: [ BarChartRodStackItem(0, 6000000000, widget.dark), BarChartRodStackItem(6000000000, 23000000000, widget.normal), BarChartRodStackItem(23000000000, 34000000000, widget.light), ], borderRadius: BorderRadius.zero, width: barsWidth, ), BarChartRodData( toY: 32000000000, rodStackItems: [ BarChartRodStackItem(0, 7000000000, widget.dark), BarChartRodStackItem(7000000000, 24000000000, widget.normal), BarChartRodStackItem(24000000000, 32000000000, widget.light), ], borderRadius: BorderRadius.zero, width: barsWidth, ), BarChartRodData( toY: 14000000000.5, rodStackItems: [ BarChartRodStackItem(0, 1000000000.5, widget.dark), BarChartRodStackItem(1000000000.5, 12000000000, widget.normal), BarChartRodStackItem(12000000000, 14000000000.5, widget.light), ], borderRadius: BorderRadius.zero, width: barsWidth, ), BarChartRodData( toY: 20000000000, rodStackItems: [ BarChartRodStackItem(0, 4000000000, widget.dark), BarChartRodStackItem(4000000000, 15000000000, widget.normal), BarChartRodStackItem(15000000000, 20000000000, widget.light), ], borderRadius: BorderRadius.zero, width: barsWidth, ), BarChartRodData( toY: 24000000000, rodStackItems: [ BarChartRodStackItem(0, 4000000000, widget.dark), BarChartRodStackItem(4000000000, 15000000000, widget.normal), BarChartRodStackItem(15000000000, 24000000000, widget.light), ], borderRadius: BorderRadius.zero, width: barsWidth, ), ], ), BarChartGroupData( x: 3, barsSpace: barsSpace, barRods: [ BarChartRodData( toY: 14000000000, rodStackItems: [ BarChartRodStackItem(0, 1000000000.5, widget.dark), BarChartRodStackItem(1000000000.5, 12000000000, widget.normal), BarChartRodStackItem(12000000000, 14000000000, widget.light), ], borderRadius: BorderRadius.zero, width: barsWidth, ), BarChartRodData( toY: 27000000000, rodStackItems: [ BarChartRodStackItem(0, 7000000000, widget.dark), BarChartRodStackItem(7000000000, 25000000000, widget.normal), BarChartRodStackItem(25000000000, 27000000000, widget.light), ], borderRadius: BorderRadius.zero, width: barsWidth, ), BarChartRodData( toY: 29000000000, rodStackItems: [ BarChartRodStackItem(0, 6000000000, widget.dark), BarChartRodStackItem(6000000000, 23000000000, widget.normal), BarChartRodStackItem(23000000000, 29000000000, widget.light), ], borderRadius: BorderRadius.zero, width: barsWidth, ), BarChartRodData( toY: 16000000000.5, rodStackItems: [ BarChartRodStackItem(0, 9000000000, widget.dark), BarChartRodStackItem(9000000000, 15000000000, widget.normal), BarChartRodStackItem(15000000000, 16000000000.5, widget.light), ], borderRadius: BorderRadius.zero, width: barsWidth, ), BarChartRodData( toY: 15000000000, rodStackItems: [ BarChartRodStackItem(0, 7000000000, widget.dark), BarChartRodStackItem(7000000000, 12000000000.5, widget.normal), BarChartRodStackItem(12000000000.5, 15000000000, widget.light), ], borderRadius: BorderRadius.zero, width: barsWidth, ), ], ), ]; } } ================================================ FILE: example/lib/presentation/samples/bar/bar_chart_sample5.dart ================================================ import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart_app/presentation/resources/app_resources.dart'; import 'package:fl_chart_app/util/app_utils.dart'; import 'package:flutter/material.dart'; class BarChartSample5 extends StatefulWidget { const BarChartSample5({super.key}); @override State createState() => BarChartSample5State(); } class BarChartSample5State extends State { static const double barWidth = 22; static const shadowOpacity = 0.2; static const mainItems = >{ 0: [2, 3, 2.5, 8], 1: [-1.8, -2.7, -3, -6.5], 2: [1.5, 2, 3.5, 6], 3: [1.5, 1.5, 4, 6.5], 4: [-2, -2, -5, -9], 5: [-1.2, -1.5, -4.3, -10], 6: [1.2, 4.8, 5, 5], }; int touchedIndex = -1; @override void initState() { super.initState(); } Widget bottomTitles(double value, TitleMeta meta) { const style = TextStyle(color: Colors.white, fontSize: 10); String text = switch (value.toInt()) { 0 => 'Mon', 1 => 'Tue', 2 => 'Wed', 3 => 'Thu', 4 => 'Fri', 5 => 'Sat', 6 => 'Sun', _ => '', }; return SideTitleWidget( meta: meta, child: Text(text, style: style), ); } Widget topTitles(double value, TitleMeta meta) { const style = TextStyle(color: Colors.white, fontSize: 10); String text = switch (value.toInt()) { 0 => 'Mon', 1 => 'Tue', 2 => 'Wed', 3 => 'Thu', 4 => 'Fri', 5 => 'Sat', 6 => 'Sun', _ => '', }; return SideTitleWidget( meta: meta, child: Text(text, style: style), ); } Widget leftTitles(double value, TitleMeta meta) { const style = TextStyle(color: Colors.white, fontSize: 10); String text; if (value == 0) { text = '0'; } else { text = '${value.toInt()}0k'; } return SideTitleWidget( angle: AppUtils().degreeToRadian(value < 0 ? -45 : 45), meta: meta, space: 4, child: Text( text, style: style, textAlign: TextAlign.center, ), ); } Widget rightTitles(double value, TitleMeta meta) { const style = TextStyle(color: Colors.white, fontSize: 10); String text; if (value == 0) { text = '0'; } else { text = '${value.toInt()}0k'; } return SideTitleWidget( angle: AppUtils().degreeToRadian(90), meta: meta, space: 0, child: Text( text, style: style, textAlign: TextAlign.center, ), ); } BarChartGroupData generateGroup( int x, double value1, double value2, double value3, double value4, ) { final isTop = value1 > 0; final sum = value1 + value2 + value3 + value4; final isTouched = touchedIndex == x; return BarChartGroupData( x: x, groupVertically: true, showingTooltipIndicators: isTouched ? [0] : [], barRods: [ BarChartRodData( toY: sum, width: barWidth, borderRadius: isTop ? const BorderRadius.only( topLeft: Radius.circular(6), topRight: Radius.circular(6), ) : const BorderRadius.only( bottomLeft: Radius.circular(6), bottomRight: Radius.circular(6), ), rodStackItems: [ BarChartRodStackItem( 0, value1, null, gradient: LinearGradient(colors: [ AppColors.contentColorWhite.withValues(alpha: 0.8), AppColors.contentColorGreen.withValues(alpha: 0.5), AppColors.contentColorCyan.withValues(alpha: 0.2), ], stops: const [ 0.1, 0.4, 0.9 ], begin: Alignment.topLeft, end: Alignment.bottomRight), label: isTouched ? 'A' : null, borderSide: BorderSide( color: Colors.white, width: isTouched ? 2 : 0, ), ), BarChartRodStackItem( value1, value1 + value2, AppColors.contentColorYellow, label: isTouched ? 'B' : null, borderSide: BorderSide( color: Colors.white, width: isTouched ? 2 : 0, ), ), BarChartRodStackItem( value1 + value2, value1 + value2 + value3, null, gradient: LinearGradient( colors: [ AppColors.contentColorPurple, AppColors.contentColorRed.withValues(alpha: 0.9), AppColors.contentColorOrange.withValues(alpha: 0.8), ], stops: const [0, 0.5, 1], begin: Alignment.topLeft, end: Alignment.bottomRight, ), label: isTouched ? 'C' : null, borderSide: BorderSide( color: Colors.white, width: isTouched ? 2 : 0, ), ), BarChartRodStackItem( value1 + value2 + value3, value1 + value2 + value3 + value4, AppColors.contentColorBlue, label: isTouched ? 'D' : null, borderSide: BorderSide( color: Colors.white, width: isTouched ? 2 : 0, ), ), ], ), BarChartRodData( toY: -sum, width: barWidth, color: Colors.transparent, borderRadius: isTop ? const BorderRadius.only( bottomLeft: Radius.circular(6), bottomRight: Radius.circular(6), ) : const BorderRadius.only( topLeft: Radius.circular(6), topRight: Radius.circular(6), ), rodStackItems: [ BarChartRodStackItem( 0, -value1, null, gradient: LinearGradient(colors: [ AppColors.contentColorWhite.withValues( alpha: isTouched ? shadowOpacity * 2 : shadowOpacity), AppColors.contentColorGreen.withValues( alpha: isTouched ? shadowOpacity * 2 : shadowOpacity), AppColors.contentColorCyan.withValues( alpha: isTouched ? shadowOpacity * 2 : shadowOpacity), ], stops: const [ 0.1, 0.4, 0.9 ], begin: Alignment.topLeft, end: Alignment.bottomRight), label: isTouched ? 'A' : null, borderSide: const BorderSide(color: Colors.transparent), ), BarChartRodStackItem( -value1, -(value1 + value2), AppColors.contentColorYellow.withValues( alpha: isTouched ? shadowOpacity * 2 : shadowOpacity), label: isTouched ? 'B' : null, borderSide: const BorderSide(color: Colors.transparent), ), BarChartRodStackItem( -(value1 + value2), -(value1 + value2 + value3), null, gradient: LinearGradient( colors: [ AppColors.contentColorPurple.withValues( alpha: isTouched ? shadowOpacity * 2 : shadowOpacity), AppColors.contentColorRed.withValues( alpha: isTouched ? (shadowOpacity * 2) - 0.1 : shadowOpacity), AppColors.contentColorOrange.withValues( alpha: isTouched ? (shadowOpacity * 2) - 0.2 : shadowOpacity), ], stops: const [0, 0.5, 1], begin: Alignment.topLeft, end: Alignment.bottomRight, ), label: isTouched ? 'C' : null, borderSide: const BorderSide(color: Colors.transparent), ), BarChartRodStackItem( -(value1 + value2 + value3), -(value1 + value2 + value3 + value4), AppColors.contentColorBlue.withValues( alpha: isTouched ? shadowOpacity * 2 : shadowOpacity), label: isTouched ? 'D' : null, borderSide: const BorderSide(color: Colors.transparent), ), ], ), ], ); } bool isShadowBar(int rodIndex) => rodIndex == 1; @override Widget build(BuildContext context) { return AspectRatio( aspectRatio: 0.8, child: Padding( padding: const EdgeInsets.only(top: 16), child: BarChart( BarChartData( alignment: BarChartAlignment.center, maxY: 20, minY: -20, groupsSpace: 12, barTouchData: BarTouchData( handleBuiltInTouches: false, touchCallback: (FlTouchEvent event, barTouchResponse) { if (!event.isInterestedForInteractions || barTouchResponse == null || barTouchResponse.spot == null) { setState(() { touchedIndex = -1; }); return; } final rodIndex = barTouchResponse.spot!.touchedRodDataIndex; if (isShadowBar(rodIndex)) { setState(() { touchedIndex = -1; }); return; } setState(() { touchedIndex = barTouchResponse.spot!.touchedBarGroupIndex; }); }, ), titlesData: FlTitlesData( show: true, topTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: 32, getTitlesWidget: topTitles, ), ), bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: 32, getTitlesWidget: bottomTitles, ), ), leftTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, getTitlesWidget: leftTitles, interval: 5, reservedSize: 42, ), ), rightTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, getTitlesWidget: rightTitles, interval: 5, reservedSize: 42, ), ), ), gridData: FlGridData( show: true, checkToShowHorizontalLine: (value) => value % 5 == 0, getDrawingHorizontalLine: (value) { if (value == 0) { return FlLine( color: AppColors.borderColor.withValues(alpha: 0.1), strokeWidth: 3, ); } return FlLine( color: AppColors.borderColor.withValues(alpha: 0.05), strokeWidth: 0.8, ); }, ), borderData: FlBorderData( show: false, ), barGroups: mainItems.entries .map( (e) => generateGroup( e.key, e.value[0], e.value[1], e.value[2], e.value[3], ), ) .toList(), ), ), ), ); } } ================================================ FILE: example/lib/presentation/samples/bar/bar_chart_sample6.dart ================================================ import 'package:fl_chart_app/presentation/resources/app_resources.dart'; import 'package:fl_chart_app/presentation/widgets/legend_widget.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; class BarChartSample6 extends StatelessWidget { const BarChartSample6({super.key}); final pilateColor = AppColors.contentColorPurple; final cyclingColor = AppColors.contentColorCyan; final quickWorkoutColor = AppColors.contentColorBlue; final betweenSpace = 0.2; BarChartGroupData generateGroupData( int x, double pilates, double quickWorkout, double cycling, ) { return BarChartGroupData( x: x, groupVertically: true, barRods: [ BarChartRodData( fromY: 0, toY: pilates, color: pilateColor, width: 5, ), BarChartRodData( fromY: pilates + betweenSpace, toY: pilates + betweenSpace + quickWorkout, color: quickWorkoutColor, width: 5, ), BarChartRodData( fromY: pilates + betweenSpace + quickWorkout + betweenSpace, toY: pilates + betweenSpace + quickWorkout + betweenSpace + cycling, color: cyclingColor, width: 5, ), ], ); } Widget bottomTitles(double value, TitleMeta meta) { const style = TextStyle(fontSize: 10); String text = switch (value.toInt()) { 0 => 'JAN', 1 => 'FEB', 2 => 'MAR', 3 => 'APR', 4 => 'MAY', 5 => 'JUN', 6 => 'JUL', 7 => 'AUG', 8 => 'SEP', 9 => 'OCT', 10 => 'NOV', 11 => 'DEC', _ => '', }; return SideTitleWidget( meta: meta, child: Text(text, style: style), ); } @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(24), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Activity', style: TextStyle( color: AppColors.contentColorBlue, fontSize: 16, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 8), LegendsListWidget( legends: [ Legend('Pilates', pilateColor), Legend('Quick workouts', quickWorkoutColor), Legend('Cycling', cyclingColor), ], ), const SizedBox(height: 14), AspectRatio( aspectRatio: 2, child: BarChart( BarChartData( alignment: BarChartAlignment.spaceBetween, titlesData: FlTitlesData( leftTitles: const AxisTitles(), rightTitles: const AxisTitles(), topTitles: const AxisTitles(), bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, getTitlesWidget: bottomTitles, reservedSize: 20, ), ), ), barTouchData: const BarTouchData(enabled: false), borderData: FlBorderData(show: false), gridData: const FlGridData(show: false), barGroups: [ generateGroupData(0, 2, 3, 2), generateGroupData(1, 2, 5, 1.7), generateGroupData(2, 1.3, 3.1, 2.8), generateGroupData(3, 3.1, 4, 3.1), generateGroupData(4, 0.8, 3.3, 3.4), generateGroupData(5, 2, 5.6, 1.8), generateGroupData(6, 1.3, 3.2, 2), generateGroupData(7, 2.3, 3.2, 3), generateGroupData(8, 2, 4.8, 2.5), generateGroupData(9, 1.2, 3.2, 2.5), generateGroupData(10, 1, 4.8, 3), generateGroupData(11, 2, 4.4, 2.8), ], maxY: 11 + (betweenSpace * 3), extraLinesData: ExtraLinesData( horizontalLines: [ HorizontalLine( y: 3.3, color: pilateColor, strokeWidth: 1, dashArray: [20, 4], ), HorizontalLine( y: 8, color: quickWorkoutColor, strokeWidth: 1, dashArray: [20, 4], ), HorizontalLine( y: 11, color: cyclingColor, strokeWidth: 1, dashArray: [20, 4], ), ], ), ), ), ), ], ), ); } } ================================================ FILE: example/lib/presentation/samples/bar/bar_chart_sample7.dart ================================================ import 'dart:math' as math; import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart_app/presentation/resources/app_resources.dart'; import 'package:flutter/material.dart'; class BarChartSample7 extends StatefulWidget { BarChartSample7({super.key}); final shadowColor = const Color(0xFFCCCCCC); final dataList = [ const _BarData(AppColors.contentColorYellow, 18, 18), const _BarData(AppColors.contentColorGreen, 17, 8), const _BarData(AppColors.contentColorOrange, 10, 15), const _BarData(AppColors.contentColorPink, 2.5, 5), const _BarData(AppColors.contentColorBlue, 2, 2.5), const _BarData(AppColors.contentColorRed, 2, 2), ]; @override State createState() => _BarChartSample7State(); } class _BarChartSample7State extends State { BarChartGroupData generateBarGroup( int x, Color color, double value, double shadowValue, ) { final isTouched = touchedGroupIndex == x; return BarChartGroupData( x: x, barRods: [ BarChartRodData( toY: value, color: color, width: 6, label: BarChartRodLabel( show: isTouched, text: value.toString(), style: TextStyle( fontWeight: FontWeight.bold, color: color, fontSize: 18, shadows: const [ Shadow( color: Colors.black26, blurRadius: 12, ) ], ), ), ), BarChartRodData( toY: shadowValue, color: widget.shadowColor, width: 6, ), ], ); } int touchedGroupIndex = -1; int rotationTurns = 1; @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(24), child: Column( children: [ Row( children: [ Expanded(child: Container()), const Text( 'Horizontal Bar Chart', style: TextStyle( color: AppColors.mainTextColor1, fontSize: 20, ), ), Expanded( child: Align( alignment: Alignment.centerRight, child: Tooltip( message: 'Rotate the chart 90 degrees (cw)', child: IconButton( onPressed: () { setState(() { rotationTurns += 1; }); }, icon: RotatedBox( quarterTurns: rotationTurns - 1, child: const Icon( Icons.rotate_90_degrees_cw, ), ), ), ), )), ], ), const SizedBox(height: 18), AspectRatio( aspectRatio: 1.4, child: BarChart( BarChartData( alignment: BarChartAlignment.spaceBetween, rotationQuarterTurns: rotationTurns, borderData: FlBorderData( show: true, border: Border.symmetric( horizontal: BorderSide( color: AppColors.borderColor.withValues(alpha: 0.2), ), ), ), titlesData: FlTitlesData( show: true, leftTitles: const AxisTitles( drawBelowEverything: true, sideTitles: SideTitles( showTitles: true, reservedSize: 30, ), ), bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: 36, getTitlesWidget: (value, meta) { final index = value.toInt(); return SideTitleWidget( meta: meta, child: _IconWidget( color: widget.dataList[index].color, isSelected: touchedGroupIndex == index, ), ); }, ), ), rightTitles: const AxisTitles(), topTitles: const AxisTitles(), ), gridData: FlGridData( show: true, drawVerticalLine: false, getDrawingHorizontalLine: (value) => FlLine( color: AppColors.borderColor.withValues(alpha: 0.2), strokeWidth: 1, ), ), barGroups: widget.dataList.asMap().entries.map((e) { final index = e.key; final data = e.value; return generateBarGroup( index, data.color, data.value, data.shadowValue, ); }).toList(), maxY: 20, barTouchData: BarTouchData( enabled: true, handleBuiltInTouches: false, touchCallback: (event, response) { final spot = response?.spot; if (event.isInterestedForInteractions && spot != null) { setState(() { touchedGroupIndex = spot.touchedBarGroupIndex; }); } else { setState(() { touchedGroupIndex = -1; }); } }, ), ), ), ), ], ), ); } } class _BarData { const _BarData(this.color, this.value, this.shadowValue); final Color color; final double value; final double shadowValue; } class _IconWidget extends ImplicitlyAnimatedWidget { const _IconWidget({ required this.color, required this.isSelected, }) : super(duration: const Duration(milliseconds: 300)); final Color color; final bool isSelected; @override ImplicitlyAnimatedWidgetState createState() => _IconWidgetState(); } class _IconWidgetState extends AnimatedWidgetBaseState<_IconWidget> { Tween? _rotationTween; @override Widget build(BuildContext context) { final rotation = math.pi * 4 * _rotationTween!.evaluate(animation); final scale = 1 + _rotationTween!.evaluate(animation) * 0.5; return Transform( transform: Matrix4.rotationZ(rotation).scaledByDouble(scale, scale, scale, 1.0), origin: const Offset(14, 14), child: Icon( widget.isSelected ? Icons.face_retouching_natural : Icons.face, color: widget.color, size: 28, ), ); } @override void forEachTween(TweenVisitor visitor) { _rotationTween = visitor( _rotationTween, widget.isSelected ? 1.0 : 0.0, (dynamic value) => Tween( begin: value as double, end: widget.isSelected ? 1.0 : 0.0, ), ) as Tween?; } } ================================================ FILE: example/lib/presentation/samples/bar/bar_chart_sample8.dart ================================================ import 'dart:math'; import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart_app/presentation/resources/app_resources.dart'; import 'package:fl_chart_app/util/extensions/color_extensions.dart'; import 'package:flutter/material.dart'; class BarChartSample8 extends StatefulWidget { BarChartSample8({super.key}); final Color barBackgroundColor = AppColors.contentColorWhite.darken().withValues(alpha: 0.3); final Color barColor = AppColors.contentColorWhite; @override State createState() => BarChartSample1State(); } class BarChartSample1State extends State { @override Widget build(BuildContext context) { return AspectRatio( aspectRatio: 1, child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Row( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.graphic_eq), const SizedBox( width: 32, ), Text( 'Sales forecasting chart', style: TextStyle( color: widget.barColor, fontSize: 18, fontWeight: FontWeight.bold, ), ), ], ), const SizedBox( height: 32, ), Expanded( child: BarChart( randomData(), ), ), ], ), ), ); } BarChartGroupData makeGroupData( int x, double y, FlErrorRange errorRange, ) { return BarChartGroupData( x: x, barRods: [ BarChartRodData( toY: y, toYErrorRange: errorRange, color: x >= 4 ? Colors.transparent : widget.barColor, borderRadius: BorderRadius.zero, borderDashArray: x >= 4 ? [4, 4] : null, width: 22, borderSide: BorderSide(color: widget.barColor, width: 2.0), ), ], ); } Widget getTitles(double value, TitleMeta meta) { const style = TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 14, ); List days = ['M', 'T', 'W', 'T', 'F', 'S', 'S']; Widget text = Text( days[value.toInt()], style: style, ); return SideTitleWidget( meta: meta, space: 16, child: text, ); } BarChartData randomData() { return BarChartData( maxY: 300.0, barTouchData: const BarTouchData( enabled: false, ), titlesData: FlTitlesData( show: true, bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, getTitlesWidget: getTitles, reservedSize: 38, ), ), leftTitles: const AxisTitles( sideTitles: SideTitles( reservedSize: 30, showTitles: true, ), ), topTitles: const AxisTitles( sideTitles: SideTitles( showTitles: false, ), ), rightTitles: const AxisTitles( sideTitles: SideTitles( showTitles: false, ), ), ), borderData: FlBorderData( show: false, ), barGroups: List.generate( 7, (i) { final y = Random().nextInt(290).toDouble() + 10; final lowerBy = y < 50 ? Random().nextDouble() * 10 : Random().nextDouble() * 30 + 5; final upperBy = y > 290 ? Random().nextDouble() * 10 : Random().nextDouble() * 30 + 5; return makeGroupData( i, y, FlErrorRange( lowerBy: lowerBy, upperBy: upperBy, ), ); }, ), gridData: const FlGridData(show: false), errorIndicatorData: FlErrorIndicatorData( painter: _errorPainter, ), ); } FlSpotErrorRangePainter _errorPainter( BarChartSpotErrorRangeCallbackInput input, ) => FlSimpleErrorPainter( lineWidth: 2.0, capLength: 14, lineColor: input.groupIndex < 4 ? AppColors.contentColorOrange : AppColors.primary.withValues(alpha: 0.5), ); } ================================================ FILE: example/lib/presentation/samples/candlestick/candlestick_chart_sample1.dart ================================================ import 'package:equatable/equatable.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart_app/presentation/resources/app_colors.dart'; import 'package:fl_chart_app/util/csv_parser.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; class CandlestickChartSample1 extends StatefulWidget { const CandlestickChartSample1({super.key}); @override State createState() => CandlestickChartSample1State(); } class CandlestickChartSample1State extends State { List>? _btcMonthlyData; int _currentMonthIndex = 0; late final List monthsNames; final int minDays = 1; final int maxDays = 31; late final FlLine _gridLine; @override void initState() { monthsNames = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December', ]; _loadData(); _gridLine = FlLine( color: Colors.blueGrey.withValues(alpha: 0.4), strokeWidth: 0.4, dashArray: [8, 4], ); super.initState(); } void _loadData() async { final data = await rootBundle .loadString('assets/data/bitcoin_2023-01-01_2023-12-31.csv'); final rows = CsvParser.parse(data); if (!mounted) { return; } setState(() { final allData = rows.skip(1).map((row) { // 2023-12-31,2024-01-01 return _BtcCandlestickData( datetime: DateTime.parse(row[0]), open: double.parse(row[2]), high: double.parse(row[3]), low: double.parse(row[4]), close: double.parse(row[5]), volume: double.parse(row[6]), marketCap: double.parse(row[7]), ); }).toList(); _btcMonthlyData = List.generate(12, (index) { final month = index + 1; final monthData = allData .where((element) => element.datetime.month == month) .toList(); monthData.sort((a, b) => a.datetime.compareTo(b.datetime)); return monthData; }); }); } @override Widget build(BuildContext context) { return Column( children: [ const SizedBox(height: 18), const Row( mainAxisSize: MainAxisSize.min, children: [ Text( 'BTC Price 2024', style: TextStyle( color: AppColors.contentColorYellow, fontSize: 20, fontWeight: FontWeight.bold, ), ), ], ), const SizedBox(height: 18), Row( children: [ Expanded( child: Align( alignment: Alignment.centerRight, child: IconButton( onPressed: _canGoPrevious ? _previousMonth : null, icon: const Icon(Icons.navigate_before_rounded), ), ), ), SizedBox( width: 92, child: Text( monthsNames[_currentMonthIndex], textAlign: TextAlign.center, style: const TextStyle( color: AppColors.contentColorWhite, fontSize: 16, fontWeight: FontWeight.bold, ), ), ), Expanded( child: Align( alignment: Alignment.centerLeft, child: IconButton( onPressed: _canGoNext ? _nextMonth : null, icon: const Icon(Icons.navigate_next_rounded), ), ), ), ], ), const SizedBox(height: 18), AspectRatio( aspectRatio: 1.5, child: Stack( children: [ if (_btcMonthlyData != null) Padding( padding: const EdgeInsets.only( top: 0.0, right: 18.0, ), child: CandlestickChart( CandlestickChartData( candlestickSpots: _btcMonthlyData![_currentMonthIndex] .asMap() .entries .map((entry) { final index = entry.key; final data = entry.value; return CandlestickSpot( x: index.toDouble(), open: data.open, high: data.high, low: data.low, close: data.close, ); }).toList(), minX: 0, maxX: 31, gridData: FlGridData( show: true, getDrawingHorizontalLine: (_) => _gridLine, getDrawingVerticalLine: (_) => _gridLine, ), titlesData: FlTitlesData( show: true, rightTitles: const AxisTitles( sideTitles: SideTitles(showTitles: false), ), topTitles: const AxisTitles( sideTitles: SideTitles(showTitles: false), ), leftTitles: AxisTitles( drawBelowEverything: true, sideTitles: SideTitles( showTitles: true, maxIncluded: false, minIncluded: false, reservedSize: 60, getTitlesWidget: _leftTitles, ), ), bottomTitles: AxisTitles( axisNameWidget: Container( margin: const EdgeInsets.only(bottom: 20), child: const Text( 'Day of month', style: TextStyle( color: AppColors.contentColorGreen, fontWeight: FontWeight.bold, fontSize: 16, ), ), ), axisNameSize: 40, sideTitles: SideTitles( showTitles: true, reservedSize: 38, maxIncluded: false, interval: 1, getTitlesWidget: _bottomTitles, ), ), ), touchedPointIndicator: AxisSpotIndicator( painter: AxisLinesIndicatorPainter( verticalLineProvider: (x) { final data = _btcMonthlyData![_currentMonthIndex][x.toInt()]; return VerticalLine( x: x, color: (data.isUp ? AppColors.contentColorGreen : AppColors.contentColorRed) .withValues(alpha: 0.5), strokeWidth: 1, ); }, horizontalLineProvider: (y) => HorizontalLine( y: y, label: HorizontalLineLabel( show: true, style: const TextStyle( color: AppColors.contentColorYellow, fontSize: 12, fontWeight: FontWeight.bold, ), labelResolver: (hLine) => hLine.y.toInt().toString(), alignment: Alignment.topLeft), color: AppColors.contentColorYellow.withValues( alpha: 0.8, ), strokeWidth: 1, ), ), ), ), ), ), if (_btcMonthlyData == null) const Center( child: CircularProgressIndicator(), ) ], ), ), ], ); } bool get _canGoNext => _currentMonthIndex < 11; bool get _canGoPrevious => _currentMonthIndex > 0; void _previousMonth() { if (!_canGoPrevious) { return; } setState(() { _currentMonthIndex--; }); } void _nextMonth() { if (!_canGoNext) { return; } setState(() { _currentMonthIndex++; }); } Widget _bottomTitles(double value, TitleMeta meta) { final day = value.toInt() + 1; final isImportantToShow = day % 5 == 0 || day == 1; if (!isImportantToShow) { return const SizedBox(); } return SideTitleWidget( meta: meta, child: Text( day.toString(), style: const TextStyle( color: AppColors.contentColorGreen, fontSize: 12, fontWeight: FontWeight.bold, ), ), ); } Widget _leftTitles(double value, TitleMeta meta) { return SideTitleWidget( meta: meta, child: Text( meta.formattedValue, style: const TextStyle( color: AppColors.contentColorYellow, fontSize: 16, fontWeight: FontWeight.bold, ), ), ); } } class _BtcCandlestickData with EquatableMixin { _BtcCandlestickData({ required this.datetime, required this.open, required this.high, required this.low, required this.close, required this.volume, required this.marketCap, }); final DateTime datetime; final double open; final double high; final double low; final double close; final double volume; final double marketCap; bool get isUp => open < close; @override List get props => [ datetime, open, high, low, close, volume, marketCap, ]; } ================================================ FILE: example/lib/presentation/samples/chart_sample.dart ================================================ import 'package:fl_chart_app/urls.dart'; import 'package:fl_chart_app/util/app_helper.dart'; import 'package:flutter/cupertino.dart'; abstract class ChartSample { final int number; final WidgetBuilder builder; ChartType get type; String get name => '${type.displayName} Sample $number'; String get url => Urls.getChartSourceCodeUrl(type, number); ChartSample(this.number, this.builder); } class LineChartSample extends ChartSample { LineChartSample(super.number, super.builder); @override ChartType get type => ChartType.line; } class BarChartSample extends ChartSample { BarChartSample(super.number, super.builder); @override ChartType get type => ChartType.bar; } class PieChartSample extends ChartSample { PieChartSample(super.number, super.builder); @override ChartType get type => ChartType.pie; } class ScatterChartSample extends ChartSample { ScatterChartSample(super.number, super.builder); @override ChartType get type => ChartType.scatter; } class RadarChartSample extends ChartSample { RadarChartSample(super.number, super.builder); @override ChartType get type => ChartType.radar; } class CandlestickChartSample extends ChartSample { CandlestickChartSample(super.number, super.builder); @override ChartType get type => ChartType.candlestick; } ================================================ FILE: example/lib/presentation/samples/chart_samples.dart ================================================ import 'package:fl_chart_app/presentation/samples/candlestick/candlestick_chart_sample1.dart'; import 'package:fl_chart_app/util/app_helper.dart'; import 'bar/bar_chart_sample1.dart'; import 'bar/bar_chart_sample2.dart'; import 'bar/bar_chart_sample3.dart'; import 'bar/bar_chart_sample4.dart'; import 'bar/bar_chart_sample5.dart'; import 'bar/bar_chart_sample6.dart'; import 'bar/bar_chart_sample7.dart'; import 'bar/bar_chart_sample8.dart'; import 'chart_sample.dart'; import 'line/line_chart_sample1.dart'; import 'line/line_chart_sample10.dart'; import 'line/line_chart_sample11.dart'; import 'line/line_chart_sample12.dart'; import 'line/line_chart_sample13.dart'; import 'line/line_chart_sample2.dart'; import 'line/line_chart_sample3.dart'; import 'line/line_chart_sample4.dart'; import 'line/line_chart_sample5.dart'; import 'line/line_chart_sample6.dart'; import 'line/line_chart_sample7.dart'; import 'line/line_chart_sample8.dart'; import 'line/line_chart_sample9.dart'; import 'pie/pie_chart_sample1.dart'; import 'pie/pie_chart_sample2.dart'; import 'pie/pie_chart_sample3.dart'; import 'radar/radar_chart_sample1.dart'; import 'scatter/scatter_chart_sample1.dart'; import 'scatter/scatter_chart_sample2.dart'; class ChartSamples { static final Map> samples = { ChartType.line: [ LineChartSample(1, (context) => const LineChartSample1()), LineChartSample(2, (context) => const LineChartSample2()), LineChartSample(3, (context) => LineChartSample3()), LineChartSample(4, (context) => LineChartSample4()), LineChartSample(5, (context) => const LineChartSample5()), LineChartSample(6, (context) => LineChartSample6()), LineChartSample(7, (context) => LineChartSample7()), LineChartSample(8, (context) => const LineChartSample8()), LineChartSample(9, (context) => LineChartSample9()), LineChartSample(10, (context) => const LineChartSample10()), LineChartSample(11, (context) => const LineChartSample11()), LineChartSample(12, (context) => const LineChartSample12()), LineChartSample(13, (context) => const LineChartSample13()), ], ChartType.bar: [ BarChartSample(1, (context) => BarChartSample1()), BarChartSample(2, (context) => BarChartSample2()), BarChartSample(3, (context) => const BarChartSample3()), BarChartSample(4, (context) => BarChartSample4()), BarChartSample(5, (context) => const BarChartSample5()), BarChartSample(6, (context) => const BarChartSample6()), BarChartSample(7, (context) => BarChartSample7()), BarChartSample(8, (context) => BarChartSample8()), ], ChartType.pie: [ PieChartSample(1, (context) => const PieChartSample1()), PieChartSample(2, (context) => const PieChartSample2()), PieChartSample(3, (context) => const PieChartSample3()), ], ChartType.scatter: [ ScatterChartSample(1, (context) => ScatterChartSample1()), ScatterChartSample(2, (context) => const ScatterChartSample2()), ], ChartType.radar: [ RadarChartSample(1, (context) => RadarChartSample1()), ], ChartType.candlestick: [ CandlestickChartSample(1, (context) => const CandlestickChartSample1()), ] }; } ================================================ FILE: example/lib/presentation/samples/line/line_chart_sample1.dart ================================================ import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart_app/presentation/resources/app_resources.dart'; import 'package:flutter/material.dart'; class _LineChart extends StatelessWidget { const _LineChart({required this.isShowingMainData}); final bool isShowingMainData; @override Widget build(BuildContext context) { return LineChart( isShowingMainData ? sampleData1 : sampleData2, duration: const Duration(milliseconds: 250), ); } LineChartData get sampleData1 => LineChartData( lineTouchData: lineTouchData1, gridData: gridData, titlesData: titlesData1, borderData: borderData, lineBarsData: lineBarsData1, minX: 0, maxX: 14, maxY: 4, minY: 0, ); LineChartData get sampleData2 => LineChartData( lineTouchData: lineTouchData2, gridData: gridData, titlesData: titlesData2, borderData: borderData, lineBarsData: lineBarsData2, minX: 0, maxX: 14, maxY: 6, minY: 0, ); LineTouchData get lineTouchData1 => LineTouchData( handleBuiltInTouches: true, touchTooltipData: LineTouchTooltipData( getTooltipColor: (touchedSpot) => Colors.blueGrey.withValues(alpha: 0.8), ), ); FlTitlesData get titlesData1 => FlTitlesData( bottomTitles: AxisTitles( sideTitles: bottomTitles, ), rightTitles: const AxisTitles( sideTitles: SideTitles(showTitles: false), ), topTitles: const AxisTitles( sideTitles: SideTitles(showTitles: false), ), leftTitles: AxisTitles( sideTitles: leftTitles(), ), ); List get lineBarsData1 => [ lineChartBarData1_1, lineChartBarData1_2, lineChartBarData1_3, ]; LineTouchData get lineTouchData2 => const LineTouchData( enabled: false, ); FlTitlesData get titlesData2 => FlTitlesData( bottomTitles: AxisTitles( sideTitles: bottomTitles, ), rightTitles: const AxisTitles( sideTitles: SideTitles(showTitles: false), ), topTitles: const AxisTitles( sideTitles: SideTitles(showTitles: false), ), leftTitles: AxisTitles( sideTitles: leftTitles(), ), ); List get lineBarsData2 => [ lineChartBarData2_1, lineChartBarData2_2, lineChartBarData2_3, ]; Widget leftTitleWidgets(double value, TitleMeta meta) { const style = TextStyle( fontWeight: FontWeight.bold, fontSize: 14, ); String text = switch (value.toInt()) { 1 => '1m', 2 => '2m', 3 => '3m', 4 => '5m', 5 => '6m', _ => '', }; return SideTitleWidget( meta: meta, child: Text( text, style: style, textAlign: TextAlign.center, ), ); } SideTitles leftTitles() => SideTitles( getTitlesWidget: leftTitleWidgets, showTitles: true, interval: 1, reservedSize: 40, ); Widget bottomTitleWidgets(double value, TitleMeta meta) { const style = TextStyle( fontWeight: FontWeight.bold, fontSize: 16, ); String text = switch (value.toInt()) { 2 => 'SEPT', 7 => 'OCT', 12 => 'DEC', _ => '', }; return SideTitleWidget( meta: meta, space: 10, child: Text(text, style: style), ); } SideTitles get bottomTitles => SideTitles( showTitles: true, reservedSize: 32, interval: 1, getTitlesWidget: bottomTitleWidgets, ); FlGridData get gridData => const FlGridData(show: false); FlBorderData get borderData => FlBorderData( show: true, border: Border( bottom: BorderSide( color: AppColors.primary.withValues(alpha: 0.2), width: 4), left: const BorderSide(color: Colors.transparent), right: const BorderSide(color: Colors.transparent), top: const BorderSide(color: Colors.transparent), ), ); LineChartBarData get lineChartBarData1_1 => LineChartBarData( isCurved: true, color: AppColors.contentColorGreen, barWidth: 8, isStrokeCapRound: true, dotData: const FlDotData(show: false), belowBarData: BarAreaData(show: false), spots: const [ FlSpot(1, 1), FlSpot(3, 1.5), FlSpot(5, 1.4), FlSpot(7, 3.4), FlSpot(10, 2), FlSpot(12, 2.2), FlSpot(13, 1.8), ], ); LineChartBarData get lineChartBarData1_2 => LineChartBarData( isCurved: true, color: AppColors.contentColorPink, barWidth: 8, isStrokeCapRound: true, dotData: const FlDotData(show: false), belowBarData: BarAreaData( show: false, color: AppColors.contentColorPink.withValues(alpha: 0), ), spots: const [ FlSpot(1, 1), FlSpot(3, 2.8), FlSpot(7, 1.2), FlSpot(10, 2.8), FlSpot(12, 2.6), FlSpot(13, 3.9), ], ); LineChartBarData get lineChartBarData1_3 => LineChartBarData( isCurved: true, color: AppColors.contentColorCyan, barWidth: 8, isStrokeCapRound: true, dotData: const FlDotData(show: false), belowBarData: BarAreaData(show: false), spots: const [ FlSpot(1, 2.8), FlSpot(3, 1.9), FlSpot(6, 3), FlSpot(10, 1.3), FlSpot(13, 2.5), ], ); LineChartBarData get lineChartBarData2_1 => LineChartBarData( isCurved: true, curveSmoothness: 0, color: AppColors.contentColorGreen.withValues(alpha: 0.5), barWidth: 4, isStrokeCapRound: true, dotData: const FlDotData(show: false), belowBarData: BarAreaData(show: false), spots: const [ FlSpot(1, 1), FlSpot(3, 4), FlSpot(5, 1.8), FlSpot(7, 5), FlSpot(10, 2), FlSpot(12, 2.2), FlSpot(13, 1.8), ], ); LineChartBarData get lineChartBarData2_2 => LineChartBarData( isCurved: true, color: AppColors.contentColorPink.withValues(alpha: 0.5), barWidth: 4, isStrokeCapRound: true, dotData: const FlDotData(show: false), belowBarData: BarAreaData( show: true, color: AppColors.contentColorPink.withValues(alpha: 0.2), ), spots: const [ FlSpot(1, 1), FlSpot(3, 2.8), FlSpot(7, 1.2), FlSpot(10, 2.8), FlSpot(12, 2.6), FlSpot(13, 3.9), ], ); LineChartBarData get lineChartBarData2_3 => LineChartBarData( isCurved: true, curveSmoothness: 0, color: AppColors.contentColorCyan.withValues(alpha: 0.5), barWidth: 2, isStrokeCapRound: true, dotData: const FlDotData(show: true), belowBarData: BarAreaData(show: false), spots: const [ FlSpot(1, 3.8), FlSpot(3, 1.9), FlSpot(6, 5), FlSpot(10, 3.3), FlSpot(13, 4.5), ], ); } class LineChartSample1 extends StatefulWidget { const LineChartSample1({super.key}); @override State createState() => LineChartSample1State(); } class LineChartSample1State extends State { late bool isShowingMainData; @override void initState() { super.initState(); isShowingMainData = true; } @override Widget build(BuildContext context) { return AspectRatio( aspectRatio: 1.23, child: Stack( children: [ Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ const SizedBox( height: 37, ), const Text( 'Monthly Sales', style: TextStyle( color: AppColors.primary, fontSize: 32, fontWeight: FontWeight.bold, letterSpacing: 2, ), textAlign: TextAlign.center, ), const SizedBox( height: 37, ), Expanded( child: Padding( padding: const EdgeInsets.only(right: 16, left: 6), child: _LineChart(isShowingMainData: isShowingMainData), ), ), const SizedBox( height: 10, ), ], ), IconButton( icon: Icon( Icons.refresh, color: Colors.white.withValues(alpha: isShowingMainData ? 1.0 : 0.5), ), onPressed: () { setState(() { isShowingMainData = !isShowingMainData; }); }, ) ], ), ); } } ================================================ FILE: example/lib/presentation/samples/line/line_chart_sample10.dart ================================================ import 'dart:async'; import 'dart:math' as math; import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart_app/presentation/resources/app_resources.dart'; import 'package:flutter/material.dart'; class LineChartSample10 extends StatefulWidget { const LineChartSample10({super.key}); final Color sinColor = AppColors.contentColorBlue; final Color cosColor = AppColors.contentColorPink; @override State createState() => _LineChartSample10State(); } class _LineChartSample10State extends State { final limitCount = 100; final sinPoints = []; final cosPoints = []; double xValue = 0; double step = 0.05; late Timer timer; @override void initState() { super.initState(); timer = Timer.periodic(const Duration(milliseconds: 40), (timer) { while (sinPoints.length > limitCount) { sinPoints.removeAt(0); cosPoints.removeAt(0); } setState(() { sinPoints.add(FlSpot(xValue, math.sin(xValue))); cosPoints.add(FlSpot(xValue, math.cos(xValue))); }); xValue += step; }); } @override Widget build(BuildContext context) { return cosPoints.isNotEmpty ? Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const SizedBox(height: 12), Text( 'x: ${xValue.toStringAsFixed(1)}', style: const TextStyle( color: AppColors.mainTextColor2, fontSize: 18, fontWeight: FontWeight.bold, ), ), Text( 'sin: ${sinPoints.last.y.toStringAsFixed(1)}', style: TextStyle( color: widget.sinColor, fontSize: 18, fontWeight: FontWeight.bold, ), ), Text( 'cos: ${cosPoints.last.y.toStringAsFixed(1)}', style: TextStyle( color: widget.cosColor, fontSize: 18, fontWeight: FontWeight.bold, ), ), const SizedBox( height: 12, ), AspectRatio( aspectRatio: 1.5, child: Padding( padding: const EdgeInsets.only(bottom: 24.0), child: LineChart( LineChartData( minY: -1, maxY: 1, minX: sinPoints.first.x, maxX: sinPoints.last.x, lineTouchData: const LineTouchData(enabled: false), clipData: const FlClipData.all(), gridData: const FlGridData( show: true, drawVerticalLine: false, ), borderData: FlBorderData(show: false), lineBarsData: [ sinLine(sinPoints), cosLine(cosPoints), ], titlesData: const FlTitlesData( show: false, ), ), ), ), ) ], ) : Container(); } LineChartBarData sinLine(List points) { return LineChartBarData( spots: points, dotData: const FlDotData( show: false, ), gradient: LinearGradient( colors: [widget.sinColor.withValues(alpha: 0), widget.sinColor], stops: const [0.1, 1.0], ), barWidth: 4, isCurved: false, ); } LineChartBarData cosLine(List points) { return LineChartBarData( spots: points, dotData: const FlDotData( show: false, ), gradient: LinearGradient( colors: [widget.cosColor.withValues(alpha: 0), widget.cosColor], stops: const [0.1, 1.0], ), barWidth: 4, isCurved: false, ); } @override void dispose() { timer.cancel(); super.dispose(); } } ================================================ FILE: example/lib/presentation/samples/line/line_chart_sample11.dart ================================================ import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; class LineChartSample11 extends StatefulWidget { const LineChartSample11({super.key}); @override State createState() => _LineChartSample11State(); } class _LineChartSample11State extends State { var baselineX = 0.0; var baselineY = 0.0; @override Widget build(BuildContext context) { return AspectRatio( aspectRatio: 1.5, child: Padding( padding: const EdgeInsets.only( top: 18.0, right: 18.0, ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Expanded( child: Row( children: [ RotatedBox( quarterTurns: 1, child: Slider( value: baselineY, onChanged: (newValue) { setState(() { baselineY = newValue; }); }, min: -10, max: 10, ), ), Expanded( child: _Chart( baselineX, (20 - (baselineY + 10)) - 10, ), ) ], ), ), Slider( value: baselineX, onChanged: (newValue) { setState(() { baselineX = newValue; }); }, min: -10, max: 10, ), ], ), ), ); } } class _Chart extends StatelessWidget { final double baselineX; final double baselineY; const _Chart(this.baselineX, this.baselineY) : super(); Widget getHorizontalTitles(double value, TitleMeta meta) { TextStyle style; if ((value - baselineX).abs() <= 0.1) { style = const TextStyle( color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold, ); } else { style = const TextStyle( color: Colors.white60, fontSize: 14, ); } return Padding( padding: const EdgeInsets.all(4.0), child: Text(meta.formattedValue, style: style), ); } Widget getVerticalTitles(double value, TitleMeta meta) { TextStyle style; if ((value - baselineY).abs() <= 0.1) { style = const TextStyle( color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold, ); } else { style = const TextStyle( color: Colors.white60, fontSize: 14, ); } return Padding( padding: const EdgeInsets.all(4.0), child: Text(meta.formattedValue, style: style), ); } FlLine getHorizontalVerticalLine(double value) { if ((value - baselineY).abs() <= 0.1) { return const FlLine( color: Colors.white70, strokeWidth: 1, dashArray: [8, 4], ); } else { return const FlLine( color: Colors.blueGrey, strokeWidth: 0.4, dashArray: [8, 4], ); } } FlLine getVerticalVerticalLine(double value) { if ((value - baselineX).abs() <= 0.1) { return const FlLine( color: Colors.white70, strokeWidth: 1, dashArray: [8, 4], ); } else { return const FlLine( color: Colors.blueGrey, strokeWidth: 0.4, dashArray: [8, 4], ); } } @override Widget build(BuildContext context) { return LineChart( LineChartData( lineBarsData: [ LineChartBarData( spots: [], ), ], titlesData: FlTitlesData( leftTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, getTitlesWidget: getVerticalTitles, reservedSize: 36, ), ), topTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, getTitlesWidget: getHorizontalTitles, reservedSize: 32), ), rightTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, getTitlesWidget: getVerticalTitles, reservedSize: 36, ), ), bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, getTitlesWidget: getHorizontalTitles, reservedSize: 32), ), ), gridData: FlGridData( show: true, drawHorizontalLine: true, drawVerticalLine: true, getDrawingHorizontalLine: getHorizontalVerticalLine, getDrawingVerticalLine: getVerticalVerticalLine, ), minY: -10, maxY: 10, baselineY: baselineY, minX: -10, maxX: 10, baselineX: baselineX, ), duration: Duration.zero, ); } } ================================================ FILE: example/lib/presentation/samples/line/line_chart_sample12.dart ================================================ import 'dart:convert'; import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart_app/presentation/presentation_utils.dart'; import 'package:fl_chart_app/presentation/resources/app_colors.dart'; import 'package:fl_chart_app/util/extensions/color_extensions.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; class LineChartSample12 extends StatefulWidget { const LineChartSample12({super.key}); @override State createState() => _LineChartSample12State(); } class _LineChartSample12State extends State { List<(DateTime, double)>? _bitcoinPriceHistory; late TransformationController _transformationController; bool _isPanEnabled = true; bool _isScaleEnabled = true; @override void initState() { _reloadData(); _transformationController = TransformationController(); super.initState(); } void _reloadData() async { final dataStr = await rootBundle.loadString( 'assets/data/btc_last_year_price.json', ); if (!mounted) { return; } final json = jsonDecode(dataStr) as Map; setState(() { _bitcoinPriceHistory = (json['prices'] as List).map((item) { final timestamp = item[0] as int; final price = item[1] as double; return (DateTime.fromMillisecondsSinceEpoch(timestamp), price); }).toList(); }); } @override Widget build(BuildContext context) { const leftReservedSize = 52.0; return Column( spacing: 16, children: [ LayoutBuilder( builder: (context, constraints) { final width = constraints.maxWidth; return width >= 380 ? Row( children: [ const SizedBox(width: leftReservedSize), const _ChartTitle(), const Spacer(), Center( child: _TransformationButtons( controller: _transformationController, ), ), ], ) : Column( children: [ const _ChartTitle(), const SizedBox(height: 16), _TransformationButtons( controller: _transformationController, ), ], ); }, ), Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), child: Row( mainAxisAlignment: MainAxisAlignment.end, spacing: 16, children: [ const Text('Pan'), Switch( value: _isPanEnabled, onChanged: (value) { setState(() { _isPanEnabled = value; }); }, ), const Text('Scale'), Switch( value: _isScaleEnabled, onChanged: (value) { setState(() { _isScaleEnabled = value; }); }, ), ], ), ), AspectRatio( aspectRatio: 1.4, child: Padding( padding: const EdgeInsets.only( top: 0.0, right: 18.0, ), child: LineChart( transformationConfig: FlTransformationConfig( scaleAxis: FlScaleAxis.horizontal, minScale: 1.0, maxScale: 25.0, panEnabled: _isPanEnabled, scaleEnabled: _isScaleEnabled, transformationController: _transformationController, ), LineChartData( lineBarsData: [ LineChartBarData( spots: _bitcoinPriceHistory?.asMap().entries.map((e) { final index = e.key; final item = e.value; final value = item.$2; return FlSpot(index.toDouble(), value); }).toList() ?? [], dotData: const FlDotData(show: false), color: AppColors.contentColorYellow, barWidth: 1, shadow: const Shadow( color: AppColors.contentColorYellow, blurRadius: 2, ), belowBarData: BarAreaData( show: true, gradient: LinearGradient( colors: [ AppColors.contentColorYellow.withValues(alpha: 0.2), AppColors.contentColorYellow.withValues(alpha: 0.0), ], stops: const [0.5, 1.0], begin: Alignment.topCenter, end: Alignment.bottomCenter, ), ), ), ], lineTouchData: LineTouchData( touchSpotThreshold: 5, getTouchLineStart: (_, __) => -double.infinity, getTouchLineEnd: (_, __) => double.infinity, getTouchedSpotIndicator: (LineChartBarData barData, List spotIndexes) { return spotIndexes.map((spotIndex) { return TouchedSpotIndicatorData( const FlLine( color: AppColors.contentColorRed, strokeWidth: 1.5, dashArray: [8, 2], ), FlDotData( show: true, getDotPainter: (spot, percent, barData, index) { return FlDotCirclePainter( radius: 6, color: AppColors.contentColorYellow, strokeWidth: 0, strokeColor: AppColors.contentColorYellow, ); }, ), ); }).toList(); }, touchTooltipData: LineTouchTooltipData( getTooltipItems: (List touchedBarSpots) { return touchedBarSpots.map((barSpot) { final price = barSpot.y; final date = _bitcoinPriceHistory![barSpot.x.toInt()].$1; return LineTooltipItem( '', const TextStyle( color: AppColors.contentColorBlack, fontWeight: FontWeight.bold, ), children: [ TextSpan( text: '${date.year}/${date.month}/${date.day}', style: TextStyle( color: AppColors.contentColorGreen.darken(20), fontWeight: FontWeight.bold, fontSize: 12, ), ), TextSpan( text: '\n${AppUtils.getFormattedCurrency( context, price, noDecimals: true, )}', style: const TextStyle( color: AppColors.contentColorYellow, fontWeight: FontWeight.bold, fontSize: 16, ), ), ], ); }).toList(); }, getTooltipColor: (LineBarSpot barSpot) => AppColors.contentColorBlack, ), ), titlesData: FlTitlesData( show: true, rightTitles: const AxisTitles( sideTitles: SideTitles(showTitles: false), ), topTitles: const AxisTitles( sideTitles: SideTitles(showTitles: false), ), leftTitles: const AxisTitles( drawBelowEverything: true, sideTitles: SideTitles( showTitles: true, reservedSize: leftReservedSize, maxIncluded: false, minIncluded: false, ), ), bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: 38, maxIncluded: false, getTitlesWidget: (double value, TitleMeta meta) { final date = _bitcoinPriceHistory![value.toInt()].$1; return SideTitleWidget( meta: meta, child: Transform.rotate( angle: -45 * 3.14 / 180, child: Text( '${date.month}/${date.day}', style: const TextStyle( color: AppColors.contentColorGreen, fontSize: 12, fontWeight: FontWeight.bold, ), ), ), ); }, ), ), ), ), duration: Duration.zero, ), ), ), ], ); } @override void dispose() { _transformationController.dispose(); super.dispose(); } } class _ChartTitle extends StatelessWidget { const _ChartTitle(); @override Widget build(BuildContext context) { return const Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: 14), Text( 'Bitcoin Price History', style: TextStyle( color: AppColors.contentColorYellow, fontWeight: FontWeight.bold, fontSize: 18, ), ), Text( '2023/12/19 - 2024/12/17', style: TextStyle( color: AppColors.contentColorGreen, fontWeight: FontWeight.bold, fontSize: 14, ), ), SizedBox(height: 14), ], ); } } class _TransformationButtons extends StatelessWidget { const _TransformationButtons({ required this.controller, }); final TransformationController controller; @override Widget build(BuildContext context) { return Column( children: [ Tooltip( message: 'Zoom in', child: IconButton( icon: const Icon( Icons.add, size: 16, ), onPressed: _transformationZoomIn, ), ), Row( mainAxisSize: MainAxisSize.min, children: [ Tooltip( message: 'Move left', child: IconButton( icon: const Icon( Icons.arrow_back_ios, size: 16, ), onPressed: _transformationMoveLeft, ), ), Tooltip( message: 'Reset zoom', child: IconButton( icon: const Icon( Icons.refresh, size: 16, ), onPressed: _transformationReset, ), ), Tooltip( message: 'Move right', child: IconButton( icon: const Icon( Icons.arrow_forward_ios, size: 16, ), onPressed: _transformationMoveRight, ), ), ], ), Tooltip( message: 'Zoom out', child: IconButton( icon: const Icon( Icons.minimize, size: 16, ), onPressed: _transformationZoomOut, ), ), ], ); } void _transformationReset() { controller.value = Matrix4.identity(); } void _transformationZoomIn() { controller.value *= Matrix4.diagonal3Values( 1.1, 1.1, 1, ); } void _transformationMoveLeft() { controller.value *= Matrix4.translationValues( 20, 0, 0, ); } void _transformationMoveRight() { controller.value *= Matrix4.translationValues( -20, 0, 0, ); } void _transformationZoomOut() { controller.value *= Matrix4.diagonal3Values( 0.9, 0.9, 1, ); } } ================================================ FILE: example/lib/presentation/samples/line/line_chart_sample13.dart ================================================ import 'package:equatable/equatable.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart_app/presentation/resources/app_resources.dart'; import 'package:fl_chart_app/util/app_utils.dart'; import 'package:fl_chart_app/util/csv_parser.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; class LineChartSample13 extends StatefulWidget { const LineChartSample13({super.key}); @override State createState() => _LineChartSample13State(); } class _LineChartSample13State extends State { List>? monthlyWeatherData; int _currentMonthIndex = 0; late final List monthsNames; final int minDays = 1; final int maxDays = 31; late final double overallMinTemp; late final double overallMaxTemp; int _interactedSpotIndex = -1; @override void initState() { monthsNames = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December', ]; _loadWeatherData(); super.initState(); } void _loadWeatherData() async { final data = await rootBundle.loadString('assets/data/amsterdam_2024_weather.csv'); final rows = CsvParser.parse(data); if (!mounted) { return; } setState(() { final allWeatherData = rows.skip(1).map((row) => _WeatherData.fromCsv(row)).toList(); monthlyWeatherData = List.generate(12, (index) { final month = index + 1; return allWeatherData .where((element) => element.datetime.month == month) .toList(); }); overallMinTemp = allWeatherData .map((e) => e.temp) .reduce((value, element) => value < element ? value : element); overallMaxTemp = allWeatherData .map((e) => e.temp) .reduce((value, element) => value > element ? value : element); }); } @override Widget build(BuildContext context) { return Column( children: [ const SizedBox(height: 18), Row( mainAxisSize: MainAxisSize.min, children: [ const Text( 'Amsterdam Weather 2024', style: TextStyle( color: AppColors.contentColorOrange, fontSize: 20, fontWeight: FontWeight.bold, ), ), Tooltip( message: 'Source: visualcrossing.com', child: IconButton( onPressed: () { AppUtils().tryToLaunchUrl( 'https://www.visualcrossing.com/weather-history/Amsterdam,Netherlands/metric/2024-01-01/2024-12-31', ); }, icon: const Icon( Icons.info_outline_rounded, color: AppColors.contentColorOrange, size: 18, )), ) ], ), const SizedBox(height: 18), Row( children: [ Expanded( child: Align( alignment: Alignment.centerRight, child: IconButton( onPressed: _canGoPrevious ? _previousMonth : null, icon: const Icon(Icons.navigate_before_rounded), ), ), ), SizedBox( width: 92, child: Text( monthsNames[_currentMonthIndex], textAlign: TextAlign.center, style: const TextStyle( color: AppColors.contentColorBlue, fontSize: 16, fontWeight: FontWeight.bold, ), ), ), Expanded( child: Align( alignment: Alignment.centerLeft, child: IconButton( onPressed: _canGoNext ? _nextMonth : null, icon: const Icon(Icons.navigate_next_rounded), ), ), ), ], ), const SizedBox(height: 18), AspectRatio( aspectRatio: 1.4, child: Stack( children: [ if (monthlyWeatherData != null) Padding( padding: const EdgeInsets.only( top: 0.0, right: 18.0, ), child: LineChart( LineChartData( minY: overallMinTemp - 5, maxY: overallMaxTemp + 5, minX: 0, maxX: 31, lineBarsData: [ LineChartBarData( spots: monthlyWeatherData![_currentMonthIndex] .asMap() .entries .map((e) { final index = e.key; final item = e.value; final value = item.temp; return FlSpot( index.toDouble(), value, yError: FlErrorRange( lowerBy: (item.tempmin - value).abs(), upperBy: item.tempmax - value, ), ); }).toList(), isCurved: false, dotData: const FlDotData(show: false), color: AppColors.contentColorBlue, barWidth: 1, errorIndicatorData: FlErrorIndicatorData( show: true, painter: _errorPainter, ), ), ], gridData: FlGridData( show: true, drawHorizontalLine: true, drawVerticalLine: false, horizontalInterval: 5, getDrawingHorizontalLine: _horizontalGridLines, ), titlesData: FlTitlesData( show: true, rightTitles: const AxisTitles( sideTitles: SideTitles(showTitles: false), ), topTitles: const AxisTitles( sideTitles: SideTitles(showTitles: false), ), leftTitles: AxisTitles( drawBelowEverything: true, sideTitles: SideTitles( showTitles: true, maxIncluded: false, minIncluded: false, reservedSize: 40, getTitlesWidget: (double value, TitleMeta meta) { return SideTitleWidget( meta: meta, child: Text( '${meta.formattedValue}°', ), ); }, ), ), bottomTitles: AxisTitles( axisNameWidget: Container( margin: const EdgeInsets.only(bottom: 20), child: const Text( 'Day of month', style: TextStyle( color: AppColors.contentColorGreen, fontWeight: FontWeight.bold, fontSize: 16, ), ), ), axisNameSize: 40, sideTitles: SideTitles( showTitles: true, reservedSize: 38, maxIncluded: false, interval: 1, getTitlesWidget: _bottomTitles, ), ), ), lineTouchData: LineTouchData( enabled: true, handleBuiltInTouches: false, touchCallback: _touchCallback, ), ), ), ), if (monthlyWeatherData == null) const Center( child: CircularProgressIndicator(), ) ], ), ), ], ); } bool get _canGoNext => _currentMonthIndex < 11; bool get _canGoPrevious => _currentMonthIndex > 0; void _previousMonth() { if (!_canGoPrevious) { return; } setState(() { _currentMonthIndex--; }); } void _nextMonth() { if (!_canGoNext) { return; } setState(() { _currentMonthIndex++; }); } FlSpotErrorRangePainter _errorPainter( LineChartSpotErrorRangeCallbackInput input, ) => FlSimpleErrorPainter( lineWidth: 1.0, lineColor: _interactedSpotIndex == input.spotIndex ? Colors.white : Colors.white38, showErrorTexts: _interactedSpotIndex == input.spotIndex, ); FlLine _horizontalGridLines(double value) { final isZero = value == 0.0; return FlLine( color: isZero ? Colors.white38 : Colors.blueGrey, strokeWidth: isZero ? 0.8 : 0.4, dashArray: isZero ? null : [8, 4], ); } Widget _bottomTitles(double value, TitleMeta meta) { final day = value.toInt() + 1; final isDayHovered = _interactedSpotIndex == day - 1; final isImportantToShow = day % 5 == 0 || day == 1; if (!isImportantToShow && !isDayHovered) { return const SizedBox(); } return SideTitleWidget( meta: meta, child: Text( day.toString(), style: TextStyle( color: isDayHovered ? AppColors.contentColorWhite : AppColors.contentColorGreen, fontSize: 12, fontWeight: FontWeight.bold, ), ), ); } void _touchCallback(FlTouchEvent event, LineTouchResponse? touchResponse) { if (!event.isInterestedForInteractions || touchResponse?.lineBarSpots == null || touchResponse!.lineBarSpots!.isEmpty) { setState(() { _interactedSpotIndex = -1; }); return; } setState(() { _interactedSpotIndex = touchResponse.lineBarSpots!.first.spotIndex; }); } @override void dispose() { super.dispose(); } } class _WeatherData with EquatableMixin { final String name; final DateTime datetime; final double tempmax; final double tempmin; final double temp; final double feelslikemax; final double feelslikemin; final double feelslike; final double dew; final double humidity; final double precip; final double precipprob; final double precipcover; final String preciptype; final double snow; final double snowdepth; final double windgust; final double windspeed; final double winddir; final double sealevelpressure; final double cloudcover; final double visibility; final double solarradiation; final double solarenergy; final double uvindex; final double severerisk; final DateTime sunrise; final DateTime sunset; final double moonphase; final String conditions; final String description; final String icon; final String stations; const _WeatherData({ required this.name, required this.datetime, required this.tempmax, required this.tempmin, required this.temp, required this.feelslikemax, required this.feelslikemin, required this.feelslike, required this.dew, required this.humidity, required this.precip, required this.precipprob, required this.precipcover, required this.preciptype, required this.snow, required this.snowdepth, required this.windgust, required this.windspeed, required this.winddir, required this.sealevelpressure, required this.cloudcover, required this.visibility, required this.solarradiation, required this.solarenergy, required this.uvindex, required this.severerisk, required this.sunrise, required this.sunset, required this.moonphase, required this.conditions, required this.description, required this.icon, required this.stations, }); // parse from csv row // name,datetime,tempmax,tempmin,temp,feelslikemax,feelslikemin,feelslike,dew,humidity,precip,precipprob,precipcover,preciptype,snow,snowdepth,windgust,windspeed,winddir,sealevelpressure,cloudcover,visibility,solarradiation,solarenergy,uvindex,severerisk,sunrise,sunset,moonphase,conditions,description,icon,stations // "Amsterdam,Netherlands",2024-01-01,9.1,6.4,8,5.3,2.5,4.1,5.1,82.4,14.26,100,37.5,rain,0,0,53.9,40.2,225.9,1000.1,88.7,20.5,20.6,1.8,2,,2024-01-01T08:50:34,2024-01-01T16:37:06,0.68,"Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999" factory _WeatherData.fromCsv(List row) => _WeatherData( name: row[0], datetime: DateTime.parse(row[1]), tempmax: double.parse(row[2]), tempmin: double.parse(row[3]), temp: double.parse(row[4]), feelslikemax: double.parse(row[5]), feelslikemin: double.parse(row[6]), feelslike: double.parse(row[7]), dew: double.parse(row[8]), humidity: double.parse(row[9]), precip: double.parse(row[10]), precipprob: double.parse(row[11]), precipcover: double.parse(row[12]), preciptype: row[13], snow: double.parse(row[14]), snowdepth: double.parse(row[15]), windgust: double.parse(row[16]), windspeed: double.parse(row[17]), winddir: double.parse(row[18]), sealevelpressure: double.parse(row[19]), cloudcover: double.parse(row[20]), visibility: double.parse(row[21]), solarradiation: double.parse(row[22]), solarenergy: double.parse(row[23]), uvindex: double.parse(row[24]), severerisk: row[25].isEmpty ? 0 : double.parse(row[25]), sunrise: DateTime.parse(row[26]), sunset: DateTime.parse(row[27]), moonphase: double.parse(row[28]), conditions: row[29], description: row[30], icon: row[31], stations: row[32], ); @override List get props => [ name, datetime, tempmax, tempmin, temp, feelslikemax, feelslikemin, feelslike, dew, humidity, precip, precipprob, precipcover, preciptype, snow, snowdepth, windgust, windspeed, winddir, sealevelpressure, cloudcover, visibility, solarradiation, solarenergy, uvindex, severerisk, sunrise, sunset, moonphase, conditions, description, icon, stations, ]; } ================================================ FILE: example/lib/presentation/samples/line/line_chart_sample2.dart ================================================ import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart_app/presentation/resources/app_resources.dart'; import 'package:flutter/material.dart'; class LineChartSample2 extends StatefulWidget { const LineChartSample2({super.key}); @override State createState() => _LineChartSample2State(); } class _LineChartSample2State extends State { List gradientColors = [ AppColors.contentColorCyan, AppColors.contentColorBlue, ]; bool showAvg = false; @override Widget build(BuildContext context) { return Stack( children: [ AspectRatio( aspectRatio: 1.70, child: Padding( padding: const EdgeInsets.only( right: 18, left: 12, top: 24, bottom: 12, ), child: LineChart( showAvg ? avgData() : mainData(), ), ), ), SizedBox( width: 60, height: 34, child: TextButton( onPressed: () { setState(() { showAvg = !showAvg; }); }, child: Text( 'avg', style: TextStyle( fontSize: 12, color: showAvg ? Colors.white.withValues(alpha: 0.5) : Colors.white, ), ), ), ), ], ); } Widget bottomTitleWidgets(double value, TitleMeta meta) { const style = TextStyle( fontWeight: FontWeight.bold, fontSize: 16, ); String text = switch (value.toInt()) { 2 => 'MAR', 5 => 'JUN', 8 => 'SEP', _ => '', }; return SideTitleWidget( meta: meta, child: Text(text, style: style), ); } Widget leftTitleWidgets(double value, TitleMeta meta) { const style = TextStyle( fontWeight: FontWeight.bold, fontSize: 15, ); String text = switch (value.toInt()) { 1 => '10K', 3 => '30k', 5 => '50k', _ => '', }; return Text(text, style: style, textAlign: TextAlign.left); } LineChartData mainData() { return LineChartData( gridData: FlGridData( show: true, drawVerticalLine: true, horizontalInterval: 1, verticalInterval: 1, getDrawingHorizontalLine: (value) { return const FlLine( color: AppColors.mainGridLineColor, strokeWidth: 1, ); }, getDrawingVerticalLine: (value) { return const FlLine( color: AppColors.mainGridLineColor, strokeWidth: 1, ); }, ), titlesData: FlTitlesData( show: true, rightTitles: const AxisTitles( sideTitles: SideTitles(showTitles: false), ), topTitles: const AxisTitles( sideTitles: SideTitles(showTitles: false), ), bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: 30, interval: 1, getTitlesWidget: bottomTitleWidgets, ), ), leftTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, interval: 1, getTitlesWidget: leftTitleWidgets, reservedSize: 42, ), ), ), borderData: FlBorderData( show: true, border: Border.all(color: const Color(0xff37434d)), ), minX: 0, maxX: 11, minY: 0, maxY: 6, lineBarsData: [ LineChartBarData( spots: const [ FlSpot(0, 3), FlSpot(2.6, 2), FlSpot(4.9, 5), FlSpot(6.8, 3.1), FlSpot(8, 4), FlSpot(9.5, 3), FlSpot(11, 4), ], isCurved: true, gradient: LinearGradient( colors: gradientColors, ), barWidth: 5, isStrokeCapRound: true, dotData: const FlDotData( show: false, ), belowBarData: BarAreaData( show: true, gradient: LinearGradient( colors: gradientColors .map((color) => color.withValues(alpha: 0.3)) .toList(), ), ), ), ], ); } LineChartData avgData() { return LineChartData( lineTouchData: const LineTouchData(enabled: false), gridData: FlGridData( show: true, drawHorizontalLine: true, verticalInterval: 1, horizontalInterval: 1, getDrawingVerticalLine: (value) { return const FlLine( color: Color(0xff37434d), strokeWidth: 1, ); }, getDrawingHorizontalLine: (value) { return const FlLine( color: Color(0xff37434d), strokeWidth: 1, ); }, ), titlesData: FlTitlesData( show: true, bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: 30, getTitlesWidget: bottomTitleWidgets, interval: 1, ), ), leftTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, getTitlesWidget: leftTitleWidgets, reservedSize: 42, interval: 1, ), ), topTitles: const AxisTitles( sideTitles: SideTitles(showTitles: false), ), rightTitles: const AxisTitles( sideTitles: SideTitles(showTitles: false), ), ), borderData: FlBorderData( show: true, border: Border.all(color: const Color(0xff37434d)), ), minX: 0, maxX: 11, minY: 0, maxY: 6, lineBarsData: [ LineChartBarData( spots: const [ FlSpot(0, 3.44), FlSpot(2.6, 3.44), FlSpot(4.9, 3.44), FlSpot(6.8, 3.44), FlSpot(8, 3.44), FlSpot(9.5, 3.44), FlSpot(11, 3.44), ], isCurved: true, gradient: LinearGradient( colors: [ ColorTween(begin: gradientColors[0], end: gradientColors[1]) .lerp(0.2)!, ColorTween(begin: gradientColors[0], end: gradientColors[1]) .lerp(0.2)!, ], ), barWidth: 5, isStrokeCapRound: true, dotData: const FlDotData( show: false, ), belowBarData: BarAreaData( show: true, gradient: LinearGradient( colors: [ ColorTween(begin: gradientColors[0], end: gradientColors[1]) .lerp(0.2)! .withValues(alpha: 0.1), ColorTween(begin: gradientColors[0], end: gradientColors[1]) .lerp(0.2)! .withValues(alpha: 0.1), ], ), ), ), ], ); } } ================================================ FILE: example/lib/presentation/samples/line/line_chart_sample3.dart ================================================ import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart_app/presentation/resources/app_resources.dart'; import 'package:flutter/material.dart'; class LineChartSample3 extends StatefulWidget { LineChartSample3({ super.key, Color? lineColor, Color? indicatorLineColor, Color? indicatorTouchedLineColor, Color? indicatorSpotStrokeColor, Color? indicatorTouchedSpotStrokeColor, Color? bottomTextColor, Color? bottomTouchedTextColor, Color? averageLineColor, Color? tooltipBgColor, Color? tooltipTextColor, }) : lineColor = lineColor ?? AppColors.contentColorRed, indicatorLineColor = indicatorLineColor ?? AppColors.contentColorYellow.withValues(alpha: 0.2), indicatorTouchedLineColor = indicatorTouchedLineColor ?? AppColors.contentColorYellow, indicatorSpotStrokeColor = indicatorSpotStrokeColor ?? AppColors.contentColorYellow.withValues(alpha: 0.5), indicatorTouchedSpotStrokeColor = indicatorTouchedSpotStrokeColor ?? AppColors.contentColorYellow, bottomTextColor = bottomTextColor ?? AppColors.contentColorYellow.withValues(alpha: 0.2), bottomTouchedTextColor = bottomTouchedTextColor ?? AppColors.contentColorYellow, averageLineColor = averageLineColor ?? AppColors.contentColorGreen.withValues(alpha: 0.8), tooltipBgColor = tooltipBgColor ?? AppColors.contentColorGreen, tooltipTextColor = tooltipTextColor ?? Colors.black; final Color lineColor; final Color indicatorLineColor; final Color indicatorTouchedLineColor; final Color indicatorSpotStrokeColor; final Color indicatorTouchedSpotStrokeColor; final Color bottomTextColor; final Color bottomTouchedTextColor; final Color averageLineColor; final Color tooltipBgColor; final Color tooltipTextColor; List get weekDays => const ['Sat', 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri']; List get yValues => const [1.3, 1, 1.8, 1.5, 2.2, 1.8, 3]; @override State createState() => _LineChartSample3State(); } class _LineChartSample3State extends State { late double touchedValue; bool fitInsideBottomTitle = true; bool fitInsideLeftTitle = false; @override void initState() { touchedValue = -1; super.initState(); } Widget leftTitleWidgets(double value, TitleMeta meta) { if (value % 1 != 0) { return Container(); } final style = TextStyle( color: AppColors.mainTextColor1.withValues(alpha: 0.5), fontSize: 10, ); String text = switch (value.toInt()) { 0 => '', 1 => '1k calories', 2 => '2k calories', 3 => '3k calories', _ => '', }; return SideTitleWidget( meta: meta, space: 6, fitInside: fitInsideLeftTitle ? SideTitleFitInsideData.fromTitleMeta(meta) : SideTitleFitInsideData.disable(), child: Text(text, style: style, textAlign: TextAlign.center), ); } Widget bottomTitleWidgets(double value, TitleMeta meta) { final isTouched = value == touchedValue; final style = TextStyle( color: isTouched ? widget.bottomTouchedTextColor : widget.bottomTextColor, fontWeight: FontWeight.bold, ); if (value % 1 != 0) { return Container(); } return SideTitleWidget( space: 4, meta: meta, fitInside: fitInsideBottomTitle ? SideTitleFitInsideData.fromTitleMeta(meta, distanceFromEdge: 0) : SideTitleFitInsideData.disable(), child: Text( widget.weekDays[value.toInt()], style: style, ), ); } @override Widget build(BuildContext context) { return Column( mainAxisSize: MainAxisSize.min, children: [ const SizedBox(height: 10), Row( mainAxisSize: MainAxisSize.min, children: [ Text( 'Average Line', style: TextStyle( color: widget.averageLineColor.withValues(alpha: 1), fontWeight: FontWeight.bold, fontSize: 16, ), ), const Text( ' and ', style: TextStyle( color: AppColors.mainTextColor1, fontWeight: FontWeight.bold, fontSize: 16, ), ), Text( 'Indicators', style: TextStyle( color: widget.indicatorLineColor.withValues(alpha: 1), fontWeight: FontWeight.bold, fontSize: 16, ), ), ], ), const SizedBox( height: 18, ), AspectRatio( aspectRatio: 2, child: Padding( padding: const EdgeInsets.only(right: 20.0, left: 12), child: LineChart( LineChartData( lineTouchData: LineTouchData( getTouchedSpotIndicator: (LineChartBarData barData, List spotIndexes) { return spotIndexes.map((spotIndex) { final spot = barData.spots[spotIndex]; if (spot.x == 0 || spot.x == 6) { return null; } return TouchedSpotIndicatorData( FlLine( color: widget.indicatorTouchedLineColor, strokeWidth: 4, ), FlDotData( getDotPainter: (spot, percent, barData, index) { if (index.isEven) { return FlDotCirclePainter( radius: 8, color: Colors.white, strokeWidth: 5, strokeColor: widget.indicatorTouchedSpotStrokeColor, ); } else { return FlDotSquarePainter( size: 16, color: Colors.white, strokeWidth: 5, strokeColor: widget.indicatorTouchedSpotStrokeColor, ); } }, ), ); }).toList(); }, touchTooltipData: LineTouchTooltipData( getTooltipColor: (touchedSpot) => widget.tooltipBgColor, getTooltipItems: (List touchedBarSpots) { return touchedBarSpots.map((barSpot) { final flSpot = barSpot; if (flSpot.x == 0 || flSpot.x == 6) { return null; } TextAlign textAlign = switch (flSpot.x.toInt()) { 1 => TextAlign.left, 5 => TextAlign.right, _ => TextAlign.center, }; return LineTooltipItem( '${widget.weekDays[flSpot.x.toInt()]} \n', TextStyle( color: widget.tooltipTextColor, fontWeight: FontWeight.bold, ), children: [ TextSpan( text: flSpot.y.toString(), style: TextStyle( color: widget.tooltipTextColor, fontWeight: FontWeight.w900, ), ), const TextSpan( text: ' k ', style: TextStyle( fontStyle: FontStyle.italic, fontWeight: FontWeight.w900, ), ), const TextSpan( text: 'calories', style: TextStyle( fontWeight: FontWeight.normal, ), ), ], textAlign: textAlign, ); }).toList(); }, ), touchCallback: (FlTouchEvent event, LineTouchResponse? lineTouch) { if (!event.isInterestedForInteractions || lineTouch == null || lineTouch.lineBarSpots == null) { setState(() { touchedValue = -1; }); return; } final value = lineTouch.lineBarSpots![0].x; if (value == 0 || value == 6) { setState(() { touchedValue = -1; }); return; } setState(() { touchedValue = value; }); }, ), extraLinesData: ExtraLinesData( horizontalLines: [ HorizontalLine( y: 1.8, color: widget.averageLineColor, strokeWidth: 3, dashArray: [20, 10], ), ], ), lineBarsData: [ LineChartBarData( isStepLineChart: true, spots: widget.yValues.asMap().entries.map((e) { return FlSpot(e.key.toDouble(), e.value); }).toList(), isCurved: false, barWidth: 4, color: widget.lineColor, belowBarData: BarAreaData( show: true, gradient: LinearGradient( colors: [ widget.lineColor.withValues(alpha: 0.5), widget.lineColor.withValues(alpha: 0), ], stops: const [0.5, 1.0], begin: Alignment.topCenter, end: Alignment.bottomCenter, ), spotsLine: BarAreaSpotsLine( show: true, flLineStyle: FlLine( color: widget.indicatorLineColor, strokeWidth: 2, ), checkToShowSpotLine: (spot) { if (spot.x == 0 || spot.x == 6) { return false; } return true; }, ), ), dotData: FlDotData( show: true, getDotPainter: (spot, percent, barData, index) { if (index.isEven) { return FlDotCirclePainter( radius: 6, color: Colors.white, strokeWidth: 3, strokeColor: widget.indicatorSpotStrokeColor, ); } else { return FlDotSquarePainter( size: 12, color: Colors.white, strokeWidth: 3, strokeColor: widget.indicatorSpotStrokeColor, ); } }, checkToShowDot: (spot, barData) { return spot.x != 0 && spot.x != 6; }, ), ), ], minY: 0, borderData: FlBorderData( show: true, border: Border.all( color: AppColors.borderColor, ), ), gridData: FlGridData( show: true, drawHorizontalLine: true, drawVerticalLine: true, checkToShowHorizontalLine: (value) => value % 1 == 0, checkToShowVerticalLine: (value) => value % 1 == 0, getDrawingHorizontalLine: (value) { if (value == 0) { return const FlLine( color: AppColors.contentColorOrange, strokeWidth: 2, ); } else { return const FlLine( color: AppColors.mainGridLineColor, strokeWidth: 0.5, ); } }, getDrawingVerticalLine: (value) { if (value == 0) { return const FlLine( color: Colors.redAccent, strokeWidth: 10, ); } else { return const FlLine( color: AppColors.mainGridLineColor, strokeWidth: 0.5, ); } }, ), titlesData: FlTitlesData( show: true, topTitles: const AxisTitles( sideTitles: SideTitles(showTitles: false), ), rightTitles: const AxisTitles( sideTitles: SideTitles(showTitles: false), ), leftTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: 46, getTitlesWidget: leftTitleWidgets, ), ), bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: 40, getTitlesWidget: bottomTitleWidgets, ), ), ), ), ), ), ), Column( children: [ const Text('Fit Inside Title Option'), const Divider(), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ const Text('Left Title'), Switch( value: fitInsideLeftTitle, onChanged: (value) => setState(() { fitInsideLeftTitle = value; }), ), const Text('Bottom Title'), Switch( value: fitInsideBottomTitle, onChanged: (value) => setState(() { fitInsideBottomTitle = value; }), ) ], ), ], ), ], ); } } ================================================ FILE: example/lib/presentation/samples/line/line_chart_sample4.dart ================================================ import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart_app/presentation/resources/app_resources.dart'; import 'package:flutter/material.dart'; class LineChartSample4 extends StatelessWidget { LineChartSample4({ super.key, Color? mainLineColor, Color? belowLineColor, Color? aboveLineColor, }) : mainLineColor = mainLineColor ?? AppColors.contentColorYellow.withValues(alpha: 1), belowLineColor = belowLineColor ?? AppColors.contentColorPink.withValues(alpha: 1), aboveLineColor = aboveLineColor ?? AppColors.contentColorPurple.withValues(alpha: 0.7); final Color mainLineColor; final Color belowLineColor; final Color aboveLineColor; Widget bottomTitleWidgets(double value, TitleMeta meta) { String text = switch (value.toInt()) { 0 => 'Jan', 1 => 'Feb', 2 => 'Mar', 3 => 'Apr', 4 => 'May', 5 => 'Jun', 6 => 'Jul', 7 => 'Aug', 8 => 'Sep', 9 => 'Oct', 10 => 'Nov', 11 => 'Dec', _ => '', }; return SideTitleWidget( meta: meta, space: 4, child: Text( text, style: TextStyle( fontSize: 10, color: mainLineColor, fontWeight: FontWeight.bold, ), ), ); } Widget leftTitleWidgets(double value, TitleMeta meta) { const style = TextStyle( color: AppColors.mainTextColor3, fontSize: 12, ); return SideTitleWidget( meta: meta, child: Text('\$ ${value + 0.5}', style: style), ); } @override Widget build(BuildContext context) { const cutOffYValue = 5.0; return AspectRatio( aspectRatio: 2, child: Padding( padding: const EdgeInsets.only( left: 12, right: 28, top: 22, bottom: 12, ), child: LineChart( LineChartData( lineTouchData: const LineTouchData(enabled: false), lineBarsData: [ LineChartBarData( spots: const [ FlSpot(0, 4), FlSpot(1, 3.5), FlSpot(2, 4.5), FlSpot(3, 1), FlSpot(4, 4), FlSpot(5, 6), FlSpot(6, 6.5), FlSpot(7, 6), FlSpot(8, 4), FlSpot(9, 6), FlSpot(10, 6), FlSpot(11, 7), ], isCurved: true, barWidth: 8, color: mainLineColor, belowBarData: BarAreaData( show: true, color: belowLineColor, cutOffY: cutOffYValue, applyCutOffY: true, ), aboveBarData: BarAreaData( show: true, color: aboveLineColor, cutOffY: cutOffYValue, applyCutOffY: true, ), dotData: const FlDotData( show: false, ), ), ], minY: 0, titlesData: FlTitlesData( show: true, topTitles: const AxisTitles( sideTitles: SideTitles(showTitles: false), ), rightTitles: const AxisTitles( sideTitles: SideTitles(showTitles: false), ), bottomTitles: AxisTitles( axisNameWidget: Text( '2019', style: TextStyle( fontSize: 10, color: mainLineColor, fontWeight: FontWeight.bold, ), ), sideTitles: SideTitles( showTitles: true, reservedSize: 18, interval: 1, getTitlesWidget: bottomTitleWidgets, ), ), leftTitles: AxisTitles( axisNameSize: 20, axisNameWidget: const Text( 'Value', style: TextStyle( color: AppColors.mainTextColor2, ), ), sideTitles: SideTitles( showTitles: true, interval: 1, reservedSize: 40, getTitlesWidget: leftTitleWidgets, ), ), ), borderData: FlBorderData( show: true, border: Border.all( color: AppColors.borderColor, ), ), gridData: FlGridData( show: true, drawVerticalLine: false, horizontalInterval: 1, checkToShowHorizontalLine: (double value) { return value == 1 || value == 6 || value == 4 || value == 5; }, ), ), ), ), ); } } ================================================ FILE: example/lib/presentation/samples/line/line_chart_sample5.dart ================================================ import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart_app/presentation/resources/app_resources.dart'; import 'package:flutter/material.dart'; class LineChartSample5 extends StatefulWidget { const LineChartSample5({ super.key, Color? gradientColor1, Color? gradientColor2, Color? gradientColor3, Color? indicatorStrokeColor, }) : gradientColor1 = gradientColor1 ?? AppColors.contentColorBlue, gradientColor2 = gradientColor2 ?? AppColors.contentColorPink, gradientColor3 = gradientColor3 ?? AppColors.contentColorRed, indicatorStrokeColor = indicatorStrokeColor ?? AppColors.mainTextColor1; final Color gradientColor1; final Color gradientColor2; final Color gradientColor3; final Color indicatorStrokeColor; @override State createState() => _LineChartSample5State(); } class _LineChartSample5State extends State { List showingTooltipOnSpots = [1, 3, 5]; List get allSpots => const [ FlSpot(0, 1), FlSpot(1, 2), FlSpot(2, 1.5), FlSpot(3, 3), FlSpot(4, 3.5), FlSpot(5, 5), FlSpot(6, 8), ]; Widget bottomTitleWidgets(double value, TitleMeta meta, double chartWidth) { final style = TextStyle( fontWeight: FontWeight.bold, color: AppColors.contentColorPink, fontFamily: 'Digital', fontSize: 18 * chartWidth / 500, ); String text = switch (value.toInt()) { 0 => '00:00', 1 => '04:00', 2 => '08:00', 3 => '12:00', 4 => '16:00', 5 => '20:00', 6 => '23:59', _ => '', }; if (text.isEmpty) { return const SizedBox.shrink(); } return SideTitleWidget( meta: meta, child: Text(text, style: style), ); } @override Widget build(BuildContext context) { final lineBarsData = [ LineChartBarData( showingIndicators: showingTooltipOnSpots, spots: allSpots, isCurved: true, barWidth: 4, shadow: const Shadow( blurRadius: 8, ), belowBarData: BarAreaData( show: true, gradient: LinearGradient( colors: [ widget.gradientColor1.withValues(alpha: 0.4), widget.gradientColor2.withValues(alpha: 0.4), widget.gradientColor3.withValues(alpha: 0.4), ], ), ), dotData: const FlDotData(show: false), gradient: LinearGradient( colors: [ widget.gradientColor1, widget.gradientColor2, widget.gradientColor3, ], stops: const [0.1, 0.4, 0.9], ), ), ]; final tooltipsOnBar = lineBarsData[0]; return AspectRatio( aspectRatio: 2.5, child: Padding( padding: const EdgeInsets.symmetric( horizontal: 24.0, vertical: 10, ), child: LayoutBuilder(builder: (context, constraints) { return LineChart( LineChartData( showingTooltipIndicators: showingTooltipOnSpots.map((index) { return ShowingTooltipIndicators([ LineBarSpot( tooltipsOnBar, lineBarsData.indexOf(tooltipsOnBar), tooltipsOnBar.spots[index], ), ]); }).toList(), lineTouchData: LineTouchData( enabled: true, handleBuiltInTouches: false, touchCallback: (FlTouchEvent event, LineTouchResponse? response) { if (response == null || response.lineBarSpots == null) { return; } if (event is FlTapUpEvent) { final spotIndex = response.lineBarSpots!.first.spotIndex; setState(() { if (showingTooltipOnSpots.contains(spotIndex)) { showingTooltipOnSpots.remove(spotIndex); } else { showingTooltipOnSpots.add(spotIndex); } }); } }, mouseCursorResolver: (FlTouchEvent event, LineTouchResponse? response) { if (response == null || response.lineBarSpots == null) { return SystemMouseCursors.basic; } return SystemMouseCursors.click; }, getTouchedSpotIndicator: (LineChartBarData barData, List spotIndexes) { return spotIndexes.map((index) { return TouchedSpotIndicatorData( const FlLine( color: Colors.pink, ), FlDotData( show: true, getDotPainter: (spot, percent, barData, index) => FlDotCirclePainter( radius: 8, color: lerpGradient( barData.gradient!.colors, barData.gradient!.stops!, percent / 100, ), strokeWidth: 2, strokeColor: widget.indicatorStrokeColor, ), ), ); }).toList(); }, touchTooltipData: LineTouchTooltipData( getTooltipColor: (touchedSpot) => Colors.pink, tooltipBorderRadius: BorderRadius.circular(8), getTooltipItems: (List lineBarsSpot) { return lineBarsSpot.map((lineBarSpot) { return LineTooltipItem( lineBarSpot.y.toString(), const TextStyle( color: Colors.white, fontWeight: FontWeight.bold, ), ); }).toList(); }, ), ), lineBarsData: lineBarsData, minY: 0, titlesData: FlTitlesData( leftTitles: const AxisTitles( axisNameWidget: Text('count'), axisNameSize: 24, sideTitles: SideTitles( showTitles: false, reservedSize: 0, ), ), bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, interval: 1, getTitlesWidget: (value, meta) { return bottomTitleWidgets( value, meta, constraints.maxWidth, ); }, reservedSize: 30, ), ), rightTitles: const AxisTitles( axisNameWidget: Text('count'), sideTitles: SideTitles( showTitles: false, reservedSize: 0, ), ), topTitles: const AxisTitles( axisNameWidget: Text( 'Wall clock', textAlign: TextAlign.left, ), axisNameSize: 24, sideTitles: SideTitles( showTitles: true, reservedSize: 0, ), ), ), gridData: const FlGridData(show: false), borderData: FlBorderData( show: true, border: Border.all( color: AppColors.borderColor, ), ), ), ); }), ), ); } } /// Lerps between a [LinearGradient] colors, based on [t] Color lerpGradient(List colors, List stops, double t) { if (colors.isEmpty) { throw ArgumentError('"colors" is empty.'); } else if (colors.length == 1) { return colors[0]; } if (stops.length != colors.length) { stops = []; /// provided gradientColorStops is invalid and we calculate it here colors.asMap().forEach((index, color) { final percent = 1.0 / (colors.length - 1); stops.add(percent * index); }); } for (var s = 0; s < stops.length - 1; s++) { final leftStop = stops[s]; final rightStop = stops[s + 1]; final leftColor = colors[s]; final rightColor = colors[s + 1]; if (t <= leftStop) { return leftColor; } else if (t < rightStop) { final sectionT = (t - leftStop) / (rightStop - leftStop); return Color.lerp(leftColor, rightColor, sectionT)!; } } return colors.last; } ================================================ FILE: example/lib/presentation/samples/line/line_chart_sample6.dart ================================================ import 'package:fl_chart_app/presentation/resources/app_resources.dart'; import 'package:fl_chart_app/util/extensions/color_extensions.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; // ignore: must_be_immutable class LineChartSample6 extends StatelessWidget { LineChartSample6({ super.key, Color? line1Color1, Color? line1Color2, Color? line2Color1, Color? line2Color2, }) : line1Color1 = line1Color1 ?? AppColors.contentColorOrange, line1Color2 = line1Color2 ?? AppColors.contentColorOrange.darken(60), line2Color1 = line2Color1 ?? AppColors.contentColorBlue.darken(60), line2Color2 = line2Color2 ?? AppColors.contentColorBlue { minSpotX = spots.first.x; maxSpotX = spots.first.x; minSpotY = spots.first.y; maxSpotY = spots.first.y; for (final spot in spots) { if (spot.x > maxSpotX) { maxSpotX = spot.x; } if (spot.x < minSpotX) { minSpotX = spot.x; } if (spot.y > maxSpotY) { maxSpotY = spot.y; } if (spot.y < minSpotY) { minSpotY = spot.y; } } } final Color line1Color1; final Color line1Color2; final Color line2Color1; final Color line2Color2; final spots = const [ FlSpot(0, 1), FlSpot(2, 5), FlSpot(4, 3), FlSpot(6, 5), ]; final spots2 = const [ FlSpot(0, 3), FlSpot(2, 1), FlSpot(4, 2), FlSpot(6, 1), ]; late double minSpotX; late double maxSpotX; late double minSpotY; late double maxSpotY; Widget leftTitleWidgets(double value, TitleMeta meta) { final style = TextStyle( color: line1Color1, fontWeight: FontWeight.bold, fontSize: 18, ); final intValue = reverseY(value, minSpotY, maxSpotY).toInt(); if (intValue == (maxSpotY + minSpotY)) { return Text('', style: style); } return Padding( padding: const EdgeInsets.only(right: 6), child: Text( intValue.toString(), style: style, textAlign: TextAlign.center, ), ); } Widget rightTitleWidgets(double value, TitleMeta meta) { final style = TextStyle( color: line2Color2, fontWeight: FontWeight.bold, fontSize: 18, ); final intValue = reverseY(value, minSpotY, maxSpotY).toInt(); if (intValue == (maxSpotY + minSpotY)) { return Text('', style: style); } return Text(intValue.toString(), style: style, textAlign: TextAlign.right); } Widget topTitleWidgets(double value, TitleMeta meta) { if (value % 1 != 0) { return Container(); } const style = TextStyle( fontWeight: FontWeight.bold, color: AppColors.mainTextColor2, ); return SideTitleWidget( meta: meta, child: Text(value.toInt().toString(), style: style), ); } @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.only(right: 22, bottom: 20), child: AspectRatio( aspectRatio: 2, child: LineChart( LineChartData( lineTouchData: LineTouchData( touchTooltipData: LineTouchTooltipData( tooltipBorderRadius: BorderRadius.zero, getTooltipColor: (spot) => Colors.white, getTooltipItems: (List touchedSpots) { return touchedSpots.map((LineBarSpot touchedSpot) { return LineTooltipItem( touchedSpot.y.toString(), TextStyle( color: touchedSpot.bar.gradient!.colors.first, fontWeight: FontWeight.bold, fontSize: 20, ), ); }).toList(); }, ), getTouchedSpotIndicator: ( _, indicators, ) { return indicators .map((int index) => const TouchedSpotIndicatorData( FlLine(color: Colors.transparent), FlDotData(show: false), )) .toList(); }, touchSpotThreshold: 12, distanceCalculator: (Offset touchPoint, Offset spotPixelCoordinates) => (touchPoint - spotPixelCoordinates).distance, ), lineBarsData: [ LineChartBarData( gradient: LinearGradient( colors: [ line1Color1, line1Color2, ], ), spots: reverseSpots(spots, minSpotY, maxSpotY), isCurved: true, isStrokeCapRound: true, barWidth: 10, belowBarData: BarAreaData( show: false, ), dotData: FlDotData( show: true, getDotPainter: (spot, percent, barData, index) { return FlDotCirclePainter( radius: 12, color: Color.lerp( line1Color1, line1Color2, percent / 100, )!, strokeColor: Colors.white, strokeWidth: 1, ); }, ), ), LineChartBarData( gradient: LinearGradient( colors: [ line2Color1, line2Color2, ], ), spots: reverseSpots(spots2, minSpotY, maxSpotY), isCurved: true, isStrokeCapRound: true, barWidth: 10, belowBarData: BarAreaData( show: false, ), dotData: FlDotData( show: true, getDotPainter: (spot, percent, barData, index) { return FlDotCirclePainter( radius: 12, color: Color.lerp( line2Color1, line2Color2, percent / 100, )!, strokeColor: Colors.white, strokeWidth: 1, ); }, ), ), ], minY: 0, maxY: maxSpotY + minSpotY, titlesData: FlTitlesData( leftTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, getTitlesWidget: leftTitleWidgets, reservedSize: 38, ), ), rightTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, getTitlesWidget: rightTitleWidgets, reservedSize: 30, ), ), bottomTitles: const AxisTitles( sideTitles: SideTitles(showTitles: false), ), topTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: 32, getTitlesWidget: topTitleWidgets, ), ), ), gridData: FlGridData( show: true, drawVerticalLine: true, checkToShowHorizontalLine: (value) { final intValue = reverseY(value, minSpotY, maxSpotY).toInt(); if (intValue == (maxSpotY + minSpotY).toInt()) { return false; } return true; }, ), borderData: FlBorderData( show: true, border: const Border( left: BorderSide(color: AppColors.borderColor), top: BorderSide(color: AppColors.borderColor), bottom: BorderSide(color: Colors.transparent), right: BorderSide(color: Colors.transparent), ), ), ), ), ), ); } double reverseY(double y, double minX, double maxX) { return (maxX + minX) - y; } List reverseSpots(List inputSpots, double minY, double maxY) { return inputSpots.map((spot) { return spot.copyWith(y: (maxY + minY) - spot.y); }).toList(); } } ================================================ FILE: example/lib/presentation/samples/line/line_chart_sample7.dart ================================================ import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart_app/presentation/resources/app_resources.dart'; import 'package:flutter/material.dart'; class LineChartSample7 extends StatelessWidget { LineChartSample7({ super.key, Color? line1Color, Color? line2Color, Color? betweenColor, }) : line1Color = line1Color ?? AppColors.contentColorGreen, line2Color = line2Color ?? AppColors.contentColorRed, betweenColor = betweenColor ?? AppColors.contentColorRed.withValues(alpha: 0.5); final Color line1Color; final Color line2Color; final Color betweenColor; Widget bottomTitleWidgets(double value, TitleMeta meta) { const style = TextStyle( fontSize: 10, fontWeight: FontWeight.bold, ); String text = switch (value.toInt()) { 0 => 'Jan', 1 => 'Feb', 2 => 'Mar', 3 => 'Apr', 4 => 'May', 5 => 'Jun', 6 => 'Jul', 7 => 'Aug', 8 => 'Sep', 9 => 'Oct', 10 => 'Nov', 11 => 'Dec', _ => '', }; return SideTitleWidget( meta: meta, space: 4, child: Text(text, style: style), ); } Widget leftTitleWidgets(double value, TitleMeta meta) { const style = TextStyle(fontSize: 10); return SideTitleWidget( meta: meta, child: Text( '\$ ${value + 0.5}', style: style, ), ); } @override Widget build(BuildContext context) { return AspectRatio( aspectRatio: 2, child: Padding( padding: const EdgeInsets.only( left: 10, right: 18, top: 10, bottom: 4, ), child: LineChart( LineChartData( lineTouchData: const LineTouchData(enabled: false), lineBarsData: [ LineChartBarData( spots: const [ FlSpot(0, 4), FlSpot(1, 3.5), FlSpot(2, 4.5), FlSpot(3, 1), FlSpot(4, 4), FlSpot(5, 6), FlSpot(6, 6.5), FlSpot(7, 6), FlSpot(8, 4), FlSpot(9, 6), FlSpot(10, 6), FlSpot(11, 7), ], isCurved: true, barWidth: 2, color: line1Color, dotData: const FlDotData( show: false, ), ), LineChartBarData( spots: const [ FlSpot(0, 7), FlSpot(1, 3), FlSpot(2, 4), FlSpot(3, 2), FlSpot(4, 3), FlSpot(5, 4), FlSpot(6, 5), FlSpot(7, 3), FlSpot(8, 1), FlSpot(9, 8), FlSpot(10, 1), FlSpot(11, 3), ], isCurved: false, barWidth: 2, color: line2Color, dotData: const FlDotData( show: false, ), ), ], betweenBarsData: [ BetweenBarsData( fromIndex: 0, toIndex: 1, color: betweenColor, ) ], minY: 0, borderData: FlBorderData( show: false, ), titlesData: FlTitlesData( bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, interval: 1, getTitlesWidget: bottomTitleWidgets, ), ), leftTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, getTitlesWidget: leftTitleWidgets, interval: 1, reservedSize: 36, ), ), topTitles: const AxisTitles( sideTitles: SideTitles(showTitles: false), ), rightTitles: const AxisTitles( sideTitles: SideTitles(showTitles: false), ), ), gridData: FlGridData( show: true, drawVerticalLine: false, horizontalInterval: 1, checkToShowHorizontalLine: (double value) { return value == 1 || value == 6 || value == 4 || value == 5; }, ), ), ), ), ); } } ================================================ FILE: example/lib/presentation/samples/line/line_chart_sample8.dart ================================================ import 'dart:async'; import 'dart:ui' as ui; import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart_app/presentation/resources/app_resources.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart' show rootBundle; import 'package:flutter_svg/flutter_svg.dart'; class LineChartSample8 extends StatefulWidget { const LineChartSample8({super.key}); @override State createState() => _LineChartSample8State(); } class _LineChartSample8State extends State { List gradientColors = const [ Color(0xffEEF3FE), Color(0xffEEF3FE), ]; bool showAvg = false; Future loadImage(String asset) async { final data = await rootBundle.load(asset); final codec = await ui.instantiateImageCodec(data.buffer.asUint8List()); final fi = await codec.getNextFrame(); return fi.image; } Future loadSvg() async { const rawSvg = ''; final pictureInfo = await vg.loadPicture(const SvgStringLoader(rawSvg), null); final sizedPicture = SizedPicture(pictureInfo.picture, 14, 14); return sizedPicture; } @override Widget build(BuildContext context) { return FutureBuilder( future: loadSvg(), builder: (BuildContext context, imageSnapshot) { if (imageSnapshot.connectionState == ConnectionState.done) { return Stack( children: [ AspectRatio( aspectRatio: 1.70, child: Padding( padding: const EdgeInsets.only( right: 18, left: 12, top: 24, bottom: 12, ), child: LineChart( mainData(imageSnapshot.data!), ), ), ), ], ); } else { return const CircularProgressIndicator(); } }, ); } Widget bottomTitleWidgets(double value, TitleMeta meta) { const style = TextStyle( fontSize: 10, color: AppColors.mainTextColor1, ); return SideTitleWidget( meta: meta, child: Text(meta.formattedValue, style: style), ); } Widget leftTitleWidgets(double value, TitleMeta meta) { IconData icon; Color color; switch (value.toInt()) { case 0: icon = Icons.wb_sunny; color = AppColors.contentColorYellow; break; case 2: icon = Icons.wine_bar_sharp; color = AppColors.contentColorRed; break; case 4: icon = Icons.watch_later; color = AppColors.contentColorGreen; break; case 6: icon = Icons.whatshot; color = AppColors.contentColorOrange; break; default: throw StateError('Invalid'); } return SideTitleWidget( meta: meta, child: Icon( icon, color: color, size: 32, ), ); } LineChartData mainData(SizedPicture sizedPicture) { return LineChartData( rangeAnnotations: RangeAnnotations( verticalRangeAnnotations: [ VerticalRangeAnnotation( x1: 2, x2: 5, color: AppColors.contentColorBlue.withValues(alpha: 0.2), ), VerticalRangeAnnotation( x1: 8, x2: 9, color: AppColors.contentColorBlue.withValues(alpha: 0.2), ), ], horizontalRangeAnnotations: [ HorizontalRangeAnnotation( y1: 2, y2: 3, color: AppColors.contentColorGreen.withValues(alpha: 0.3), ), ], ), // uncomment to see ExtraLines with RangeAnnotations extraLinesData: ExtraLinesData( // extraLinesOnTop: true, horizontalLines: [ HorizontalLine( y: 5, color: AppColors.contentColorGreen, strokeWidth: 2, dashArray: [5, 10], label: HorizontalLineLabel( show: true, alignment: Alignment.topRight, padding: const EdgeInsets.only(right: 5, bottom: 5), style: const TextStyle( fontSize: 9, fontWeight: FontWeight.bold, ), labelResolver: (line) => 'H: ${line.y}', ), ), ], verticalLines: [ VerticalLine( x: 5.7, color: AppColors.contentColorBlue, strokeWidth: 2, dashArray: [5, 10], label: VerticalLineLabel( show: true, alignment: Alignment.bottomRight, padding: const EdgeInsets.only(left: 5, bottom: 5), style: const TextStyle( fontSize: 9, fontWeight: FontWeight.bold, ), direction: LabelDirection.vertical, labelResolver: (line) => 'V: ${line.x}', ), ), VerticalLine( x: 8.5, color: Colors.transparent, sizedPicture: sizedPicture, ), VerticalLine( x: 3.5, color: Colors.transparent, sizedPicture: sizedPicture, ) ], ), gridData: const FlGridData( show: true, drawVerticalLine: false, drawHorizontalLine: false, verticalInterval: 1, ), titlesData: FlTitlesData( show: true, bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: 30, getTitlesWidget: bottomTitleWidgets, interval: 4, ), ), leftTitles: AxisTitles( drawBelowEverything: true, sideTitles: SideTitles( interval: 2, showTitles: true, getTitlesWidget: leftTitleWidgets, reservedSize: 40, ), ), rightTitles: const AxisTitles( sideTitles: SideTitles(showTitles: false), ), topTitles: const AxisTitles( sideTitles: SideTitles(showTitles: false), ), ), lineTouchData: LineTouchData( getTouchLineEnd: (data, index) => double.infinity, getTouchedSpotIndicator: (LineChartBarData barData, List spotIndexes) { return spotIndexes.map((spotIndex) { return TouchedSpotIndicatorData( const FlLine(color: AppColors.contentColorOrange, strokeWidth: 3), FlDotData( getDotPainter: (spot, percent, barData, index) => FlDotCirclePainter( radius: 8, color: AppColors.contentColorOrange, ), ), ); }).toList(); }, touchTooltipData: LineTouchTooltipData( getTooltipColor: (touchedSpot) => AppColors.contentColorRed, getTooltipItems: (List touchedSpots) => touchedSpots .map((LineBarSpot touchedSpot) => LineTooltipItem( touchedSpot.y.toString(), const TextStyle( color: AppColors.contentColorWhite, fontWeight: FontWeight.bold, fontSize: 16, ), )) .toList(), ), ), borderData: FlBorderData( show: true, border: Border.all( color: AppColors.borderColor, ), ), minX: 0, maxX: 11, minY: 0, maxY: 6, lineBarsData: [ LineChartBarData( spots: const [ FlSpot(0, 1), FlSpot(2, 1), FlSpot(4.9, 5), FlSpot(6.8, 5), FlSpot(7.5, 3.5), FlSpot.nullSpot, FlSpot(7.5, 2), FlSpot(8, 1), FlSpot(10, 2), FlSpot(11, 2.5), ], dashArray: [10, 6], isCurved: true, color: AppColors.contentColorRed, barWidth: 4, isStrokeCapRound: true, dotData: const FlDotData( show: false, ), ), ], ); } } ================================================ FILE: example/lib/presentation/samples/line/line_chart_sample9.dart ================================================ import 'dart:math'; import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart_app/presentation/resources/app_resources.dart'; import 'package:flutter/material.dart'; // ignore: must_be_immutable class LineChartSample9 extends StatelessWidget { LineChartSample9({super.key}); final spots = List.generate(101, (i) => (i - 50) / 10) .map((x) => FlSpot(x, cos(x))) .toList(); Widget bottomTitleWidgets(double value, TitleMeta meta, double chartWidth) { if (value % 1 != 0) { return Container(); } final style = TextStyle( color: AppColors.contentColorBlue, fontWeight: FontWeight.bold, fontSize: min(18, 18 * chartWidth / 300), ); return SideTitleWidget( meta: meta, space: 16, child: Text(meta.formattedValue, style: style), ); } Widget leftTitleWidgets(double value, TitleMeta meta, double chartWidth) { final style = TextStyle( color: AppColors.contentColorYellow, fontWeight: FontWeight.bold, fontSize: min(18, 18 * chartWidth / 300), ); return SideTitleWidget( meta: meta, space: 16, child: Text(meta.formattedValue, style: style), ); } @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.only( left: 12, bottom: 12, right: 20, top: 20, ), child: AspectRatio( aspectRatio: 1, child: LayoutBuilder( builder: (context, constraints) { return LineChart( LineChartData( lineTouchData: LineTouchData( touchTooltipData: LineTouchTooltipData( maxContentWidth: 100, getTooltipColor: (touchedSpot) => Colors.black, getTooltipItems: (touchedSpots) { return touchedSpots.map((LineBarSpot touchedSpot) { final textStyle = TextStyle( color: touchedSpot.bar.gradient?.colors[0] ?? touchedSpot.bar.color, fontWeight: FontWeight.bold, fontSize: 14, ); return LineTooltipItem( '${touchedSpot.x}, ${touchedSpot.y.toStringAsFixed(2)}', textStyle, ); }).toList(); }, ), handleBuiltInTouches: true, getTouchLineStart: (data, index) => 0, ), lineBarsData: [ LineChartBarData( color: AppColors.contentColorPink, spots: spots, isCurved: true, isStrokeCapRound: true, barWidth: 3, belowBarData: BarAreaData( show: false, ), dotData: const FlDotData(show: false), ), ], minY: -1.5, maxY: 1.5, titlesData: FlTitlesData( leftTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, getTitlesWidget: (value, meta) => leftTitleWidgets(value, meta, constraints.maxWidth), reservedSize: 56, ), drawBelowEverything: true, ), rightTitles: const AxisTitles( sideTitles: SideTitles(showTitles: false), ), bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, getTitlesWidget: (value, meta) => bottomTitleWidgets(value, meta, constraints.maxWidth), reservedSize: 36, interval: 1, ), drawBelowEverything: true, ), topTitles: const AxisTitles( sideTitles: SideTitles(showTitles: false), ), ), gridData: FlGridData( show: true, drawHorizontalLine: true, drawVerticalLine: true, horizontalInterval: 1.5, verticalInterval: 5, checkToShowHorizontalLine: (value) { return value.toInt() == 0; }, getDrawingHorizontalLine: (_) => FlLine( color: AppColors.contentColorBlue.withValues(alpha: 1), dashArray: [8, 2], strokeWidth: 0.8, ), getDrawingVerticalLine: (_) => FlLine( color: AppColors.contentColorYellow.withValues(alpha: 1), dashArray: [8, 2], strokeWidth: 0.8, ), checkToShowVerticalLine: (value) { return value.toInt() == 0; }, ), borderData: FlBorderData(show: false), ), ); }, ), ), ); } } ================================================ FILE: example/lib/presentation/samples/pie/pie_chart_sample1.dart ================================================ import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart_app/presentation/resources/app_resources.dart'; import 'package:fl_chart_app/presentation/widgets/indicator.dart'; import 'package:flutter/material.dart'; class PieChartSample1 extends StatefulWidget { const PieChartSample1({super.key}); @override State createState() => PieChartSample1State(); } class PieChartSample1State extends State { int touchedIndex = -1; @override Widget build(BuildContext context) { return AspectRatio( aspectRatio: 1.3, child: Column( children: [ const SizedBox( height: 28, ), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Indicator( color: AppColors.contentColorBlue, text: 'One', isSquare: false, size: touchedIndex == 0 ? 18 : 16, textColor: touchedIndex == 0 ? AppColors.mainTextColor1 : AppColors.mainTextColor3, ), Indicator( color: AppColors.contentColorYellow, text: 'Two', isSquare: false, size: touchedIndex == 1 ? 18 : 16, textColor: touchedIndex == 1 ? AppColors.mainTextColor1 : AppColors.mainTextColor3, ), Indicator( color: AppColors.contentColorPink, text: 'Three', isSquare: false, size: touchedIndex == 2 ? 18 : 16, textColor: touchedIndex == 2 ? AppColors.mainTextColor1 : AppColors.mainTextColor3, ), Indicator( color: AppColors.contentColorGreen, text: 'Four', isSquare: false, size: touchedIndex == 3 ? 18 : 16, textColor: touchedIndex == 3 ? AppColors.mainTextColor1 : AppColors.mainTextColor3, ), ], ), const SizedBox( height: 18, ), Expanded( child: AspectRatio( aspectRatio: 1, child: PieChart( PieChartData( pieTouchData: PieTouchData( touchCallback: (FlTouchEvent event, pieTouchResponse) { setState(() { if (!event.isInterestedForInteractions || pieTouchResponse == null || pieTouchResponse.touchedSection == null) { touchedIndex = -1; return; } touchedIndex = pieTouchResponse .touchedSection!.touchedSectionIndex; }); }, ), startDegreeOffset: 180, borderData: FlBorderData( show: false, ), sectionsSpace: 1, centerSpaceRadius: 0, sections: showingSections(), ), ), ), ), ], ), ); } List showingSections() { final colors = [ AppColors.contentColorBlue, AppColors.contentColorYellow, AppColors.contentColorPink, AppColors.contentColorGreen, ]; final radiusValues = [80, 65, 60, 70]; //This can be removed because no title is being displayed in the first place final titlePositionPercentageOffsets = [.55, .55, .6, .55]; return List.generate( 4, (i) { final isTouched = i == touchedIndex; return PieChartSectionData( color: colors[i], value: 25, title: '', radius: radiusValues[i], titlePositionPercentageOffset: titlePositionPercentageOffsets[i], borderSide: isTouched ? const BorderSide(color: AppColors.contentColorWhite, width: 6) : BorderSide( color: AppColors.contentColorWhite.withValues(alpha: 0)), ); }, ); } } ================================================ FILE: example/lib/presentation/samples/pie/pie_chart_sample2.dart ================================================ import 'package:fl_chart_app/presentation/resources/app_resources.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart_app/presentation/widgets/indicator.dart'; import 'package:flutter/material.dart'; class PieChartSample2 extends StatefulWidget { const PieChartSample2({super.key}); @override State createState() => PieChart2State(); } class PieChart2State extends State { int touchedIndex = -1; @override Widget build(BuildContext context) { return AspectRatio( aspectRatio: 1.3, child: Row( children: [ const SizedBox( height: 18, ), Expanded( child: AspectRatio( aspectRatio: 1, child: PieChart( PieChartData( pieTouchData: PieTouchData( touchCallback: (FlTouchEvent event, pieTouchResponse) { setState(() { if (!event.isInterestedForInteractions || pieTouchResponse == null || pieTouchResponse.touchedSection == null) { touchedIndex = -1; return; } touchedIndex = pieTouchResponse .touchedSection!.touchedSectionIndex; }); }, ), borderData: FlBorderData( show: false, ), sectionsSpace: 0, centerSpaceRadius: 40, sections: showingSections(), ), ), ), ), const Column( mainAxisAlignment: MainAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.start, children: [ Indicator( color: AppColors.contentColorBlue, text: 'First', isSquare: true, ), SizedBox( height: 4, ), Indicator( color: AppColors.contentColorYellow, text: 'Second', isSquare: true, ), SizedBox( height: 4, ), Indicator( color: AppColors.contentColorPurple, text: 'Third', isSquare: true, ), SizedBox( height: 4, ), Indicator( color: AppColors.contentColorGreen, text: 'Fourth', isSquare: true, ), SizedBox( height: 18, ), ], ), const SizedBox( width: 28, ), ], ), ); } List showingSections() { return List.generate(4, (i) { final isTouched = i == touchedIndex; final fontSize = isTouched ? 25.0 : 16.0; final radius = isTouched ? 60.0 : 50.0; const shadows = [Shadow(color: Colors.black, blurRadius: 2)]; return switch (i) { 0 => PieChartSectionData( color: AppColors.contentColorBlue, value: 40, title: '40%', radius: radius, titleStyle: TextStyle( fontSize: fontSize, fontWeight: FontWeight.bold, color: AppColors.mainTextColor1, shadows: shadows, ), ), 1 => PieChartSectionData( color: AppColors.contentColorYellow, value: 30, title: '30%', radius: radius, titleStyle: TextStyle( fontSize: fontSize, fontWeight: FontWeight.bold, color: AppColors.mainTextColor1, shadows: shadows, ), ), 2 => PieChartSectionData( color: AppColors.contentColorPurple, value: 15, title: '15%', radius: radius, titleStyle: TextStyle( fontSize: fontSize, fontWeight: FontWeight.bold, color: AppColors.mainTextColor1, shadows: shadows, ), ), 3 => PieChartSectionData( color: AppColors.contentColorGreen, value: 15, title: '15%', radius: radius, titleStyle: TextStyle( fontSize: fontSize, fontWeight: FontWeight.bold, color: AppColors.mainTextColor1, shadows: shadows, ), ), _ => throw StateError('Invalid'), }; }); } } ================================================ FILE: example/lib/presentation/samples/pie/pie_chart_sample3.dart ================================================ import 'package:fl_chart_app/presentation/resources/app_resources.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; class PieChartSample3 extends StatefulWidget { const PieChartSample3({super.key}); @override State createState() => PieChartSample3State(); } class PieChartSample3State extends State { int touchedIndex = 0; @override Widget build(BuildContext context) { return AspectRatio( aspectRatio: 1.3, child: AspectRatio( aspectRatio: 1, child: PieChart( PieChartData( pieTouchData: PieTouchData( touchCallback: (FlTouchEvent event, pieTouchResponse) { setState(() { if (!event.isInterestedForInteractions || pieTouchResponse == null || pieTouchResponse.touchedSection == null) { touchedIndex = -1; return; } touchedIndex = pieTouchResponse.touchedSection!.touchedSectionIndex; }); }, ), borderData: FlBorderData( show: false, ), sectionsSpace: 0, centerSpaceRadius: 0, sections: showingSections(), ), ), ), ); } List showingSections() { return List.generate(4, (i) { final isTouched = i == touchedIndex; final fontSize = isTouched ? 20.0 : 16.0; final radius = isTouched ? 110.0 : 100.0; final widgetSize = isTouched ? 55.0 : 40.0; const shadows = [Shadow(color: Colors.black, blurRadius: 2)]; return switch (i) { 0 => PieChartSectionData( color: AppColors.contentColorBlue, value: 40, title: '40%', radius: radius, titleStyle: TextStyle( fontSize: fontSize, fontWeight: FontWeight.bold, color: const Color(0xffffffff), shadows: shadows, ), badgeWidget: _Badge( 'assets/icons/ophthalmology-svgrepo-com.svg', size: widgetSize, borderColor: AppColors.contentColorBlack, ), badgePositionPercentageOffset: .98, ), 1 => PieChartSectionData( color: AppColors.contentColorYellow, value: 30, title: '30%', radius: radius, titleStyle: TextStyle( fontSize: fontSize, fontWeight: FontWeight.bold, color: const Color(0xffffffff), shadows: shadows, ), badgeWidget: _Badge( 'assets/icons/librarian-svgrepo-com.svg', size: widgetSize, borderColor: AppColors.contentColorBlack, ), badgePositionPercentageOffset: .98, ), 2 => PieChartSectionData( color: AppColors.contentColorPurple, value: 16, title: '16%', radius: radius, titleStyle: TextStyle( fontSize: fontSize, fontWeight: FontWeight.bold, color: const Color(0xffffffff), shadows: shadows, ), badgeWidget: _Badge( 'assets/icons/fitness-svgrepo-com.svg', size: widgetSize, borderColor: AppColors.contentColorBlack, ), badgePositionPercentageOffset: .98, ), 3 => PieChartSectionData( color: AppColors.contentColorGreen, value: 15, title: '15%', radius: radius, titleStyle: TextStyle( fontSize: fontSize, fontWeight: FontWeight.bold, color: const Color(0xffffffff), shadows: shadows, ), badgeWidget: _Badge( 'assets/icons/worker-svgrepo-com.svg', size: widgetSize, borderColor: AppColors.contentColorBlack, ), badgePositionPercentageOffset: .98, ), _ => throw StateError('Invalid'), }; }); } } class _Badge extends StatelessWidget { const _Badge(this.svgAsset, { required this.size, required this.borderColor, }); final String svgAsset; final double size; final Color borderColor; @override Widget build(BuildContext context) { return AnimatedContainer( duration: PieChart.defaultDuration, width: size, height: size, decoration: BoxDecoration( color: Colors.white, shape: BoxShape.circle, border: Border.all( color: borderColor, width: 2, ), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: .5), offset: const Offset(3, 3), blurRadius: 3, ), ], ), padding: EdgeInsets.all(size * .15), child: Center( child: SvgPicture.asset( svgAsset, ), ), ); } } ================================================ FILE: example/lib/presentation/samples/radar/radar_chart_sample1.dart ================================================ import 'package:fl_chart_app/presentation/resources/app_resources.dart'; import 'package:fl_chart_app/util/extensions/color_extensions.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; class RadarChartSample1 extends StatefulWidget { RadarChartSample1({super.key}); final gridColor = AppColors.contentColorPurple.lighten(80); final titleColor = AppColors.contentColorPurple.lighten(80); final fashionColor = AppColors.contentColorRed; final artColor = AppColors.contentColorCyan; final boxingColor = AppColors.contentColorGreen; final entertainmentColor = AppColors.contentColorWhite; final offRoadColor = AppColors.contentColorYellow; @override State createState() => _RadarChartSample1State(); } class _RadarChartSample1State extends State { int selectedDataSetIndex = -1; double angleValue = 0; bool relativeAngleMode = true; @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ const Text( 'Title configuration', style: TextStyle( color: AppColors.mainTextColor2, ), ), Row( children: [ const Text( 'Angle', style: TextStyle( color: AppColors.mainTextColor2, ), ), Slider( value: angleValue, max: 360, onChanged: (double value) => setState(() => angleValue = value), ), ], ), Row( children: [ Checkbox( value: relativeAngleMode, onChanged: (v) => setState(() => relativeAngleMode = v!), ), const Text('Relative'), ], ), GestureDetector( onTap: () { setState(() { selectedDataSetIndex = -1; }); }, child: Text( 'Categories'.toUpperCase(), style: const TextStyle( fontSize: 32, fontWeight: FontWeight.w300, color: AppColors.mainTextColor1, ), ), ), const SizedBox(height: 4), Column( crossAxisAlignment: CrossAxisAlignment.start, children: rawDataSets() .asMap() .map((index, value) { final isSelected = index == selectedDataSetIndex; return MapEntry( index, GestureDetector( onTap: () { setState(() { selectedDataSetIndex = index; }); }, child: AnimatedContainer( duration: const Duration(milliseconds: 300), margin: const EdgeInsets.symmetric(vertical: 2), height: 26, decoration: BoxDecoration( color: isSelected ? AppColors.pageBackground : Colors.transparent, borderRadius: BorderRadius.circular(46), ), padding: const EdgeInsets.symmetric( vertical: 4, horizontal: 6, ), child: Row( mainAxisSize: MainAxisSize.min, children: [ AnimatedContainer( duration: const Duration(milliseconds: 400), curve: Curves.easeInToLinear, padding: EdgeInsets.all(isSelected ? 8 : 6), decoration: BoxDecoration( color: value.color, shape: BoxShape.circle, ), ), const SizedBox(width: 8), AnimatedDefaultTextStyle( duration: const Duration(milliseconds: 300), curve: Curves.easeInToLinear, style: TextStyle( color: isSelected ? value.color : widget.gridColor, ), child: Text(value.title), ), ], ), ), ), ); }) .values .toList(), ), AspectRatio( aspectRatio: 1.3, child: RadarChart( RadarChartData( radarTouchData: RadarTouchData( touchCallback: (FlTouchEvent event, response) { if (!event.isInterestedForInteractions) { setState(() { selectedDataSetIndex = -1; }); return; } setState(() { selectedDataSetIndex = response?.touchedSpot?.touchedDataSetIndex ?? -1; }); }, ), dataSets: showingDataSets(), radarBackgroundColor: Colors.transparent, borderData: FlBorderData(show: false), radarBorderData: const BorderSide(color: Colors.transparent), titlePositionPercentageOffset: 0.2, titleTextStyle: TextStyle(color: widget.titleColor, fontSize: 14), getTitle: (index, angle) { final usedAngle = relativeAngleMode ? angle + angleValue : angleValue; return switch (index) { 0 => RadarChartTitle( text: 'Mobile or Tablet', angle: usedAngle, ), 2 => RadarChartTitle( text: 'Desktop', angle: usedAngle, ), 1 => RadarChartTitle(text: 'TV', angle: usedAngle), _ => const RadarChartTitle(text: '', angle: 0), }; }, tickCount: 1, ticksTextStyle: const TextStyle(color: Colors.transparent, fontSize: 10), tickBorderData: const BorderSide(color: Colors.transparent), gridBorderData: BorderSide(color: widget.gridColor, width: 2), ), duration: const Duration(milliseconds: 400), ), ), ], ), ); } List showingDataSets() { return rawDataSets().asMap().entries.map((entry) { final index = entry.key; final rawDataSet = entry.value; final isSelected = index == selectedDataSetIndex ? true : selectedDataSetIndex == -1 ? true : false; return RadarDataSet( fillColor: isSelected ? rawDataSet.color.withValues(alpha: 0.2) : rawDataSet.color.withValues(alpha: 0.05), borderColor: isSelected ? rawDataSet.color : rawDataSet.color.withValues(alpha: 0.25), entryRadius: isSelected ? 3 : 2, dataEntries: rawDataSet.values.map((e) => RadarEntry(value: e)).toList(), borderWidth: isSelected ? 2.3 : 2, ); }).toList(); } List rawDataSets() { return [ RawDataSet( title: 'Fashion', color: widget.fashionColor, values: [ 300, 50, 250, ], ), RawDataSet( title: 'Art & Tech', color: widget.artColor, values: [ 250, 100, 200, ], ), RawDataSet( title: 'Entertainment', color: widget.entertainmentColor, values: [ 200, 150, 50, ], ), RawDataSet( title: 'Off-road Vehicle', color: widget.offRoadColor, values: [ 150, 200, 150, ], ), RawDataSet( title: 'Boxing', color: widget.boxingColor, values: [ 100, 250, 100, ], ), ]; } } class RawDataSet { RawDataSet({ required this.title, required this.color, required this.values, }); final String title; final Color color; final List values; } ================================================ FILE: example/lib/presentation/samples/scatter/scatter_chart_sample1.dart ================================================ import 'dart:math'; import 'package:fl_chart_app/presentation/resources/app_resources.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; class ScatterChartSample1 extends StatefulWidget { ScatterChartSample1({super.key}); final blue1 = AppColors.contentColorBlue.withValues(alpha: 0.5); final blue2 = AppColors.contentColorBlue; @override State createState() => ScatterChartSample1State(); } class ScatterChartSample1State extends State { final maxX = 50.0; final maxY = 50.0; final radius = 8.0; bool showFlutter = true; @override Widget build(BuildContext context) { return GestureDetector( onTap: () { setState(() { showFlutter = !showFlutter; }); }, child: AspectRatio( aspectRatio: 1, child: ScatterChart( ScatterChartData( scatterSpots: showFlutter ? flutterLogoData() : randomData(), minX: 0, maxX: maxX, minY: 0, maxY: maxY, borderData: FlBorderData( show: false, ), gridData: const FlGridData( show: false, ), titlesData: const FlTitlesData( show: false, ), scatterTouchData: ScatterTouchData( enabled: false, ), ), duration: const Duration(milliseconds: 600), curve: Curves.fastOutSlowIn, ), ), ); } List flutterLogoData() { return [ /// section 1 ScatterSpot( 20, 14.5, dotPainter: FlDotCirclePainter(color: widget.blue1, radius: radius), ), ScatterSpot( 20, 14.5, dotPainter: FlDotCirclePainter(color: widget.blue1, radius: radius), ), ScatterSpot( 22, 16.5, dotPainter: FlDotCirclePainter(color: widget.blue1, radius: radius), ), ScatterSpot( 24, 18.5, dotPainter: FlDotCirclePainter(color: widget.blue1, radius: radius), ), ScatterSpot( 22, 12.5, dotPainter: FlDotCirclePainter(color: widget.blue1, radius: radius), ), ScatterSpot( 24, 14.5, dotPainter: FlDotCirclePainter(color: widget.blue1, radius: radius), ), ScatterSpot( 26, 16.5, dotPainter: FlDotCirclePainter(color: widget.blue1, radius: radius), ), ScatterSpot( 24, 10.5, dotPainter: FlDotCirclePainter(color: widget.blue1, radius: radius), ), ScatterSpot( 26, 12.5, dotPainter: FlDotCirclePainter(color: widget.blue1, radius: radius), ), ScatterSpot( 28, 14.5, dotPainter: FlDotCirclePainter(color: widget.blue1, radius: radius), ), ScatterSpot( 26, 8.5, dotPainter: FlDotCirclePainter(color: widget.blue1, radius: radius), ), ScatterSpot( 28, 10.5, dotPainter: FlDotCirclePainter(color: widget.blue1, radius: radius), ), ScatterSpot( 30, 12.5, dotPainter: FlDotCirclePainter(color: widget.blue1, radius: radius), ), ScatterSpot( 28, 6.5, dotPainter: FlDotCirclePainter(color: widget.blue1, radius: radius), ), ScatterSpot( 30, 8.5, dotPainter: FlDotCirclePainter(color: widget.blue1, radius: radius), ), ScatterSpot( 32, 10.5, dotPainter: FlDotCirclePainter(color: widget.blue1, radius: radius), ), ScatterSpot( 30, 4.5, dotPainter: FlDotCirclePainter(color: widget.blue1, radius: radius), ), ScatterSpot( 32, 6.5, dotPainter: FlDotCirclePainter(color: widget.blue1, radius: radius), ), ScatterSpot( 34, 8.5, dotPainter: FlDotCirclePainter(color: widget.blue1, radius: radius), ), ScatterSpot( 34, 4.5, dotPainter: FlDotCirclePainter(color: widget.blue1, radius: radius), ), ScatterSpot( 36, 6.5, dotPainter: FlDotCirclePainter(color: widget.blue1, radius: radius), ), ScatterSpot( 38, 4.5, dotPainter: FlDotCirclePainter(color: widget.blue1, radius: radius), ), /// section 2 ScatterSpot( 20, 14.5, dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius), ), ScatterSpot( 22, 12.5, dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius), ), ScatterSpot( 24, 10.5, dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius), ), ScatterSpot( 22, 16.5, dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius), ), ScatterSpot( 24, 14.5, dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius), ), ScatterSpot( 26, 12.5, dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius), ), ScatterSpot( 24, 18.5, dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius), ), ScatterSpot( 26, 16.5, dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius), ), ScatterSpot( 28, 14.5, dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius), ), ScatterSpot( 26, 20.5, dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius), ), ScatterSpot( 28, 18.5, dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius), ), ScatterSpot( 30, 16.5, dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius), ), ScatterSpot( 28, 22.5, dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius), ), ScatterSpot( 30, 20.5, dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius), ), ScatterSpot( 32, 18.5, dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius), ), ScatterSpot( 30, 24.5, dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius), ), ScatterSpot( 32, 22.5, dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius), ), ScatterSpot( 34, 20.5, dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius), ), ScatterSpot( 34, 24.5, dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius), ), ScatterSpot( 36, 22.5, dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius), ), ScatterSpot( 38, 24.5, dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius), ), /// section 3 ScatterSpot( 10, 25, dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius), ), ScatterSpot( 12, 23, dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius), ), ScatterSpot( 14, 21, dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius), ), ScatterSpot( 12, 27, dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius), ), ScatterSpot( 14, 25, dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius), ), ScatterSpot( 16, 23, dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius), ), ScatterSpot( 14, 29, dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius), ), ScatterSpot( 16, 27, dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius), ), ScatterSpot( 18, 25, dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius), ), ScatterSpot( 16, 31, dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius), ), ScatterSpot( 18, 29, dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius), ), ScatterSpot( 20, 27, dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius), ), ScatterSpot( 18, 33, dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius), ), ScatterSpot( 20, 31, dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius), ), ScatterSpot( 22, 29, dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius), ), ScatterSpot( 20, 35, dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius), ), ScatterSpot( 22, 33, dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius), ), ScatterSpot( 24, 31, dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius), ), ScatterSpot( 22, 37, dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius), ), ScatterSpot( 24, 35, dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius), ), ScatterSpot( 26, 33, dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius), ), ScatterSpot( 24, 39, dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius), ), ScatterSpot( 26, 37, dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius), ), ScatterSpot( 28, 35, dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius), ), ScatterSpot( 26, 41, dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius), ), ScatterSpot( 28, 39, dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius), ), ScatterSpot( 30, 37, dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius), ), ScatterSpot( 28, 43, dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius), ), ScatterSpot( 30, 41, dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius), ), ScatterSpot( 32, 39, dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius), ), ScatterSpot( 30, 45, dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius), ), ScatterSpot( 32, 43, dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius), ), ScatterSpot( 34, 41, dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius), ), ScatterSpot( 34, 45, dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius), ), ScatterSpot( 36, 43, dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius), ), ScatterSpot( 38, 45, dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius), ), ]; } List randomData() { const blue1Count = 21; const blue2Count = 57; return List.generate(blue1Count + blue2Count, (i) { Color color; if (i < blue1Count) { color = widget.blue1; } else { color = widget.blue2; } return ScatterSpot( (Random().nextDouble() * (maxX - 8)) + 4, (Random().nextDouble() * (maxY - 8)) + 4, dotPainter: FlDotCirclePainter( color: color, radius: (Random().nextDouble() * 16) + 4, ), ); }); } } ================================================ FILE: example/lib/presentation/samples/scatter/scatter_chart_sample2.dart ================================================ import 'dart:math'; import 'package:fl_chart_app/presentation/resources/app_resources.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; class ScatterChartSample2 extends StatefulWidget { const ScatterChartSample2({super.key}); @override State createState() => _ScatterChartSample2State(); } class _ScatterChartSample2State extends State { int touchedIndex = -1; Color greyColor = Colors.grey; final _availableColors = [ AppColors.contentColorGreen, AppColors.contentColorYellow, AppColors.contentColorPink, AppColors.contentColorOrange, AppColors.contentColorPurple, AppColors.contentColorBlue, AppColors.contentColorRed, AppColors.contentColorCyan, AppColors.contentColorBlue, AppColors.contentColorGreen, AppColors.contentColorPink, ]; List selectedSpots = []; PainterType _currentPaintType = PainterType.circle; static FlDotPainter _getPaint(PainterType type, double size, Color color) { switch (type) { case PainterType.circle: return FlDotCirclePainter( color: color, radius: size, ); case PainterType.square: return FlDotSquarePainter( color: color, size: size * 2, strokeWidth: 0, ); case PainterType.cross: return FlDotCrossPainter( color: color, size: size * 2, width: max(size / 5, 2), ); } } @override Widget build(BuildContext context) { // (x, y, size) final data = [ (4.0, 4.0, 4.0), (2.0, 5.0, 12.0), (4.0, 5.0, 8.0), (8.0, 6.0, 20.0), (5.0, 7.0, 14.0), (7.0, 2.0, 18.0), (3.0, 2.0, 36.0), (2.0, 8.0, 22.0), (8.0, 8.0, 32.0), (5.0, 2.5, 24.0), (3.0, 7.0, 18.0), ]; return AspectRatio( aspectRatio: 1, child: Stack( children: [ ScatterChart( ScatterChartData( scatterSpots: data.asMap().entries.map((e) { final index = e.key; final (double x, double y, double size) = e.value; return ScatterSpot( x, y, dotPainter: _getPaint( _currentPaintType, size, selectedSpots.contains(index) ? _availableColors[index % _availableColors.length] : AppColors.contentColorWhite.withValues(alpha: 0.5), ), ); }).toList(), minX: 0, maxX: 10, minY: 0, maxY: 10, borderData: FlBorderData( show: false, ), gridData: FlGridData( show: true, drawHorizontalLine: true, checkToShowHorizontalLine: (value) => true, getDrawingHorizontalLine: (value) => const FlLine( color: AppColors.gridLinesColor, ), drawVerticalLine: true, checkToShowVerticalLine: (value) => true, getDrawingVerticalLine: (value) => const FlLine( color: AppColors.gridLinesColor, ), ), titlesData: const FlTitlesData( show: false, ), showingTooltipIndicators: selectedSpots, scatterTouchData: ScatterTouchData( enabled: true, handleBuiltInTouches: false, mouseCursorResolver: (FlTouchEvent touchEvent, ScatterTouchResponse? response) { return response == null || response.touchedSpot == null ? MouseCursor.defer : SystemMouseCursors.click; }, touchTooltipData: ScatterTouchTooltipData( getTooltipColor: (ScatterSpot touchedBarSpot) { return touchedBarSpot.dotPainter.mainColor; }, getTooltipItems: (ScatterSpot touchedBarSpot) { final bool isBgDark = switch ((touchedBarSpot.x, touchedBarSpot.y)) { (4.0, 4.0) => false, (2.0, 5.0) => false, (4.0, 5.0) => true, (8.0, 6.0) => true, (5.0, 7.0) => true, (7.0, 2.0) => true, (3.0, 2.0) => true, (2.0, 8.0) => false, (8.0, 8.0) => true, (5.0, 2.5) => false, (3.0, 7.0) => true, _ => false, }; final color1 = isBgDark ? Colors.grey[100] : Colors.black87; final color2 = isBgDark ? Colors.white : Colors.black; return ScatterTooltipItem( 'X: ', textStyle: TextStyle( height: 1.2, color: color1, fontStyle: FontStyle.italic, ), bottomMargin: 10, children: [ TextSpan( text: '${touchedBarSpot.x.toInt()} \n', style: TextStyle( color: color2, fontStyle: FontStyle.normal, fontWeight: FontWeight.bold, ), ), TextSpan( text: 'Y: ', style: TextStyle( height: 1.2, color: color1, fontStyle: FontStyle.italic, ), ), TextSpan( text: touchedBarSpot.y.toInt().toString(), style: TextStyle( color: color2, fontStyle: FontStyle.normal, fontWeight: FontWeight.bold, ), ), ], ); }, ), touchCallback: (FlTouchEvent event, ScatterTouchResponse? touchResponse) { if (touchResponse == null || touchResponse.touchedSpot == null) { return; } if (event is FlTapUpEvent) { final sectionIndex = touchResponse.touchedSpot!.spotIndex; setState(() { if (selectedSpots.contains(sectionIndex)) { selectedSpots.remove(sectionIndex); } else { selectedSpots.add(sectionIndex); } }); } }, ), ), ), Align( alignment: Alignment.topLeft, child: Padding( padding: const EdgeInsets.all(8.0), child: DropdownButton( value: _currentPaintType, items: PainterType.values .map((e) => DropdownMenuItem( value: e, child: Text(e.name), )) .toList(), onChanged: (PainterType? value) { setState(() { _currentPaintType = value!; }); }, ), ), ), ], ), ); } } enum PainterType { circle, square, cross, } ================================================ FILE: example/lib/presentation/widgets/chart_holder.dart ================================================ import 'package:fl_chart_app/presentation/resources/app_resources.dart'; import 'package:fl_chart_app/presentation/samples/chart_sample.dart'; import 'package:fl_chart_app/util/app_utils.dart'; import 'package:flutter/material.dart'; class ChartHolder extends StatelessWidget { final ChartSample chartSample; const ChartHolder({ super.key, required this.chartSample, }); @override Widget build(BuildContext context) { return Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Row( children: [ const SizedBox(width: 6), Text( chartSample.name, style: const TextStyle( color: AppColors.primary, fontSize: 18, fontWeight: FontWeight.bold, ), ), Expanded(child: Container()), IconButton( onPressed: () => AppUtils().tryToLaunchUrl(chartSample.url), icon: const Icon( Icons.code, color: AppColors.primary, ), tooltip: 'Source code', ), ], ), const SizedBox(height: 2), Container( decoration: const BoxDecoration( color: AppColors.itemsBackground, borderRadius: BorderRadius.all(Radius.circular(AppDimens.defaultRadius)), ), child: chartSample.builder(context), ), ], ); } } ================================================ FILE: example/lib/presentation/widgets/download_native_app_button.dart ================================================ import 'package:fl_chart_app/presentation/resources/app_colors.dart'; import 'package:flutter/material.dart'; class DownloadNativeAppButton extends StatelessWidget { const DownloadNativeAppButton({ super.key, required this.onClose, required this.onDownload, }); final VoidCallback onClose; final VoidCallback onDownload; @override Widget build(BuildContext context) { return Container( decoration: BoxDecoration( color: AppColors.contentColorYellow, borderRadius: BorderRadius.circular(999), ), child: Material( color: Colors.transparent, child: InkWell( borderRadius: BorderRadius.circular(999), onTap: onDownload, child: Padding( padding: const EdgeInsets.symmetric( vertical: 4, horizontal: 8, ), child: Row( mainAxisSize: MainAxisSize.min, children: [ const SizedBox(width: 4), const Icon( size: 28, Icons.download_for_offline, color: AppColors.contentColorBlack, ), const SizedBox(width: 4), const Text( 'Download Native App', style: TextStyle( color: AppColors.contentColorBlack, fontSize: 16, fontWeight: FontWeight.w900, ), ), IconButton( onPressed: onClose, icon: const Icon( size: 16, Icons.close, color: AppColors.contentColorBlack, ), ), ], ), ), ), ), ); } } ================================================ FILE: example/lib/presentation/widgets/indicator.dart ================================================ import 'package:flutter/material.dart'; class Indicator extends StatelessWidget { const Indicator({ super.key, required this.color, required this.text, required this.isSquare, this.size = 16, this.textColor, }); final Color color; final String text; final bool isSquare; final double size; final Color? textColor; @override Widget build(BuildContext context) { return Row( children: [ Container( width: size, height: size, decoration: BoxDecoration( shape: isSquare ? BoxShape.rectangle : BoxShape.circle, color: color, ), ), const SizedBox( width: 4, ), Text( text, style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: textColor, ), ) ], ); } } ================================================ FILE: example/lib/presentation/widgets/legend_widget.dart ================================================ import 'package:flutter/material.dart'; class LegendWidget extends StatelessWidget { const LegendWidget({ super.key, required this.name, required this.color, }); final String name; final Color color; @override Widget build(BuildContext context) { return Row( mainAxisSize: MainAxisSize.min, children: [ Container( width: 10, height: 10, decoration: BoxDecoration( shape: BoxShape.circle, color: color, ), ), const SizedBox(width: 6), Text( name, style: const TextStyle( color: Color(0xff757391), fontSize: 12, ), ), ], ); } } class LegendsListWidget extends StatelessWidget { const LegendsListWidget({ super.key, required this.legends, }); final List legends; @override Widget build(BuildContext context) { return Wrap( spacing: 16, children: legends .map( (e) => LegendWidget( name: e.name, color: e.color, ), ) .toList(), ); } } class Legend { Legend(this.name, this.color); final String name; final Color color; } ================================================ FILE: example/lib/urls.dart ================================================ import 'package:fl_chart_app/util/app_helper.dart'; class Urls { static const flChartUrl = 'https://flchart.dev'; static const flChartGithubUrl = 'https://github.com/imaNNeo/fl_chart'; static String get aboutUrl => '$flChartUrl/about'; static String get downloadUrl => '$flChartUrl/download'; static String getChartSourceCodeUrl(ChartType chartType, int sampleNumber) { final chartDir = chartType.name.toLowerCase(); return 'https://github.com/imaNNeo/fl_chart/blob/main/example/lib/presentation/samples/$chartDir/${chartDir}_chart_sample$sampleNumber.dart'; } static String getChartDocumentationUrl(ChartType chartType) { final chartDir = chartType.name.toLowerCase(); return 'https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/${chartDir}_chart.md'; } static String getVersionReleaseUrl(String version) => '$flChartGithubUrl/releases/tag/$version'; } ================================================ FILE: example/lib/util/app_helper.dart ================================================ import 'package:fl_chart_app/presentation/resources/app_resources.dart'; import 'package:fl_chart_app/urls.dart'; enum ChartType { line, bar, pie, scatter, radar, candlestick } extension ChartTypeExtension on ChartType { String get displayName => '$simpleName Chart'; String get simpleName => switch (this) { ChartType.line => 'Line', ChartType.bar => 'Bar', ChartType.pie => 'Pie', ChartType.scatter => 'Scatter', ChartType.radar => 'Radar', ChartType.candlestick => 'Candlestick', }; String get documentationUrl => Urls.getChartDocumentationUrl(this); String get assetIcon => AppAssets.getChartIcon(this); } ================================================ FILE: example/lib/util/app_utils.dart ================================================ import 'dart:math' as math; import 'package:url_launcher/url_launcher.dart'; class AppUtils { factory AppUtils() { return _singleton; } AppUtils._internal(); static final AppUtils _singleton = AppUtils._internal(); double degreeToRadian(double degree) { return degree * math.pi / 180; } double radianToDegree(double radian) { return radian * 180 / math.pi; } Future tryToLaunchUrl(String url) async { final uri = Uri.parse(url); if (await canLaunchUrl(uri)) { return await launchUrl(uri); } return false; } } ================================================ FILE: example/lib/util/csv_parser.dart ================================================ class CsvParser { static List> parse(String rawCsvData) { final lines = rawCsvData.split('\n').where((line) => line.isNotEmpty).toList(); final headers = _parseCsvLine(lines.first); return [ headers, ...lines.skip(1).map((line) => _parseCsvLine(line)), ]; } static List _parseCsvLine(String line) { final values = []; final buffer = StringBuffer(); bool insideQuotes = false; for (int i = 0; i < line.length; i++) { final char = line[i]; if (char == '"') { if (insideQuotes && i + 1 < line.length && line[i + 1] == '"') { // Handle escaped quotes buffer.write('"'); i++; // Skip the next quote } else { // Toggle the insideQuotes flag insideQuotes = !insideQuotes; } } else if (char == ',' && !insideQuotes) { // End of value values.add(buffer.toString()); buffer.clear(); } else { // Normal character buffer.write(char); } } // Add the last value values.add(buffer.toString()); return values; } } ================================================ FILE: example/lib/util/device_info.dart ================================================ import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:universal_platform/universal_platform.dart'; enum FormFactorType { monitor, smallPhone, largePhone, tablet } // Copied from https://github.com/gskinnerTeam/flutter-folio/blob/main/lib/_utils/device_info.dart class DeviceOS { // Syntax sugar, proxy the UniversalPlatform methods so our views can reference a single class static bool isIOS = UniversalPlatform.isIOS; static bool isAndroid = UniversalPlatform.isAndroid; static bool isMacOS = UniversalPlatform.isMacOS; static bool isLinux = UniversalPlatform.isLinux; static bool isWindows = UniversalPlatform.isWindows; // Higher level device class abstractions (more syntax sugar for the views) static bool isWeb = kIsWeb; static bool get isDesktop => isWindows || isMacOS || isLinux; static bool get isMobile => isAndroid || isIOS; static bool get isDesktopOrWeb => isDesktop || isWeb; static bool get isMobileOrWeb => isMobile || isWeb; } class DeviceScreen { // Get the device form factor as best we can. // Otherwise we will use the screen size to determine which class we fall into. static FormFactorType get(BuildContext context) { var shortestSide = MediaQuery.of(context).size.shortestSide; if (shortestSide <= 300) return FormFactorType.smallPhone; if (shortestSide <= 600) return FormFactorType.largePhone; if (shortestSide <= 900) return FormFactorType.tablet; return FormFactorType.monitor; } // Shortcuts for various mobile device types static bool isPhone(BuildContext context) => isSmallPhone(context) || isLargePhone(context); static bool isTablet(BuildContext context) => get(context) == FormFactorType.tablet; static bool isMonitor(BuildContext context) => get(context) == FormFactorType.monitor; static bool isSmallPhone(BuildContext context) => get(context) == FormFactorType.smallPhone; static bool isLargePhone(BuildContext context) => get(context) == FormFactorType.largePhone; } ================================================ FILE: example/lib/util/extensions/color_extensions.dart ================================================ import 'dart:ui'; extension ColorExtension on Color { /// Convert the color to a darken color based on the [percent] Color darken([int percent = 40]) { assert(1 <= percent && percent <= 100); final value = 1 - percent / 100; return Color.fromARGB( _floatToInt8(a), (_floatToInt8(r) * value).round(), (_floatToInt8(g) * value).round(), (_floatToInt8(b) * value).round(), ); } Color lighten([int percent = 40]) { assert(1 <= percent && percent <= 100); final value = percent / 100; return Color.fromARGB( _floatToInt8(a), (_floatToInt8(r) + ((255 - _floatToInt8(r)) * value)).round(), (_floatToInt8(g) + ((255 - _floatToInt8(g)) * value)).round(), (_floatToInt8(b) + ((255 - _floatToInt8(b)) * value)).round(), ); } Color avg(Color other) { final red = (_floatToInt8(r) + _floatToInt8(other.r)) ~/ 2; final green = (_floatToInt8(g) + _floatToInt8(other.g)) ~/ 2; final blue = (_floatToInt8(b) + _floatToInt8(other.b)) ~/ 2; final alpha = (_floatToInt8(a) + _floatToInt8(other.a)) ~/ 2; return Color.fromARGB(alpha, red, green, blue); } // Int color components were deprecated in Flutter 3.27.0. // This method is used to convert the new double color components to the // old int color components. // // Taken from the Color class. int _floatToInt8(double x) { return (x * 255.0).round() & 0xff; } } ================================================ FILE: example/lib/util/extensions/iterable_extensions.dart ================================================ extension IterableToMapExtension on Iterable> { Map get asMap => Map.fromEntries(this); } ================================================ FILE: example/lib/util/extensions/list_extensions.dart ================================================ extension ListToMapExtension on List> { Map get asMap => Map.fromEntries(this); } ================================================ FILE: example/linux/.gitignore ================================================ flutter/ephemeral ================================================ FILE: example/linux/CMakeLists.txt ================================================ # Project-level configuration. cmake_minimum_required(VERSION 3.13) project(runner LANGUAGES CXX) # The name of the executable created for the application. Change this to change # the on-disk name of your application. set(BINARY_NAME "fLChartApp") # The unique GTK application identifier for this application. See: # https://wiki.gnome.org/HowDoI/ChooseApplicationID set(APPLICATION_ID "dev.flchart.app") # Explicitly opt in to modern CMake behaviors to avoid warnings with recent # versions of CMake. cmake_policy(SET CMP0063 NEW) # Load bundled libraries from the lib/ directory relative to the binary. 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() # Define build configuration 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. # # Be cautious about adding new options here, as plugins use this function by # default. In most cases, you should add new options to specific targets instead # of modifying this function. 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() # Flutter library and tool build rules. set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") add_subdirectory(${FLUTTER_MANAGED_DIR}) # System-level dependencies. find_package(PkgConfig REQUIRED) pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) # Application build; see runner/CMakeLists.txt. add_subdirectory("runner") # Run the Flutter tool portions of the build. This must not be removed. 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) foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) install(FILES "${bundled_library}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) endforeach(bundled_library) # Copy the native assets provided by the build.dart from all packages. set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") install(DIRECTORY "${NATIVE_ASSETS_DIR}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) # 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: example/linux/flutter/CMakeLists.txt ================================================ # This file controls Flutter-level build steps. It should not be edited. 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: example/linux/flutter/generated_plugin_registrant.cc ================================================ // // Generated file. Do not edit. // // clang-format off #include "generated_plugin_registrant.h" #include void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); } ================================================ FILE: example/linux/flutter/generated_plugin_registrant.h ================================================ // // Generated file. Do not edit. // // clang-format off #ifndef GENERATED_PLUGIN_REGISTRANT_ #define GENERATED_PLUGIN_REGISTRANT_ #include // Registers Flutter plugins. void fl_register_plugins(FlPluginRegistry* registry); #endif // GENERATED_PLUGIN_REGISTRANT_ ================================================ FILE: example/linux/flutter/generated_plugins.cmake ================================================ # # Generated file, do not edit. # list(APPEND FLUTTER_PLUGIN_LIST url_launcher_linux ) list(APPEND FLUTTER_FFI_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) foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) endforeach(ffi_plugin) ================================================ FILE: example/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: example/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, "FL Chart App"); 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, "FL Chart App"); } 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: example/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: example/linux/runner/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.13) project(runner LANGUAGES CXX) # Define the application target. To change its name, change BINARY_NAME in the # top-level CMakeLists.txt, not the value here, or `flutter run` will no longer # work. # # Any new source files that you add to the application should be added here. add_executable(${BINARY_NAME} "main.cc" "my_application.cc" "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" ) # Apply the standard set of build settings. This can be removed for applications # that need different build settings. apply_standard_settings(${BINARY_NAME}) # Add preprocessor definitions for the application ID. add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") # Add dependency libraries. Add any application-specific dependencies here. target_link_libraries(${BINARY_NAME} PRIVATE flutter) target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") ================================================ FILE: example/linux/runner/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: example/linux/runner/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) // Called when first Flutter frame received. static void first_frame_cb(MyApplication* self, FlView *view) { gtk_widget_show(gtk_widget_get_toplevel(GTK_WIDGET(view))); } // 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, "fl_chart_app"); 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, "fl_chart_app"); } gtk_window_set_default_size(window, 1280, 720); 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); GdkRGBA background_color; // Background defaults to black, override it here if necessary, e.g. #00000000 for transparent. gdk_rgba_parse(&background_color, "#000000"); fl_view_set_background_color(view, &background_color); gtk_widget_show(GTK_WIDGET(view)); gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); // Show the window when Flutter renders. // Requires the view to be realized so we can start rendering. g_signal_connect_swapped(view, "first-frame", G_CALLBACK(first_frame_cb), self); gtk_widget_realize(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 GApplication::startup. static void my_application_startup(GApplication* application) { //MyApplication* self = MY_APPLICATION(object); // Perform any actions required at application startup. G_APPLICATION_CLASS(my_application_parent_class)->startup(application); } // Implements GApplication::shutdown. static void my_application_shutdown(GApplication* application) { //MyApplication* self = MY_APPLICATION(object); // Perform any actions required at application shutdown. G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); } // 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_APPLICATION_CLASS(klass)->startup = my_application_startup; G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; G_OBJECT_CLASS(klass)->dispose = my_application_dispose; } static void my_application_init(MyApplication* self) {} MyApplication* my_application_new() { // Set the program name to the application ID, which helps various systems // like GTK and desktop environments map this running application to its // corresponding .desktop file. This ensures better integration by allowing // the application to be recognized beyond its binary name. g_set_prgname(APPLICATION_ID); return MY_APPLICATION(g_object_new(my_application_get_type(), "application-id", APPLICATION_ID, "flags", G_APPLICATION_NON_UNIQUE, nullptr)); } ================================================ FILE: example/linux/runner/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: example/macos/.gitignore ================================================ # Flutter-related **/Flutter/ephemeral/ **/Pods/ # Xcode-related **/dgph **/xcuserdata/ ================================================ FILE: example/macos/Flutter/Flutter-Debug.xcconfig ================================================ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" ================================================ FILE: example/macos/Flutter/Flutter-Release.xcconfig ================================================ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" ================================================ FILE: example/macos/Flutter/GeneratedPluginRegistrant.swift ================================================ // // Generated file. Do not edit. // import FlutterMacOS import Foundation import package_info_plus import path_provider_foundation import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) } ================================================ FILE: example/macos/Podfile ================================================ platform :osx, '10.15' # 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: example/macos/Runner/AppDelegate.swift ================================================ import Cocoa import FlutterMacOS @main class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { return true } } ================================================ FILE: example/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: example/macos/Runner/Base.lproj/MainMenu.xib ================================================ ================================================ FILE: example/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 = FL Chart App // The application's bundle identifier PRODUCT_BUNDLE_IDENTIFIER = dev.flchart.app.exampleNew // The copyright displayed in application information PRODUCT_COPYRIGHT = Copyright © 2025 dev.flchart.app. All rights reserved. ================================================ FILE: example/macos/Runner/Configs/Debug.xcconfig ================================================ #include "../../Flutter/Flutter-Debug.xcconfig" #include "Warnings.xcconfig" ================================================ FILE: example/macos/Runner/Configs/Release.xcconfig ================================================ #include "../../Flutter/Flutter-Release.xcconfig" #include "Warnings.xcconfig" ================================================ FILE: example/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: example/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: example/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: example/macos/Runner/MainFlutterWindow.swift ================================================ import Cocoa import FlutterMacOS class MainFlutterWindow: NSWindow { override func awakeFromNib() { let flutterViewController = FlutterViewController() let windowFrame = self.frame self.contentViewController = flutterViewController self.setFrame(windowFrame, display: true) RegisterGeneratedPlugins(registry: flutterViewController) super.awakeFromNib() } } ================================================ FILE: example/macos/Runner/Release.entitlements ================================================ com.apple.security.app-sandbox ================================================ FILE: example/macos/Runner.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 54; 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 */; }; CD3C0E2CB86F97227A57A462 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0A6D7D2B0395281ED83347AF /* 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 */ 0A6D7D2B0395281ED83347AF /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 290F1FF0B02A72A883051281 /* 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 = ""; }; 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 /* fl_chart.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = fl_chart.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 = ""; }; 4FD09C27857801C704F06757 /* 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 = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; A0C43ED3CC8F9FE13408EE45 /* 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 = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 33CC10EA2044A3C60003C045 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( CD3C0E2CB86F97227A57A462 /* 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 */, F4A78FA3CEF98654E14A7A43 /* Pods */, ); sourceTree = ""; }; 33CC10EE2044A3C60003C045 /* Products */ = { isa = PBXGroup; children = ( 33CC10ED2044A3C60003C045 /* fl_chart.app */, ); name = Products; sourceTree = ""; }; 33CC11242044D66E0003C045 /* Resources */ = { isa = PBXGroup; children = ( 33CC10F22044A3C60003C045 /* Assets.xcassets */, 33CC10F42044A3C60003C045 /* MainMenu.xib */, 33CC10F72044A3C60003C045 /* Info.plist */, ); name = Resources; path = ..; sourceTree = ""; }; 33CEB47122A05771004F2AC0 /* Flutter */ = { isa = PBXGroup; children = ( 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, ); path = Flutter; sourceTree = ""; }; 33FAB671232836740065AC1E /* Runner */ = { isa = PBXGroup; children = ( 33CC10F02044A3C60003C045 /* AppDelegate.swift */, 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, 33E51913231747F40026EE4D /* DebugProfile.entitlements */, 33E51914231749380026EE4D /* Release.entitlements */, 33CC11242044D66E0003C045 /* Resources */, 33BA886A226E78AF003329D5 /* Configs */, ); path = Runner; sourceTree = ""; }; D73912EC22F37F3D000D13A0 /* Frameworks */ = { isa = PBXGroup; children = ( 0A6D7D2B0395281ED83347AF /* Pods_Runner.framework */, ); name = Frameworks; sourceTree = ""; }; F4A78FA3CEF98654E14A7A43 /* Pods */ = { isa = PBXGroup; children = ( 290F1FF0B02A72A883051281 /* Pods-Runner.debug.xcconfig */, 4FD09C27857801C704F06757 /* Pods-Runner.release.xcconfig */, A0C43ED3CC8F9FE13408EE45 /* Pods-Runner.profile.xcconfig */, ); name = Pods; path = Pods; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 33CC10EC2044A3C60003C045 /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( 1D2964E42B7E6986BA4115FC /* [CP] Check Pods Manifest.lock */, 33CC10E92044A3C60003C045 /* Sources */, 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, 06400C1AB8DC605AC029909F /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); dependencies = ( 33CC11202044C79F0003C045 /* PBXTargetDependency */, ); name = Runner; productName = Runner; productReference = 33CC10ED2044A3C60003C045 /* fl_chart.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 33CC10E52044A3C60003C045 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; LastUpgradeCheck = 1510; 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 */ 06400C1AB8DC605AC029909F /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; 1D2964E42B7E6986BA4115FC /* [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; alwaysOutOfDate = 1; 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.15; 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.15; 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.15; 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: example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ================================================ ================================================ FILE: example/macos/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: example/macos/RunnerTests/RunnerTests.swift ================================================ import Cocoa import FlutterMacOS import XCTest class RunnerTests: XCTestCase { func testExample() { // If you add code to the Runner application, consider adding tests here. // See https://developer.apple.com/documentation/xctest for more information about using XCTest. } } ================================================ FILE: example/pubspec.yaml ================================================ name: fl_chart_app description: FL Chart App is an application to demonstrate samples of the fl_chart (A Flutter package to draw charts). publish_to: 'none' version: 1.2.0+10200 environment: sdk: ^3.0.0 dependencies: flutter: sdk: flutter flutter_web_plugins: sdk: flutter cupertino_icons: ^1.0.8 google_fonts: ^6.3.2 flutter_svg: ^2.2.1 universal_platform: ^1.1.0 flutter_staggered_grid_view: ^0.7.0 url_launcher: ^6.3.2 go_router: ^16.2.4 dartx: ^1.2.0 fl_chart: path: ../ flutter_bloc: ^9.1.1 package_info_plus: ^9.0.0 equatable: ^2.0.7 intl: ^0.20.2 dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^6.0.0 flutter: uses-material-design: true fonts: - family: Digital fonts: - asset: assets/fonts/digital-7.ttf assets: - assets/icons/ - assets/fonts/ - assets/data/ ================================================ FILE: example/web/CNAME ================================================ app.flchart.dev ================================================ FILE: example/web/index.html ================================================ FL Chart App
================================================ FILE: example/web/manifest.json ================================================ { "name": "FL Chart App", "short_name": "FL Chart App", "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" }, { "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" } ] } ================================================ FILE: example/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: example/windows/CMakeLists.txt ================================================ # Project-level configuration. cmake_minimum_required(VERSION 3.14) project(fl_chart_app LANGUAGES CXX) # The name of the executable created for the application. Change this to change # the on-disk name of your application. set(BINARY_NAME "fl_chart_app") # Explicitly opt in to modern CMake behaviors to avoid warnings with recent # versions of CMake. cmake_policy(SET CMP0063 NEW) # Define build configuration option. 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() # Define settings for the Profile build mode. 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. # # Be cautious about adding new options here, as plugins use this function by # default. In most cases, you should add new options to specific targets instead # of modifying this function. 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() # Flutter library and tool build rules. set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") add_subdirectory(${FLUTTER_MANAGED_DIR}) # Application build; see runner/CMakeLists.txt. 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: example/windows/flutter/CMakeLists.txt ================================================ # This file controls Flutter-level build steps. It should not be edited. cmake_minimum_required(VERSION 3.14) 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") # Set fallback configurations for older versions of the flutter tool. if (NOT DEFINED FLUTTER_TARGET_PLATFORM) set(FLUTTER_TARGET_PLATFORM "windows-x64") endif() # === 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" ${FLUTTER_TARGET_PLATFORM} $ 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: example/windows/flutter/generated_plugin_registrant.cc ================================================ // // Generated file. Do not edit. // // clang-format off #include "generated_plugin_registrant.h" #include void RegisterPlugins(flutter::PluginRegistry* registry) { UrlLauncherWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("UrlLauncherWindows")); } ================================================ FILE: example/windows/flutter/generated_plugin_registrant.h ================================================ // // Generated file. Do not edit. // // clang-format off #ifndef GENERATED_PLUGIN_REGISTRANT_ #define GENERATED_PLUGIN_REGISTRANT_ #include // Registers Flutter plugins. void RegisterPlugins(flutter::PluginRegistry* registry); #endif // GENERATED_PLUGIN_REGISTRANT_ ================================================ FILE: example/windows/flutter/generated_plugins.cmake ================================================ # # Generated file, do not edit. # list(APPEND FLUTTER_PLUGIN_LIST url_launcher_windows ) list(APPEND FLUTTER_FFI_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) foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) endforeach(ffi_plugin) ================================================ FILE: example/windows/runner/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.14) project(runner LANGUAGES CXX) # Define the application target. To change its name, change BINARY_NAME in the # top-level CMakeLists.txt, not the value here, or `flutter run` will no longer # work. # # Any new source files that you add to the application should be added here. add_executable(${BINARY_NAME} WIN32 "flutter_window.cpp" "main.cpp" "utils.cpp" "win32_window.cpp" "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" "Runner.rc" "runner.exe.manifest" ) # Apply the standard set of build settings. This can be removed for applications # that need different build settings. apply_standard_settings(${BINARY_NAME}) # Add preprocessor definitions for the build version. target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") # Disable Windows macros that collide with C++ standard library functions. target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") # Add dependency libraries and include directories. Add any application-specific # dependencies here. target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") # Run the Flutter tool portions of the build. This must not be removed. add_dependencies(${BINARY_NAME} flutter_assemble) ================================================ FILE: example/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 // #if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) #define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD #else #define VERSION_AS_NUMBER 1,0,0,0 #endif #if defined(FLUTTER_VERSION) #define VERSION_AS_STRING FLUTTER_VERSION #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", "dev.flchart.app" "\0" VALUE "FileDescription", "FL Chart App" "\0" VALUE "FileVersion", VERSION_AS_STRING "\0" VALUE "InternalName", "FL Chart App" "\0" VALUE "LegalCopyright", "Copyright (C) 2023 dev.flchart.app. All rights reserved." "\0" VALUE "OriginalFilename", "fl_chart_app.exe" "\0" VALUE "ProductName", "fl_chart_app" "\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: example/windows/runner/flutter_window.cpp ================================================ #include "flutter_window.h" #include #include "flutter/generated_plugin_registrant.h" FlutterWindow::FlutterWindow(const flutter::DartProject& project) : 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()); SetChildContent(flutter_controller_->view()->GetNativeWindow()); flutter_controller_->engine()->SetNextFrameCallback([&]() { this->Show(); }); // Flutter can complete the first frame before the "show window" callback is // registered. The following call ensures a frame is pending to ensure the // window is shown. It is a no-op if the first frame hasn't completed yet. flutter_controller_->ForceRedraw(); return true; } void FlutterWindow::OnDestroy() { if (flutter_controller_) { 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: example/windows/runner/flutter_window.h ================================================ #ifndef RUNNER_FLUTTER_WINDOW_H_ #define RUNNER_FLUTTER_WINDOW_H_ #include #include #include #include "win32_window.h" // A window that does nothing but host a Flutter view. class FlutterWindow : public Win32Window { public: // Creates a new FlutterWindow hosting a Flutter view running |project|. explicit FlutterWindow(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 project to run. flutter::DartProject project_; // The Flutter instance hosted by this window. std::unique_ptr flutter_controller_; }; #endif // RUNNER_FLUTTER_WINDOW_H_ ================================================ FILE: example/windows/runner/main.cpp ================================================ #include #include #include #include "flutter_window.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); flutter::DartProject project(L"data"); std::vector command_line_arguments = GetCommandLineArguments(); project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); FlutterWindow window(project); Win32Window::Point origin(10, 10); Win32Window::Size size(1280, 720); if (!window.Create(L"FL Chart App", origin, size)) { return EXIT_FAILURE; } window.SetQuitOnClose(true); ::MSG msg; while (::GetMessage(&msg, nullptr, 0, 0)) { ::TranslateMessage(&msg); ::DispatchMessage(&msg); } ::CoUninitialize(); return EXIT_SUCCESS; } ================================================ FILE: example/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: example/windows/runner/runner.exe.manifest ================================================ PerMonitorV2 ================================================ FILE: example/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); std::string utf8_string; if (target_length == 0 || target_length > utf8_string.max_size()) { return 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: example/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: example/windows/runner/win32_window.cpp ================================================ #include "win32_window.h" #include #include #include "resource.h" namespace { /// Window attribute that enables dark mode window decorations. /// /// Redefined in case the developer's machine has a Windows SDK older than /// version 10.0.22000.0. /// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute #ifndef DWMWA_USE_IMMERSIVE_DARK_MODE #define DWMWA_USE_IMMERSIVE_DARK_MODE 20 #endif constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; /// Registry key for app theme preference. /// /// A value of 0 indicates apps should use dark mode. A non-zero or missing /// value indicates apps should use light mode. constexpr const wchar_t kGetPreferredBrightnessRegKey[] = L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; // 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::Create(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, 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; } UpdateTheme(window); return OnCreate(); } bool Win32Window::Show() { return ShowWindow(window_handle_, SW_SHOWNORMAL); } // 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; case WM_DWMCOLORIZATIONCOLORCHANGED: UpdateTheme(hwnd); 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. } void Win32Window::UpdateTheme(HWND const window) { DWORD light_mode; DWORD light_mode_size = sizeof(light_mode); LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, kGetPreferredBrightnessRegValue, RRF_RT_REG_DWORD, nullptr, &light_mode, &light_mode_size); if (result == ERROR_SUCCESS) { BOOL enable_dark_mode = light_mode == 0; DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, &enable_dark_mode, sizeof(enable_dark_mode)); } } ================================================ FILE: example/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 a win32 window with |title| that is positioned and sized 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 this function will scale the inputted width and height as // as appropriate for the default monitor. The window is invisible until // |Show| is called. Returns true if the window was created successfully. bool Create(const std::wstring& title, const Point& origin, const Size& size); // Show the current window. Returns true if the window was successfully shown. bool Show(); // 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; // Update the window frame's theme to match the system theme. static void UpdateTheme(HWND const window); 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: fl_chart.iml ================================================ ================================================ FILE: lib/fl_chart.dart ================================================ export 'src/chart/bar_chart/bar_chart.dart'; export 'src/chart/bar_chart/bar_chart_data.dart'; export 'src/chart/base/axis_chart/axis_chart_data.dart'; export 'src/chart/base/axis_chart/axis_chart_widgets.dart'; export 'src/chart/base/axis_chart/scale_axis.dart'; export 'src/chart/base/axis_chart/transformation_config.dart'; export 'src/chart/base/base_chart/base_chart_data.dart'; export 'src/chart/base/base_chart/fl_touch_event.dart'; export 'src/chart/candlestick_chart/candlestick_chart.dart'; export 'src/chart/candlestick_chart/candlestick_chart_data.dart'; export 'src/chart/line_chart/line_chart.dart'; export 'src/chart/line_chart/line_chart_data.dart'; export 'src/chart/pie_chart/pie_chart.dart'; export 'src/chart/pie_chart/pie_chart_data.dart'; export 'src/chart/radar_chart/radar_chart.dart'; export 'src/chart/radar_chart/radar_chart_data.dart'; export 'src/chart/scatter_chart/scatter_chart.dart'; export 'src/chart/scatter_chart/scatter_chart_data.dart'; ================================================ FILE: lib/src/chart/bar_chart/bar_chart.dart ================================================ import 'package:fl_chart/src/chart/bar_chart/bar_chart_data.dart'; import 'package:fl_chart/src/chart/bar_chart/bar_chart_helper.dart'; import 'package:fl_chart/src/chart/bar_chart/bar_chart_renderer.dart'; import 'package:fl_chart/src/chart/base/axis_chart/axis_chart_scaffold_widget.dart'; import 'package:fl_chart/src/chart/base/axis_chart/scale_axis.dart'; import 'package:fl_chart/src/chart/base/axis_chart/transformation_config.dart'; import 'package:fl_chart/src/chart/base/base_chart/base_chart_data.dart'; import 'package:fl_chart/src/chart/base/base_chart/fl_touch_event.dart'; import 'package:flutter/cupertino.dart'; /// Renders a bar chart as a widget, using provided [BarChartData]. class BarChart extends ImplicitlyAnimatedWidget { /// [data] determines how the [BarChart] should be look like, /// when you make any change in the [BarChartData], it updates /// new values with animation, and duration is [duration]. /// also you can change the [curve] /// which default is [Curves.linear]. BarChart( this.data, { this.chartRendererKey, super.key, @Deprecated('Please use [duration] instead') Duration? swapAnimationDuration, Duration duration = const Duration(milliseconds: 150), @Deprecated('Please use [curve] instead') Curve? swapAnimationCurve, Curve curve = Curves.linear, this.transformationConfig = const FlTransformationConfig(), }) : assert( switch (data.alignment) { BarChartAlignment.center || BarChartAlignment.end || BarChartAlignment.start => transformationConfig.scaleAxis != FlScaleAxis.horizontal && transformationConfig.scaleAxis != FlScaleAxis.free, _ => true, }, 'Can not scale horizontally when BarChartAlignment is center, ' 'end or start', ), super( duration: swapAnimationDuration ?? duration, curve: swapAnimationCurve ?? curve, ); /// Determines how the [BarChart] should be look like. final BarChartData data; /// {@macro fl_chart.AxisChartScaffoldWidget.transformationConfig} final FlTransformationConfig transformationConfig; /// We pass this key to our renderers which are supposed to /// render the chart itself (without anything around the chart). final Key? chartRendererKey; /// Creates a [_BarChartState] @override _BarChartState createState() => _BarChartState(); } class _BarChartState extends AnimatedWidgetBaseState { /// we handle under the hood animations (implicit animations) via this tween, /// it lerps between the old [BarChartData] to the new one. BarChartDataTween? _barChartDataTween; /// If [BarTouchData.handleBuiltInTouches] is true, we override the callback to handle touches internally, /// but we need to keep the provided callback to notify it too. BaseTouchCallback? _providedTouchCallback; final Map> _showingTouchedTooltips = {}; final _barChartHelper = BarChartHelper(); @override Widget build(BuildContext context) { final showingData = _getData(); return AxisChartScaffoldWidget( data: showingData, transformationConfig: widget.transformationConfig, chartBuilder: (context, chartVirtualRect) => BarChartLeaf( data: _withTouchedIndicators(_barChartDataTween!.evaluate(animation)), targetData: _withTouchedIndicators(showingData), key: widget.chartRendererKey, chartVirtualRect: chartVirtualRect, canBeScaled: widget.transformationConfig.scaleAxis != FlScaleAxis.none, ), ); } BarChartData _withTouchedIndicators(BarChartData barChartData) { if (!barChartData.barTouchData.enabled || !barChartData.barTouchData.handleBuiltInTouches) { return barChartData; } final newGroups = []; for (var i = 0; i < barChartData.barGroups.length; i++) { final group = barChartData.barGroups[i]; newGroups.add( group.copyWith( showingTooltipIndicators: _showingTouchedTooltips[i], ), ); } return barChartData.copyWith( barGroups: newGroups, ); } BarChartData _getData() { var newData = widget.data; if (newData.minY.isNaN || newData.maxY.isNaN) { final (minY, maxY) = _barChartHelper.calculateMaxAxisValues(newData.barGroups); newData = newData.copyWith( minY: newData.minY.isNaN ? minY : newData.minY, maxY: newData.maxY.isNaN ? maxY : newData.maxY, ); } final barTouchData = newData.barTouchData; if (barTouchData.enabled && barTouchData.handleBuiltInTouches) { _providedTouchCallback = barTouchData.touchCallback; return newData.copyWith( barTouchData: newData.barTouchData.copyWith(touchCallback: _handleBuiltInTouch), ); } return newData; } void _handleBuiltInTouch( FlTouchEvent event, BarTouchResponse? touchResponse, ) { if (!mounted) { return; } _providedTouchCallback?.call(event, touchResponse); if (!event.isInterestedForInteractions || touchResponse == null || touchResponse.spot == null) { setState(_showingTouchedTooltips.clear); return; } setState(() { final spot = touchResponse.spot!; final groupIndex = spot.touchedBarGroupIndex; final rodIndex = spot.touchedRodDataIndex; _showingTouchedTooltips.clear(); _showingTouchedTooltips[groupIndex] = [rodIndex]; }); } @override void forEachTween(TweenVisitor visitor) { _barChartDataTween = visitor( _barChartDataTween, _getData(), (dynamic value) => BarChartDataTween(begin: value as BarChartData, end: widget.data), ) as BarChartDataTween?; } } ================================================ FILE: lib/src/chart/bar_chart/bar_chart_data.dart ================================================ // coverage:ignore-file import 'dart:math'; import 'dart:ui'; import 'package:equatable/equatable.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/extensions/color_extension.dart'; import 'package:fl_chart/src/utils/lerp.dart'; import 'package:fl_chart/src/utils/utils.dart'; import 'package:flutter/material.dart'; /// [BarChart] needs this class to render itself. /// /// It holds data needed to draw a bar chart, /// including bar lines, colors, spaces, touches, ... class BarChartData extends AxisChartData with EquatableMixin { /// [BarChart] draws some [barGroups] and aligns them using [alignment], /// if [alignment] is [BarChartAlignment.center], you can define [groupsSpace] /// to apply space between them. /// /// It draws some titles on left, top, right, bottom sides per each axis number, /// you can modify [titlesData] to have your custom titles, /// also you can define the axis title (one text per axis) for each side /// using [axisTitleData], you can restrict the y axis using [minY], and [maxY] values. /// /// It draws a color as a background behind everything you can set it using [backgroundColor], /// then a grid over it, you can customize it using [gridData], /// and it draws 4 borders around your chart, you can customize it using [borderData]. /// /// You can annotate some regions with a highlight color using [rangeAnnotations]. /// /// You can modify [barTouchData] to customize touch behaviors and responses. /// /// Horizontal lines are drawn with [extraLinesData]. Vertical lines will not be painted if received. /// Please see issue #1149 (https://github.com/imaNNeo/fl_chart/issues/1149) for vertical lines. BarChartData({ List? barGroups, double? groupsSpace, BarChartAlignment? alignment, FlTitlesData? titlesData, BarTouchData? barTouchData, double? maxY, double? minY, super.baselineY, FlGridData? gridData, super.borderData, RangeAnnotations? rangeAnnotations, super.backgroundColor, ExtraLinesData? extraLinesData, super.rotationQuarterTurns, this.errorIndicatorData = const FlErrorIndicatorData(), }) : barGroups = barGroups ?? const [], groupsSpace = groupsSpace ?? 16, alignment = alignment ?? BarChartAlignment.spaceEvenly, barTouchData = barTouchData ?? const BarTouchData(), super( titlesData: titlesData ?? const FlTitlesData( topTitles: AxisTitles(), ), gridData: gridData ?? const FlGridData(), rangeAnnotations: rangeAnnotations ?? const RangeAnnotations(), extraLinesData: extraLinesData ?? const ExtraLinesData(), minX: 0, maxX: 1, maxY: maxY ?? double.nan, minY: minY ?? double.nan, ); /// [BarChart] draws [barGroups] that each of them contains a list of [BarChartRodData]. final List barGroups; /// Apply space between the [barGroups]. final double groupsSpace; /// Arrange the [barGroups], see [BarChartAlignment]. final BarChartAlignment alignment; /// Handles touch behaviors and responses. final BarTouchData barTouchData; /// Holds data for showing error (threshold) indicators on the spots in /// the different [BarChartGroupData.barRods] final FlErrorIndicatorData errorIndicatorData; /// Copies current [BarChartData] to a new [BarChartData], /// and replaces provided values. BarChartData copyWith({ List? barGroups, double? groupsSpace, BarChartAlignment? alignment, FlTitlesData? titlesData, RangeAnnotations? rangeAnnotations, BarTouchData? barTouchData, FlGridData? gridData, FlBorderData? borderData, double? maxY, double? minY, double? baselineY, Color? backgroundColor, ExtraLinesData? extraLinesData, int? rotationQuarterTurns, FlErrorIndicatorData? errorIndicatorData, }) => BarChartData( barGroups: barGroups ?? this.barGroups, groupsSpace: groupsSpace ?? this.groupsSpace, alignment: alignment ?? this.alignment, titlesData: titlesData ?? this.titlesData, rangeAnnotations: rangeAnnotations ?? this.rangeAnnotations, barTouchData: barTouchData ?? this.barTouchData, gridData: gridData ?? this.gridData, borderData: borderData ?? this.borderData, maxY: maxY ?? this.maxY, minY: minY ?? this.minY, baselineY: baselineY ?? this.baselineY, backgroundColor: backgroundColor ?? this.backgroundColor, extraLinesData: extraLinesData ?? this.extraLinesData, rotationQuarterTurns: rotationQuarterTurns ?? this.rotationQuarterTurns, errorIndicatorData: errorIndicatorData ?? this.errorIndicatorData, ); /// Lerps a [BaseChartData] based on [t] value, check [Tween.lerp]. @override BarChartData lerp(BaseChartData a, BaseChartData b, double t) { if (a is BarChartData && b is BarChartData) { return BarChartData( barGroups: lerpBarChartGroupDataList(a.barGroups, b.barGroups, t), groupsSpace: lerpDouble(a.groupsSpace, b.groupsSpace, t), alignment: b.alignment, titlesData: FlTitlesData.lerp(a.titlesData, b.titlesData, t), rangeAnnotations: RangeAnnotations.lerp(a.rangeAnnotations, b.rangeAnnotations, t), barTouchData: b.barTouchData, gridData: FlGridData.lerp(a.gridData, b.gridData, t), borderData: FlBorderData.lerp(a.borderData, b.borderData, t), maxY: lerpDouble(a.maxY, b.maxY, t), minY: lerpDouble(a.minY, b.minY, t), baselineY: lerpDouble(a.baselineY, b.baselineY, t), backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t), extraLinesData: ExtraLinesData.lerp(a.extraLinesData, b.extraLinesData, t), rotationQuarterTurns: b.rotationQuarterTurns, errorIndicatorData: FlErrorIndicatorData.lerp( a.errorIndicatorData, b.errorIndicatorData, t, ), ); } else { throw Exception('Illegal State'); } } /// Used for equality check, see [EquatableMixin]. @override List get props => [ barGroups, groupsSpace, alignment, titlesData, barTouchData, maxY, minY, baselineY, gridData, borderData, rangeAnnotations, backgroundColor, extraLinesData, rotationQuarterTurns, errorIndicatorData, ]; } /// defines arrangement of [barGroups], check [MainAxisAlignment] for more details. enum BarChartAlignment { start, end, center, spaceEvenly, spaceAround, spaceBetween, } /// Represents a group of rods (or bars) inside the [BarChart]. /// /// in the [BarChart] we have some rods, they can be grouped or not, /// if you want to have grouped bars, simply put them in each group, /// otherwise just pass one of them in each group. class BarChartGroupData with EquatableMixin { /// [BarChart] renders groups, and arrange them using [alignment], /// [x] value defines the group's value in the x axis (set them incrementally). /// it renders a list of [BarChartRodData] that represents a rod (or a bar) in the bar chart, /// and applies [barsSpace] between them. /// /// you can show some tooltipIndicators (a popup with an information) /// on top of each [BarChartRodData] using [showingTooltipIndicators], /// just put indices you want to show it on top of them. BarChartGroupData({ required this.x, bool? groupVertically, List? barRods, double? barsSpace, List? showingTooltipIndicators, }) : groupVertically = groupVertically ?? false, barRods = barRods ?? const [], barsSpace = barsSpace ?? 2, showingTooltipIndicators = showingTooltipIndicators ?? const []; /// Order along the x axis in which titles, and titles only, will be shown. /// /// Note [x] does not reorder bars from [barRods]; instead, it gets the title /// in [x] position through [SideTitles.getTitlesWidget] function. final int x; /// If set true, it will show bars below/above each other. /// Otherwise, it will show bars beside each other. final bool groupVertically; /// [BarChart] renders [barRods] that represents a rod (or a bar) in the bar chart. final List barRods; /// [BarChart] applies [barsSpace] between [barRods] if [groupVertically] is false. final double barsSpace; /// you can show some tooltipIndicators (a popup with an information) /// on top of each [BarChartRodData] using [showingTooltipIndicators], /// just put indices you want to show it on top of them. /// /// An important point is that you have to disable the default touch behaviour /// to show the tooltip manually, see [BarTouchData.handleBuiltInTouches]. final List showingTooltipIndicators; /// width of the group (sum of all [BarChartRodData]'s width and spaces) double get width { if (barRods.isEmpty) { return 0; } if (groupVertically) { return barRods.map((rodData) => rodData.width).reduce(max); } else { final sumWidth = barRods .map((rodData) => rodData.width) .reduce((first, second) => first + second); final spaces = (barRods.length - 1) * barsSpace; return sumWidth + spaces; } } /// Copies current [BarChartGroupData] to a new [BarChartGroupData], /// and replaces provided values. BarChartGroupData copyWith({ int? x, bool? groupVertically, List? barRods, double? barsSpace, List? showingTooltipIndicators, }) => BarChartGroupData( x: x ?? this.x, groupVertically: groupVertically ?? this.groupVertically, barRods: barRods ?? this.barRods, barsSpace: barsSpace ?? this.barsSpace, showingTooltipIndicators: showingTooltipIndicators ?? this.showingTooltipIndicators, ); /// Lerps a [BarChartGroupData] based on [t] value, check [Tween.lerp]. static BarChartGroupData lerp( BarChartGroupData a, BarChartGroupData b, double t, ) => BarChartGroupData( x: (a.x + (b.x - a.x) * t).round(), groupVertically: b.groupVertically, barRods: lerpBarChartRodDataList(a.barRods, b.barRods, t), barsSpace: lerpDouble(a.barsSpace, b.barsSpace, t), showingTooltipIndicators: lerpIntList( a.showingTooltipIndicators, b.showingTooltipIndicators, t, ), ); /// Used for equality check, see [EquatableMixin]. @override List get props => [ x, groupVertically, barRods, barsSpace, showingTooltipIndicators, ]; } /// Holds data about rendering each rod (or bar) in the [BarChart]. class BarChartRodData with EquatableMixin { /// [BarChart] renders rods vertically from zero to [toY], /// and the x is equivalent to the [BarChartGroupData.x] value. /// /// It renders each rod using [color], [width], and [borderRadius] for rounding corners and also [borderSide] for stroke border. /// Optionally you can use [borderDashArray] if you want your borders to have dashed lines. /// /// This bar draws with provided [color] or [gradient]. /// You must provide one of them. /// /// If you want to have a bar drawn in rear of this rod, use [backDrawRodData], /// it uses to have a bar with a passive color in rear of the rod, /// for example you can use it as the maximum value place holder. /// /// If you are a fan of stacked charts (If you don't know what is it, google it), /// you can fill up the [rodStackItems] to have a Stacked Chart. /// for example if you want to have a Stacked Chart with three colors: /// ```dart /// BarChartRodData( /// y: 9, /// color: Colors.grey, /// rodStackItems: [ /// BarChartRodStackItem(0, 3, Colors.red), /// BarChartRodStackItem(3, 6, Colors.green), /// BarChartRodStackItem(6, 9, Colors.blue), /// ] /// ) /// ``` BarChartRodData({ double? fromY, required this.toY, this.toYErrorRange, Color? color, this.gradient, double? width, BorderRadius? borderRadius, this.borderDashArray, BorderSide? borderSide, BackgroundBarChartRodData? backDrawRodData, List? rodStackItems, this.label = const BarChartRodLabel(show: false), }) : fromY = fromY ?? 0, color = color ?? ((color == null && gradient == null) ? Colors.cyan : null), width = width ?? 8, borderRadius = Utils().normalizeBorderRadius(borderRadius, width ?? 8), borderSide = Utils().normalizeBorderSide(borderSide, width ?? 8), backDrawRodData = backDrawRodData ?? BackgroundBarChartRodData(), rodStackItems = rodStackItems ?? const []; /// [BarChart] renders rods vertically from [fromY]. final double fromY; /// [BarChart] renders rods vertically from [fromY] to [toY]. final double toY; /// If the data has error range/threshold, it will be rendered /// with this error range. So you can provide the /// [FlErrorRange.lowerBy] and [FlErrorRange.upperBy] that is relative to /// the [toY] property. /// /// If you want to customize the visual representation of the error range, /// you can use [BarChartData.errorIndicatorData] to customize the error range final FlErrorRange? toYErrorRange; /// If provided, this [BarChartRodData] draws with this [color] /// Otherwise we use [gradient] to draw the background. /// It throws an exception if you provide both [color] and [gradient] final Color? color; /// If provided, this [BarChartRodData] draws with this [gradient]. /// Otherwise we use [color] to draw the background. /// It throws an exception if you provide both [color] and [gradient] final Gradient? gradient; /// [BarChart] renders each rods with this value. final double width; /// If you want to have a rounded rod, set this value. final BorderRadius? borderRadius; /// If you want to have dashed border, set this value. final List? borderDashArray; /// If you want to have a border for rod, set this value. final BorderSide borderSide; /// If you want to have a bar drawn in rear of this rod, use [backDrawRodData], /// it uses to have a bar with a passive color in rear of the rod, /// for example you can use it as the maximum value place holder. final BackgroundBarChartRodData backDrawRodData; /// If you are a fan of stacked charts (If you don't know what is it, google it), /// you can fill up the [rodStackItems] to have a Stacked Chart. final List rodStackItems; /// Optional label to display near the rod tip. final BarChartRodLabel label; /// Determines the upward or downward direction bool isUpward() => toY >= fromY; /// Copies current [BarChartRodData] to a new [BarChartRodData], /// and replaces provided values. BarChartRodData copyWith({ double? fromY, double? toY, FlErrorRange? toYErrorRange, Color? color, Gradient? gradient, double? width, BorderRadius? borderRadius, List? dashArray, BorderSide? borderSide, BackgroundBarChartRodData? backDrawRodData, List? rodStackItems, BarChartRodLabel? label, }) => BarChartRodData( fromY: fromY ?? this.fromY, toY: toY ?? this.toY, toYErrorRange: toYErrorRange ?? this.toYErrorRange, color: color ?? this.color, gradient: gradient ?? this.gradient, width: width ?? this.width, borderRadius: borderRadius ?? this.borderRadius, borderDashArray: borderDashArray, borderSide: borderSide ?? this.borderSide, backDrawRodData: backDrawRodData ?? this.backDrawRodData, rodStackItems: rodStackItems ?? this.rodStackItems, label: label ?? this.label, ); /// Lerps a [BarChartRodData] based on [t] value, check [Tween.lerp]. static BarChartRodData lerp(BarChartRodData a, BarChartRodData b, double t) => BarChartRodData( gradient: Gradient.lerp(a.gradient, b.gradient, t), color: Color.lerp(a.color, b.color, t), width: lerpDouble(a.width, b.width, t), borderRadius: BorderRadius.lerp(a.borderRadius, b.borderRadius, t), borderDashArray: lerpIntList(a.borderDashArray, b.borderDashArray, t), borderSide: BorderSide.lerp(a.borderSide, b.borderSide, t), fromY: lerpDouble(a.fromY, b.fromY, t), toY: lerpDouble(a.toY, b.toY, t)!, toYErrorRange: FlErrorRange.lerp(a.toYErrorRange, b.toYErrorRange, t), backDrawRodData: BackgroundBarChartRodData.lerp( a.backDrawRodData, b.backDrawRodData, t, ), rodStackItems: lerpBarChartRodStackList(a.rodStackItems, b.rodStackItems, t), label: BarChartRodLabel.lerpBarChartRodLabel(a.label, b.label, t), ); /// Used for equality check, see [EquatableMixin]. @override List get props => [ fromY, toY, toYErrorRange, width, borderRadius, borderDashArray, borderSide, backDrawRodData, rodStackItems, color, gradient, label, ]; } /// A colored section of Stacked Chart rod item /// /// Each [BarChartRodData] can have a list of [BarChartRodStackItem] (with different colors /// and position) to represent a Stacked Chart rod, class BarChartRodStackItem with EquatableMixin { /// Renders a section of Stacked Chart from [fromY] to [toY] with [color] or [gradient] /// for example if you want to have a Stacked Chart with three colors: /// ```dart /// BarChartRodData( /// y: 9, /// color: Colors.grey, /// rodStackItems: [ /// BarChartRodStackItem(0, 3, Colors.red), /// BarChartRodStackItem(3, 6, Colors.green), /// BarChartRodStackItem(6, 9, Colors.blue), /// ] /// ) /// ``` /// To use the [gradient], set [color] to null BarChartRodStackItem( this.fromY, this.toY, this.color, { this.gradient, this.label, this.labelStyle, this.borderSide = Utils.defaultBorderSide, }) : assert( color != null || gradient != null, 'You must provide either a color or gradient', ); final String? label; final TextStyle? labelStyle; /// Renders a Stacked Chart section from [fromY] final double fromY; /// Renders a Stacked Chart section to [toY] final double toY; /// Renders a Stacked Chart section with [color] final Color? color; /// Renders a Stacked Chart section with [gradient] final Gradient? gradient; /// Renders border stroke for a Stacked Chart section final BorderSide borderSide; /// Copies current [BarChartRodStackItem] to a new [BarChartRodStackItem], /// and replaces provided values. BarChartRodStackItem copyWith({ double? fromY, double? toY, Color? color, Gradient? gradient, String? label, TextStyle? labelStyle, BorderSide? borderSide, }) => BarChartRodStackItem( fromY ?? this.fromY, toY ?? this.toY, color ?? this.color, gradient: gradient ?? this.gradient, label: label ?? this.label, labelStyle: labelStyle ?? this.labelStyle, borderSide: borderSide ?? this.borderSide, ); /// Lerps a [BarChartRodStackItem] based on [t] value, check [Tween.lerp]. static BarChartRodStackItem lerp( BarChartRodStackItem a, BarChartRodStackItem b, double t, ) => BarChartRodStackItem( lerpDouble(a.fromY, b.fromY, t)!, lerpDouble(a.toY, b.toY, t)!, Color.lerp(a.color, b.color, t), gradient: Gradient.lerp(a.gradient, b.gradient, t), label: b.label, labelStyle: b.labelStyle, borderSide: BorderSide.lerp(a.borderSide, b.borderSide, t), ); /// Used for equality check, see [EquatableMixin]. @override List get props => [fromY, toY, color, gradient, label, labelStyle, borderSide]; } /// Holds values to draw a rod in rear of the main rod. /// /// If you want to have a bar drawn in rear of the main rod, use [BarChartRodData.backDrawRodData], /// it uses to have a bar with a passive color in rear of the rod, /// for example you can use it as the maximum value place holder in rear of your rod. class BackgroundBarChartRodData with EquatableMixin { /// It will be rendered in rear of the main rod, /// background starts to show from [fromY] to [toY], /// It draws with [color] or [gradient]. You must provide one of them, /// you prevent to show it, using [show] property. BackgroundBarChartRodData({ double? fromY, double? toY, bool? show, Color? color, this.gradient, }) : fromY = fromY ?? 0, toY = toY ?? 0, show = show ?? false, color = color ?? ((color == null && gradient == null) ? Colors.blueGrey : null); /// Determines to show or hide this final bool show; /// [fromY] is where background starts to show final double fromY; /// background starts to show from [fromY] to [toY] final double toY; /// If provided, Background draws with this [color] /// Otherwise we use [gradient] to draw the background. /// It throws an exception if you provide both [color] and [gradient] final Color? color; /// If provided, background draws with this [gradient]. /// Otherwise we use [color] to draw the background. /// It throws an exception if you provide both [color] and [gradient] final Gradient? gradient; /// Lerps a [BackgroundBarChartRodData] based on [t] value, check [Tween.lerp]. static BackgroundBarChartRodData lerp( BackgroundBarChartRodData a, BackgroundBarChartRodData b, double t, ) => BackgroundBarChartRodData( fromY: lerpDouble(a.fromY, b.fromY, t), toY: lerpDouble(a.toY, b.toY, t), color: Color.lerp(a.color, b.color, t), gradient: Gradient.lerp(a.gradient, b.gradient, t), show: b.show, ); /// Used for equality check, see [EquatableMixin]. @override List get props => [ show, fromY, toY, color, gradient, ]; } /// Holds data to handle touch events, and touch responses in the [BarChart]. /// /// There is a touch flow, explained [here](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/handle_touches.md) /// in a simple way, each chart's renderer captures the touch events, and passes the pointerEvent /// to the painter, and gets touched spot, and wraps it into a concrete [BarTouchResponse]. class BarTouchData extends FlTouchData with EquatableMixin { /// You can disable or enable the touch system using [enabled] flag, /// /// [touchCallback] notifies you about the happened touch/pointer events. /// It gives you a [FlTouchEvent] which is the happened event such as [FlPointerHoverEvent], [FlTapUpEvent], ... /// It also gives you a [BarTouchResponse] which contains information /// about the elements that has touched. /// /// Using [mouseCursorResolver] you can change the mouse cursor /// based on the provided [FlTouchEvent] and [BarTouchResponse] /// /// if [handleBuiltInTouches] is true, [BarChart] shows a tooltip popup on top of the bars if /// touch occurs (or you can show it manually using, [BarChartGroupData.showingTooltipIndicators]), /// You can customize this tooltip using [touchTooltipData]. /// If you need to have a distance threshold for handling touches, use [touchExtraThreshold]. /// If [allowTouchBarBackDraw] sets to true, touches will work /// on [BarChartRodData.backDrawRodData] too (by default it only works on the main rods). const BarTouchData({ bool? enabled, BaseTouchCallback? touchCallback, MouseCursorResolver? mouseCursorResolver, Duration? longPressDuration, BarTouchTooltipData? touchTooltipData, EdgeInsets? touchExtraThreshold, bool? allowTouchBarBackDraw, bool? handleBuiltInTouches, }) : touchTooltipData = touchTooltipData ?? const BarTouchTooltipData(), touchExtraThreshold = touchExtraThreshold ?? const EdgeInsets.all(4), allowTouchBarBackDraw = allowTouchBarBackDraw ?? false, handleBuiltInTouches = handleBuiltInTouches ?? true, super( enabled ?? true, touchCallback, mouseCursorResolver, longPressDuration, ); /// Configs of how touch tooltip popup. final BarTouchTooltipData touchTooltipData; /// Distance threshold to handle the touch event. final EdgeInsets touchExtraThreshold; /// Determines to handle touches on the back draw bar. final bool allowTouchBarBackDraw; /// Determines to handle default built-in touch responses, /// [BarTouchResponse] shows a tooltip popup above the touched spot. final bool handleBuiltInTouches; /// Copies current [BarTouchData] to a new [BarTouchData], /// and replaces provided values. BarTouchData copyWith({ bool? enabled, BaseTouchCallback? touchCallback, MouseCursorResolver? mouseCursorResolver, Duration? longPressDuration, BarTouchTooltipData? touchTooltipData, EdgeInsets? touchExtraThreshold, bool? allowTouchBarBackDraw, bool? handleBuiltInTouches, }) => BarTouchData( enabled: enabled ?? this.enabled, touchCallback: touchCallback ?? this.touchCallback, mouseCursorResolver: mouseCursorResolver ?? this.mouseCursorResolver, longPressDuration: longPressDuration ?? this.longPressDuration, touchTooltipData: touchTooltipData ?? this.touchTooltipData, touchExtraThreshold: touchExtraThreshold ?? this.touchExtraThreshold, allowTouchBarBackDraw: allowTouchBarBackDraw ?? this.allowTouchBarBackDraw, handleBuiltInTouches: handleBuiltInTouches ?? this.handleBuiltInTouches, ); /// Used for equality check, see [EquatableMixin]. @override List get props => [ enabled, touchCallback, mouseCursorResolver, longPressDuration, touchTooltipData, touchExtraThreshold, allowTouchBarBackDraw, handleBuiltInTouches, ]; } /// Controls showing tooltip on top or bottom. enum TooltipDirection { /// Tooltip shows on top if value is positive, on bottom if value is negative. auto, /// Tooltip always shows on top. top, /// Tooltip always shows on bottom. bottom, } /// Holds representation data for showing tooltip popup on top of rods. class BarTouchTooltipData with EquatableMixin { /// if [BarTouchData.handleBuiltInTouches] is true, /// [BarChart] shows a tooltip popup on top of rods automatically when touch happens, /// otherwise you can show it manually using [BarChartGroupData.showingTooltipIndicators]. /// Tooltip shows on top of rods, with [getTooltipColor] as a background color. /// You can set the corner radius using [tooltipRoundedRadius], /// or if you need a custom border, you can use [tooltipBorderRadius]. /// Note that if both [tooltipRoundedRadius] and [tooltipBorderRadius] are set, /// the value from [tooltipBorderRadius] will be used. /// If you want to have a padding inside the tooltip, fill [tooltipPadding], /// or If you want to have a bottom margin, set [tooltipMargin]. /// Content of the tooltip will provide using [getTooltipItem] callback, you can override it /// and pass your custom data to show in the tooltip. /// You can restrict the tooltip's width using [maxContentWidth]. /// Sometimes, [BarChart] shows the tooltip outside of the chart, /// you can set [fitInsideHorizontally] true to force it to shift inside the chart horizontally, /// also you can set [fitInsideVertically] true to force it to shift inside the chart vertically. const BarTouchTooltipData({ BorderRadius? tooltipBorderRadius, EdgeInsets? tooltipPadding, double? tooltipMargin, FLHorizontalAlignment? tooltipHorizontalAlignment, double? tooltipHorizontalOffset, double? maxContentWidth, GetBarTooltipItem? getTooltipItem, GetBarTooltipColor? getTooltipColor, bool? fitInsideHorizontally, bool? fitInsideVertically, TooltipDirection? direction, double? rotateAngle, BorderSide? tooltipBorder, }) : _tooltipBorderRadius = tooltipBorderRadius, tooltipPadding = tooltipPadding ?? const EdgeInsets.symmetric(horizontal: 16, vertical: 8), tooltipMargin = tooltipMargin ?? 16, tooltipHorizontalAlignment = tooltipHorizontalAlignment ?? FLHorizontalAlignment.center, tooltipHorizontalOffset = tooltipHorizontalOffset ?? 0, maxContentWidth = maxContentWidth ?? 120, getTooltipItem = getTooltipItem ?? defaultBarTooltipItem, getTooltipColor = getTooltipColor ?? defaultBarTooltipColor, fitInsideHorizontally = fitInsideHorizontally ?? false, fitInsideVertically = fitInsideVertically ?? false, direction = direction ?? TooltipDirection.auto, rotateAngle = rotateAngle ?? 0.0, tooltipBorder = tooltipBorder ?? BorderSide.none, super(); /// Sets a rounded radius for the tooltip. final BorderRadius? _tooltipBorderRadius; /// Sets a rounded radius for the tooltip. BorderRadius get tooltipBorderRadius => _tooltipBorderRadius ?? BorderRadius.circular(4); /// Applies a padding for showing contents inside the tooltip. final EdgeInsets tooltipPadding; /// Applies a bottom margin for showing tooltip on top of rods. final double tooltipMargin; /// Controls showing tooltip on left side, right side or center aligned with rod, default is center final FLHorizontalAlignment tooltipHorizontalAlignment; /// Applies horizontal offset for showing tooltip, default is zero. final double tooltipHorizontalOffset; /// Restricts the tooltip's width. final double maxContentWidth; /// Retrieves data for showing content inside the tooltip. final GetBarTooltipItem getTooltipItem; /// Forces the tooltip to shift horizontally inside the chart, if overflow happens. final bool fitInsideHorizontally; /// Forces the tooltip to shift vertically inside the chart, if overflow happens. final bool fitInsideVertically; /// Controls showing tooltip on top or bottom, default is auto. final TooltipDirection direction; /// Controls the rotation of the tooltip (in degrees) final double rotateAngle; /// The tooltip border color. final BorderSide tooltipBorder; /// Retrieves data for setting background color of the tooltip. final GetBarTooltipColor getTooltipColor; /// Used for equality check, see [EquatableMixin]. @override List get props => [ _tooltipBorderRadius, tooltipPadding, tooltipMargin, tooltipHorizontalAlignment, tooltipHorizontalOffset, maxContentWidth, getTooltipItem, fitInsideHorizontally, fitInsideVertically, rotateAngle, tooltipBorder, getTooltipColor, ]; } /// Provides a [BarTooltipItem] for showing content inside the [BarTouchTooltipData]. /// /// You can override [BarTouchTooltipData.getTooltipItem], it gives you /// [group], [groupIndex], [rod], and [rodIndex] that touch happened on, /// then you should and pass your custom [BarTooltipItem] to show inside the tooltip popup. typedef GetBarTooltipItem = BarTooltipItem? Function( BarChartGroupData group, int groupIndex, BarChartRodData rod, int rodIndex, ); /// Default implementation for [BarTouchTooltipData.getTooltipItem]. BarTooltipItem? defaultBarTooltipItem( BarChartGroupData group, int groupIndex, BarChartRodData rod, int rodIndex, ) { final color = rod.gradient?.colors.first ?? rod.color; final textStyle = TextStyle( color: color, fontWeight: FontWeight.bold, fontSize: 14, ); return BarTooltipItem(rod.toY.toString(), textStyle); } /// Holds data needed for showing custom tooltip content. class BarTooltipItem with EquatableMixin { /// content of the tooltip, is a [text] String with a [textStyle], /// [textDirection] and optional [children]. BarTooltipItem( this.text, this.textStyle, { this.textAlign = TextAlign.center, this.textDirection = TextDirection.ltr, this.children, }); /// Text of the content. final String text; /// TextStyle of the showing content. final TextStyle textStyle; /// TextAlign of the showing content. final TextAlign textAlign; /// Direction of showing text. final TextDirection textDirection; /// Add further style and format to the text of the tooltip final List? children; /// Used for equality check, see [EquatableMixin]. @override List get props => [ text, textStyle, textAlign, textDirection, children, ]; } //// Provides a [Color] to show different background color for each rod /// /// You can override [BarTouchTooltipData.getTooltipColor], it gives you /// [group] that touch happened on, then you should and pass your custom [Color] to set background color /// of tooltip popup. typedef GetBarTooltipColor = Color Function( BarChartGroupData group, ); /// Default implementation for [BarTouchTooltipData.getTooltipColor]. Color defaultBarTooltipColor(BarChartGroupData group) => Colors.blueGrey.darken(15); /// Holds information about touch response in the [BarChart]. /// /// You can override [BarTouchData.touchCallback] to handle touch events, /// it gives you a [BarTouchResponse] and you can do whatever you want. class BarTouchResponse extends AxisBaseTouchResponse { /// If touch happens, [BarChart] processes it internally and passes out a BarTouchedSpot /// that contains a [spot], it gives you information about the touched spot. BarTouchResponse({ required super.touchLocation, required super.touchChartCoordinate, required this.spot, }); /// Gives information about the touched spot final BarTouchedSpot? spot; /// Copies current [BarTouchResponse] to a new [BarTouchResponse], /// and replaces provided values. BarTouchResponse copyWith({ Offset? touchLocation, Offset? touchChartCoordinate, BarTouchedSpot? spot, }) => BarTouchResponse( touchLocation: touchLocation ?? this.touchLocation, touchChartCoordinate: touchChartCoordinate ?? this.touchChartCoordinate, spot: spot ?? this.spot, ); } /// It gives you information about the touched spot. class BarTouchedSpot extends TouchedSpot with EquatableMixin { /// When touch happens, a [BarTouchedSpot] returns as a output, /// it tells you where the touch happened. /// [touchedBarGroup], and [touchedBarGroupIndex] tell you in which group touch happened, /// [touchedRodData], and [touchedRodDataIndex] tell you in which rod touch happened, /// [touchedStackItem], and [touchedStackItemIndex] tell you in which rod stack touch happened /// ([touchedStackItemIndex] means nothing found). /// You can also have the touched x and y in the chart as a [FlSpot] using [spot] value, /// and you can have the local touch coordinates on the screen as a [Offset] using [offset] value. BarTouchedSpot( this.touchedBarGroup, this.touchedBarGroupIndex, this.touchedRodData, this.touchedRodDataIndex, this.touchedStackItem, this.touchedStackItemIndex, FlSpot spot, Offset offset, ) : super(spot, offset); final BarChartGroupData touchedBarGroup; final int touchedBarGroupIndex; final BarChartRodData touchedRodData; final int touchedRodDataIndex; /// It can be null, if nothing found final BarChartRodStackItem? touchedStackItem; /// It can be -1, if nothing found final int touchedStackItemIndex; /// Used for equality check, see [EquatableMixin]. @override List get props => [ touchedBarGroup, touchedBarGroupIndex, touchedRodData, touchedRodDataIndex, touchedStackItem, touchedStackItemIndex, spot, offset, ]; } /// It is the input of the [GetSpotRangeErrorPainter] callback in /// the [BarChartData.errorIndicatorData] /// /// As you see, we have some properties that are related to each individual /// rod (the object we show the error range on top of it). /// For example, /// [group] is the group that the rod belongs to, /// [groupIndex] is the index of the group, /// [rod] is the rod that the error range belongs to, /// [barRodIndex] is the index of the rod in the group. class BarChartSpotErrorRangeCallbackInput extends FlSpotErrorRangeCallbackInput { BarChartSpotErrorRangeCallbackInput({ required this.group, required this.groupIndex, required this.rod, required this.barRodIndex, }); // The group that the rod belongs to final BarChartGroupData group; // The index of the group that the rod belongs to final int groupIndex; // The rod that the error range belongs to final BarChartRodData rod; // The index of the rod in the group final int barRodIndex; @override List get props => [ group, groupIndex, rod, barRodIndex, ]; } /// Label configuration for a bar chart rod. class BarChartRodLabel extends FlLabel { const BarChartRodLabel({ super.show, super.text, super.style, super.angle, super.textDirection, this.offset = const Offset(0, 8), }); /// Offset from the rod tip to position the label. /// [Offset.dx] shifts horizontally, [Offset.dy] shifts vertically. final Offset offset; /// Lerps a [BarChartRodLabel] based on [t] value. static BarChartRodLabel lerpBarChartRodLabel( BarChartRodLabel a, BarChartRodLabel b, double t, ) { return BarChartRodLabel( show: b.show, text: b.text, style: TextStyle.lerp(a.style, b.style, t), angle: lerpDouble(a.angle, b.angle, t)!, textDirection: b.textDirection, offset: Offset.lerp(a.offset, b.offset, t)!, ); } @override BarChartRodLabel copyWith({ bool? show, String? text, TextStyle? style, double? angle, TextDirection? textDirection, Offset? offset, }) => BarChartRodLabel( show: show ?? this.show, text: text ?? this.text, style: style ?? this.style, angle: angle ?? this.angle, textDirection: textDirection ?? this.textDirection, offset: offset ?? this.offset, ); @override List get props => [show, text, style, angle, textDirection, offset]; } /// It lerps a [BarChartData] to another [BarChartData] (handles animation for updating values) class BarChartDataTween extends Tween { BarChartDataTween({required BarChartData begin, required BarChartData end}) : super(begin: begin, end: end); /// Lerps a [BarChartData] based on [t] value, check [Tween.lerp]. @override BarChartData lerp(double t) => begin!.lerp(begin!, end!, t); } ================================================ FILE: lib/src/chart/bar_chart/bar_chart_helper.dart ================================================ import 'dart:math'; import 'package:fl_chart/src/chart/bar_chart/bar_chart_data.dart'; /// Contains anything that helps BarChart works class BarChartHelper { /// Calculates minY, and maxY based on [barGroups], /// returns cached values, to prevent redundant calculations. (double minY, double maxY) calculateMaxAxisValues( List barGroups, ) { if (barGroups.isEmpty) { return (0, 0); } final BarChartGroupData barGroup; try { barGroup = barGroups.firstWhere((element) => element.barRods.isNotEmpty); } catch (_) { // There is no barChartGroupData with at least one barRod return (0, 0); } var maxY = max(barGroup.barRods[0].fromY, barGroup.barRods[0].toY); var minY = min(barGroup.barRods[0].fromY, barGroup.barRods[0].toY); for (var i = 0; i < barGroups.length; i++) { final barGroup = barGroups[i]; for (var j = 0; j < barGroup.barRods.length; j++) { final rod = barGroup.barRods[j]; maxY = max(maxY, rod.fromY); minY = min(minY, rod.fromY); maxY = max(maxY, rod.toY); minY = min(minY, rod.toY); if (rod.backDrawRodData.show) { maxY = max(maxY, rod.backDrawRodData.fromY); minY = min(minY, rod.backDrawRodData.fromY); maxY = max(maxY, rod.backDrawRodData.toY); minY = min(minY, rod.backDrawRodData.toY); } } } return (minY, maxY); } } ================================================ FILE: lib/src/chart/bar_chart/bar_chart_painter.dart ================================================ import 'dart:core'; import 'dart:math'; import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/chart/base/axis_chart/axis_chart_painter.dart'; import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart'; import 'package:fl_chart/src/extensions/bar_chart_data_extension.dart'; import 'package:fl_chart/src/extensions/paint_extension.dart'; import 'package:fl_chart/src/extensions/path_extension.dart'; import 'package:fl_chart/src/extensions/rrect_extension.dart'; import 'package:fl_chart/src/utils/canvas_wrapper.dart'; import 'package:fl_chart/src/utils/utils.dart'; import 'package:flutter/material.dart'; /// Paints [BarChartData] in the canvas, it can be used in a [CustomPainter] class BarChartPainter extends AxisChartPainter { /// Paints [dataList] into canvas, it is the animating [BarChartData], /// [targetData] is the animation's target and remains the same /// during animation, then we should use it when we need to show /// tooltips or something like that, because [dataList] is changing constantly. /// /// [textScale] used for scaling texts inside the chart, /// parent can use [MediaQuery.textScaleFactor] to respect /// the system's font size. BarChartPainter() : super() { _barPaint = Paint()..style = PaintingStyle.fill; _barStrokePaint = Paint()..style = PaintingStyle.stroke; _bgTouchTooltipPaint = Paint() ..style = PaintingStyle.fill ..color = Colors.white; _borderTouchTooltipPaint = Paint() ..style = PaintingStyle.stroke ..color = Colors.transparent ..strokeWidth = 1.0; _clipPaint = Paint(); } late Paint _barPaint; late Paint _barStrokePaint; late Paint _bgTouchTooltipPaint; late Paint _borderTouchTooltipPaint; late Paint _clipPaint; List? _groupBarsPosition; /// Paints [BarChartData] into the provided canvas. @override void paint( BuildContext context, CanvasWrapper canvasWrapper, PaintHolder holder, ) { if (holder.chartVirtualRect != null) { final canvasRect = Offset.zero & canvasWrapper.size; canvasWrapper ..saveLayer( canvasRect, _clipPaint, ) ..clipRect(canvasRect); } super.paint(context, canvasWrapper, holder); final data = holder.data; final targetData = holder.targetData; if (data.barGroups.isEmpty) { return; } final usableSize = holder.getChartUsableSize(canvasWrapper.size); final groupsX = data.calculateGroupsX(usableSize.width); final adjustment = holder.chartVirtualRect?.left ?? 0; final groupsXAdjusted = groupsX.map((e) => e + adjustment).toList(); _groupBarsPosition = calculateGroupAndBarsPosition( usableSize, groupsXAdjusted, data.barGroups, ); if (!data.extraLinesData.extraLinesOnTop) { super.drawHorizontalLines( context, canvasWrapper, holder, usableSize, ); } drawBars(canvasWrapper, _groupBarsPosition!, holder); drawBarLabels(context, canvasWrapper, _groupBarsPosition!, holder); drawErrorIndicatorData(canvasWrapper, _groupBarsPosition!, holder); if (data.extraLinesData.extraLinesOnTop) { super.drawHorizontalLines( context, canvasWrapper, holder, usableSize, ); } if (holder.chartVirtualRect != null) { canvasWrapper.restore(); } for (var i = 0; i < data.barGroups.length; i++) { final barGroup = data.barGroups[i]; for (var j = 0; j < barGroup.barRods.length; j++) { if (!barGroup.showingTooltipIndicators.contains(j)) { continue; } final barRod = barGroup.barRods[j]; drawTouchTooltip( context, canvasWrapper, _groupBarsPosition!, targetData.barTouchData.touchTooltipData, barGroup, i, barRod, j, holder, ); } } } /// Calculates bars position alongside group positions. @visibleForTesting List calculateGroupAndBarsPosition( Size viewSize, List groupsX, List barGroups, ) { if (groupsX.length != barGroups.length) { throw Exception('inconsistent state groupsX.length != barGroups.length'); } final groupBarsPosition = []; for (var i = 0; i < barGroups.length; i++) { final barGroup = barGroups[i]; final groupX = groupsX[i]; if (barGroup.groupVertically) { groupBarsPosition.add( GroupBarsPosition( groupX, List.generate(barGroup.barRods.length, (index) => groupX), ), ); continue; } var tempX = 0.0; final barsX = []; barGroup.barRods.asMap().forEach((barIndex, barRod) { final widthHalf = barRod.width / 2; barsX.add(groupX - (barGroup.width / 2) + tempX + widthHalf); tempX += barRod.width + barGroup.barsSpace; }); groupBarsPosition.add(GroupBarsPosition(groupX, barsX)); } return groupBarsPosition; } @visibleForTesting void drawBars( CanvasWrapper canvasWrapper, List groupBarsPosition, PaintHolder holder, ) { final data = holder.data; final viewSize = canvasWrapper.size; for (var i = 0; i < data.barGroups.length; i++) { final barGroup = data.barGroups[i]; for (var j = 0; j < barGroup.barRods.length; j++) { final barRod = barGroup.barRods[j]; final widthHalf = barRod.width / 2; final borderRadius = barRod.borderRadius ?? BorderRadius.circular(barRod.width / 2); final borderSide = barRod.borderSide; final x = groupBarsPosition[i].barsX[j]; final left = x - widthHalf; final right = x + widthHalf; final cornerHeight = max(borderRadius.topLeft.y, borderRadius.topRight.y) + max(borderRadius.bottomLeft.y, borderRadius.bottomRight.y); RRect barRRect; /// Draw [BackgroundBarChartRodData] if (barRod.backDrawRodData.show && barRod.backDrawRodData.toY != barRod.backDrawRodData.fromY) { if (barRod.backDrawRodData.toY > barRod.backDrawRodData.fromY) { // positive final bottom = getPixelY( max(data.minY, barRod.backDrawRodData.fromY), viewSize, holder, ); final top = min( getPixelY(barRod.backDrawRodData.toY, viewSize, holder), bottom - cornerHeight, ); barRRect = RRect.fromLTRBAndCorners( left, top, right, bottom, topLeft: borderRadius.topLeft, topRight: borderRadius.topRight, bottomLeft: borderRadius.bottomLeft, bottomRight: borderRadius.bottomRight, ); } else { // negative final top = getPixelY( min(data.maxY, barRod.backDrawRodData.fromY), viewSize, holder, ); final bottom = max( getPixelY(barRod.backDrawRodData.toY, viewSize, holder), top + cornerHeight, ); barRRect = RRect.fromLTRBAndCorners( left, top, right, bottom, topLeft: borderRadius.topLeft, topRight: borderRadius.topRight, bottomLeft: borderRadius.bottomLeft, bottomRight: borderRadius.bottomRight, ); } final backDraw = barRod.backDrawRodData; _barPaint.setColorOrGradient( backDraw.color, backDraw.gradient, barRRect.getRect(), ); canvasWrapper.drawRRect(barRRect, _barPaint); } // draw Main Rod if (barRod.toY != barRod.fromY) { if (barRod.toY > barRod.fromY) { // positive final bottom = getPixelY(max(data.minY, barRod.fromY), viewSize, holder); final top = min( getPixelY(barRod.toY, viewSize, holder), bottom - cornerHeight, ); barRRect = RRect.fromLTRBAndCorners( left, top, right, bottom, topLeft: borderRadius.topLeft, topRight: borderRadius.topRight, bottomLeft: borderRadius.bottomLeft, bottomRight: borderRadius.bottomRight, ); } else { // negative final top = getPixelY(min(data.maxY, barRod.fromY), viewSize, holder); final bottom = max( getPixelY(barRod.toY, viewSize, holder), top + cornerHeight, ); barRRect = RRect.fromLTRBAndCorners( left, top, right, bottom, topLeft: borderRadius.topLeft, topRight: borderRadius.topRight, bottomLeft: borderRadius.bottomLeft, bottomRight: borderRadius.bottomRight, ); } _barPaint.setColorOrGradient( barRod.color, barRod.gradient, barRRect.getRect(), ); canvasWrapper.drawRRect(barRRect, _barPaint); // draw rod stack if (barRod.rodStackItems.isNotEmpty) { // Calculate scale factor to ensure minimum height for corner radius final totalHeightPixels = (getPixelY(barRod.fromY, viewSize, holder) - getPixelY(barRod.toY, viewSize, holder)) .abs(); final scaleFactor = totalHeightPixels < cornerHeight ? cornerHeight / totalHeightPixels : 1.0; for (var i = 0; i < barRod.rodStackItems.length; i++) { final stackItem = barRod.rodStackItems[i]; var stackFromY = getPixelY(stackItem.fromY, viewSize, holder); var stackToY = getPixelY(stackItem.toY, viewSize, holder); // Apply scale factor only when needed if (scaleFactor > 1.0) { final basePixelY = getPixelY(barRod.fromY, viewSize, holder); stackFromY = basePixelY - (basePixelY - stackFromY) * scaleFactor; stackToY = basePixelY - (basePixelY - stackToY) * scaleFactor; } final isNegative = stackItem.toY < stackItem.fromY; final rect = isNegative ? Rect.fromLTRB(left, stackFromY, right, stackToY) : Rect.fromLTRB(left, stackToY, right, stackFromY); _barPaint.setColorOrGradient( stackItem.color, stackItem.gradient, rect, ); canvasWrapper ..save() ..clipRect(rect) ..drawRRect(barRRect, _barPaint) ..restore(); if (stackItem.label != null) { final textStyle = stackItem.labelStyle ?? const TextStyle( color: Colors.white, fontSize: 12, fontWeight: FontWeight.bold, ); final labelText = stackItem.label!; final textSpan = TextSpan(text: labelText, style: textStyle); final textPainter = TextPainter( text: textSpan, textAlign: TextAlign.center, textDirection: TextDirection.ltr, textScaler: holder.textScaler, )..layout(); // Calculate rotation final rotation = -holder.data.rotationQuarterTurns * (pi / 2); final centerX = x; final centerY = (stackFromY + stackToY) / 2; // Check if text fits vertically final segmentHeight = (stackFromY - stackToY).abs(); if (textPainter.height < segmentHeight) { canvasWrapper ..save() ..translate(centerX, centerY) ..rotate(rotation) ..translate( -textPainter.width / 2, -textPainter.height / 2, ); textPainter.paint(canvasWrapper.canvas, Offset.zero); canvasWrapper.restore(); } } // draw border stroke for each stack item drawStackItemBorderStroke( canvasWrapper, stackItem, i, barRod.rodStackItems.length, barRod.width, barRRect, viewSize, holder, ); } } // draw border stroke if (borderSide.width > 0 && borderSide.color.a > 0) { _barStrokePaint ..color = borderSide.color ..strokeWidth = borderSide.width; final borderPath = Path()..addRRect(barRRect); canvasWrapper.drawPath( borderPath.toDashedPath( barRod.borderDashArray, ), _barStrokePaint, ); } } } } } @visibleForTesting void drawBarLabels( BuildContext context, CanvasWrapper canvasWrapper, List groupBarsPosition, PaintHolder holder, ) { final data = holder.data; final viewSize = canvasWrapper.size; for (var i = 0; i < data.barGroups.length; i++) { final barGroup = data.barGroups[i]; for (var j = 0; j < barGroup.barRods.length; j++) { final barRod = barGroup.barRods[j]; final rodLabel = barRod.label; if (!rodLabel.show) continue; final labelText = rodLabel.text; final labelStyle = rodLabel.style; if (labelText.isEmpty) continue; final effectiveStyle = Utils().getThemeAwareTextStyle(context, labelStyle); final textSpan = TextSpan(text: labelText, style: effectiveStyle); final textPainter = TextPainter( text: textSpan, textAlign: TextAlign.center, textDirection: rodLabel.textDirection, textScaler: holder.textScaler, )..layout(); final x = groupBarsPosition[i].barsX[j]; final tipPixelY = getPixelY(barRod.toY, viewSize, holder); final isUpward = barRod.toY >= barRod.fromY; final drawX = x + rodLabel.offset.dx; final drawY = isUpward ? tipPixelY - rodLabel.offset.dy - textPainter.height : tipPixelY + rodLabel.offset.dy; final chartRotation = -data.rotationQuarterTurns * (pi / 2); final labelAngle = rodLabel.angle * (pi / 180); canvasWrapper ..save() ..translate(drawX, drawY + textPainter.height / 2) ..rotate(chartRotation + labelAngle) ..translate(-textPainter.width / 2, -textPainter.height / 2); textPainter.paint(canvasWrapper.canvas, Offset.zero); canvasWrapper.restore(); } } } @visibleForTesting void drawErrorIndicatorData( CanvasWrapper canvasWrapper, List groupBarsPosition, PaintHolder holder, ) { final data = holder.data; final errorIndicatorData = data.errorIndicatorData; if (!errorIndicatorData.show) { return; } final viewSize = canvasWrapper.size; for (var i = 0; i < data.barGroups.length; i++) { final barGroup = data.barGroups[i]; for (var j = 0; j < barGroup.barRods.length; j++) { final barRod = barGroup.barRods[j]; if (barRod.toYErrorRange == null) { continue; } final x = groupBarsPosition[i].barsX[j]; final y = getPixelY(barRod.toY, viewSize, holder); final top = getPixelY( barRod.toY + barRod.toYErrorRange!.upperBy, viewSize, holder, ) - y; final bottom = getPixelY( barRod.toY - barRod.toYErrorRange!.lowerBy, viewSize, holder, ) - y; final relativeErrorPixelsRect = Rect.fromLTRB( 0, top, 0, bottom, ); final painter = errorIndicatorData.painter( BarChartSpotErrorRangeCallbackInput( group: barGroup, groupIndex: i, rod: barRod, barRodIndex: j, ), ); canvasWrapper.drawErrorIndicator( painter, FlSpot( barGroup.x.toDouble(), barRod.toY, yError: barRod.toYErrorRange, ), Offset(x, y), relativeErrorPixelsRect, holder.data, ); } } } @visibleForTesting void drawTouchTooltip( BuildContext context, CanvasWrapper canvasWrapper, List groupPositions, BarTouchTooltipData tooltipData, BarChartGroupData showOnBarGroup, int barGroupIndex, BarChartRodData showOnRodData, int barRodIndex, PaintHolder holder, ) { final viewSize = canvasWrapper.size; const textsBelowMargin = 4; final tooltipItem = tooltipData.getTooltipItem( showOnBarGroup, barGroupIndex, showOnRodData, barRodIndex, ); if (tooltipItem == null) { return; } final span = TextSpan( style: Utils().getThemeAwareTextStyle(context, tooltipItem.textStyle), text: tooltipItem.text, children: tooltipItem.children, ); final tp = TextPainter( text: span, textAlign: tooltipItem.textAlign, textDirection: tooltipItem.textDirection, textScaler: holder.textScaler, )..layout(maxWidth: tooltipData.maxContentWidth); /// creating TextPainters to calculate the width and height of the tooltip final drawingTextPainter = tp; /// biggerWidth /// some texts maybe larger, then we should /// draw the tooltip' width as wide as biggerWidth /// /// sumTextsHeight /// sum up all Texts height, then we should /// draw the tooltip's height as tall as sumTextsHeight final textWidth = drawingTextPainter.width; final textHeight = drawingTextPainter.height + textsBelowMargin; final barX = groupPositions[barGroupIndex].barsX[barRodIndex]; /// if we have multiple bar lines, /// there are more than one FlCandidate on touch area, /// we should get the most top FlSpot Offset to draw the tooltip on top of it final barToYPixel = Offset( barX, getPixelY(showOnRodData.toY, viewSize, holder), ); final barFromYPixel = Offset( barX, getPixelY(showOnRodData.fromY, viewSize, holder), ); final tooltipWidth = textWidth + tooltipData.tooltipPadding.horizontal; final tooltipHeight = textHeight + tooltipData.tooltipPadding.vertical; var barTopY = min(barToYPixel.dy, barFromYPixel.dy); var barBottomY = max(barToYPixel.dy, barFromYPixel.dy); final drawTooltipOnTop = tooltipData.direction == TooltipDirection.top || (tooltipData.direction == TooltipDirection.auto && showOnRodData.isUpward()); // Shift tooltip anchor to avoid overlapping with rod label final rodLabel = showOnRodData.label; if (rodLabel.show && rodLabel.text.isNotEmpty) { final labelStyle = Utils().getThemeAwareTextStyle(context, rodLabel.style); final labelSpan = TextSpan(text: rodLabel.text, style: labelStyle); final labelPainter = TextPainter( text: labelSpan, textAlign: TextAlign.center, textDirection: rodLabel.textDirection, textScaler: holder.textScaler, )..layout(); final labelSpace = labelPainter.height + rodLabel.offset.dy; if (drawTooltipOnTop) { barTopY -= labelSpace; } else { barBottomY += labelSpace; } } final tooltipOriginPoint = Offset( barX, drawTooltipOnTop ? barTopY : barBottomY, ); final isZoomed = holder.chartVirtualRect != null; if (isZoomed && !canvasWrapper.size.contains(tooltipOriginPoint)) { return; } final tooltipTop = drawTooltipOnTop ? barTopY - tooltipHeight - tooltipData.tooltipMargin : barBottomY + tooltipData.tooltipMargin; final tooltipLeft = getTooltipLeft( barToYPixel.dx, tooltipWidth, tooltipData.tooltipHorizontalAlignment, tooltipData.tooltipHorizontalOffset, ); /// draw the background rect with rounded radius // ignore: omit_local_variable_types Rect rect = Rect.fromLTWH( tooltipLeft, tooltipTop, tooltipWidth, tooltipHeight, ); if (tooltipData.fitInsideHorizontally) { if (rect.left < 0) { final shiftAmount = 0 - rect.left; rect = Rect.fromLTRB( rect.left + shiftAmount, rect.top, rect.right + shiftAmount, rect.bottom, ); } if (rect.right > viewSize.width) { final shiftAmount = rect.right - viewSize.width; rect = Rect.fromLTRB( rect.left - shiftAmount, rect.top, rect.right - shiftAmount, rect.bottom, ); } } if (tooltipData.fitInsideVertically) { if (rect.top < 0) { final shiftAmount = 0 - rect.top; rect = Rect.fromLTRB( rect.left, rect.top + shiftAmount, rect.right, rect.bottom + shiftAmount, ); } if (rect.bottom > viewSize.height) { final shiftAmount = rect.bottom - viewSize.height; rect = Rect.fromLTRB( rect.left, rect.top - shiftAmount, rect.right, rect.bottom - shiftAmount, ); } } final roundedRect = RRect.fromRectAndCorners( rect, topLeft: tooltipData.tooltipBorderRadius.topLeft, topRight: tooltipData.tooltipBorderRadius.topRight, bottomLeft: tooltipData.tooltipBorderRadius.bottomLeft, bottomRight: tooltipData.tooltipBorderRadius.bottomRight, ); /// set tooltip's background color for each rod _bgTouchTooltipPaint.color = tooltipData.getTooltipColor(showOnBarGroup); final rotateAngle = tooltipData.rotateAngle; final rectRotationOffset = Offset(0, Utils().calculateRotationOffset(rect.size, rotateAngle).dy); final rectDrawOffset = Offset(roundedRect.left, roundedRect.top); final textRotationOffset = Utils().calculateRotationOffset(tp.size, rotateAngle); /// draw the texts one by one in below of each other final top = tooltipData.tooltipPadding.top; final drawOffset = Offset( rect.center.dx - (tp.width / 2), rect.topCenter.dy + top - textRotationOffset.dy + rectRotationOffset.dy, ); if (tooltipData.tooltipBorder != BorderSide.none) { _borderTouchTooltipPaint ..color = tooltipData.tooltipBorder.color ..strokeWidth = tooltipData.tooltipBorder.width; } final reverseQuarterTurnsAngle = -holder.data.rotationQuarterTurns * 90; canvasWrapper.drawRotated( size: rect.size, rotationOffset: rectRotationOffset, drawOffset: rectDrawOffset, angle: reverseQuarterTurnsAngle + rotateAngle, drawCallback: () { canvasWrapper ..drawRRect(roundedRect, _bgTouchTooltipPaint) ..drawRRect(roundedRect, _borderTouchTooltipPaint) ..drawText(tp, drawOffset); }, ); } @visibleForTesting void drawStackItemBorderStroke( CanvasWrapper canvasWrapper, BarChartRodStackItem stackItem, int index, int rodStacksSize, double barThickSize, RRect barRRect, Size drawSize, PaintHolder holder, ) { if (stackItem.borderSide.width == 0 || stackItem.borderSide.color.a == 0) { return; } RRect strokeBarRect; if (index == 0) { strokeBarRect = RRect.fromLTRBAndCorners( barRRect.left, getPixelY(stackItem.toY, drawSize, holder), barRRect.right, getPixelY(stackItem.fromY, drawSize, holder), bottomLeft: stackItem.fromY < stackItem.toY ? barRRect.blRadius : Radius.zero, bottomRight: stackItem.fromY < stackItem.toY ? barRRect.brRadius : Radius.zero, topLeft: stackItem.fromY < stackItem.toY ? Radius.zero : barRRect.tlRadius, topRight: stackItem.fromY < stackItem.toY ? Radius.zero : barRRect.trRadius, ); } else if (index == rodStacksSize - 1) { strokeBarRect = RRect.fromLTRBAndCorners( barRRect.left, max(getPixelY(stackItem.toY, drawSize, holder), barRRect.top), barRRect.right, getPixelY(stackItem.fromY, drawSize, holder), bottomLeft: stackItem.fromY < stackItem.toY ? Radius.zero : barRRect.blRadius, bottomRight: stackItem.fromY < stackItem.toY ? Radius.zero : barRRect.brRadius, topLeft: stackItem.fromY < stackItem.toY ? barRRect.tlRadius : Radius.zero, topRight: stackItem.fromY < stackItem.toY ? barRRect.trRadius : Radius.zero, ); } else { strokeBarRect = RRect.fromLTRBR( barRRect.left, getPixelY(stackItem.toY, drawSize, holder), barRRect.right, getPixelY(stackItem.fromY, drawSize, holder), Radius.zero, ); } _barStrokePaint ..color = stackItem.borderSide.color ..strokeWidth = min(stackItem.borderSide.width, barThickSize / 2); canvasWrapper.drawRRect(strokeBarRect, _barStrokePaint); } /// Makes a [BarTouchedSpot] based on the provided [localPosition] /// /// Processes [localPosition] and checks /// the elements of the chart that are near the offset, /// then makes a [BarTouchedSpot] from the elements that has been touched. /// /// Returns null if finds nothing! BarTouchedSpot? handleTouch( Offset localPosition, Size size, PaintHolder holder, ) { final data = holder.data; final targetData = holder.targetData; final touchedPoint = localPosition; if (targetData.barGroups.isEmpty) { return null; } final viewSize = holder.getChartUsableSize(size); // Check if the touch is outside the canvas bounds final isZoomed = holder.chartVirtualRect != null; if (isZoomed && !size.contains(touchedPoint)) { return null; } if (_groupBarsPosition == null) { final groupsX = data.calculateGroupsX(viewSize.width); _groupBarsPosition = calculateGroupAndBarsPosition(viewSize, groupsX, data.barGroups); } /// Find the nearest barRod for (var i = 0; i < _groupBarsPosition!.length; i++) { final groupBarPos = _groupBarsPosition![i]; for (var j = 0; j < groupBarPos.barsX.length; j++) { final barX = groupBarPos.barsX[j]; final barWidth = targetData.barGroups[i].barRods[j].width; final halfBarWidth = barWidth / 2; double barTopY; double barBotY; final isUpward = targetData.barGroups[i].barRods[j].isUpward(); if (isUpward) { barTopY = getPixelY( targetData.barGroups[i].barRods[j].toY, viewSize, holder, ); barBotY = getPixelY( targetData.barGroups[i].barRods[j].fromY + targetData.barGroups[i].barRods[j].backDrawRodData.fromY, viewSize, holder, ); } else { barTopY = getPixelY( targetData.barGroups[i].barRods[j].fromY + targetData.barGroups[i].barRods[j].backDrawRodData.fromY, viewSize, holder, ); barBotY = getPixelY( targetData.barGroups[i].barRods[j].toY, viewSize, holder, ); } final backDrawBarY = getPixelY( targetData.barGroups[i].barRods[j].backDrawRodData.toY, viewSize, holder, ); final touchExtraThreshold = targetData.barTouchData.touchExtraThreshold; final isXInTouchBounds = (touchedPoint.dx <= barX + halfBarWidth + touchExtraThreshold.right) && (touchedPoint.dx >= barX - halfBarWidth - touchExtraThreshold.left); bool isYInBarBounds; if (isUpward) { isYInBarBounds = (touchedPoint.dy <= barBotY + touchExtraThreshold.bottom) && (touchedPoint.dy >= barTopY - touchExtraThreshold.top); } else { isYInBarBounds = (touchedPoint.dy >= barTopY - touchExtraThreshold.top) && (touchedPoint.dy <= barBotY + touchExtraThreshold.bottom); } bool isYInBarBackDrawBounds; if (isUpward) { isYInBarBackDrawBounds = (touchedPoint.dy <= barBotY + touchExtraThreshold.bottom) && (touchedPoint.dy >= backDrawBarY - touchExtraThreshold.top); } else { isYInBarBackDrawBounds = (touchedPoint.dy >= barTopY - touchExtraThreshold.top) && (touchedPoint.dy <= backDrawBarY + touchExtraThreshold.bottom); } final isYInTouchBounds = (targetData.barTouchData.allowTouchBarBackDraw && isYInBarBackDrawBounds) || isYInBarBounds; if (isXInTouchBounds && isYInTouchBounds) { final nearestGroup = targetData.barGroups[i]; final nearestBarRod = nearestGroup.barRods[j]; final nearestSpot = FlSpot(nearestGroup.x.toDouble(), nearestBarRod.toY); final nearestSpotPos = Offset(barX, getPixelY(nearestSpot.y, viewSize, holder)); var touchedStackIndex = -1; BarChartRodStackItem? touchedStack; for (var stackIndex = 0; stackIndex < nearestBarRod.rodStackItems.length; stackIndex++) { final stackItem = nearestBarRod.rodStackItems[stackIndex]; final fromPixel = getPixelY(stackItem.fromY, viewSize, holder); final toPixel = getPixelY(stackItem.toY, viewSize, holder); if (touchedPoint.dy <= fromPixel && touchedPoint.dy >= toPixel) { touchedStackIndex = stackIndex; touchedStack = stackItem; break; } } return BarTouchedSpot( nearestGroup, i, nearestBarRod, j, touchedStack, touchedStackIndex, nearestSpot, nearestSpotPos, ); } } } return null; } } @visibleForTesting class GroupBarsPosition { GroupBarsPosition(this.groupX, this.barsX); final double groupX; final List barsX; } ================================================ FILE: lib/src/chart/bar_chart/bar_chart_renderer.dart ================================================ import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/chart/bar_chart/bar_chart_painter.dart'; import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart'; import 'package:fl_chart/src/chart/base/base_chart/render_base_chart.dart'; import 'package:fl_chart/src/utils/canvas_wrapper.dart'; import 'package:flutter/cupertino.dart'; // coverage:ignore-start /// Low level BarChart Widget. class BarChartLeaf extends LeafRenderObjectWidget { const BarChartLeaf({ super.key, required this.data, required this.targetData, required this.canBeScaled, required this.chartVirtualRect, }); final BarChartData data; final BarChartData targetData; final Rect? chartVirtualRect; final bool canBeScaled; @override RenderBarChart createRenderObject(BuildContext context) => RenderBarChart( context, data, targetData, MediaQuery.of(context).textScaler, chartVirtualRect, canBeScaled: canBeScaled, ); @override void updateRenderObject(BuildContext context, RenderBarChart renderObject) { renderObject ..data = data ..targetData = targetData ..textScaler = MediaQuery.of(context).textScaler ..buildContext = context ..chartVirtualRect = chartVirtualRect ..canBeScaled = canBeScaled; } } // coverage:ignore-end /// Renders our BarChart, also handles hitTest. class RenderBarChart extends RenderBaseChart { RenderBarChart( BuildContext context, BarChartData data, BarChartData targetData, TextScaler textScaler, Rect? chartVirtualRect, { required bool canBeScaled, }) : _data = data, _targetData = targetData, _textScaler = textScaler, _chartVirtualRect = chartVirtualRect, super(targetData.barTouchData, context, canBeScaled: canBeScaled); BarChartData get data => _data; BarChartData _data; set data(BarChartData value) { if (_data == value) return; _data = value; markNeedsPaint(); } BarChartData get targetData => _targetData; BarChartData _targetData; set targetData(BarChartData value) { if (_targetData == value) return; _targetData = value; super.updateBaseTouchData(_targetData.barTouchData); markNeedsPaint(); } TextScaler get textScaler => _textScaler; TextScaler _textScaler; set textScaler(TextScaler value) { if (_textScaler == value) return; _textScaler = value; markNeedsPaint(); } Rect? get chartVirtualRect => _chartVirtualRect; Rect? _chartVirtualRect; set chartVirtualRect(Rect? value) { if (_chartVirtualRect == value) return; _chartVirtualRect = value; markNeedsPaint(); } // We couldn't mock [size] property of this class, that's why we have this @visibleForTesting Size? mockTestSize; @visibleForTesting BarChartPainter painter = BarChartPainter(); PaintHolder get paintHolder => PaintHolder(data, targetData, textScaler, chartVirtualRect); @override void paint(PaintingContext context, Offset offset) { final canvas = context.canvas ..save() ..translate(offset.dx, offset.dy); painter.paint( buildContext, CanvasWrapper(canvas, mockTestSize ?? size), paintHolder, ); canvas.restore(); } @override bool hitTestSelf(Offset position) { if (!targetData.barTouchData.enabled) { return false; } return super.hitTestSelf(position); } @override BarTouchResponse getResponseAtLocation(Offset localPosition) { final chartSize = mockTestSize ?? size; return BarTouchResponse( touchLocation: localPosition, touchChartCoordinate: painter.getChartCoordinateFromPixel( localPosition, chartSize, paintHolder, ), spot: painter.handleTouch( localPosition, chartSize, paintHolder, ), ); } } ================================================ FILE: lib/src/chart/base/axis_chart/axis_chart_data.dart ================================================ // coverage:ignore-file import 'dart:ui'; import 'package:equatable/equatable.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/chart/base/axis_chart/axis_chart_painter.dart'; import 'package:fl_chart/src/extensions/paint_extension.dart'; import 'package:fl_chart/src/utils/canvas_wrapper.dart'; import 'package:fl_chart/src/utils/lerp.dart'; import 'package:fl_chart/src/utils/utils.dart'; import 'package:flutter/material.dart' hide Image; /// This is the base class for axis base charts data /// that contains a [FlGridData] that holds data for showing grid lines, /// also we have [minX], [maxX], [minY], [maxY] values /// we use them to determine how much is the scale of chart, /// and calculate x and y according to the scale. /// each child have to set it in their constructor. abstract class AxisChartData extends BaseChartData with EquatableMixin { AxisChartData({ FlGridData? gridData, required this.titlesData, RangeAnnotations? rangeAnnotations, required this.minX, required this.maxX, double? baselineX, required this.minY, required this.maxY, double? baselineY, FlClipData? clipData, Color? backgroundColor, super.borderData, ExtraLinesData? extraLinesData, this.rotationQuarterTurns = 0, }) : gridData = gridData ?? const FlGridData(), rangeAnnotations = rangeAnnotations ?? const RangeAnnotations(), baselineX = baselineX ?? 0, baselineY = baselineY ?? 0, clipData = clipData ?? const FlClipData.none(), backgroundColor = backgroundColor ?? Colors.transparent, extraLinesData = extraLinesData ?? const ExtraLinesData(); final FlGridData gridData; final FlTitlesData titlesData; final RangeAnnotations rangeAnnotations; final double minX; final double maxX; final double baselineX; final double minY; final double maxY; final double baselineY; /// clip the chart to the border (prevent draw outside the border) final FlClipData clipData; /// A background color which is drawn behind the chart. final Color backgroundColor; /// Difference of [maxY] and [minY] double get verticalDiff => maxY - minY; /// Difference of [maxX] and [minX] double get horizontalDiff => maxX - minX; /// Extra horizontal or vertical lines to draw on the chart. final ExtraLinesData extraLinesData; /// Rotates the chart by 90 degrees clockwise in each turn final int rotationQuarterTurns; /// Used for equality check, see [EquatableMixin]. @override List get props => [ gridData, titlesData, rangeAnnotations, minX, maxX, baselineX, minY, maxY, baselineY, clipData, backgroundColor, borderData, extraLinesData, rotationQuarterTurns, ]; } /// This class holds the touch response details of the axis-based charts abstract class AxisBaseTouchResponse extends BaseTouchResponse { AxisBaseTouchResponse({ required super.touchLocation, required this.touchChartCoordinate, }); /// The axis coordinate of the touch in chart's coordinate system. final Offset touchChartCoordinate; } /// Represents a side of the chart enum AxisSide { left, top, right, bottom; AxisSide rotateByQuarterTurns(int quarterTurns) { const values = AxisSide.values; return values[(values.indexOf(this) + quarterTurns) % values.length]; } } /// Represents where the [SideTitles] are drawn in relation to the chart. enum SideTitleAlignment { outside, border, inside } /// Contains meta information about the drawing title. class TitleMeta { TitleMeta({ required this.min, required this.max, required this.parentAxisSize, required this.axisPosition, required this.appliedInterval, required this.sideTitles, required this.formattedValue, required this.axisSide, required this.rotationQuarterTurns, }) : assert( rotationQuarterTurns >= 0, "TitleMeta.rotationQuarterTurns couldn't be negative", ); /// min axis value final double min; /// max axis value final double max; /// parent axis max width/height final double parentAxisSize; /// The position (in pixel) that applied to /// this drawing title along its axis. final double axisPosition; /// The interval that applied to this drawing title final double appliedInterval; /// Reference of [SideTitles] object. final SideTitles sideTitles; /// Formatted value that is suitable to show, for example 100, 2k, 5m, ... final String formattedValue; /// Determines the axis side of titles (left, top, right, bottom) final AxisSide axisSide; /// Chart is rotated by 90 degrees clockwise in each turn /// /// default is zero, which means chart is normal and upward final int rotationQuarterTurns; } /// It gives you the axis value and gets a String value based on it. typedef GetTitleWidgetFunction = Widget Function(double value, TitleMeta meta); /// The default [SideTitles.getTitlesWidget] function. /// /// formats the axis number to a shorter string using [formatNumber]. Widget defaultGetTitle(double value, TitleMeta meta) { return SideTitleWidget( meta: meta, child: Text( meta.formattedValue, ), ); } /// Holds data for showing label values on axis numbers class SideTitles with EquatableMixin { /// It draws some title on an axis, per axis values, /// [showTitles] determines showing or hiding this side, /// /// Texts are depend on the axis value, you can override [getTitles], /// it gives you an axis value (double value) and a [TitleMeta] which contains /// additional information about the axis. /// Then you should return a [Widget] to show. /// It allows you to do anything you want, For example you can show icons /// instead of texts, because it accepts a [Widget] /// /// [reservedSize] determines the maximum space that your titles need, /// (All titles will stretch using this value) /// /// Texts are showing with provided [interval]. If you don't provide anything, /// we try to find a suitable value to set as [interval] under the hood. const SideTitles({ this.showTitles = false, this.getTitlesWidget = defaultGetTitle, this.reservedSize = 22, this.interval, this.minIncluded = true, this.maxIncluded = true, }) : assert(interval != 0, "SideTitles.interval couldn't be zero"); /// Determines showing or hiding this side titles final bool showTitles; /// You can override it to pass your custom widget to show in each axis value /// We recommend you to use [SideTitleWidget]. /// /// If you decide to implement your custom widget /// (instead of [SideTitleWidget]), you have to take care of the alignment, /// space to the chart and also the rotation (if you are rotating the chart, /// for example for Horizontal Bar Chart) final GetTitleWidgetFunction getTitlesWidget; /// It determines the maximum space that your titles need, /// (All titles will stretch using this value) final double reservedSize; /// Texts are showing with provided [interval]. If you don't provide anything, /// we try to find a suitable value to set as [interval] under the hood. final double? interval; /// If true (default), a title for the minimum data value is included /// independent of the sampling interval final bool minIncluded; /// If true (default), a title for the maximum data value is included /// independent of the sampling interval final bool maxIncluded; /// Lerps a [SideTitles] based on [t] value, check [Tween.lerp]. static SideTitles lerp(SideTitles a, SideTitles b, double t) => SideTitles( showTitles: b.showTitles, getTitlesWidget: b.getTitlesWidget, reservedSize: lerpDouble(a.reservedSize, b.reservedSize, t)!, interval: lerpDouble(a.interval, b.interval, t), minIncluded: b.minIncluded, maxIncluded: b.maxIncluded, ); /// Copies current [SideTitles] to a new [SideTitles], /// and replaces provided values. SideTitles copyWith({ bool? showTitles, GetTitleWidgetFunction? getTitlesWidget, double? reservedSize, double? interval, bool? minIncluded, bool? maxIncluded, }) => SideTitles( showTitles: showTitles ?? this.showTitles, getTitlesWidget: getTitlesWidget ?? this.getTitlesWidget, reservedSize: reservedSize ?? this.reservedSize, interval: interval ?? this.interval, minIncluded: minIncluded ?? this.minIncluded, maxIncluded: maxIncluded ?? this.maxIncluded, ); /// Used for equality check, see [EquatableMixin]. @override List get props => [ showTitles, getTitlesWidget, reservedSize, interval, minIncluded, maxIncluded, ]; } /// Force child widget to be positioned inside its /// corresponding axis bounding box /// /// To makes things simpler, it's recommended to use /// [SideTitleFitInsideData.fromTitleMeta] and pass the /// TitleMeta provided from [SideTitles.getTitlesWidget] class SideTitleFitInsideData with EquatableMixin { /// Force child widget to be positioned inside its /// corresponding axis bounding box /// /// To makes things simpler, it's recommended to use /// [SideTitleFitInsideData.fromTitleMeta] and pass the /// TitleMeta provided from [SideTitles.getTitlesWidget] /// /// Some translations will be applied to force /// children to be positioned inside the parent axis bounding box. /// /// Will override the [SideTitleWidget.space] and caused /// spacing between [SideTitles] children might be not equal. const SideTitleFitInsideData({ required this.enabled, required this.axisPosition, required this.parentAxisSize, required this.distanceFromEdge, }); /// Create a disabled [SideTitleFitInsideData]. /// If used, the child widget wouldn't be fitted /// inside its corresponding axis bounding box factory SideTitleFitInsideData.disable() => const SideTitleFitInsideData( enabled: false, distanceFromEdge: 0, parentAxisSize: 0, axisPosition: 0, ); /// Help to Create [SideTitleFitInsideData] from [TitleMeta]. /// [TitleMeta] is provided by [SideTitles.getTitlesWidget] function. factory SideTitleFitInsideData.fromTitleMeta( TitleMeta meta, { bool enabled = true, double distanceFromEdge = 6, }) => SideTitleFitInsideData( enabled: enabled, distanceFromEdge: distanceFromEdge, parentAxisSize: meta.parentAxisSize, axisPosition: meta.axisPosition, ); /// Whether to enable fit inside to SideTitleWidget final bool enabled; /// Distance between child widget and its closest corresponding axis edge final double distanceFromEdge; /// Parent axis max width/height final double parentAxisSize; /// The position (in pixel) that applied to /// the child widget along its corresponding axis. final double axisPosition; @override List get props => [ enabled, distanceFromEdge, parentAxisSize, axisPosition, ]; } /// Holds data for showing each side titles (left, top, right, bottom) class AxisTitles with EquatableMixin { /// you can provide [axisName] if you want to show a general /// label on this axis, /// /// [axisNameSize] determines the maximum size that [axisName] can use /// /// [sideTitles] property is responsible to show your axis side labels const AxisTitles({ this.axisNameWidget, this.axisNameSize = 16, this.sideTitles = const SideTitles(), this.drawBelowEverything = true, this.sideTitleAlignment = SideTitleAlignment.outside, }); /// Determines the size of [axisName] final double axisNameSize; /// It shows the name of axis, for example your x-axis shows year, /// then you might want to show it using [axisNameWidget] property as a widget final Widget? axisNameWidget; /// It is responsible to show your axis side labels. final SideTitles sideTitles; /// If titles are showing on top of your tooltip, you can draw them below everything. /// /// In the future, we will convert tooltips to a widget, that would solve this problem. final bool drawBelowEverything; /// Where the [SideTitles] are drawn in relation to the chart. final SideTitleAlignment sideTitleAlignment; /// If there is something to show as axisTitles, it returns true bool get showAxisTitles => axisNameWidget != null && axisNameSize != 0; /// If there is something to show as sideTitles, it returns true bool get showSideTitles => sideTitles.showTitles && sideTitles.reservedSize != 0; /// Lerps a [AxisTitles] based on [t] value, check [Tween.lerp]. static AxisTitles lerp(AxisTitles a, AxisTitles b, double t) => AxisTitles( axisNameWidget: b.axisNameWidget, axisNameSize: lerpDouble(a.axisNameSize, b.axisNameSize, t)!, sideTitles: SideTitles.lerp(a.sideTitles, b.sideTitles, t), drawBelowEverything: b.drawBelowEverything, sideTitleAlignment: b.sideTitleAlignment, ); /// Copies current [SideTitles] to a new [SideTitles], /// and replaces provided values. AxisTitles copyWith({ Widget? axisNameWidget, double? axisNameSize, SideTitles? sideTitles, bool? drawBelowEverything, SideTitleAlignment? sideTitleAlignment, }) => AxisTitles( axisNameWidget: axisNameWidget ?? this.axisNameWidget, axisNameSize: axisNameSize ?? this.axisNameSize, sideTitles: sideTitles ?? this.sideTitles, drawBelowEverything: drawBelowEverything ?? this.drawBelowEverything, sideTitleAlignment: sideTitleAlignment ?? this.sideTitleAlignment, ); /// Used for equality check, see [EquatableMixin]. @override List get props => [ axisNameWidget, axisNameSize, sideTitles, drawBelowEverything, sideTitleAlignment, ]; } /// Holds data for showing titles on each side of charts. class FlTitlesData with EquatableMixin { /// [show] determines showing or hiding all titles, /// [leftTitles], [topTitles], [rightTitles], [bottomTitles] defines /// side titles of left, top, right, bottom sides respectively. const FlTitlesData({ this.show = true, this.leftTitles = const AxisTitles( sideTitles: SideTitles( reservedSize: 44, showTitles: true, ), ), this.topTitles = const AxisTitles( sideTitles: SideTitles( reservedSize: 30, showTitles: true, ), ), this.rightTitles = const AxisTitles( sideTitles: SideTitles( reservedSize: 44, showTitles: true, ), ), this.bottomTitles = const AxisTitles( sideTitles: SideTitles( reservedSize: 30, showTitles: true, ), ), }); final bool show; final AxisTitles leftTitles; final AxisTitles topTitles; final AxisTitles rightTitles; final AxisTitles bottomTitles; /// Lerps a [FlTitlesData] based on [t] value, check [Tween.lerp]. static FlTitlesData lerp(FlTitlesData a, FlTitlesData b, double t) => FlTitlesData( show: b.show, leftTitles: AxisTitles.lerp(a.leftTitles, b.leftTitles, t), rightTitles: AxisTitles.lerp(a.rightTitles, b.rightTitles, t), bottomTitles: AxisTitles.lerp(a.bottomTitles, b.bottomTitles, t), topTitles: AxisTitles.lerp(a.topTitles, b.topTitles, t), ); /// Copies current [FlTitlesData] to a new [FlTitlesData], /// and replaces provided values. FlTitlesData copyWith({ bool? show, AxisTitles? leftTitles, AxisTitles? topTitles, AxisTitles? rightTitles, AxisTitles? bottomTitles, }) => FlTitlesData( show: show ?? this.show, leftTitles: leftTitles ?? this.leftTitles, topTitles: topTitles ?? this.topTitles, rightTitles: rightTitles ?? this.rightTitles, bottomTitles: bottomTitles ?? this.bottomTitles, ); /// Used for equality check, see [EquatableMixin]. @override List get props => [ show, leftTitles, topTitles, rightTitles, bottomTitles, ]; } /// Represents a conceptual position in cartesian (axis based) space. @immutable class FlSpot { /// [x] determines cartesian (axis based) horizontally position /// 0 means most left point of the chart /// /// [y] determines cartesian (axis based) vertically position /// 0 means most bottom point of the chart const FlSpot( this.x, this.y, { this.xError, this.yError, }); final double x; final double y; final FlErrorRange? xError; final FlErrorRange? yError; /// Copies current [FlSpot] to a new [FlSpot], /// and replaces provided values. // Prevent polymorphism FlSpot copyWith({ double? x, double? y, FlErrorRange? xError, FlErrorRange? yError, }) => FlSpot( x ?? this.x, y ?? this.y, xError: xError ?? this.xError, yError: yError ?? this.yError, ); ///Prints x and y coordinates of FlSpot list @override String toString() => '($x, $y, $xError, $yError)'; /// Used for splitting lines, or maybe other concepts. static const FlSpot nullSpot = FlSpot(double.nan, double.nan); /// Sets zero for x and y static const FlSpot zero = FlSpot(0, 0); /// Determines if [x] or [y] is null. bool isNull() => this == nullSpot; /// Determines if [x] and [y] is not null. bool isNotNull() => !isNull(); /// Lerps a [FlSpot] based on [t] value, check [Tween.lerp]. static FlSpot lerp(FlSpot a, FlSpot b, double t) { if (a == FlSpot.nullSpot) { return b; } if (b == FlSpot.nullSpot) { return a; } return FlSpot( lerpDouble(a.x, b.x, t)!, lerpDouble(a.y, b.y, t)!, xError: FlErrorRange.lerp(a.xError, b.xError, t), yError: FlErrorRange.lerp(a.yError, b.yError, t), ); } /// Two [FlSpot] are equal if their [x] and [y] are equal. @override bool operator ==(Object other) { if (identical(this, other)) return true; if (other is! FlSpot) { return false; } if (x.isNaN && y.isNaN && other.x.isNaN && other.y.isNaN) { return true; } return other.x == x && other.y == y && other.xError == xError && other.yError == yError; } /// Override hashCode @override int get hashCode => x.hashCode ^ y.hashCode ^ xError.hashCode ^ yError.hashCode; } /// Represents a range of values that can be used to show error bars/threshold /// /// [lowerBy] and [upperBy] are the values that will be added and subtracted /// from the main value. It means that they should be non-negative. /// Also it means that they are relative to the main value. class FlErrorRange with EquatableMixin { const FlErrorRange({ required this.lowerBy, required this.upperBy, }) : assert(lowerBy >= 0, 'lowerBy must be non-negative'), assert(upperBy >= 0, 'upperBy must be non-negative'); /// Creates a symmetric error range. /// It sets [lowerBy] and [upperBy] to the same [value]. const FlErrorRange.symmetric(double value) : lowerBy = value, upperBy = value, assert(value >= 0, 'value must be non-negative'); /// determines the lower bound of the error range, it will be subtracted from /// the main value. So it is non-negative and it is relative to the main value final double lowerBy; /// determines the lower bound of the error range, it will be added to /// the main value. So it is non-negative and it is relative to the main value final double upperBy; /// Lerps a [FlErrorRange] based on [t] value static FlErrorRange? lerp(FlErrorRange? a, FlErrorRange? b, double t) { if (a != null && b != null) { return FlErrorRange( lowerBy: lerpDouble(a.lowerBy, b.lowerBy, t)!, upperBy: lerpDouble(a.upperBy, b.upperBy, t)!, ); } return b; } @override List get props => [lowerBy, upperBy]; } /// Responsible to hold grid data, class FlGridData with EquatableMixin { /// Responsible for rendering grid lines behind the content of charts, /// [show] determines showing or hiding all grids, /// /// [AxisChartPainter] draws horizontal lines from left to right of the chart, /// with increasing y value, it increases by [horizontalInterval]. /// Representation of each line determines by [getDrawingHorizontalLine] callback, /// it gives you a double value (in the y axis), and you should return a [FlLine] that represents /// a horizontal line. /// You are allowed to show or hide any horizontal line, using [checkToShowHorizontalLine] callback, /// it gives you a double value (in the y axis), and you should return a boolean that determines /// showing or hiding specified line. /// or you can hide all horizontal lines by setting [drawHorizontalLine] false. /// /// [AxisChartPainter] draws vertical lines from bottom to top of the chart, /// with increasing x value, it increases by [verticalInterval]. /// Representation of each line determines by [getDrawingVerticalLine] callback, /// it gives you a double value (in the x axis), and you should return a [FlLine] that represents /// a horizontal line. /// You are allowed to show or hide any vertical line, using [checkToShowVerticalLine] callback, /// it gives you a double value (in the x axis), and you should return a boolean that determines /// showing or hiding specified line. /// or you can hide all vertical lines by setting [drawVerticalLine] false. const FlGridData({ this.show = true, this.drawHorizontalLine = true, this.horizontalInterval, this.getDrawingHorizontalLine = defaultGridLine, this.checkToShowHorizontalLine = showAllGrids, this.drawVerticalLine = true, this.verticalInterval, this.getDrawingVerticalLine = defaultGridLine, this.checkToShowVerticalLine = showAllGrids, }) : assert( horizontalInterval != 0, "FlGridData.horizontalInterval couldn't be zero", ), assert( verticalInterval != 0, "FlGridData.verticalInterval couldn't be zero", ); /// Determines showing or hiding all horizontal and vertical lines. final bool show; /// Determines showing or hiding all horizontal lines. final bool drawHorizontalLine; /// Determines interval between horizontal lines, left it null to be auto calculated. final double? horizontalInterval; /// Gives you a y value, and gets a [FlLine] that represents specified line. final GetDrawingGridLine getDrawingHorizontalLine; /// Gives you a y value, and gets a boolean that determines showing or hiding specified line. final CheckToShowGrid checkToShowHorizontalLine; /// Determines showing or hiding all vertical lines. final bool drawVerticalLine; /// Determines interval between vertical lines, left it null to be auto calculated. final double? verticalInterval; /// Gives you a x value, and gets a [FlLine] that represents specified line. final GetDrawingGridLine getDrawingVerticalLine; /// Gives you a x value, and gets a boolean that determines showing or hiding specified line. final CheckToShowGrid checkToShowVerticalLine; /// Lerps a [FlGridData] based on [t] value, check [Tween.lerp]. static FlGridData lerp(FlGridData a, FlGridData b, double t) => FlGridData( show: b.show, drawHorizontalLine: b.drawHorizontalLine, horizontalInterval: lerpDouble(a.horizontalInterval, b.horizontalInterval, t), getDrawingHorizontalLine: b.getDrawingHorizontalLine, checkToShowHorizontalLine: b.checkToShowHorizontalLine, drawVerticalLine: b.drawVerticalLine, verticalInterval: lerpDouble(a.verticalInterval, b.verticalInterval, t), getDrawingVerticalLine: b.getDrawingVerticalLine, checkToShowVerticalLine: b.checkToShowVerticalLine, ); /// Copies current [FlGridData] to a new [FlGridData], /// and replaces provided values. FlGridData copyWith({ bool? show, bool? drawHorizontalLine, double? horizontalInterval, GetDrawingGridLine? getDrawingHorizontalLine, CheckToShowGrid? checkToShowHorizontalLine, bool? drawVerticalLine, double? verticalInterval, GetDrawingGridLine? getDrawingVerticalLine, CheckToShowGrid? checkToShowVerticalLine, }) => FlGridData( show: show ?? this.show, drawHorizontalLine: drawHorizontalLine ?? this.drawHorizontalLine, horizontalInterval: horizontalInterval ?? this.horizontalInterval, getDrawingHorizontalLine: getDrawingHorizontalLine ?? this.getDrawingHorizontalLine, checkToShowHorizontalLine: checkToShowHorizontalLine ?? this.checkToShowHorizontalLine, drawVerticalLine: drawVerticalLine ?? this.drawVerticalLine, verticalInterval: verticalInterval ?? this.verticalInterval, getDrawingVerticalLine: getDrawingVerticalLine ?? this.getDrawingVerticalLine, checkToShowVerticalLine: checkToShowVerticalLine ?? this.checkToShowVerticalLine, ); /// Used for equality check, see [EquatableMixin]. @override List get props => [ show, drawHorizontalLine, horizontalInterval, getDrawingHorizontalLine, checkToShowHorizontalLine, drawVerticalLine, verticalInterval, getDrawingVerticalLine, checkToShowVerticalLine, ]; } /// Determines showing or hiding specified line. typedef CheckToShowGrid = bool Function(double value); /// Shows all lines. bool showAllGrids(double value) => true; /// Determines the appearance of specified line. /// /// It gives you an axis [value] (horizontal or vertical), /// you should pass a [FlLine] that represents style of specified line. typedef GetDrawingGridLine = FlLine Function(double value); /// Returns a grey line for all values. FlLine defaultGridLine(double value) => const FlLine( color: Colors.blueGrey, strokeWidth: 0.4, dashArray: [8, 4], ); /// Defines style of a line. class FlLine with EquatableMixin { /// Renders a line, color it by [color], /// thickness is defined by [strokeWidth], /// and if you want to have dashed line, you should fill [dashArray], /// it is a circular array of dash offsets and lengths. /// For example, the array `[5, 10]` would result in dashes 5 pixels long /// followed by blank spaces 10 pixels long. const FlLine({ Color? color, this.gradient, this.strokeWidth = 2, this.dashArray, }) : color = color ?? ((color == null && gradient == null) ? Colors.black : null); /// Defines color of the line. final Color? color; /// Defines the gradient of the line. final Gradient? gradient; /// Defines thickness of the line. final double strokeWidth; /// Defines dash effect of the line. /// /// it is a circular array of dash offsets and lengths. /// For example, the array `[5, 10]` would result in dashes 5 pixels long /// followed by blank spaces 10 pixels long. final List? dashArray; /// Lerps a [FlLine] based on [t] value, check [Tween.lerp]. static FlLine lerp(FlLine a, FlLine b, double t) => FlLine( color: Color.lerp(a.color, b.color, t), gradient: Gradient.lerp(a.gradient, b.gradient, t), strokeWidth: lerpDouble(a.strokeWidth, b.strokeWidth, t)!, dashArray: lerpIntList(a.dashArray, b.dashArray, t), ); /// Copies current [FlLine] to a new [FlLine], /// and replaces provided values. FlLine copyWith({ Color? color, Gradient? gradient, double? strokeWidth, List? dashArray, }) => FlLine( color: color ?? this.color, gradient: gradient ?? this.gradient, strokeWidth: strokeWidth ?? this.strokeWidth, dashArray: dashArray ?? this.dashArray, ); /// Used for equality check, see [EquatableMixin]. @override List get props => [ color, gradient, strokeWidth, dashArray, ]; } /// holds information about touched spot on the axis based charts. abstract class TouchedSpot with EquatableMixin { /// [spot] represents the spot inside our axis based chart, /// 0, 0 is bottom left, and 1, 1 is top right. /// /// [offset] is the touch position in device pixels, /// 0, 0 is top, left, and 1, 1 is bottom right. TouchedSpot( this.spot, this.offset, ); /// Represents the spot inside our axis based chart, /// 0, 0 is bottom left, and 1, 1 is top right. final FlSpot spot; /// Represents the touch position in device pixels, /// 0, 0 is top, left, and 1, 1 is bottom right. final Offset offset; /// Used for equality check, see [EquatableMixin]. @override List get props => [ spot, offset, ]; } /// Holds data for rendering horizontal and vertical range annotations. class RangeAnnotations with EquatableMixin { /// Axis based charts can annotate some horizontal and vertical regions, /// using [horizontalRangeAnnotations], and [verticalRangeAnnotations] respectively. const RangeAnnotations({ this.horizontalRangeAnnotations = const [], this.verticalRangeAnnotations = const [], }); final List horizontalRangeAnnotations; final List verticalRangeAnnotations; /// Lerps a [RangeAnnotations] based on [t] value, check [Tween.lerp]. static RangeAnnotations lerp( RangeAnnotations a, RangeAnnotations b, double t, ) => RangeAnnotations( horizontalRangeAnnotations: lerpHorizontalRangeAnnotationList( a.horizontalRangeAnnotations, b.horizontalRangeAnnotations, t, )!, verticalRangeAnnotations: lerpVerticalRangeAnnotationList( a.verticalRangeAnnotations, b.verticalRangeAnnotations, t, )!, ); /// Copies current [RangeAnnotations] to a new [RangeAnnotations], /// and replaces provided values. RangeAnnotations copyWith({ List? horizontalRangeAnnotations, List? verticalRangeAnnotations, }) => RangeAnnotations( horizontalRangeAnnotations: horizontalRangeAnnotations ?? this.horizontalRangeAnnotations, verticalRangeAnnotations: verticalRangeAnnotations ?? this.verticalRangeAnnotations, ); /// Used for equality check, see [EquatableMixin]. @override List get props => [ horizontalRangeAnnotations, verticalRangeAnnotations, ]; } /// Defines an annotation region in y (vertical) axis. class HorizontalRangeAnnotation with EquatableMixin { /// Annotates a horizontal region from most left to most right point of the chart, and /// from [y1] to [y2], and fills the area with [color] or [gradient]. HorizontalRangeAnnotation({ required this.y1, required this.y2, Color? color, this.gradient, }) : color = color ?? ((color == null && gradient == null) ? Colors.white : null); /// Determines starting point in vertical (y) axis. final double y1; /// Determines ending point in vertical (y) axis. final double y2; /// If provided, this [HorizontalRangeAnnotation] draws with this [color] /// Otherwise we use [gradient] to draw the background. /// It draws with [gradient] if you provide both [color] and [gradient]. /// If none is provided, it draws with a white color. final Color? color; /// If provided, this [HorizontalRangeAnnotation] draws with this [gradient] /// Otherwise we use [color] to draw the background. /// It draws with [gradient] if you provide both [color] and [gradient]. /// If none is provided, it draws with a white color. final Gradient? gradient; /// Lerps a [HorizontalRangeAnnotation] based on [t] value, check [Tween.lerp]. static HorizontalRangeAnnotation lerp( HorizontalRangeAnnotation a, HorizontalRangeAnnotation b, double t, ) => HorizontalRangeAnnotation( y1: lerpDouble(a.y1, b.y1, t)!, y2: lerpDouble(a.y2, b.y2, t)!, color: Color.lerp(a.color, b.color, t), gradient: Gradient.lerp(a.gradient, b.gradient, t), ); /// Copies current [HorizontalRangeAnnotation] to a new [HorizontalRangeAnnotation], /// and replaces provided values. HorizontalRangeAnnotation copyWith({ double? y1, double? y2, Color? color, Gradient? gradient, }) => HorizontalRangeAnnotation( y1: y1 ?? this.y1, y2: y2 ?? this.y2, color: color ?? this.color, gradient: gradient ?? this.gradient, ); /// Used for equality check, see [EquatableMixin]. @override List get props => [ y1, y2, color, gradient, ]; } /// Defines an annotation region in x (horizontal) axis. class VerticalRangeAnnotation with EquatableMixin { /// Annotates a vertical region from most bottom to most top point of the chart, and /// from [x1] to [x2], and fills the area with [color] or [gradient]. VerticalRangeAnnotation({ required this.x1, required this.x2, Color? color, this.gradient, }) : color = color ?? ((color == null && gradient == null) ? Colors.white : null); /// Determines starting point in horizontal (x) axis. final double x1; /// Determines ending point in horizontal (x) axis. final double x2; /// If provided, this [VerticalRangeAnnotation] draws with this [color] /// Otherwise we use [gradient] to draw the background. /// It draws with [gradient] if you provide both [color] and [gradient]. /// If none is provided, it draws with a white color. final Color? color; /// If provided, this [VerticalRangeAnnotation] draws with this [gradient] /// Otherwise we use [color] to draw the background. /// It draws with [gradient] if you provide both [color] and [gradient]. /// If none is provided, it draws with a white color. final Gradient? gradient; /// Lerps a [VerticalRangeAnnotation] based on [t] value, check [Tween.lerp]. static VerticalRangeAnnotation lerp( VerticalRangeAnnotation a, VerticalRangeAnnotation b, double t, ) => VerticalRangeAnnotation( x1: lerpDouble(a.x1, b.x1, t)!, x2: lerpDouble(a.x2, b.x2, t)!, color: Color.lerp(a.color, b.color, t), gradient: Gradient.lerp(a.gradient, b.gradient, t), ); /// Copies current [VerticalRangeAnnotation] to a new [VerticalRangeAnnotation], /// and replaces provided values. VerticalRangeAnnotation copyWith({ double? x1, double? x2, Color? color, Gradient? gradient, }) => VerticalRangeAnnotation( x1: x1 ?? this.x1, x2: x2 ?? this.x2, color: color ?? this.color, gradient: gradient ?? this.gradient, ); /// Used for equality check, see [EquatableMixin]. @override List get props => [ x1, x2, color, gradient, ]; } /// Holds data for drawing extra horizontal lines. /// /// [LineChart] draws some [HorizontalLine] (set by [LineChartData.extraLinesData]), /// in below or above of everything, it draws from left to right side of the chart. class HorizontalLine extends FlLine with EquatableMixin { /// [LineChart] draws horizontal lines from left to right side of the chart /// in the provided [y] value, and color it using [color]. /// You can define the thickness using [strokeWidth] /// /// It draws a [label] over it. /// /// You can have a dashed line by filling [dashArray] with dash size and space respectively. /// /// It draws an image in left side of the chart, use [sizedPicture] for vectors, /// or [image] for any kind of image. HorizontalLine({ required this.y, HorizontalLineLabel? label, super.color, super.gradient, super.strokeWidth, super.dashArray, this.image, this.sizedPicture, this.strokeCap = StrokeCap.butt, }) : label = label ?? HorizontalLineLabel(); /// Draws from left to right of the chart using the [y] value. final double y; /// Use it for any kind of image, to draw it in left side of the chart. final Image? image; /// Use it for vector images, to draw it in left side of the chart. final SizedPicture? sizedPicture; /// Draws a text label over the line. final HorizontalLineLabel label; /// if not drawing dash line, then this is the StrokeCap for the line. /// i.e. if the two ends of the line is round or butt or square. final StrokeCap strokeCap; /// Lerps a [HorizontalLine] based on [t] value, check [Tween.lerp]. static HorizontalLine lerp(HorizontalLine a, HorizontalLine b, double t) => HorizontalLine( y: lerpDouble(a.y, b.y, t)!, label: HorizontalLineLabel.lerp(a.label, b.label, t), color: Color.lerp(a.color, b.color, t), gradient: Gradient.lerp(a.gradient, b.gradient, t), strokeWidth: lerpDouble(a.strokeWidth, b.strokeWidth, t)!, dashArray: lerpIntList(a.dashArray, b.dashArray, t), image: b.image, sizedPicture: b.sizedPicture, strokeCap: b.strokeCap, ); /// Used for equality check, see [EquatableMixin]. @override List get props => [ y, label, color, strokeWidth, dashArray, image, sizedPicture, strokeCap, ]; } /// Holds data for drawing extra vertical lines. /// /// [LineChart] draws some [VerticalLine] (set by [LineChartData.extraLinesData]), /// in below or above of everything, it draws from bottom to top side of the chart. class VerticalLine extends FlLine with EquatableMixin { /// [LineChart] draws vertical lines from bottom to top side of the chart /// in the provided [x] value, and color it using [color]. /// You can define the thickness using [strokeWidth] /// /// It draws a [label] over it. /// /// You can have a dashed line by filling [dashArray] with dash size and space respectively. /// /// It draws an image in bottom side of the chart, use [sizedPicture] for vectors, /// or [image] for any kind of image. VerticalLine({ required this.x, VerticalLineLabel? label, super.color, super.gradient, super.strokeWidth, super.dashArray, this.image, this.sizedPicture, this.strokeCap = StrokeCap.butt, }) : label = label ?? VerticalLineLabel(); /// Draws from bottom to top of the chart using the [x] value. final double x; /// Use it for any kind of image, to draw it in bottom side of the chart. final Image? image; /// Use it for vector images, to draw it in bottom side of the chart. final SizedPicture? sizedPicture; /// Draws a text label over the line. final VerticalLineLabel label; /// if not drawing dash line, then this is the StrokeCap for the line. /// i.e. if the two ends of the line is round or butt or square. final StrokeCap strokeCap; /// Lerps a [VerticalLine] based on [t] value, check [Tween.lerp]. static VerticalLine lerp(VerticalLine a, VerticalLine b, double t) => VerticalLine( x: lerpDouble(a.x, b.x, t)!, label: VerticalLineLabel.lerp(a.label, b.label, t), color: Color.lerp(a.color, b.color, t), gradient: Gradient.lerp(a.gradient, b.gradient, t), strokeWidth: lerpDouble(a.strokeWidth, b.strokeWidth, t)!, dashArray: lerpIntList(a.dashArray, b.dashArray, t), image: b.image, sizedPicture: b.sizedPicture, strokeCap: b.strokeCap, ); /// Copies current [VerticalLine] to a new [VerticalLine] /// and replaces provided values. VerticalLine copyVerticalLineWith({ double? x, VerticalLineLabel? label, Color? color, double? strokeWidth, List? dashArray, Image? image, SizedPicture? sizedPicture, StrokeCap? strokeCap, }) => VerticalLine( x: x ?? this.x, label: label ?? this.label, color: color ?? this.color, strokeWidth: strokeWidth ?? this.strokeWidth, dashArray: dashArray ?? this.dashArray, image: image ?? this.image, sizedPicture: sizedPicture ?? this.sizedPicture, strokeCap: strokeCap ?? this.strokeCap, ); /// Used for equality check, see [EquatableMixin]. @override List get props => [ x, label, color, strokeWidth, dashArray, image, sizedPicture, strokeCap, ]; } /// Draws a title on the [HorizontalLine] class HorizontalLineLabel extends FlLineLabel with EquatableMixin { /// Draws a title on the [HorizontalLine], align it with [alignment] over the line, /// applies [padding] for spaces, and applies [style for changing color, /// size, ... of the text. /// Drawing text will retrieve through [labelResolver], /// you can override it with your custom data. /// [show] determines showing label or not. /// [direction] determines if the direction of the text should be horizontal or vertical. HorizontalLineLabel({ super.padding = const EdgeInsets.all(6), super.style, super.alignment = Alignment.topLeft, super.show = false, super.direction = LabelDirection.horizontal, this.labelResolver = HorizontalLineLabel.defaultLineLabelResolver, }); /// Resolves a label for showing. final String Function(HorizontalLine) labelResolver; /// Returns the [HorizontalLine.y] as the drawing label. static String defaultLineLabelResolver(HorizontalLine line) => line.y.toStringAsFixed(1); /// Lerps a [HorizontalLineLabel] based on [t] value, check [Tween.lerp]. static HorizontalLineLabel lerp( HorizontalLineLabel a, HorizontalLineLabel b, double t, ) => HorizontalLineLabel( padding: EdgeInsets.lerp( a.padding as EdgeInsets, b.padding as EdgeInsets, t, )!, style: TextStyle.lerp(a.style, b.style, t), alignment: Alignment.lerp(a.alignment, b.alignment, t)!, labelResolver: b.labelResolver, show: b.show, direction: b.direction, ); /// Used for equality check, see [EquatableMixin]. @override List get props => [ labelResolver, show, padding, style, alignment, direction, ]; } /// Draws a title on the [VerticalLine] class VerticalLineLabel extends FlLineLabel with EquatableMixin { /// Draws a title on the [VerticalLine], align it with [alignment] over the line, /// applies [padding] for spaces, and applies [style for changing color, /// size, ... of the text. /// Drawing text will retrieve through [labelResolver], /// you can override it with your custom data. /// [show] determines showing label or not. /// [direction] determines if the direction of the text should be horizontal or vertical. VerticalLineLabel({ super.padding = const EdgeInsets.all(6), super.style = const TextStyle( color: Colors.black, fontWeight: FontWeight.bold, fontSize: 14, ), super.alignment = Alignment.bottomRight, super.show = false, super.direction = LabelDirection.horizontal, this.labelResolver = VerticalLineLabel.defaultLineLabelResolver, }); /// Resolves a label for showing. final String Function(VerticalLine) labelResolver; /// Returns the [VerticalLine.x] as the drawing label. static String defaultLineLabelResolver(VerticalLine line) => line.x.toStringAsFixed(1); /// Lerps a [VerticalLineLabel] based on [t] value, check [Tween.lerp]. static VerticalLineLabel lerp( VerticalLineLabel a, VerticalLineLabel b, double t, ) => VerticalLineLabel( padding: EdgeInsets.lerp( a.padding as EdgeInsets, b.padding as EdgeInsets, t, )!, style: TextStyle.lerp(a.style, b.style, t), alignment: Alignment.lerp(a.alignment, b.alignment, t)!, labelResolver: b.labelResolver, show: b.show, direction: b.direction, ); /// Used for equality check, see [EquatableMixin]. @override List get props => [ labelResolver, show, padding, style, alignment, direction, ]; } /// Holds data for showing a vector image inside the chart. /// /// for example: /// ```dart /// Future loadSvg() async { /// const String rawSvg = 'your svg string'; /// final DrawableRoot svgRoot = await svg.fromSvgString(rawSvg, rawSvg); /// final sizedPicture = SizedPicture(svgRoot.toPicture(), 14, 14); /// return sizedPicture; /// } /// ``` class SizedPicture with EquatableMixin { /// [picture] is the showing image, /// it can retrieve from a svg icon, /// for example: /// ```dart /// const String rawSvg = 'your svg string'; /// final DrawableRoot svgRoot = await svg.fromSvgString(rawSvg, rawSvg); /// final picture = svgRoot.toPicture() /// ``` /// [width] and [height] determines the size of our picture. SizedPicture(this.picture, this.width, this.height); /// Is the showing image. final Picture picture; /// width of our [picture]. final int width; /// height of our [picture]. final int height; /// Used for equality check, see [EquatableMixin]. @override List get props => [ picture, width, height, ]; } /// Draws some straight horizontal or vertical lines in the [LineChart] class ExtraLinesData with EquatableMixin { /// [LineChart] draws some straight horizontal or vertical lines, /// you should set [LineChartData.extraLinesData]. /// Draws horizontal lines using [horizontalLines], /// and vertical lines using [verticalLines]. /// /// If [extraLinesOnTop] sets true, it draws the line above the main bar lines, otherwise /// it draws them below the main bar lines. const ExtraLinesData({ this.horizontalLines = const [], this.verticalLines = const [], this.extraLinesOnTop = true, }); final List horizontalLines; final List verticalLines; final bool extraLinesOnTop; /// Lerps a [ExtraLinesData] based on [t] value, check [Tween.lerp]. static ExtraLinesData lerp(ExtraLinesData a, ExtraLinesData b, double t) => ExtraLinesData( extraLinesOnTop: b.extraLinesOnTop, horizontalLines: lerpHorizontalLineList( a.horizontalLines, b.horizontalLines, t, )!, verticalLines: lerpVerticalLineList( a.verticalLines, b.verticalLines, t, )!, ); /// Used for equality check, see [EquatableMixin]. @override List get props => [ horizontalLines, verticalLines, extraLinesOnTop, ]; } /// This class contains the interface that all DotPainters should conform to. abstract class FlDotPainter with EquatableMixin { const FlDotPainter(); /// This method should be overridden to draw the dot shape. void draw(Canvas canvas, FlSpot spot, Offset offsetInCanvas); /// This method should be overridden to return the size of the shape. Size getSize(FlSpot spot); /// Used to show default UIs, for example [defaultScatterTooltipItem] Color get mainColor; FlDotPainter lerp(FlDotPainter a, FlDotPainter b, double t); /// Used to implement touch behaviour of this dot, for example, /// it behaves like a square of [getSize] /// Check [FlDotCirclePainter.hitTest] for an example of an implementation bool hitTest( FlSpot spot, Offset touched, Offset center, double extraThreshold, ) { final size = getSize(spot); final spotRect = Rect.fromCenter( center: center, width: size.width, height: size.height, ); final thresholdRect = spotRect.inflate(extraThreshold); return thresholdRect.contains(touched); } } /// This class is an implementation of a [FlDotPainter] that draws /// a circled shape class FlDotCirclePainter extends FlDotPainter { /// The color of the circle is determined determined by [color], /// [radius] determines the radius of the circle. /// You can have a stroke line around the circle, /// by setting the thickness with [strokeWidth], /// and you can change the color of of the stroke with [strokeColor]. FlDotCirclePainter({ this.color = Colors.green, double? radius, this.strokeColor = const Color.fromRGBO(76, 175, 80, 1), this.strokeWidth = 0.0, }) : radius = radius ?? 4.0; /// The fill color to use for the circle final Color color; /// Customizes the radius of the circle final double radius; /// The stroke color to use for the circle final Color strokeColor; /// The stroke width to use for the circle final double strokeWidth; /// Implementation of the parent class to draw the circle @override void draw(Canvas canvas, FlSpot spot, Offset offsetInCanvas) { if (strokeWidth != 0.0 && strokeColor.a != 0.0) { canvas.drawCircle( offsetInCanvas, radius + (strokeWidth / 2), Paint() ..color = strokeColor ..strokeWidth = strokeWidth ..style = PaintingStyle.stroke, ); } canvas.drawCircle( offsetInCanvas, radius, Paint() ..color = color ..style = PaintingStyle.fill, ); } /// Implementation of the parent class to get the size of the circle @override Size getSize(FlSpot spot) => Size.fromRadius(radius + strokeWidth); @override Color get mainColor => color; FlDotCirclePainter _lerp( FlDotCirclePainter a, FlDotCirclePainter b, double t, ) => FlDotCirclePainter( color: Color.lerp(a.color, b.color, t)!, radius: lerpDouble(a.radius, b.radius, t), strokeColor: Color.lerp(a.strokeColor, b.strokeColor, t)!, strokeWidth: lerpDouble(a.strokeWidth, b.strokeWidth, t)!, ); @override FlDotPainter lerp(FlDotPainter a, FlDotPainter b, double t) { if (a is! FlDotCirclePainter || b is! FlDotCirclePainter) { return b; } return _lerp(a, b, t); } @override bool hitTest( FlSpot spot, Offset touched, Offset center, double extraThreshold, ) { final distance = (touched - center).distance.abs(); return distance < radius + extraThreshold; } /// Used for equality check, see [EquatableMixin]. @override List get props => [ color, radius, strokeColor, strokeWidth, ]; } /// This class is an implementation of a [FlDotPainter] that draws /// a squared shape class FlDotSquarePainter extends FlDotPainter { /// The color of the square is determined determined by [color], /// [size] determines the size of the square. /// You can have a stroke line around the square, /// by setting the thickness with [strokeWidth], /// and you can change the color of of the stroke with [strokeColor]. FlDotSquarePainter({ this.color = Colors.green, this.size = 4.0, this.strokeColor = const Color.fromRGBO(76, 175, 80, 1), this.strokeWidth = 1.0, }); /// The fill color to use for the square final Color color; /// Customizes the size of the square final double size; /// The stroke color to use for the square final Color strokeColor; /// The stroke width to use for the square final double strokeWidth; /// Implementation of the parent class to draw the square @override void draw(Canvas canvas, FlSpot spot, Offset offsetInCanvas) { if (strokeWidth != 0.0 && strokeColor.a != 0.0) { canvas.drawRect( Rect.fromCircle( center: offsetInCanvas, radius: (size / 2) + (strokeWidth / 2), ), Paint() ..color = strokeColor ..strokeWidth = strokeWidth ..style = PaintingStyle.stroke, ); } canvas.drawRect( Rect.fromCircle( center: offsetInCanvas, radius: size / 2, ), Paint() ..color = color ..style = PaintingStyle.fill, ); } /// Implementation of the parent class to get the size of the square @override Size getSize(FlSpot spot) => Size.square(size + strokeWidth); @override Color get mainColor => color; /// Used for equality check, see [EquatableMixin]. @override List get props => [ color, size, strokeColor, strokeWidth, ]; FlDotSquarePainter _lerp( FlDotSquarePainter a, FlDotSquarePainter b, double t, ) => FlDotSquarePainter( color: Color.lerp(a.color, b.color, t)!, size: lerpDouble(a.size, b.size, t)!, strokeColor: Color.lerp(a.strokeColor, b.strokeColor, t)!, strokeWidth: lerpDouble(a.strokeWidth, b.strokeWidth, t)!, ); @override FlDotPainter lerp(FlDotPainter a, FlDotPainter b, double t) { if (a is! FlDotSquarePainter || b is! FlDotSquarePainter) { return b; } return _lerp(a, b, t); } } /// This class is an implementation of a [FlDotPainter] that draws /// a cross (X mark) shape class FlDotCrossPainter extends FlDotPainter { /// The [color] and [width] properties determines the color and thickness of the cross shape, /// [size] determines the width and height of the shape. FlDotCrossPainter({ this.color = Colors.green, this.size = 8.0, this.width = 2.0, }); /// The fill color to use for the X mark final Color color; /// Determines size (width and height) of shape. final double size; /// Determines thickness of X mark. final double width; /// Implementation of the parent class to draw the cross @override void draw(Canvas canvas, FlSpot spot, Offset offsetInCanvas) { final path = Path() ..moveTo(offsetInCanvas.dx, offsetInCanvas.dy) ..relativeMoveTo(-size / 2, -size / 2) ..relativeLineTo(size, size) ..moveTo(offsetInCanvas.dx, offsetInCanvas.dy) ..relativeMoveTo(size / 2, -size / 2) ..relativeLineTo(-size, size); final paint = Paint() ..style = PaintingStyle.stroke ..strokeWidth = width ..color = color; canvas.drawPath(path, paint); } /// Implementation of the parent class to get the size of the circle @override Size getSize(FlSpot spot) => Size(size, size); @override Color get mainColor => color; FlDotCrossPainter _lerp( FlDotCrossPainter a, FlDotCrossPainter b, double t, ) => FlDotCrossPainter( color: Color.lerp(a.color, b.color, t)!, size: lerpDouble(a.size, b.size, t)!, width: lerpDouble(a.width, b.width, t)!, ); @override FlDotPainter lerp(FlDotPainter a, FlDotPainter b, double t) { if (a is! FlDotCrossPainter || b is! FlDotCrossPainter) { return b; } return _lerp(a, b, t); } /// Used for equality check, see [EquatableMixin]. @override List get props => [ color, size, width, ]; } /// Holds the information about the error range of a spot /// /// We support horizontal and vertical error range/indicator for our axis based /// charts such as [LineChart], [BarChart] and [PieChart] /// /// For example, in [LineChart] you can add [FlSpot.xError] and [FlSpot.yError] /// in your data points, so we can draw error indicators for them. /// And it works relative to the point that you are setting the error range /// /// For [BarChart], you can set the [BarChartRodData.toYErrorRange] to have /// vertical error range for each bar. (relative to [BarChartRodData.toY] value) /// /// [show] is tru by default, it means that we show /// the error indicator lines (if you provide them in [FlSpot]s) /// /// [painter] is a callback that allows you to return a /// [FlSpotErrorRangePainter] per each data point which is responsible for /// drawing the error indicator. You can use the default [FlSimpleErrorPainter] /// or create your own by extending our abstract [FlSpotErrorRangePainter] class FlErrorIndicatorData with EquatableMixin { const FlErrorIndicatorData({ this.show = true, this.painter = _defaultGetSpotRangeErrorPainter, }); /// Determines showing the error indicator or not final bool show; /// A callback that allows you to return a [FlSpotErrorRangePainter] /// per each data point (for example [FlSpot] in line chart) final GetSpotRangeErrorPainter painter; /// Lerps a [FlErrorIndicatorData] based on [t] value. static FlErrorIndicatorData lerp( FlErrorIndicatorData a, FlErrorIndicatorData b, double t, ) => FlErrorIndicatorData( show: b.show, painter: b.painter, ); @override List get props => [ show, painter, ]; } /// A callback that allows you to return a [FlSpotErrorRangePainter] based on /// the provided specific data point (for example [FlSpot] in [LineChart]) /// /// So [input] is different based on the chart type, /// for example in [LineChart] it will be [LineChartSpotErrorRangeCallbackInput] typedef GetSpotRangeErrorPainter = FlSpotErrorRangePainter Function( T input, ); /// The default [GetSpotRangeErrorPainter] for [FlErrorIndicatorData], /// it draws a simple and typical error indicator using [FlSimpleErrorPainter] FlSpotErrorRangePainter _defaultGetSpotRangeErrorPainter( FlSpotErrorRangeCallbackInput input, ) => FlSimpleErrorPainter(); /// The abstract painter that is responsible for drawing the error range of /// a point in our axis based charts such as [LineChart] and [BarChart] /// /// It has a [draw] method that you should override to draw the error range /// as you like /// /// The default implementation is [FlSpotErrorRangePainter]. It is a simple and /// common error indicator painter. /// /// You can see how does it look in the [example app](https://app.flchart.dev/) abstract class FlSpotErrorRangePainter with EquatableMixin { const FlSpotErrorRangePainter(); /// Draws the error range of a point in our axis based charts /// /// [canvas] is the canvas that you should draw on it /// [offsetInCanvas] is the absolute position/offset of the point in /// the canvas that you can use it as your center point /// [origin] is the relative point point that you should draw /// the error range on it (it is based on the chart values) /// [errorRelativeRect] is the relative rect that you should draw the error, /// it is absolute and you can shift it with [offsetInCanvas] to draw your /// shape inside it. /// [axisChartData] is the axis chart data that you can use it to get more /// information about the chart /// /// You can take a look at our default implementation [FlSimpleErrorPainter] void draw( Canvas canvas, Offset offsetInCanvas, FlSpot origin, Rect errorRelativeRect, AxisChartData axisChartData, ); } /// The default implementation of [FlSpotErrorRangePainter] /// /// It draws a simple and common error indicator for the error range of a point /// in our axis based charts such as [LineChart] and [BarChart] /// /// You can see how does it look in the [example app](https://app.flchart.dev/) /// /// You can customize the lines using [lineColor], [lineWidth], [capLength], /// /// You can customize the text using [showErrorTexts], [errorTextStyle] /// and [errorTextDirection] /// /// You can customize the alignment of the error lines using [crossAlignment] class FlSimpleErrorPainter extends FlSpotErrorRangePainter with EquatableMixin { FlSimpleErrorPainter({ this.lineColor = Colors.white, this.lineWidth = 1.0, this.capLength = 8.0, this.crossAlignment = 0, this.showErrorTexts = false, this.errorTextStyle = const TextStyle( color: Colors.white, fontSize: 12, ), this.errorTextDirection = TextDirection.ltr, }) { _linePaint = Paint() ..color = lineColor ..strokeWidth = lineWidth ..style = PaintingStyle.stroke; assert( crossAlignment >= -1 && crossAlignment <= 1, 'crossAlignment must be between -1 (start) and 1 (end)', ); } /// The color of the error lines final Color lineColor; /// The thickness of the error lines final double lineWidth; /// The length of the cap of the error lines final double capLength; /// The alignment of the error lines, /// it should be between -1 (start) and 1 (end) final double crossAlignment; /// Determines showing the error texts or not final bool showErrorTexts; /// The style of the error texts final TextStyle errorTextStyle; /// The direction of the error texts final TextDirection errorTextDirection; late final Paint _linePaint; @override void draw( Canvas canvas, Offset offsetInCanvas, FlSpot origin, Rect errorRelativeRect, AxisChartData axisChartData, ) { final rect = errorRelativeRect.shift(offsetInCanvas); final hasVerticalError = errorRelativeRect.height != 0; if (hasVerticalError) { _drawDirectErrorLine( canvas, Offset(offsetInCanvas.dx, rect.top), Offset(offsetInCanvas.dx, rect.bottom), ); if (showErrorTexts) { // lower _drawErrorText( canvas: canvas, rect: rect, isHorizontal: false, isLower: true, text: Utils().formatNumber( axisChartData.minY, axisChartData.maxY, origin.y - origin.yError!.lowerBy, ), textStyle: errorTextStyle, ); // upper _drawErrorText( canvas: canvas, rect: rect, isHorizontal: false, isLower: false, text: Utils().formatNumber( axisChartData.minY, axisChartData.maxY, origin.y + origin.yError!.upperBy, ), textStyle: errorTextStyle, ); } } final hasHorizontalError = errorRelativeRect.width != 0; if (hasHorizontalError) { _drawDirectErrorLine( canvas, Offset(rect.left, offsetInCanvas.dy), Offset(rect.right, offsetInCanvas.dy), ); if (showErrorTexts) { // lower _drawErrorText( canvas: canvas, rect: rect, isHorizontal: true, isLower: true, text: Utils().formatNumber( axisChartData.minX, axisChartData.maxX, origin.x - origin.xError!.lowerBy, ), textStyle: errorTextStyle, ); // upper _drawErrorText( canvas: canvas, rect: rect, isHorizontal: true, isLower: false, text: Utils().formatNumber( axisChartData.minX, axisChartData.maxX, origin.x + origin.xError!.upperBy, ), textStyle: errorTextStyle, ); } } } void _drawDirectErrorLine(Canvas canvas, Offset from, Offset to) { final isLineVertical = from.dx == to.dx; final mainLineOffset = crossAlignment * capLength; if (isLineVertical) { from = Offset(from.dx + mainLineOffset, from.dy); to = Offset(to.dx + mainLineOffset, to.dy); } else { from = Offset(from.dx, from.dy + mainLineOffset); to = Offset(to.dx, to.dy + mainLineOffset); } canvas.drawLine( from, to, _linePaint, ); final t = (crossAlignment + 1) / 2; final end = capLength - lerpDouble(0, capLength, t)!; final start = capLength - end; // Draw edge lines if (isLineVertical) { canvas // draw top cap ..drawLine( Offset(from.dx - start, from.dy), Offset(from.dx + end, from.dy), _linePaint, ) // draw bottom cap ..drawLine( Offset(to.dx - start, to.dy), Offset(to.dx + end, to.dy), _linePaint, ); } else { canvas // draw left cap ..drawLine( Offset(from.dx, from.dy - start), Offset(from.dx, from.dy + end), _linePaint, ) // draw right cap ..drawLine( Offset(to.dx, to.dy - start), Offset(to.dx, to.dy + end), _linePaint, ); } } void _drawErrorText({ required Canvas canvas, required Rect rect, required bool isHorizontal, required bool isLower, required String text, required TextStyle textStyle, }) { final lowerText = TextPainter( text: TextSpan( text: text, style: textStyle, ), textDirection: TextDirection.ltr, )..layout(); const spacing = 4.0; final textX = isHorizontal ? isLower ? rect.left - lowerText.width - spacing : rect.right + spacing : rect.center.dx - lowerText.width / 2; final textY = isHorizontal ? rect.center.dy - lowerText.height / 2 : isLower ? rect.bottom + spacing : rect.top - lowerText.width - spacing; lowerText.paint( canvas, Offset( textX, textY, ), ); } @override List get props => [ lineColor, lineWidth, capLength, crossAlignment, showErrorTexts, errorTextStyle, errorTextDirection, ]; } /// The abstract class that is used as the input of /// the [GetSpotRangeErrorPainter] callback. /// /// So as you know, we have this feature in our axis-based charts and each chart /// has its own input type, for example in [LineChart] /// it is [LineChartSpotErrorRangeCallbackInput] (which contains the [FlSpot]) abstract class FlSpotErrorRangeCallbackInput with EquatableMixin {} typedef ValueInCanvasProvider = double Function(double axisValue); /// The class to hold the information about showing a specific point /// in the axis-based charts /// /// You can use the [x] and [y] properties to set the point, Otherwise it /// uses the touch point (if `handleBuiltinTouches` is true) /// /// There's a [painter] property that manages the drawing of the point. /// We have a default implementation of the painter which is /// [AxisLinesIndicatorPainter], it draws a horizontal and a vertical line /// that goes through the point. /// /// You can override the [painter] by implementing your own /// [AxisSpotIndicatorPainter] implementation. /// /// For more information, look at our default implementation: /// [AxisLinesIndicatorPainter]. class AxisSpotIndicator with EquatableMixin { const AxisSpotIndicator({ this.x, this.y, required this.painter, }); final double? x; final double? y; final AxisSpotIndicatorPainter painter; /// Lerps a [AxisSpotIndicator] based on [t] value, check [Tween.lerp]. static AxisSpotIndicator lerp( AxisSpotIndicator a, AxisSpotIndicator b, double t, ) => AxisSpotIndicator( x: lerpDouble(a.x, b.x, t), y: lerpDouble(a.y, b.y, t), painter: a.painter.lerp(b.painter, t), ); /// Used for equality check, see [EquatableMixin]. @override List get props => [ x, y, painter, ]; } /// The abstract class that is used to draw the point indicator /// /// You can create your own custom painter by extending this class /// and implementing the [paint] method. /// /// You can also use the default implementation which is /// [AxisLinesIndicatorPainter], it draws a horizontal and a vertical line /// that goes through the point. abstract class AxisSpotIndicatorPainter { const AxisSpotIndicatorPainter(); /// Draws the point indicator void paint( BuildContext context, Canvas canvas, Size viewSize, AxisSpotIndicator axisPointIndicator, ValueInCanvasProvider xInCanvasProvider, ValueInCanvasProvider yInCanvasProvider, AxisChartData axisChartData, ); /// Lerps a [AxisSpotIndicatorPainter] based on [t] value, check [Tween.lerp]. AxisSpotIndicatorPainter lerp( AxisSpotIndicatorPainter b, double t, ); } /// The default implementation of the [AxisSpotIndicatorPainter] /// /// It draws a horizontal and a vertical line that goes through the point class AxisLinesIndicatorPainter extends AxisSpotIndicatorPainter { AxisLinesIndicatorPainter({ required this.verticalLineProvider, required this.horizontalLineProvider, }); final VerticalLine? Function(double x)? verticalLineProvider; final HorizontalLine? Function(double y)? horizontalLineProvider; /// The paint object that is used to draw the lines final _linePaint = Paint(); /// The paint object that is used to draw the images final _imagePaint = Paint(); void _drawHorizontalLine( BuildContext context, CanvasWrapper canvasWrapper, HorizontalLine line, Offset from, Offset to, ) { _linePaint ..setColorOrGradientForLine( line.color, line.gradient, from: from, to: to, ) ..style = PaintingStyle.stroke ..strokeWidth = line.strokeWidth ..transparentIfWidthIsZero() ..strokeCap = line.strokeCap; canvasWrapper.drawDashedLine( from, to, _linePaint, line.dashArray, ); if (line.sizedPicture != null) { final centerX = line.sizedPicture!.width / 2; final centerY = line.sizedPicture!.height / 2; final xPosition = centerX; final yPosition = to.dy - centerY; canvasWrapper ..save() ..translate(xPosition, yPosition) ..drawPicture(line.sizedPicture!.picture) ..restore(); } if (line.image != null) { final centerX = line.image!.width / 2; final centerY = line.image!.height / 2; final centeredImageOffset = Offset(centerX, to.dy - centerY); canvasWrapper.drawImage( line.image!, centeredImageOffset, _imagePaint, ); } if (line.label.show) { final label = line.label; final style = TextStyle(fontSize: 11, color: line.color).merge(label.style); final padding = label.padding as EdgeInsets; final span = TextSpan( text: label.labelResolver(line), style: Utils().getThemeAwareTextStyle(context, style), ); final tp = TextPainter( text: span, textDirection: TextDirection.ltr, )..layout(); switch (label.direction) { case LabelDirection.horizontal: case LabelDirection.horizontalMirrored: canvasWrapper.drawText( tp, label.alignment.withinRect( Rect.fromLTRB( from.dx + padding.left, from.dy - padding.bottom - tp.height, to.dx - padding.right - tp.width, to.dy + padding.top, ), ), ); case LabelDirection.vertical: case LabelDirection.verticalMirrored: canvasWrapper.drawVerticalText( tp, label.alignment.withinRect( Rect.fromLTRB( from.dx + padding.left + tp.height, from.dy - padding.bottom - tp.width, to.dx - padding.right, to.dy + padding.top, ), ), ); } } } void _drawVerticalLine( BuildContext context, CanvasWrapper canvasWrapper, VerticalLine line, Offset from, Offset to, ) { final viewSize = canvasWrapper.size; _linePaint ..setColorOrGradientForLine( line.color, line.gradient, from: from, to: to, ) ..strokeWidth = line.strokeWidth ..style = PaintingStyle.stroke ..transparentIfWidthIsZero() ..strokeCap = line.strokeCap; canvasWrapper.drawDashedLine( from, to, _linePaint, line.dashArray, ); if (line.sizedPicture != null) { final centerX = line.sizedPicture!.width / 2; final centerY = line.sizedPicture!.height / 2; final xPosition = to.dx - centerX; final yPosition = viewSize.height - centerY; canvasWrapper ..save() ..translate(xPosition, yPosition) ..drawPicture(line.sizedPicture!.picture) ..restore(); } if (line.image != null) { final centerX = line.image!.width / 2; final centerY = line.image!.height + 2; final centeredImageOffset = Offset(to.dx - centerX, viewSize.height - centerY); canvasWrapper.drawImage( line.image!, centeredImageOffset, _imagePaint, ); } if (line.label.show) { final label = line.label; final style = TextStyle(fontSize: 11, color: line.color).merge(label.style); final padding = label.padding as EdgeInsets; final span = TextSpan( text: label.labelResolver(line), style: Utils().getThemeAwareTextStyle(context, style), ); final tp = TextPainter( text: span, textDirection: TextDirection.ltr, )..layout(); switch (label.direction) { case LabelDirection.horizontal: case LabelDirection.horizontalMirrored: canvasWrapper.drawText( tp, label.alignment.withinRect( Rect.fromLTRB( from.dx - padding.right - tp.width, from.dy + padding.top, to.dx + padding.left, to.dy - padding.bottom - tp.height, ), ), ); case LabelDirection.vertical: case LabelDirection.verticalMirrored: canvasWrapper.drawVerticalText( tp, label.alignment.withinRect( Rect.fromLTRB( from.dx - padding.right, from.dy + padding.top, to.dx + padding.left + tp.height, to.dy - padding.bottom - tp.width, ), ), ); } } } @override void paint( BuildContext context, Canvas canvas, Size viewSize, AxisSpotIndicator axisPointIndicator, ValueInCanvasProvider xInCanvasProvider, ValueInCanvasProvider yInCanvasProvider, AxisChartData axisChartData, ) { final canvasWrapper = CanvasWrapper(canvas, viewSize); final horizontalLine = axisPointIndicator.y == null || horizontalLineProvider == null ? null : horizontalLineProvider!(axisPointIndicator.y!); if (horizontalLine != null) { final left = Offset( xInCanvasProvider(axisChartData.minX), yInCanvasProvider(horizontalLine.y), ); final right = Offset( xInCanvasProvider(axisChartData.maxX), yInCanvasProvider(horizontalLine.y), ); _drawHorizontalLine( context, canvasWrapper, horizontalLine, left, right, ); } final verticalLine = axisPointIndicator.x == null || verticalLineProvider == null ? null : verticalLineProvider!(axisPointIndicator.x!); if (verticalLine != null) { final top = Offset( xInCanvasProvider(verticalLine.x), yInCanvasProvider(axisChartData.maxY), ); final bottom = Offset( xInCanvasProvider(verticalLine.x), yInCanvasProvider(axisChartData.minY), ); _drawVerticalLine( context, canvasWrapper, verticalLine, top, bottom, ); } } /// Lerps a [AxisLinesIndicatorPainter] based on [t] value, check [Tween.lerp]. AxisLinesIndicatorPainter _lerp( AxisLinesIndicatorPainter b, double t, ) => AxisLinesIndicatorPainter( horizontalLineProvider: b.horizontalLineProvider, verticalLineProvider: b.verticalLineProvider, ); /// Used for equality check, see [EquatableMixin]. @override AxisSpotIndicatorPainter lerp( AxisSpotIndicatorPainter b, double t, ) { if (b is! AxisLinesIndicatorPainter) { return b; } return _lerp(b, t); } } ================================================ FILE: lib/src/chart/base/axis_chart/axis_chart_extensions.dart ================================================ import 'package:fl_chart/fl_chart.dart'; extension FlSpotListExtension on List { /// Splits a line by [FlSpot.nullSpot] values inside it. List> splitByNullSpots() { final barList = >[[]]; // handle nullability by splitting off the list into multiple // separate lists when separated by nulls for (final spot in this) { if (spot.isNotNull()) { barList.last.add(spot); } else if (barList.last.isNotEmpty) { barList.add([]); } } // remove last item if one or more last spots were null if (barList.last.isEmpty) { barList.removeLast(); } return barList; } } ================================================ FILE: lib/src/chart/base/axis_chart/axis_chart_helper.dart ================================================ import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/utils/utils.dart'; import 'package:flutter/material.dart'; class AxisChartHelper { factory AxisChartHelper() { return _singleton; } AxisChartHelper._internal(); static final _singleton = AxisChartHelper._internal(); /// Iterates over an axis from [min] to [max]. /// /// [interval] determines each step /// /// If [minIncluded] is true, it starts from [min] value, /// otherwise it starts from [min] + [interval] /// /// If [maxIncluded] is true, it ends at [max] value, /// otherwise it ends at [max] - [interval] Iterable iterateThroughAxis({ required double min, bool minIncluded = true, required double max, bool maxIncluded = true, required double baseLine, required double interval, }) sync* { final initialValue = Utils() .getBestInitialIntervalValue(min, max, interval, baseline: baseLine); var axisSeek = initialValue; final firstPositionOverlapsWithMin = axisSeek == min; if (!minIncluded && firstPositionOverlapsWithMin) { // If initial value is equal to data minimum, // move first label one interval further axisSeek += interval; } final diff = max - min; final count = diff ~/ interval; final lastPosition = initialValue + (count * interval); final lastPositionOverlapsWithMax = lastPosition == max; final end = !maxIncluded && lastPositionOverlapsWithMax ? max - interval : max; final epsilon = interval / 100000; if (minIncluded && !firstPositionOverlapsWithMin) { // Data minimum shall be included and is not yet covered yield min; } while (axisSeek <= end + epsilon) { yield axisSeek; axisSeek += interval; } if (maxIncluded && !lastPositionOverlapsWithMax) { yield max; } } /// Calculate translate offset to keep [SideTitle] child /// placed inside its corresponding axis. /// The offset will translate the child to the closest edge inside /// of the corresponding axis bounding box Offset calcFitInsideOffset({ required AxisSide axisSide, required double? childSize, required double parentAxisSize, required double axisPosition, required double distanceFromEdge, }) { if (childSize == null) return Offset.zero; // Find title alignment along its axis final axisMid = parentAxisSize / 2; final mainAxisAlignment = (axisPosition - axisMid).isNegative ? MainAxisAlignment.start : MainAxisAlignment.end; // Find if child widget overflowed outside the chart late bool isOverflowed; if (mainAxisAlignment == MainAxisAlignment.start) { isOverflowed = (axisPosition - (childSize / 2)).isNegative; } else { isOverflowed = (axisPosition + (childSize / 2)) > parentAxisSize; } if (!isOverflowed) return Offset.zero; // Calc offset if child overflowed late double offset; if (mainAxisAlignment == MainAxisAlignment.start) { offset = (childSize / 2) - axisPosition + distanceFromEdge; } else { offset = -(childSize / 2) + (parentAxisSize - axisPosition) - distanceFromEdge; } return switch (axisSide) { AxisSide.left || AxisSide.right => Offset(0, offset), AxisSide.top || AxisSide.bottom => Offset(offset, 0), }; } } ================================================ FILE: lib/src/chart/base/axis_chart/axis_chart_painter.dart ================================================ import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/chart/bar_chart/bar_chart_painter.dart'; import 'package:fl_chart/src/chart/base/axis_chart/axis_chart_helper.dart'; import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart'; import 'package:fl_chart/src/chart/line_chart/line_chart_painter.dart'; import 'package:fl_chart/src/extensions/paint_extension.dart'; import 'package:fl_chart/src/utils/canvas_wrapper.dart'; import 'package:fl_chart/src/utils/utils.dart'; import 'package:flutter/material.dart'; /// This class is responsible to draw the grid behind all axis base charts. /// also we have two useful function [getPixelX] and [getPixelY] that used /// in child classes -> [BarChartPainter], [LineChartPainter] /// [dataList] is the currently showing data (it may produced by an animation using lerp function), /// [targetData] is the target data, that animation is going to show (if animating) abstract class AxisChartPainter extends BaseChartPainter { AxisChartPainter() { _gridPaint = Paint()..style = PaintingStyle.stroke; _backgroundPaint = Paint()..style = PaintingStyle.fill; _rangeAnnotationPaint = Paint()..style = PaintingStyle.fill; _extraLinesPaint = Paint()..style = PaintingStyle.stroke; _imagePaint = Paint(); _clipPaint = Paint(); } late Paint _gridPaint; late Paint _backgroundPaint; late Paint _extraLinesPaint; late Paint _imagePaint; late Paint _clipPaint; /// [_rangeAnnotationPaint] draws range annotations; late Paint _rangeAnnotationPaint; /// Paints [AxisChartData] into the provided canvas. @override void paint( BuildContext context, CanvasWrapper canvasWrapper, PaintHolder holder, ) { super.paint(context, canvasWrapper, holder); drawBackground(canvasWrapper, holder); drawRangeAnnotation(canvasWrapper, holder); drawGrid(canvasWrapper, holder); } @visibleForTesting void drawGrid(CanvasWrapper canvasWrapper, PaintHolder holder) { final data = holder.data; if (!data.gridData.show) { return; } final viewSize = canvasWrapper.size; // Show Vertical Grid if (data.gridData.drawVerticalLine) { final verticalInterval = data.gridData.verticalInterval ?? Utils().getEfficientInterval( viewSize.width, data.horizontalDiff, ); final axisValues = AxisChartHelper().iterateThroughAxis( min: data.minX, minIncluded: false, max: data.maxX, maxIncluded: false, baseLine: data.baselineX, interval: verticalInterval, ); for (final axisValue in axisValues) { if (!data.gridData.checkToShowVerticalLine(axisValue)) { continue; } final bothX = getPixelX(axisValue, viewSize, holder); final x1 = bothX; const y1 = 0.0; final x2 = bothX; final y2 = viewSize.height; final from = Offset(x1, y1); final to = Offset(x2, y2); final flLineStyle = data.gridData.getDrawingVerticalLine(axisValue); _gridPaint ..setColorOrGradientForLine( flLineStyle.color, flLineStyle.gradient, from: from, to: to, ) ..strokeWidth = flLineStyle.strokeWidth ..transparentIfWidthIsZero(); canvasWrapper.drawDashedLine( from, to, _gridPaint, flLineStyle.dashArray, ); } } // Show Horizontal Grid if (data.gridData.drawHorizontalLine) { final horizontalInterval = data.gridData.horizontalInterval ?? Utils().getEfficientInterval(viewSize.height, data.verticalDiff); final axisValues = AxisChartHelper().iterateThroughAxis( min: data.minY, minIncluded: false, max: data.maxY, maxIncluded: false, baseLine: data.baselineY, interval: horizontalInterval, ); for (final axisValue in axisValues) { if (!data.gridData.checkToShowHorizontalLine(axisValue)) { continue; } final flLine = data.gridData.getDrawingHorizontalLine(axisValue); final bothY = getPixelY(axisValue, viewSize, holder); const x1 = 0.0; final y1 = bothY; final x2 = viewSize.width; final y2 = bothY; final from = Offset(x1, y1); final to = Offset(x2, y2); _gridPaint ..setColorOrGradientForLine( flLine.color, flLine.gradient, from: from, to: to, ) ..strokeWidth = flLine.strokeWidth ..transparentIfWidthIsZero(); canvasWrapper.drawDashedLine( from, to, _gridPaint, flLine.dashArray, ); } } } /// This function draws a colored background behind the chart. @visibleForTesting void drawBackground(CanvasWrapper canvasWrapper, PaintHolder holder) { final data = holder.data; if (data.backgroundColor.a == 0.0) { return; } final viewSize = canvasWrapper.size; _backgroundPaint.color = data.backgroundColor; canvasWrapper.drawRect( Rect.fromLTWH(0, 0, viewSize.width, viewSize.height), _backgroundPaint, ); } @visibleForTesting void drawRangeAnnotation(CanvasWrapper canvasWrapper, PaintHolder holder) { final data = holder.data; final viewSize = canvasWrapper.size; if (data.rangeAnnotations.verticalRangeAnnotations.isNotEmpty) { for (final annotation in data.rangeAnnotations.verticalRangeAnnotations) { final from = Offset(getPixelX(annotation.x1, viewSize, holder), 0); final to = Offset( getPixelX(annotation.x2, viewSize, holder), viewSize.height, ); final rect = Rect.fromPoints(from, to); _rangeAnnotationPaint.setColorOrGradient( annotation.color, annotation.gradient, rect, ); canvasWrapper.drawRect(rect, _rangeAnnotationPaint); } } if (data.rangeAnnotations.horizontalRangeAnnotations.isNotEmpty) { for (final annotation in data.rangeAnnotations.horizontalRangeAnnotations) { final from = Offset(0, getPixelY(annotation.y1, viewSize, holder)); final to = Offset( viewSize.width, getPixelY(annotation.y2, viewSize, holder), ); final rect = Rect.fromPoints(from, to); _rangeAnnotationPaint.setColorOrGradient( annotation.color, annotation.gradient, rect, ); canvasWrapper.drawRect(rect, _rangeAnnotationPaint); } } } void drawExtraLines( BuildContext context, CanvasWrapper canvasWrapper, PaintHolder holder, ) { if (holder.chartVirtualRect != null) { canvasWrapper.restore(); } super.paint(context, canvasWrapper, holder); final data = holder.data; final viewSize = canvasWrapper.size; if (data.extraLinesData.horizontalLines.isNotEmpty) { drawHorizontalLines(context, canvasWrapper, holder, viewSize); } if (data.extraLinesData.verticalLines.isNotEmpty) { drawVerticalLines(context, canvasWrapper, holder, viewSize); } if (holder.chartVirtualRect != null) { canvasWrapper ..saveLayer( Offset.zero & canvasWrapper.size, _clipPaint, ) ..clipRect(Offset.zero & canvasWrapper.size); } } void drawHorizontalLines( BuildContext context, CanvasWrapper canvasWrapper, PaintHolder holder, Size viewSize, ) { for (final line in holder.data.extraLinesData.horizontalLines) { final from = Offset(0, getPixelY(line.y, viewSize, holder)); final to = Offset(viewSize.width, getPixelY(line.y, viewSize, holder)); final isLineOutsideOfChart = from.dy < 0 || to.dy < 0 || from.dy > viewSize.height || to.dy > viewSize.height; if (!isLineOutsideOfChart) { _extraLinesPaint ..setColorOrGradientForLine( line.color, line.gradient, from: from, to: to, ) ..strokeWidth = line.strokeWidth ..transparentIfWidthIsZero() ..strokeCap = line.strokeCap; canvasWrapper.drawDashedLine( from, to, _extraLinesPaint, line.dashArray, ); if (line.sizedPicture != null) { final centerX = line.sizedPicture!.width / 2; final centerY = line.sizedPicture!.height / 2; final xPosition = centerX; final yPosition = to.dy - centerY; canvasWrapper ..save() ..translate(xPosition, yPosition) ..drawPicture(line.sizedPicture!.picture) ..restore(); } if (line.image != null) { final centerX = line.image!.width / 2; final centerY = line.image!.height / 2; final centeredImageOffset = Offset(centerX, to.dy - centerY); canvasWrapper.drawImage( line.image!, centeredImageOffset, _imagePaint, ); } if (line.label.show) { final label = line.label; final style = TextStyle(fontSize: 11, color: line.color).merge(label.style); final padding = label.padding as EdgeInsets; final span = TextSpan( text: label.labelResolver(line), style: Utils().getThemeAwareTextStyle(context, style), ); final tp = TextPainter( text: span, textDirection: TextDirection.ltr, )..layout(); switch (label.direction) { case LabelDirection.horizontal: case LabelDirection.horizontalMirrored: canvasWrapper.drawText( tp, label.alignment.withinRect( Rect.fromLTRB( from.dx + padding.left, from.dy - padding.bottom - tp.height, to.dx - padding.right - tp.width, to.dy + padding.top, ), ), label.direction == LabelDirection.horizontalMirrored ? -180 : null, ); case LabelDirection.vertical: canvasWrapper.drawVerticalText( tp, label.alignment.withinRect( Rect.fromLTRB( from.dx + padding.left + tp.height, from.dy - padding.bottom - tp.width, to.dx - padding.right, to.dy + padding.top, ), ), ); case LabelDirection.verticalMirrored: canvasWrapper.drawVerticalText( tp, label.alignment.withinRect( Rect.fromLTRB( from.dx + padding.left, from.dy - padding.bottom, to.dx - padding.right - tp.height, to.dy + padding.top + tp.width, ), ), -90, ); } } } } } void drawVerticalLines( BuildContext context, CanvasWrapper canvasWrapper, PaintHolder holder, Size viewSize, ) { for (final line in holder.data.extraLinesData.verticalLines) { final from = Offset(getPixelX(line.x, viewSize, holder), 0); final to = Offset(getPixelX(line.x, viewSize, holder), viewSize.height); final isLineOutsideOfChart = from.dx < 0 || to.dx < 0 || from.dx > viewSize.width || to.dx > viewSize.width; if (!isLineOutsideOfChart) { _extraLinesPaint ..setColorOrGradientForLine( line.color, line.gradient, from: from, to: to, ) ..strokeWidth = line.strokeWidth ..transparentIfWidthIsZero() ..strokeCap = line.strokeCap; canvasWrapper.drawDashedLine( from, to, _extraLinesPaint, line.dashArray, ); if (line.sizedPicture != null) { final centerX = line.sizedPicture!.width / 2; final centerY = line.sizedPicture!.height / 2; final xPosition = to.dx - centerX; final yPosition = viewSize.height - centerY; canvasWrapper ..save() ..translate(xPosition, yPosition) ..drawPicture(line.sizedPicture!.picture) ..restore(); } if (line.image != null) { final centerX = line.image!.width / 2; final centerY = line.image!.height + 2; final centeredImageOffset = Offset(to.dx - centerX, viewSize.height - centerY); canvasWrapper.drawImage( line.image!, centeredImageOffset, _imagePaint, ); } if (line.label.show) { final label = line.label; final style = TextStyle(fontSize: 11, color: line.color).merge(label.style); final padding = label.padding as EdgeInsets; final span = TextSpan( text: label.labelResolver(line), style: Utils().getThemeAwareTextStyle(context, style), ); final tp = TextPainter( text: span, textDirection: TextDirection.ltr, )..layout(); switch (label.direction) { case LabelDirection.horizontal: case LabelDirection.horizontalMirrored: canvasWrapper.drawText( tp, label.alignment.withinRect( Rect.fromLTRB( from.dx - padding.right - tp.width, from.dy + padding.top, to.dx + padding.left, to.dy - padding.bottom - tp.height, ), ), label.direction == LabelDirection.horizontalMirrored ? -180 : null, ); case LabelDirection.vertical: canvasWrapper.drawVerticalText( tp, label.alignment.withinRect( Rect.fromLTRB( from.dx - padding.right, from.dy + padding.top, to.dx + padding.left + tp.height, to.dy - padding.bottom - tp.width, ), ), ); case LabelDirection.verticalMirrored: canvasWrapper.drawVerticalText( tp, label.alignment.withinRect( Rect.fromLTRB( from.dx - padding.right + tp.height, from.dy + padding.top - tp.width, to.dx + padding.left, to.dy - padding.bottom, ), ), -90, ); } } } } } /// With this function we can convert our [FlSpot] x /// to the view base axis x . /// the view 0, 0 is on the top/left, but the spots is bottom/left double getPixelX( double spotX, Size viewSize, PaintHolder holder, ) { final usableSize = holder.getChartUsableSize(viewSize); final pixelXUnadjusted = _getPixelX(spotX, holder.data, usableSize); // Adjust the position relative to the canvas if chartVirtualRect // is provided final adjustment = holder.chartVirtualRect?.left ?? 0; return pixelXUnadjusted + adjustment; } double _getPixelX(double spotX, D data, Size usableSize) { final deltaX = data.maxX - data.minX; if (deltaX == 0.0) { return 0; } return ((spotX - data.minX) / deltaX) * usableSize.width; } /// With this function we can convert our [FlSpot] y /// to the view base axis y. double getPixelY( double spotY, Size viewSize, PaintHolder holder, ) { final usableSize = holder.getChartUsableSize(viewSize); final pixelYUnadjusted = _getPixelY(spotY, holder.data, usableSize); // Adjust the position relative to the canvas if chartVirtualRect // is provided final adjustment = holder.chartVirtualRect?.top ?? 0; return pixelYUnadjusted + adjustment; } double _getPixelY(double spotY, D data, Size usableSize) { final deltaY = data.maxY - data.minY; if (deltaY == 0.0) { return usableSize.height; } return usableSize.height - (((spotY - data.minY) / deltaY) * usableSize.height); } /// Converts pixel X position to axis X coordinates double getXForPixel( double pixelX, Size viewSize, PaintHolder holder, ) { final usableSize = holder.getChartUsableSize(viewSize); final adjustment = holder.chartVirtualRect?.left ?? 0; final unadjustedPixelX = pixelX - adjustment; final deltaX = holder.data.maxX - holder.data.minX; if (deltaX == 0.0) return holder.data.minX; return (unadjustedPixelX / usableSize.width) * deltaX + holder.data.minX; } /// Converts pixel Y position to axis Y coordinates double getYForPixel( double pixelY, Size viewSize, PaintHolder holder, ) { final usableSize = holder.getChartUsableSize(viewSize); final adjustment = holder.chartVirtualRect?.top ?? 0; final unadjustedPixelY = pixelY - adjustment; final deltaY = holder.data.maxY - holder.data.minY; if (deltaY == 0.0) return holder.data.minY; return holder.data.maxY - (unadjustedPixelY / usableSize.height) * deltaY; } /// Converts pixel coordinates to chart coordinates Offset getChartCoordinateFromPixel( Offset pixelOffset, Size viewSize, PaintHolder holder, ) => Offset( getXForPixel(pixelOffset.dx, viewSize, holder), getYForPixel(pixelOffset.dy, viewSize, holder), ); /// With this function we can get horizontal /// position for the tooltip. double getTooltipLeft( double dx, double tooltipWidth, FLHorizontalAlignment tooltipHorizontalAlignment, double tooltipHorizontalOffset, ) => switch (tooltipHorizontalAlignment) { FLHorizontalAlignment.center => dx - (tooltipWidth / 2) + tooltipHorizontalOffset, FLHorizontalAlignment.right => dx + tooltipHorizontalOffset, FLHorizontalAlignment.left => dx - tooltipWidth + tooltipHorizontalOffset, }; } ================================================ FILE: lib/src/chart/base/axis_chart/axis_chart_scaffold_widget.dart ================================================ import 'package:fl_chart/src/chart/base/axis_chart/axis_chart_data.dart'; import 'package:fl_chart/src/chart/base/axis_chart/scale_axis.dart'; import 'package:fl_chart/src/chart/base/axis_chart/side_titles/side_titles_widget.dart'; import 'package:fl_chart/src/chart/base/axis_chart/transformation_config.dart'; import 'package:fl_chart/src/chart/base/custom_interactive_viewer.dart'; import 'package:fl_chart/src/extensions/fl_titles_data_extension.dart'; import 'package:flutter/material.dart'; /// A builder to build a chart. /// /// The [chartVirtualRect] is the virtual chart virtual rect to be used when /// laying out the chart's content. It is transformed based on users' /// interactions like scaling and panning. typedef ChartBuilder = Widget Function( BuildContext context, Rect? chartVirtualRect, ); /// A scaffold to show a scalable axis-based chart /// /// It contains some placeholders to represent an axis-based chart. /// /// It's something like the below graph: /// |----------------------| /// | | top | | /// |------|-------|-------| /// | left | chart | right | /// |------|-------|-------| /// | | bottom| | /// |----------------------| /// /// `left`, `top`, `right`, `bottom` are some place holders to show titles /// provided by [AxisChartData.titlesData] around the chart /// `chart` is a centered place holder to show a raw chart. The chart is /// built using [chartBuilder]. class AxisChartScaffoldWidget extends StatefulWidget { const AxisChartScaffoldWidget({ super.key, required this.chartBuilder, required this.data, this.transformationConfig = const FlTransformationConfig(), }); /// The builder to build the chart. final ChartBuilder chartBuilder; /// The data to build the chart. final AxisChartData data; /// {@template fl_chart.AxisChartScaffoldWidget.transformationConfig} /// The transformation configuration of the chart. /// /// Used to configure scaling and panning of the chart. /// {@endtemplate} final FlTransformationConfig transformationConfig; @override State createState() => _AxisChartScaffoldWidgetState(); } class _AxisChartScaffoldWidgetState extends State { late TransformationController _transformationController; final GlobalKey _chartKey = GlobalKey(); FlTransformationConfig get _transformationConfig => widget.transformationConfig; bool get _canScaleHorizontally => _transformationConfig.scaleAxis == FlScaleAxis.horizontal || _transformationConfig.scaleAxis == FlScaleAxis.free; bool get _canScaleVertically => _transformationConfig.scaleAxis == FlScaleAxis.vertical || _transformationConfig.scaleAxis == FlScaleAxis.free; @override void initState() { super.initState(); _transformationController = _transformationConfig.transformationController ?? TransformationController(); _transformationController.addListener(_transformationControllerListener); } @override void dispose() { _transformationController.removeListener(_transformationControllerListener); if (_transformationConfig.transformationController == null) { _transformationController.dispose(); } super.dispose(); } @override void didUpdateWidget(AxisChartScaffoldWidget oldWidget) { super.didUpdateWidget(oldWidget); switch (( oldWidget.transformationConfig.transformationController, widget.transformationConfig.transformationController )) { case (null, null): break; case (null, TransformationController()): _transformationController.dispose(); _transformationController = widget.transformationConfig.transformationController!; _transformationController .addListener(_transformationControllerListener); case (TransformationController(), null): _transformationController .removeListener(_transformationControllerListener); _transformationController = TransformationController(); _transformationController .addListener(_transformationControllerListener); case (TransformationController(), TransformationController()): if (oldWidget.transformationConfig.transformationController != widget.transformationConfig.transformationController) { _transformationController .removeListener(_transformationControllerListener); _transformationController = widget.transformationConfig.transformationController!; _transformationController .addListener(_transformationControllerListener); } } } void _transformationControllerListener() { setState(() {}); } // Applies the inverse transformation to the chart to get the zoomed // bounding box. // // The transformation matrix is inverted because the bounding box needs to // grow beyond the chart's boundaries when the chart is scaled in order // for its content to be laid out on the larger area. This leads to the // scaling effect. Rect? _calculateAdjustedRect(Rect rect) { final scale = _transformationController.value.getMaxScaleOnAxis(); if (scale == 1.0) { return null; } final inverseMatrix = Matrix4.inverted(_transformationController.value); final chartVirtualQuad = CustomInteractiveViewer.transformViewport( inverseMatrix, rect, ); final chartVirtualRect = CustomInteractiveViewer.axisAlignedBoundingBox( chartVirtualQuad, ); return Rect.fromLTWH( _canScaleHorizontally ? chartVirtualRect.left : rect.left, _canScaleVertically ? chartVirtualRect.top : rect.top, _canScaleHorizontally ? chartVirtualRect.width : rect.width, _canScaleVertically ? chartVirtualRect.height : rect.height, ); } bool get showLeftTitles { if (!widget.data.titlesData.show) { return false; } final showAxisTitles = widget.data.titlesData.leftTitles.showAxisTitles; final showSideTitles = widget.data.titlesData.leftTitles.showSideTitles; return showAxisTitles || showSideTitles; } bool get showRightTitles { if (!widget.data.titlesData.show) { return false; } final showAxisTitles = widget.data.titlesData.rightTitles.showAxisTitles; final showSideTitles = widget.data.titlesData.rightTitles.showSideTitles; return showAxisTitles || showSideTitles; } bool get showTopTitles { if (!widget.data.titlesData.show) { return false; } final showAxisTitles = widget.data.titlesData.topTitles.showAxisTitles; final showSideTitles = widget.data.titlesData.topTitles.showSideTitles; return showAxisTitles || showSideTitles; } bool get showBottomTitles { if (!widget.data.titlesData.show) { return false; } final showAxisTitles = widget.data.titlesData.bottomTitles.showAxisTitles; final showSideTitles = widget.data.titlesData.bottomTitles.showSideTitles; return showAxisTitles || showSideTitles; } List _stackWidgets(BoxConstraints constraints) { final margin = widget.data.titlesData.allSidesPadding; final borderData = widget.data.borderData.isVisible() ? widget.data.borderData.border : null; final borderWidth = borderData == null ? 0 : borderData.dimensions.horizontal; final borderHeight = borderData == null ? 0 : borderData.dimensions.vertical; final rect = Rect.fromLTRB( 0, 0, constraints.maxWidth - margin.horizontal - borderWidth, constraints.maxHeight - margin.vertical - borderHeight, ); final adjustedRect = _calculateAdjustedRect(rect); final virtualRect = switch (_transformationConfig.scaleAxis) { FlScaleAxis.none => null, FlScaleAxis() => adjustedRect, }; final chart = KeyedSubtree( key: _chartKey, child: widget.chartBuilder(context, virtualRect), ); final child = switch (_transformationConfig.scaleAxis) { FlScaleAxis.none => chart, FlScaleAxis() => CustomInteractiveViewer( transformationController: _transformationController, clipBehavior: Clip.none, trackpadScrollCausesScale: _transformationConfig.trackpadScrollCausesScale, maxScale: _transformationConfig.maxScale, minScale: _transformationConfig.minScale, panEnabled: _transformationConfig.panEnabled, scaleEnabled: _transformationConfig.scaleEnabled, child: SizedBox( width: rect.width, height: rect.height, child: chart, ), ), }; final widgets = [ Container( margin: margin, decoration: BoxDecoration(border: borderData), child: child, ), ]; int insertIndex(bool drawBelow) => drawBelow ? 0 : widgets.length; if (showLeftTitles) { widgets.insert( insertIndex(widget.data.titlesData.leftTitles.drawBelowEverything), SideTitlesWidget( side: AxisSide.left, axisChartData: widget.data, parentSize: constraints.biggest, chartVirtualRect: adjustedRect, ), ); } if (showTopTitles) { widgets.insert( insertIndex(widget.data.titlesData.topTitles.drawBelowEverything), SideTitlesWidget( side: AxisSide.top, axisChartData: widget.data, parentSize: constraints.biggest, chartVirtualRect: adjustedRect, ), ); } if (showRightTitles) { widgets.insert( insertIndex(widget.data.titlesData.rightTitles.drawBelowEverything), SideTitlesWidget( side: AxisSide.right, axisChartData: widget.data, parentSize: constraints.biggest, chartVirtualRect: adjustedRect, ), ); } if (showBottomTitles) { widgets.insert( insertIndex(widget.data.titlesData.bottomTitles.drawBelowEverything), SideTitlesWidget( side: AxisSide.bottom, axisChartData: widget.data, parentSize: constraints.biggest, chartVirtualRect: adjustedRect, ), ); } return widgets; } @override Widget build(BuildContext context) { return LayoutBuilder( builder: (context, constraints) { return RotatedBox( quarterTurns: widget.data.rotationQuarterTurns, child: Stack( children: _stackWidgets(constraints), ), ); }, ); } } ================================================ FILE: lib/src/chart/base/axis_chart/axis_chart_widgets.dart ================================================ import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/chart/base/axis_chart/axis_chart_helper.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; /// Wraps a [child] widget and applies some default behaviours /// /// Recommended to be used in [SideTitles.getTitlesWidget] /// You need to pass [axisSide] value that provided by [TitleMeta] /// It forces the widget to be close to the chart. /// It also applies a [space] to the chart. /// You can also fill [angle] in radians if you need to rotate your widget. /// To force widget to be positioned within its axis bounding box, /// define [fitInside] by passing [SideTitleFitInsideData] class SideTitleWidget extends StatefulWidget { const SideTitleWidget({ super.key, required this.child, required this.meta, this.space = 8.0, this.angle = 0.0, this.fitInside = const SideTitleFitInsideData( enabled: false, distanceFromEdge: 0, parentAxisSize: 0, axisPosition: 0, ), }); final TitleMeta meta; final double space; final Widget child; final double angle; /// Define fitInside options with [SideTitleFitInsideData] /// /// To makes things simpler, it's recommended to use /// [SideTitleFitInsideData.fromTitleMeta] and pass the /// TitleMeta provided from [SideTitles.getTitlesWidget] /// /// If [fitInside.enabled] is true, the widget will be placed /// inside the parent axis bounding box. /// /// Some translations will be applied to force /// children to be positioned inside the parent axis bounding box. /// /// Will override the [SideTitleWidget.space] and caused /// spacing between [SideTitles] children might be not equal. final SideTitleFitInsideData fitInside; @override State createState() => _SideTitleWidgetState(); } class _SideTitleWidgetState extends State { Alignment _getAlignment() => switch (widget.meta.axisSide) { AxisSide.left => Alignment.centerRight, AxisSide.top => Alignment.bottomCenter, AxisSide.right => Alignment.centerLeft, AxisSide.bottom => Alignment.topCenter, }; EdgeInsets _getMargin() => switch (widget.meta.axisSide) { AxisSide.left => EdgeInsets.only(right: widget.space), AxisSide.top => EdgeInsets.only(bottom: widget.space), AxisSide.right => EdgeInsets.only(left: widget.space), AxisSide.bottom => EdgeInsets.only(top: widget.space), }; /// Calculate child width/height final GlobalKey widgetKey = GlobalKey(); double? _childSize; void _getChildSize(Duration duration) { // If fitInside is false, no need to find child size if (!widget.fitInside.enabled) return; // If childSize is not null, no need to find the size anymore if (_childSize != null) return; final context = widgetKey.currentContext; if (context == null) return; // Set size based on its axis side final size = switch (widget.meta.axisSide) { AxisSide.left || AxisSide.right => context.size?.height ?? 0, AxisSide.top || AxisSide.bottom => context.size?.width ?? 0, }; // If childSize is the same, no need to set new value if (_childSize == size) return; setState(() => _childSize = size); } @override void initState() { super.initState(); SchedulerBinding.instance.addPostFrameCallback(_getChildSize); } @override void didUpdateWidget(covariant SideTitleWidget oldWidget) { super.didUpdateWidget(oldWidget); SchedulerBinding.instance.addPostFrameCallback(_getChildSize); } @override Widget build(BuildContext context) { return Transform.translate( offset: !widget.fitInside.enabled ? Offset.zero : AxisChartHelper().calcFitInsideOffset( axisSide: widget.meta.axisSide, childSize: _childSize, parentAxisSize: widget.fitInside.parentAxisSize, axisPosition: widget.fitInside.axisPosition, distanceFromEdge: widget.fitInside.distanceFromEdge, ), child: Transform.rotate( angle: widget.angle, child: Container( key: widgetKey, margin: _getMargin(), alignment: _getAlignment(), child: RotatedBox( quarterTurns: -widget.meta.rotationQuarterTurns, child: widget.child, ), ), ), ); } } ================================================ FILE: lib/src/chart/base/axis_chart/scale_axis.dart ================================================ enum FlScaleAxis { /// Scales the horizontal axis. horizontal, /// Scales the vertical axis. vertical, /// Scales both the horizontal and vertical axes. free, /// Does not scale the axes. none; /// Axes that allow scaling. static const scalingEnabledAxis = [ free, horizontal, vertical, ]; } ================================================ FILE: lib/src/chart/base/axis_chart/side_titles/side_titles_flex.dart ================================================ import 'dart:math' as math; import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; /// Inspired from [Flex] class SideTitlesFlex extends MultiChildRenderObjectWidget { SideTitlesFlex({ super.key, required this.direction, required this.axisSideMetaData, List widgetHolders = const [], }) : axisSideTitlesMetaData = widgetHolders.map((e) => e.metaData).toList(), super(children: widgetHolders.map((e) => e.widget).toList()); final Axis direction; final AxisSideMetaData axisSideMetaData; final List axisSideTitlesMetaData; @override AxisSideTitlesRenderFlex createRenderObject(BuildContext context) { return AxisSideTitlesRenderFlex( direction: direction, axisSideMetaData: axisSideMetaData, axisSideTitlesMetaData: axisSideTitlesMetaData, ); } @override void updateRenderObject( BuildContext context, covariant AxisSideTitlesRenderFlex renderObject, ) { renderObject ..direction = direction ..axisSideMetaData = axisSideMetaData ..axisSideTitlesMetaData = axisSideTitlesMetaData; } @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.add(EnumProperty('direction', direction)); } } class AxisSideTitlesRenderFlex extends RenderBox with ContainerRenderObjectMixin, RenderBoxContainerDefaultsMixin, DebugOverflowIndicatorMixin { AxisSideTitlesRenderFlex({ Axis direction = Axis.horizontal, required AxisSideMetaData axisSideMetaData, required List axisSideTitlesMetaData, }) : _direction = direction, _axisSideMetaData = axisSideMetaData, _axisSideTitlesMetaData = axisSideTitlesMetaData; Axis get direction => _direction; Axis _direction; set direction(Axis value) { if (_direction != value) { _direction = value; markNeedsLayout(); } } AxisSideMetaData get axisSideMetaData => _axisSideMetaData; AxisSideMetaData _axisSideMetaData; set axisSideMetaData(AxisSideMetaData value) { if (_axisSideMetaData != value) { _axisSideMetaData = value; markNeedsLayout(); } } List get axisSideTitlesMetaData => _axisSideTitlesMetaData; List _axisSideTitlesMetaData; set axisSideTitlesMetaData(List value) { if (_axisSideTitlesMetaData != value) { _axisSideTitlesMetaData = value; markNeedsLayout(); } } @override void setupParentData(RenderBox child) { if (child.parentData is! FlexParentData) { child.parentData = FlexParentData(); } } @override bool get debugNeedsLayout => false; @override double? computeDistanceToActualBaseline(TextBaseline baseline) { if (_direction == Axis.horizontal) { return defaultComputeDistanceToHighestActualBaseline(baseline); } return defaultComputeDistanceToFirstActualBaseline(baseline); } double _getCrossSize(Size size) { switch (_direction) { case Axis.horizontal: return size.height; case Axis.vertical: return size.width; } } double _getMainSize(Size size) { switch (_direction) { case Axis.horizontal: return size.width; case Axis.vertical: return size.height; } } @override Size computeDryLayout(BoxConstraints constraints) { final sizes = _computeSizes( layoutChild: ChildLayoutHelper.dryLayoutChild, constraints: constraints, ); switch (_direction) { case Axis.horizontal: return constraints.constrain(Size(sizes.mainSize, sizes.crossSize)); case Axis.vertical: return constraints.constrain(Size(sizes.crossSize, sizes.mainSize)); } } _LayoutSizes _computeSizes({ required BoxConstraints constraints, required ChildLayouter layoutChild, }) { // Determine used flex factor, size inflexible items, calculate free space. final maxMainSize = _direction == Axis.horizontal ? constraints.maxWidth : constraints.maxHeight; final canFlex = maxMainSize < double.infinity; var crossSize = 0.0; var allocatedSize = 0.0; // Sum of the sizes of the non-flexible children. var child = firstChild; while (child != null) { final childParentData = child.parentData! as FlexParentData; // Stretch final innerConstraints = switch (_direction) { Axis.horizontal => BoxConstraints.tightFor( height: constraints.maxHeight, ), Axis.vertical => BoxConstraints.tightFor( width: constraints.maxWidth, ), }; final childSize = layoutChild(child, innerConstraints); allocatedSize += _getMainSize(childSize); crossSize = math.max(crossSize, _getCrossSize(childSize)); assert(child.parentData == childParentData); child = childParentData.nextSibling; } final idealSize = canFlex ? maxMainSize : allocatedSize; return _LayoutSizes( mainSize: idealSize, crossSize: crossSize, allocatedSize: allocatedSize, ); } @override void performLayout() { final constraints = this.constraints; final sizes = _computeSizes( layoutChild: ChildLayoutHelper.layoutChild, constraints: constraints, ); var actualSize = sizes.mainSize; var crossSize = sizes.crossSize; // Align items along the main axis. switch (_direction) { case Axis.horizontal: size = constraints.constrain(Size(actualSize, crossSize)); actualSize = size.width; crossSize = size.height; case Axis.vertical: size = constraints.constrain(Size(crossSize, actualSize)); actualSize = size.height; crossSize = size.width; } // Position elements var child = firstChild; var counter = 0; while (child != null) { final childParentData = child.parentData! as FlexParentData; final metaData = _axisSideTitlesMetaData[counter]; final double childCrossPosition; // Stretch childCrossPosition = 0.0; final childMainPosition = metaData.axisPixelLocation - (_getMainSize(child.size) / 2); childParentData.offset = switch (_direction) { Axis.horizontal => Offset(childMainPosition, childCrossPosition), Axis.vertical => Offset(childCrossPosition, childMainPosition), }; child = childParentData.nextSibling; counter++; } } @override bool hitTestChildren(BoxHitTestResult result, {required Offset position}) { return defaultHitTestChildren(result, position: position); } @override void paint(PaintingContext context, Offset offset) { // There's no point in drawing the children if we're empty. if (size.isEmpty) { return; } _clipRectLayer.layer = null; defaultPaint(context, offset); } final LayerHandle _clipRectLayer = LayerHandle(); @override void dispose() { _clipRectLayer.layer = null; super.dispose(); } @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.add(EnumProperty('direction', direction)); } } class _LayoutSizes { const _LayoutSizes({ required this.mainSize, required this.crossSize, required this.allocatedSize, }); final double mainSize; final double crossSize; final double allocatedSize; } class AxisSideMetaData { AxisSideMetaData(this.minValue, this.maxValue, this.axisViewSize); final double minValue; final double maxValue; final double axisViewSize; double get diff => maxValue - minValue; } class AxisSideTitleMetaData with EquatableMixin { AxisSideTitleMetaData(this.axisValue, this.axisPixelLocation); final double axisValue; final double axisPixelLocation; @override List get props => [ axisValue, axisPixelLocation, ]; } class AxisSideTitleWidgetHolder { AxisSideTitleWidgetHolder(this.metaData, this.widget); final AxisSideTitleMetaData metaData; final Widget widget; } ================================================ FILE: lib/src/chart/base/axis_chart/side_titles/side_titles_widget.dart ================================================ import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/chart/base/axis_chart/axis_chart_helper.dart'; import 'package:fl_chart/src/chart/base/axis_chart/side_titles/side_titles_flex.dart'; import 'package:fl_chart/src/extensions/bar_chart_data_extension.dart'; import 'package:fl_chart/src/extensions/edge_insets_extension.dart'; import 'package:fl_chart/src/extensions/fl_border_data_extension.dart'; import 'package:fl_chart/src/extensions/fl_titles_data_extension.dart'; import 'package:fl_chart/src/extensions/size_extension.dart'; import 'package:fl_chart/src/utils/utils.dart'; import 'package:flutter/material.dart'; class SideTitlesWidget extends StatefulWidget { const SideTitlesWidget({ super.key, required this.side, required this.axisChartData, required this.parentSize, this.chartVirtualRect, }); final AxisSide side; final AxisChartData axisChartData; final Size parentSize; final Rect? chartVirtualRect; @override State createState() => _SideTitlesWidgetState(); } class _SideTitlesWidgetState extends State { bool get isHorizontal => widget.side == AxisSide.top || widget.side == AxisSide.bottom; bool get isVertical => !isHorizontal; double get minX => widget.axisChartData.minX; double get maxX => widget.axisChartData.maxX; double get baselineX => widget.axisChartData.baselineX; double get minY => widget.axisChartData.minY; double get maxY => widget.axisChartData.maxY; double get baselineY => widget.axisChartData.baselineY; double get axisMin => isHorizontal ? minX : minY; double get axisMax => isHorizontal ? maxX : maxY; double get axisBaseLine => isHorizontal ? baselineX : baselineY; FlTitlesData get titlesData => widget.axisChartData.titlesData; bool get isLeftOrTop => widget.side == AxisSide.left || widget.side == AxisSide.top; bool get isRightOrBottom => widget.side == AxisSide.right || widget.side == AxisSide.bottom; AxisTitles get axisTitles => switch (widget.side) { AxisSide.left => titlesData.leftTitles, AxisSide.top => titlesData.topTitles, AxisSide.right => titlesData.rightTitles, AxisSide.bottom => titlesData.bottomTitles, }; SideTitles get sideTitles => axisTitles.sideTitles; Axis get direction => isHorizontal ? Axis.horizontal : Axis.vertical; Axis get counterDirection => isHorizontal ? Axis.vertical : Axis.horizontal; Alignment get alignment => switch (widget.side) { AxisSide.left => Alignment.centerLeft, AxisSide.top => Alignment.topCenter, AxisSide.right => Alignment.centerRight, AxisSide.bottom => Alignment.bottomCenter, }; EdgeInsets get thisSidePadding { final titlesPadding = titlesData.allSidesPadding; final borderPadding = widget.axisChartData.borderData.allSidesPadding; return switch (widget.side) { AxisSide.right || AxisSide.left => titlesPadding.onlyTopBottom + borderPadding.onlyTopBottom, AxisSide.top || AxisSide.bottom => titlesPadding.onlyLeftRight + borderPadding.onlyLeftRight, }; } double get thisSidePaddingTotal { final borderPadding = widget.axisChartData.borderData.allSidesPadding; final titlesPadding = titlesData.allSidesPadding; return switch (widget.side) { AxisSide.right || AxisSide.left => titlesPadding.vertical + borderPadding.vertical, AxisSide.top || AxisSide.bottom => titlesPadding.horizontal + borderPadding.horizontal, }; } Size get viewSize { late Size size; final chartVirtualRect = widget.chartVirtualRect; if (chartVirtualRect == null) { size = widget.parentSize; } else { size = chartVirtualRect.size + Offset(thisSidePaddingTotal, thisSidePaddingTotal); } return size.rotateByQuarterTurns( widget.axisChartData.rotationQuarterTurns, ); } double get axisOffset { final chartVirtualRect = widget.chartVirtualRect; if (chartVirtualRect == null) { return 0; } return switch (widget.side) { AxisSide.left || AxisSide.right => chartVirtualRect.top, AxisSide.top || AxisSide.bottom => chartVirtualRect.left, }; } List makeWidgets( double axisViewSize, double axisMin, double axisMax, AxisSide side, ) { List axisPositions; final interval = sideTitles.interval ?? Utils().getEfficientInterval( axisViewSize, axisMax - axisMin, ); if (isHorizontal && widget.axisChartData is BarChartData) { final barChartData = widget.axisChartData as BarChartData; if (barChartData.barGroups.isEmpty) { return []; } final xLocations = barChartData.calculateGroupsX(axisViewSize); axisPositions = xLocations.asMap().entries.map((e) { final index = e.key; final xLocation = e.value; final xValue = barChartData.barGroups[index].x; final adjustedLocation = xLocation + axisOffset; return AxisSideTitleMetaData(xValue.toDouble(), adjustedLocation); }).toList(); } else { final axisValues = AxisChartHelper().iterateThroughAxis( min: axisMin, max: axisMax, minIncluded: sideTitles.minIncluded, maxIncluded: sideTitles.maxIncluded, baseLine: axisBaseLine, interval: interval, ); axisPositions = axisValues.map((axisValue) { final axisDiff = axisMax - axisMin; var portion = 0.0; if (axisDiff > 0) { portion = (axisValue - axisMin) / axisDiff; } if (isVertical) { portion = 1 - portion; } final axisLocation = portion * axisViewSize + axisOffset; return AxisSideTitleMetaData(axisValue, axisLocation); }).toList(); } axisPositions = _getPositionsWithinChartRange(axisPositions, side); return axisPositions.map( (metaData) { return AxisSideTitleWidgetHolder( metaData, sideTitles.getTitlesWidget( metaData.axisValue, TitleMeta( min: axisMin, max: axisMax, appliedInterval: interval, sideTitles: sideTitles, formattedValue: Utils().formatNumber( axisMin, axisMax, metaData.axisValue, ), axisSide: side, parentAxisSize: axisViewSize, axisPosition: metaData.axisPixelLocation, rotationQuarterTurns: widget.axisChartData.rotationQuarterTurns, ), ), ); }, ).toList(); } List _getPositionsWithinChartRange( List axisPositions, AxisSide side, ) { final chartSize = Size( widget.parentSize.width - thisSidePaddingTotal, widget.parentSize.height - thisSidePaddingTotal, ).rotateByQuarterTurns(widget.axisChartData.rotationQuarterTurns); // Add 1 pixel to the chart's edges to avoid clipping the last title. final chartRect = (Offset.zero & chartSize).inflate(1); return axisPositions.where((metaData) { final location = metaData.axisPixelLocation; return switch (side) { AxisSide.left || AxisSide.right => chartRect.contains(Offset(0, location)), AxisSide.top || AxisSide.bottom => chartRect.contains(Offset(location, 0)), }; }).toList(); } @override Widget build(BuildContext context) { if (!axisTitles.showAxisTitles && !axisTitles.showSideTitles) { return Container(); } final axisViewSize = isHorizontal ? viewSize.width : viewSize.height; return Align( alignment: alignment, child: Flex( direction: counterDirection, mainAxisSize: MainAxisSize.min, children: [ if (isLeftOrTop && axisTitles.axisNameWidget != null) _AxisTitleWidget( axisTitles: axisTitles, side: widget.side, axisViewSize: axisViewSize, ), if (sideTitles.showTitles) Container( width: isHorizontal ? axisViewSize : sideTitles.reservedSize, height: isHorizontal ? sideTitles.reservedSize : axisViewSize, margin: thisSidePadding, child: SideTitlesFlex( direction: direction, axisSideMetaData: AxisSideMetaData( axisMin, axisMax, axisViewSize - thisSidePaddingTotal, ), widgetHolders: makeWidgets( axisViewSize - thisSidePaddingTotal, axisMin, axisMax, widget.side, ), ), ), if (isRightOrBottom && axisTitles.axisNameWidget != null) _AxisTitleWidget( axisTitles: axisTitles, side: widget.side, axisViewSize: axisViewSize, ), ], ), ); } } class _AxisTitleWidget extends StatelessWidget { const _AxisTitleWidget({ required this.axisTitles, required this.side, required this.axisViewSize, }); final AxisTitles axisTitles; final AxisSide side; final double axisViewSize; int get axisNameQuarterTurns => switch (side) { AxisSide.right => 3, AxisSide.left => 3, AxisSide.top => 0, AxisSide.bottom => 0, }; bool get isHorizontal => side == AxisSide.top || side == AxisSide.bottom; @override Widget build(BuildContext context) { return SizedBox( width: isHorizontal ? axisViewSize : axisTitles.axisNameSize, height: isHorizontal ? axisTitles.axisNameSize : axisViewSize, child: Center( child: RotatedBox( quarterTurns: axisNameQuarterTurns, child: axisTitles.axisNameWidget, ), ), ); } } ================================================ FILE: lib/src/chart/base/axis_chart/transformation_config.dart ================================================ import 'package:fl_chart/src/chart/base/axis_chart/scale_axis.dart'; import 'package:flutter/widgets.dart'; /// Configuration for the transformation of an axis-based chart. class FlTransformationConfig { const FlTransformationConfig({ this.scaleAxis = FlScaleAxis.none, this.minScale = 1, this.maxScale = 2.5, this.panEnabled = true, this.scaleEnabled = true, this.trackpadScrollCausesScale = false, this.transformationController, }) : assert(minScale >= 1, 'minScale must be greater than or equal to 1'), assert( maxScale >= minScale, 'maxScale must be greater than or equal to minScale', ); /// Determines what axis of the chart should be scaled. final FlScaleAxis scaleAxis; /// The minimum scale of the chart. /// /// Ignored when [scaleAxis] is [FlScaleAxis.none]. final double minScale; /// The maximum scale of the chart. /// /// Ignored when [scaleAxis] is [FlScaleAxis.none]. final double maxScale; /// If false, the user will be prevented from panning. /// /// Ignored when [scaleAxis] is [FlScaleAxis.none]. final bool panEnabled; /// If false, the user will be prevented from scaling. /// /// Ignored when [scaleAxis] is [FlScaleAxis.none]. final bool scaleEnabled; /// Whether trackpad scroll causes scale. /// /// Ignored when [scaleAxis] is [FlScaleAxis.none]. final bool trackpadScrollCausesScale; /// The transformation controller to control the transformation of the chart. final TransformationController? transformationController; } ================================================ FILE: lib/src/chart/base/base_chart/base_chart_data.dart ================================================ // coverage:ignore-file import 'dart:core'; import 'dart:ui'; import 'package:equatable/equatable.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart'; import 'package:fl_chart/src/extensions/border_extension.dart'; import 'package:flutter/material.dart'; /// This class holds all data needed for [BaseChartPainter]. /// /// In this phase we draw the border, /// and handle touches in an abstract way. abstract class BaseChartData with EquatableMixin { /// It draws 4 borders around your chart, you can customize it using [borderData], /// [touchData] defines the touch behavior and responses. BaseChartData({ FlBorderData? borderData, }) : borderData = borderData ?? FlBorderData(); /// Holds data to drawing border around the chart. final FlBorderData borderData; BaseChartData lerp(BaseChartData a, BaseChartData b, double t); /// Used for equality check, see [EquatableMixin]. @override List get props => [ borderData, ]; } /// Holds data to drawing border around the chart. class FlBorderData with EquatableMixin { /// [show] Determines showing or hiding border around the chart. /// [border] Determines the visual look of 4 borders, see [Border]. FlBorderData({ bool? show, Border? border, }) : show = show ?? true, border = border ?? Border.all(); final bool show; final Border border; /// returns false if all borders have 0 width or 0 opacity bool isVisible() => show && border.isVisible(); /// Lerps a [FlBorderData] based on [t] value, check [Tween.lerp]. static FlBorderData lerp(FlBorderData a, FlBorderData b, double t) => FlBorderData( show: b.show, border: Border.lerp(a.border, b.border, t), ); /// Copies current [FlBorderData] to a new [FlBorderData], /// and replaces provided values. FlBorderData copyWith({ bool? show, Border? border, }) => FlBorderData( show: show ?? this.show, border: border ?? this.border, ); /// Used for equality check, see [EquatableMixin]. @override List get props => [ show, border, ]; } /// Holds data to handle touch events, and touch responses in abstract way. /// /// There is a touch flow, explained [here](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/handle_touches.md) /// in a simple way, each chart's renderer captures the touch events, and passes the pointerEvent /// to the painter, and gets touched spot, and wraps it into a concrete [BaseTouchResponse]. abstract class FlTouchData with EquatableMixin { /// You can disable or enable the touch system using [enabled] flag, const FlTouchData( this.enabled, this.touchCallback, this.mouseCursorResolver, this.longPressDuration, ); /// You can disable or enable the touch system using [enabled] flag, final bool enabled; /// [touchCallback] notifies you about the happened touch/pointer events. /// It gives you a [FlTouchEvent] which is the happened event such as [FlPointerHoverEvent], [FlTapUpEvent], ... /// It also gives you a [BaseTouchResponse] which is the chart specific type and contains information /// about the elements that has touched. final BaseTouchCallback? touchCallback; /// Using [mouseCursorResolver] you can change the mouse cursor /// based on the provided [FlTouchEvent] and [BaseTouchResponse] final MouseCursorResolver? mouseCursorResolver; /// This property that allows to customize the duration of the longPress gesture. /// default to 500 milliseconds refer to [kLongPressTimeout]. final Duration? longPressDuration; /// Used for equality check, see [EquatableMixin]. @override List get props => [ enabled, touchCallback, mouseCursorResolver, longPressDuration, ]; } /// Holds data to clipping chart around its borders. class FlClipData with EquatableMixin { /// Creates data that clips specified sides const FlClipData({ required this.top, required this.bottom, required this.left, required this.right, }); /// Creates data that clips all sides const FlClipData.all() : this(top: true, bottom: true, left: true, right: true); /// Creates data that clips only top and bottom side const FlClipData.vertical() : this(top: true, bottom: true, left: false, right: false); /// Creates data that clips only left and right side const FlClipData.horizontal() : this(top: false, bottom: false, left: true, right: true); /// Creates data that doesn't clip any side const FlClipData.none() : this(top: false, bottom: false, left: false, right: false); final bool top; final bool bottom; final bool left; final bool right; /// Checks whether any of the sides should be clipped bool get any => top || bottom || left || right; /// Copies current [FlBorderData] to a new [FlBorderData], /// and replaces provided values. FlClipData copyWith({ bool? top, bool? bottom, bool? left, bool? right, }) => FlClipData( top: top ?? this.top, bottom: bottom ?? this.bottom, left: left ?? this.left, right: right ?? this.right, ); /// Used for equality check, see [EquatableMixin]. @override List get props => [top, bottom, left, right]; } /// Chart's touch callback. typedef BaseTouchCallback = void Function( FlTouchEvent, R?, ); /// It gives you the happened [FlTouchEvent] and existed [R] data at the event's location, /// then you should provide a [MouseCursor] to change the cursor at the event's location. /// For example you can pass the [SystemMouseCursors.click] to change the mouse cursor to click. typedef MouseCursorResolver = MouseCursor Function( FlTouchEvent, R?, ); /// This class holds the touch response details of charts. abstract class BaseTouchResponse { BaseTouchResponse({ required this.touchLocation, }); /// The location of the touch in pixels on the screen. final Offset touchLocation; } /// A reusable label configuration for chart elements. class FlLabel with EquatableMixin { const FlLabel({ this.show = true, this.text = '', this.style, this.angle = 0, this.textDirection = TextDirection.ltr, }); /// Whether the label is visible. final bool show; /// The text content of the label. final String text; /// The text style of the label. final TextStyle? style; /// Rotation angle of the label in degrees. final double angle; /// Text direction of the label. final TextDirection textDirection; /// Lerps a [FlLabel] based on [t] value, check [Tween.lerp]. static FlLabel lerp(FlLabel a, FlLabel b, double t) => FlLabel( show: b.show, text: b.text, style: TextStyle.lerp(a.style, b.style, t), angle: lerpDouble(a.angle, b.angle, t)!, textDirection: b.textDirection, ); /// Copies current [FlLabel] to a new [FlLabel], /// and replaces provided values. FlLabel copyWith({ bool? show, String? text, TextStyle? style, double? angle, TextDirection? textDirection, }) => FlLabel( show: show ?? this.show, text: text ?? this.text, style: style ?? this.style, angle: angle ?? this.angle, textDirection: textDirection ?? this.textDirection, ); /// Used for equality check, see [EquatableMixin]. @override List get props => [show, text, style, angle, textDirection]; } /// Controls an element horizontal alignment to given point. enum FLHorizontalAlignment { /// Element shown horizontally center aligned to a given point. center, /// Element shown on the left side of the given point. left, /// Element shown on the right side of the given point. right, } ================================================ FILE: lib/src/chart/base/base_chart/base_chart_painter.dart ================================================ // coverage:ignore-file import 'package:fl_chart/src/chart/base/base_chart/base_chart_data.dart'; import 'package:fl_chart/src/utils/canvas_wrapper.dart'; import 'package:flutter/material.dart'; /// Base class of our painters. class BaseChartPainter { /// Draws some basic elements const BaseChartPainter(); // Paints [BaseChartData] into the provided canvas. void paint( BuildContext context, CanvasWrapper canvasWrapper, PaintHolder holder, ) {} } /// Holds data for painting on canvas class PaintHolder { /// Holds data for painting on canvas const PaintHolder( this.data, this.targetData, this.textScaler, [ this.chartVirtualRect, ]); /// [data] is what we need to show frame by frame (it might be changed by an animator) final Data data; /// [targetData] is the target of animation that is playing. final Data targetData; /// system [TextScaler] used for scaling texts for better readability final TextScaler textScaler; /// The virtual rect representing the chart when it is scaled or panned. /// /// The chart will be drawn in this virtual canvas, and then clipped to the /// actual canvas. /// /// When the chart is scaled, the virtual canvas will be larger than the /// actual canvas. This will lead to the content being laid out on the larger /// area. Thus resulting in the scaling effect. /// /// Null when not scaling or panning. final Rect? chartVirtualRect; /// Returns the size of the chart that is actually being painted. /// /// When scaling the chart, the chart is painted on a larger area to simulate /// the zoom effect. This function returns the size of the area that is /// actually being painted. /// /// When not scaled it returns the actual size of the chart. Size getChartUsableSize(Size viewSize) { return chartVirtualRect?.size ?? viewSize; } } ================================================ FILE: lib/src/chart/base/base_chart/fl_touch_event.dart ================================================ // coverage:ignore-file import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/services.dart'; /// Parent class for several kind of touch/pointer events (like tap, panMode, longPressStart, ...) abstract class FlTouchEvent { const FlTouchEvent(); /// Represents the position of happened touch/pointer event /// /// Some events such as [FlPanCancelEvent] and [FlTapCancelEvent] /// doesn't have any position (their details come from flutter engine). /// That's why this field is nullable Offset? get localPosition => null; /// excludes exit or up events to show interactions on charts bool get isInterestedForInteractions { final isLinux = defaultTargetPlatform == TargetPlatform.linux; final isMacOS = defaultTargetPlatform == TargetPlatform.macOS; final isWindows = defaultTargetPlatform == TargetPlatform.windows; final isDesktopOrWeb = kIsWeb || isLinux || isMacOS || isWindows; /// In desktop when mouse hovers into a chart element using [FlPointerHoverEvent], we show the interaction /// and when tap happens at the same position, interaction will be dismissed because of [FlTapUpEvent]. /// That's why we exclude it on desktop or web if (isDesktopOrWeb && this is FlTapUpEvent) { return true; } return this is! FlPanEndEvent && this is! FlPanCancelEvent && this is! FlPointerExitEvent && this is! FlLongPressEnd && this is! FlTapUpEvent && this is! FlTapCancelEvent; } } /// When a pointer has contacted the screen and might begin to move /// /// The [details] object provides the position of the touch. /// Inspired from [GestureDragDownCallback] class FlPanDownEvent extends FlTouchEvent { const FlPanDownEvent(this.details); /// Contains information of happened touch gesture final DragDownDetails details; /// Represents the position of happened touch/pointer event @override Offset get localPosition => details.localPosition; } /// When a pointer has contacted the screen and has begun to move. /// /// The [details] object provides the position of the touch when it first /// touched the surface. /// Inspired from [GestureDragStartCallback]. class FlPanStartEvent extends FlTouchEvent { /// Creates const FlPanStartEvent(this.details); /// Contains information of happened touch gesture final DragStartDetails details; /// Represents the position of happened touch/pointer event @override Offset get localPosition => details.localPosition; } /// When a pointer that is in contact with the screen and moving /// has moved again. /// /// The [details] object provides the position of the touch and the distance it /// has traveled since the last update. /// Inspired from [GestureDragUpdateCallback] class FlPanUpdateEvent extends FlTouchEvent { const FlPanUpdateEvent(this.details); /// Contains information of happened touch gesture final DragUpdateDetails details; /// Represents the position of happened touch/pointer event @override Offset get localPosition => details.localPosition; } /// When the pointer that previously triggered a [FlPanStartEvent] did not complete. /// Inspired from [GestureDragCancelCallback] class FlPanCancelEvent extends FlTouchEvent { const FlPanCancelEvent(); } /// When a pointer that was previously in contact with the screen /// and moving is no longer in contact with the screen. /// /// The velocity at which the pointer was moving when it stopped contacting /// the screen is available in the [details]. /// Inspired from [GestureDragEndCallback] class FlPanEndEvent extends FlTouchEvent { const FlPanEndEvent(this.details); /// Contains information of happened touch gesture final DragEndDetails details; } /// When a pointer that might cause a tap has contacted the /// screen. /// /// The position at which the pointer contacted the screen is available in the /// [details]. /// Inspired from [GestureTapDownCallback] class FlTapDownEvent extends FlTouchEvent { const FlTapDownEvent(this.details); /// Contains information of happened touch gesture final TapDownDetails details; /// Represents the position of happened touch/pointer event @override Offset get localPosition => details.localPosition; } /// When the pointer that previously triggered a [FlTapDownEvent] will not end up causing a tap. /// Inspired from [GestureTapCancelCallback] class FlTapCancelEvent extends FlTouchEvent { const FlTapCancelEvent(); } /// When a pointer that will trigger a tap has stopped contacting /// the screen. /// /// The position at which the pointer stopped contacting the screen is available /// in the [details]. /// Inspired from [GestureTapUpCallback] class FlTapUpEvent extends FlTouchEvent { const FlTapUpEvent(this.details); /// Contains information of happened touch gesture final TapUpDetails details; /// Represents the position of happened touch/pointer event @override Offset get localPosition => details.localPosition; } /// Called When a pointer has remained in contact with the screen at the /// same location for a long period of time. /// /// Details are available in the [details]. /// /// Inspired from [GestureLongPressStartCallback] class FlLongPressStart extends FlTouchEvent { const FlLongPressStart(this.details); /// Contains information of happened touch gesture final LongPressStartDetails details; /// Represents the position of happened touch/pointer event @override Offset get localPosition => details.localPosition; } /// When a pointer is moving after being held in contact at the same /// location for a long period of time. Reports the new position and its offset /// from the original down position. /// /// Details are available in the [details] /// /// Inspired from [GestureLongPressMoveUpdateCallback] class FlLongPressMoveUpdate extends FlTouchEvent { const FlLongPressMoveUpdate(this.details); /// Contains information of happened touch gesture final LongPressMoveUpdateDetails details; /// Represents the position of happened touch/pointer event @override Offset get localPosition => details.localPosition; } /// When a pointer stops contacting the screen after a long press /// gesture was detected. Also reports the position where the pointer stopped /// contacting the screen. /// /// Details are available in the [details] /// /// Inspired from [GestureLongPressEndCallback] class FlLongPressEnd extends FlTouchEvent { const FlLongPressEnd(this.details); /// Contains information of happened touch gesture final LongPressEndDetails details; /// Represents the position of happened touch/pointer event @override Offset get localPosition => details.localPosition; } /// The pointer has moved with respect to the device while the pointer is or is /// not in contact with the device, and it has entered our chart. /// /// Details are available in the [event] /// /// Inspired from [PointerEnterEventListener] class FlPointerEnterEvent extends FlTouchEvent { const FlPointerEnterEvent(this.event); /// Contains information of happened pointer event final PointerEnterEvent event; /// Represents the position of happened touch/pointer event @override Offset get localPosition => event.localPosition; } /// The pointer has moved with respect to the device while the pointer is not /// in contact with the device. /// /// Details are available in the [event] /// /// Inspired from [PointerHoverEventListener] class FlPointerHoverEvent extends FlTouchEvent { const FlPointerHoverEvent(this.event); /// Contains information of happened pointer event final PointerHoverEvent event; /// Represents the position of happened touch/pointer event @override Offset get localPosition => event.localPosition; } /// The pointer has moved with respect to the device while the pointer is or is /// not in contact with the device, and exited our chart. /// /// Inspired from [PointerExitEventListener] which contains [PointerExitEvent] class FlPointerExitEvent extends FlTouchEvent { const FlPointerExitEvent(this.event); /// Contains information of happened pointer event final PointerExitEvent event; /// Represents the position of happened touch/pointer event @override Offset get localPosition => event.localPosition; } ================================================ FILE: lib/src/chart/base/base_chart/render_base_chart.dart ================================================ // coverage:ignore-file import 'package:fl_chart/src/chart/base/base_chart/base_chart_data.dart'; import 'package:fl_chart/src/chart/base/base_chart/fl_touch_event.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; /// It implements shared logics between our renderers such as touch/pointer events recognition, size, layout, ... abstract class RenderBaseChart extends RenderBox implements MouseTrackerAnnotation { /// We use [FlTouchData] to retrieve [FlTouchData.touchCallback] and [FlTouchData.mouseCursorResolver] /// to invoke them when touch happens. RenderBaseChart( FlTouchData? touchData, BuildContext context, { required bool canBeScaled, }) : _canBeScaled = canBeScaled, _buildContext = context { updateBaseTouchData(touchData); initGestureRecognizers(); } bool get canBeScaled => _canBeScaled; bool _canBeScaled; set canBeScaled(bool value) { if (_canBeScaled == value) return; _canBeScaled = value; markNeedsPaint(); } // We use buildContext to retrieve Theme data BuildContext get buildContext => _buildContext; BuildContext _buildContext; set buildContext(BuildContext value) { _buildContext = value; markNeedsPaint(); } void updateBaseTouchData(FlTouchData? value) { _touchCallback = value?.touchCallback; _mouseCursorResolver = value?.mouseCursorResolver; _longPressDuration = value?.longPressDuration; } BaseTouchCallback? _touchCallback; MouseCursorResolver? _mouseCursorResolver; Duration? _longPressDuration; MouseCursor _latestMouseCursor = MouseCursor.defer; late bool _validForMouseTracker; /// Recognizes pan gestures, such as onDown, onStart, onUpdate, onCancel, ... @visibleForTesting late PanGestureRecognizer panGestureRecognizer; /// Recognizes tap gestures, such as onTapDown, onTapCancel and onTapUp @visibleForTesting late TapGestureRecognizer tapGestureRecognizer; /// Recognizes longPress gestures, such as onLongPressStart, onLongPressMoveUpdate and onLongPressEnd @visibleForTesting late LongPressGestureRecognizer longPressGestureRecognizer; /// Initializes our recognizers and implement their callbacks. void initGestureRecognizers() { panGestureRecognizer = PanGestureRecognizer(); panGestureRecognizer ..onDown = (dragDownDetails) { _notifyTouchEvent(FlPanDownEvent(dragDownDetails)); } ..onStart = (dragStartDetails) { _notifyTouchEvent(FlPanStartEvent(dragStartDetails)); } ..onUpdate = (dragUpdateDetails) { _notifyTouchEvent(FlPanUpdateEvent(dragUpdateDetails)); } ..onCancel = () { _notifyTouchEvent(const FlPanCancelEvent()); } ..onEnd = (dragEndDetails) { _notifyTouchEvent(FlPanEndEvent(dragEndDetails)); }; tapGestureRecognizer = TapGestureRecognizer(); tapGestureRecognizer ..onTapDown = (tapDownDetails) { _notifyTouchEvent(FlTapDownEvent(tapDownDetails)); } ..onTapCancel = () { _notifyTouchEvent(const FlTapCancelEvent()); } ..onTapUp = (tapUpDetails) { _notifyTouchEvent(FlTapUpEvent(tapUpDetails)); }; longPressGestureRecognizer = LongPressGestureRecognizer(duration: _longPressDuration); longPressGestureRecognizer ..onLongPressStart = (longPressStartDetails) { _notifyTouchEvent(FlLongPressStart(longPressStartDetails)); } ..onLongPressMoveUpdate = (longPressMoveUpdateDetails) { _notifyTouchEvent( FlLongPressMoveUpdate(longPressMoveUpdateDetails), ); } ..onLongPressEnd = (longPressEndDetails) => _notifyTouchEvent(FlLongPressEnd(longPressEndDetails)); } @override void performLayout() { size = computeDryLayout(constraints); } @override Size computeDryLayout(BoxConstraints constraints) { return Size(constraints.maxWidth, constraints.maxHeight); } @override bool hitTestSelf(Offset position) => true; /// Feeds our gesture recognizers for handling events, we also handle [PointerHoverEvent] here. /// /// Our gesture recognizers are responsible for notifying us about happened gestures (such as tap, panMove, ...) /// we need to give them [PointerDownEvent] then they will listen to the global [GestureBinding] for further events. /// /// We need to handle [PointerHoverEvent] because there is no gesture recognizer /// for mouse hover events (in fact they don't have any gestures, they are just events). @override void handleEvent(PointerEvent event, covariant BoxHitTestEntry entry) { assert(debugHandleEvent(event, entry)); if (_touchCallback == null) { return; } if (event is PointerDownEvent) { longPressGestureRecognizer.addPointer(event); tapGestureRecognizer.addPointer(event); if (!canBeScaled) { panGestureRecognizer.addPointer(event); } } else if (event is PointerHoverEvent) { _notifyTouchEvent(FlPointerHoverEvent(event)); } } /// Here we handle mouse hover enter event @override PointerEnterEventListener? get onEnter => (event) => _notifyTouchEvent(FlPointerEnterEvent(event)); /// Here we handle mouse hover exit event @override PointerExitEventListener? get onExit => (event) => _notifyTouchEvent(FlPointerExitEvent(event)); /// Invokes the [_touchCallback] to notify listeners of this [FlTouchEvent] /// /// We get a [BaseTouchResponse] using [getResponseAtLocation] for events which contains a localPosition. /// Then we invoke [_touchCallback] using the [event] and [response]. void _notifyTouchEvent(FlTouchEvent event) { if (_touchCallback == null) { return; } final localPosition = event.localPosition; R? response; if (localPosition != null) { response = getResponseAtLocation(localPosition); } _touchCallback!(event, response); if (_mouseCursorResolver == null) { _latestMouseCursor = MouseCursor.defer; } else { _latestMouseCursor = _mouseCursorResolver!(event, response); } } /// Represents the mouse cursor style when hovers on our chart /// In the future we can change it runtime, for example we can turn it to /// [SystemMouseCursors.click] when mouse hovers a specific point of our chart. @override MouseCursor get cursor => _latestMouseCursor; /// [MouseTracker] will catch us if this variable is true @override bool get validForMouseTracker => _validForMouseTracker; /// Charts need to implement this class to tell us what [BaseTouchResponse] is available at provided [localPosition] /// When touch/pointer event happens, we send it to the user alongside the [FlTouchEvent] using [_touchCallback] R getResponseAtLocation(Offset localPosition); @override void attach(PipelineOwner owner) { super.attach(owner); _validForMouseTracker = true; } @override void detach() { _validForMouseTracker = false; super.detach(); } } ================================================ FILE: lib/src/chart/base/custom_interactive_viewer.dart ================================================ // coverage:ignore-file // This file is copied from Flutter's InteractiveViewer widget. // The only change is that the child is not wrapped in a `Transform` so // we can react to the transformation ourselves. // // This should be removed once the official InteractiveViewer allows to disable // the Transform widget. // Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:async'; import 'dart:math' as math; import 'package:flutter/foundation.dart' show clampDouble; import 'package:flutter/gestures.dart'; import 'package:flutter/physics.dart'; import 'package:flutter/widgets.dart'; import 'package:vector_math/vector_math_64.dart' show Matrix4, Quad, Vector3; typedef CustomInteractiveViewerWidgetBuilder = Widget Function( BuildContext context, Quad viewport, ); @immutable class CustomInteractiveViewer extends StatefulWidget { CustomInteractiveViewer({ super.key, this.clipBehavior = Clip.hardEdge, this.panAxis = PanAxis.free, this.boundaryMargin = EdgeInsets.zero, this.constrained = true, this.maxScale = 2.5, this.minScale = 0.8, this.interactionEndFrictionCoefficient = _kDrag, this.onInteractionEnd, this.onInteractionStart, this.onInteractionUpdate, this.panEnabled = true, this.scaleEnabled = true, this.scaleFactor = kDefaultMouseScrollToScaleFactor, this.transformationController, this.alignment, this.trackpadScrollCausesScale = false, required this.child, }) : assert(minScale > 0), assert(interactionEndFrictionCoefficient > 0), assert(minScale.isFinite), assert(maxScale > 0), assert(!maxScale.isNaN), assert(maxScale >= minScale), assert( (boundaryMargin.horizontal.isInfinite && boundaryMargin.vertical.isInfinite) || (boundaryMargin.top.isFinite && boundaryMargin.right.isFinite && boundaryMargin.bottom.isFinite && boundaryMargin.left.isFinite), ), builder = null; CustomInteractiveViewer.builder({ super.key, this.clipBehavior = Clip.hardEdge, this.panAxis = PanAxis.free, this.boundaryMargin = EdgeInsets.zero, this.maxScale = 2.5, this.minScale = 0.8, this.interactionEndFrictionCoefficient = _kDrag, this.onInteractionEnd, this.onInteractionStart, this.onInteractionUpdate, this.panEnabled = true, this.scaleEnabled = true, this.scaleFactor = 200.0, this.transformationController, this.alignment, this.trackpadScrollCausesScale = false, required CustomInteractiveViewerWidgetBuilder this.builder, }) : assert(minScale > 0), assert(interactionEndFrictionCoefficient > 0), assert(minScale.isFinite), assert(maxScale > 0), assert(!maxScale.isNaN), assert(maxScale >= minScale), assert( (boundaryMargin.horizontal.isInfinite && boundaryMargin.vertical.isInfinite) || (boundaryMargin.top.isFinite && boundaryMargin.right.isFinite && boundaryMargin.bottom.isFinite && boundaryMargin.left.isFinite), ), constrained = false, child = null; final Alignment? alignment; final Clip clipBehavior; final PanAxis panAxis; final EdgeInsets boundaryMargin; final CustomInteractiveViewerWidgetBuilder? builder; final Widget? child; final bool constrained; final bool panEnabled; final bool scaleEnabled; final bool trackpadScrollCausesScale; final double scaleFactor; final double maxScale; final double minScale; final double interactionEndFrictionCoefficient; final GestureScaleEndCallback? onInteractionEnd; final GestureScaleStartCallback? onInteractionStart; final GestureScaleUpdateCallback? onInteractionUpdate; final TransformationController? transformationController; static const double _kDrag = 0.0000135; /// Returns the closest point to the given point on the given line segment. @visibleForTesting static Vector3 getNearestPointOnLine(Vector3 point, Vector3 l1, Vector3 l2) { final lengthSquared = math.pow(l2.x - l1.x, 2.0).toDouble() + math.pow(l2.y - l1.y, 2.0).toDouble(); // In this case, l1 == l2. if (lengthSquared == 0) { return l1; } // Calculate how far down the line segment the closest point is and return // the point. final l1P = point - l1; final l1L2 = l2 - l1; final fraction = clampDouble(l1P.dot(l1L2) / lengthSquared, 0, 1); return l1 + l1L2 * fraction; } /// Returns the axis aligned bounding box for the given Quad, which might not /// be axis aligned. static Rect axisAlignedBoundingBox(Quad quad) { var xMin = quad.point0.x; var xMax = quad.point0.x; var yMin = quad.point0.y; var yMax = quad.point0.y; for (final point in [ quad.point1, quad.point2, quad.point3, ]) { if (point.x < xMin) { xMin = point.x; } else if (point.x > xMax) { xMax = point.x; } if (point.y < yMin) { yMin = point.y; } else if (point.y > yMax) { yMax = point.y; } } return Rect.fromLTRB(xMin, yMin, xMax, yMax); } /// Given a quad, return its axis aligned bounding box. @visibleForTesting static Quad getAxisAlignedBoundingBox(Quad quad) { final double minX = math.min( quad.point0.x, math.min( quad.point1.x, math.min( quad.point2.x, quad.point3.x, ), ), ); final double minY = math.min( quad.point0.y, math.min( quad.point1.y, math.min( quad.point2.y, quad.point3.y, ), ), ); final double maxX = math.max( quad.point0.x, math.max( quad.point1.x, math.max( quad.point2.x, quad.point3.x, ), ), ); final double maxY = math.max( quad.point0.y, math.max( quad.point1.y, math.max( quad.point2.y, quad.point3.y, ), ), ); return Quad.points( Vector3(minX, minY, 0), Vector3(maxX, minY, 0), Vector3(maxX, maxY, 0), Vector3(minX, maxY, 0), ); } /// Returns true iff the point is inside the rectangle given by the Quad, /// inclusively. /// Algorithm from https://math.stackexchange.com/a/190373. @visibleForTesting static bool pointIsInside(Vector3 point, Quad quad) { final aM = point - quad.point0; final aB = quad.point1 - quad.point0; final aD = quad.point3 - quad.point0; final aMAB = aM.dot(aB); final aBAB = aB.dot(aB); final aMAD = aM.dot(aD); final aDAD = aD.dot(aD); return 0 <= aMAB && aMAB <= aBAB && 0 <= aMAD && aMAD <= aDAD; } /// Get the point inside (inclusively) the given Quad that is nearest to the /// given Vector3. @visibleForTesting static Vector3 getNearestPointInside(Vector3 point, Quad quad) { // If the point is inside the axis aligned bounding box, then it's ok where // it is. if (pointIsInside(point, quad)) { return point; } // Otherwise, return the nearest point on the quad. final closestPoints = [ CustomInteractiveViewer.getNearestPointOnLine( point, quad.point0, quad.point1, ), CustomInteractiveViewer.getNearestPointOnLine( point, quad.point1, quad.point2, ), CustomInteractiveViewer.getNearestPointOnLine( point, quad.point2, quad.point3, ), CustomInteractiveViewer.getNearestPointOnLine( point, quad.point3, quad.point0, ), ]; var minDistance = double.infinity; late Vector3 closestOverall; for (final closePoint in closestPoints) { final distance = math.sqrt( math.pow(point.x - closePoint.x, 2) + math.pow(point.y - closePoint.y, 2), ); if (distance < minDistance) { minDistance = distance; closestOverall = closePoint; } } return closestOverall; } /// Transform the four corners of the viewport by the inverse of the given /// matrix. This gives the viewport after the child has been transformed by the /// given matrix. The viewport transforms as the inverse of the child (i.e. /// moving the child left is equivalent to moving the viewport right). static Quad transformViewport(Matrix4 matrix, Rect viewport) { final inverseMatrix = matrix.clone()..invert(); return Quad.points( inverseMatrix.transform3( Vector3( viewport.topLeft.dx, viewport.topLeft.dy, 0, ), ), inverseMatrix.transform3( Vector3( viewport.topRight.dx, viewport.topRight.dy, 0, ), ), inverseMatrix.transform3( Vector3( viewport.bottomRight.dx, viewport.bottomRight.dy, 0, ), ), inverseMatrix.transform3( Vector3( viewport.bottomLeft.dx, viewport.bottomLeft.dy, 0, ), ), ); } @override State createState() => _CustomInteractiveViewerState(); } class _CustomInteractiveViewerState extends State with TickerProviderStateMixin { TransformationController? _transformationController; final GlobalKey _childKey = GlobalKey(); final GlobalKey _parentKey = GlobalKey(); Animation? _animation; Animation? _scaleAnimation; late Offset _scaleAnimationFocalPoint; late AnimationController _controller; late AnimationController _scaleController; Axis? _currentAxis; // Used with panAxis. Offset? _referenceFocalPoint; // Point where the current gesture began. double? _scaleStart; // Scale value at start of scaling gesture. final double _currentRotation = 0; // Rotation of _transformationController.value. _GestureType? _gestureType; // The _boundaryRect is calculated by adding the boundaryMargin to the size of // the child. Rect get _boundaryRect { assert(_childKey.currentContext != null); assert(!widget.boundaryMargin.left.isNaN); assert(!widget.boundaryMargin.right.isNaN); assert(!widget.boundaryMargin.top.isNaN); assert(!widget.boundaryMargin.bottom.isNaN); final childRenderBox = _childKey.currentContext!.findRenderObject()! as RenderBox; final childSize = childRenderBox.size; final boundaryRect = widget.boundaryMargin.inflateRect(Offset.zero & childSize); assert( !boundaryRect.isEmpty, "CustomInteractiveViewer's child must have nonzero dimensions.", ); // Boundaries that are partially infinite are not allowed because Matrix4's // rotation and translation methods don't handle infinite well. assert( boundaryRect.isFinite || (boundaryRect.left.isInfinite && boundaryRect.top.isInfinite && boundaryRect.right.isInfinite && boundaryRect.bottom.isInfinite), 'boundaryRect must either be infinite in all directions or finite in all directions.', ); return boundaryRect; } // The Rect representing the child's parent. Rect get _viewport { assert(_parentKey.currentContext != null); final parentRenderBox = _parentKey.currentContext!.findRenderObject()! as RenderBox; return Offset.zero & parentRenderBox.size; } // Return a new matrix representing the given matrix after applying the given // translation. Matrix4 _matrixTranslate(Matrix4 matrix, Offset translation) { if (translation == Offset.zero) { return matrix.clone(); } final Offset alignedTranslation; if (_currentAxis != null) { alignedTranslation = switch (widget.panAxis) { PanAxis.horizontal => _alignAxis(translation, Axis.horizontal), PanAxis.vertical => _alignAxis(translation, Axis.vertical), PanAxis.aligned => _alignAxis(translation, _currentAxis!), PanAxis.free => translation, }; } else { alignedTranslation = translation; } final nextMatrix = matrix.clone() ..translateByDouble( alignedTranslation.dx, alignedTranslation.dy, 0, 1, ); // Transform the viewport to determine where its four corners will be after // the child has been transformed. final nextViewport = CustomInteractiveViewer.transformViewport( nextMatrix, _viewport, ); // If the boundaries are infinite, then no need to check if the translation // fits within them. if (_boundaryRect.isInfinite) { return nextMatrix; } // Expand the boundaries with rotation. This prevents the problem where a // mismatch in orientation between the viewport and boundaries effectively // limits translation. With this approach, all points that are visible with // no rotation are visible after rotation. final boundariesAabbQuad = _getAxisAlignedBoundingBoxWithRotation( _boundaryRect, _currentRotation, ); // If the given translation fits completely within the boundaries, allow it. final offendingDistance = _exceedsBy(boundariesAabbQuad, nextViewport); if (offendingDistance == Offset.zero) { return nextMatrix; } // Desired translation goes out of bounds, so translate to the nearest // in-bounds point instead. final nextTotalTranslation = _getMatrixTranslation(nextMatrix); final currentScale = matrix.getMaxScaleOnAxis(); final correctedTotalTranslation = Offset( nextTotalTranslation.dx - offendingDistance.dx * currentScale, nextTotalTranslation.dy - offendingDistance.dy * currentScale, ); final correctedMatrix = matrix.clone() ..setTranslation( Vector3( correctedTotalTranslation.dx, correctedTotalTranslation.dy, 0, ), ); // Double check that the corrected translation fits. final correctedViewport = CustomInteractiveViewer.transformViewport( correctedMatrix, _viewport, ); final offendingCorrectedDistance = _exceedsBy(boundariesAabbQuad, correctedViewport); if (offendingCorrectedDistance == Offset.zero) { return correctedMatrix; } // If the corrected translation doesn't fit in either direction, don't allow // any translation at all. This happens when the viewport is larger than the // entire boundary. if (offendingCorrectedDistance.dx != 0.0 && offendingCorrectedDistance.dy != 0.0) { return matrix.clone(); } // Otherwise, allow translation in only the direction that fits. This // happens when the viewport is larger than the boundary in one direction. final unidirectionalCorrectedTotalTranslation = Offset( offendingCorrectedDistance.dx == 0.0 ? correctedTotalTranslation.dx : 0.0, offendingCorrectedDistance.dy == 0.0 ? correctedTotalTranslation.dy : 0.0, ); return matrix.clone() ..setTranslation( Vector3( unidirectionalCorrectedTotalTranslation.dx, unidirectionalCorrectedTotalTranslation.dy, 0, ), ); } // Return a new matrix representing the given matrix after applying the given // scale. Matrix4 _matrixScale(Matrix4 matrix, double scale) { if (scale == 1.0) { return matrix.clone(); } assert(scale != 0.0); // Don't allow a scale that results in an overall scale beyond min/max // scale. final currentScale = _transformationController!.value.getMaxScaleOnAxis(); final double totalScale = math.max( currentScale * scale, // Ensure that the scale cannot make the child so big that it can't fit // inside the boundaries (in either direction). math.max( _viewport.width / _boundaryRect.width, _viewport.height / _boundaryRect.height, ), ); final clampedTotalScale = clampDouble( totalScale, widget.minScale, widget.maxScale, ); final clampedScale = clampedTotalScale / currentScale; return matrix.clone() ..scaleByDouble(clampedScale, clampedScale, clampedScale, 1); } // Returns true iff the given _GestureType is enabled. bool _gestureIsSupported(_GestureType? gestureType) { return switch (gestureType) { _GestureType.scale => widget.scaleEnabled, _GestureType.pan || null => widget.panEnabled, }; } // Decide which type of gesture this is by comparing the amount of scale // and rotation in the gesture, if any. Scale starts at 1 and rotation // starts at 0. Pan will have no scale and no rotation because it uses only one // finger. _GestureType _getGestureType(ScaleUpdateDetails details) { final scale = !widget.scaleEnabled ? 1.0 : details.scale; if (scale != 1) { return _GestureType.scale; } else { return _GestureType.pan; } } // Handle the start of a gesture. All of pan, scale, and rotate are handled // with GestureDetector's scale gesture. void _onScaleStart(ScaleStartDetails details) { widget.onInteractionStart?.call(details); if (_controller.isAnimating) { _controller ..stop() ..reset(); _animation?.removeListener(_onAnimate); _animation = null; } if (_scaleController.isAnimating) { _scaleController ..stop() ..reset(); _scaleAnimation?.removeListener(_onScaleAnimate); _scaleAnimation = null; } _gestureType = null; _currentAxis = null; _scaleStart = _transformationController!.value.getMaxScaleOnAxis(); _referenceFocalPoint = _transformationController!.toScene( details.localFocalPoint, ); } // Handle an update to an ongoing gesture. All of pan, scale, and rotate are // handled with GestureDetector's scale gesture. void _onScaleUpdate(ScaleUpdateDetails details) { final scale = _transformationController!.value.getMaxScaleOnAxis(); _scaleAnimationFocalPoint = details.localFocalPoint; final focalPointScene = _transformationController!.toScene( details.localFocalPoint, ); if (_gestureType == _GestureType.pan) { // When a gesture first starts, it sometimes has no change in scale and // rotation despite being a two-finger gesture. Here the gesture is // allowed to be reinterpreted as its correct type after originally // being marked as a pan. _gestureType = _getGestureType(details); } else { _gestureType ??= _getGestureType(details); } if (!_gestureIsSupported(_gestureType)) { widget.onInteractionUpdate?.call(details); return; } switch (_gestureType!) { case _GestureType.scale: assert(_scaleStart != null); // details.scale gives us the amount to change the scale as of the // start of this gesture, so calculate the amount to scale as of the // previous call to _onScaleUpdate. final desiredScale = _scaleStart! * details.scale; final scaleChange = desiredScale / scale; _transformationController!.value = _matrixScale( _transformationController!.value, scaleChange, ); // While scaling, translate such that the user's two fingers stay on // the same places in the scene. That means that the focal point of // the scale should be on the same place in the scene before and after // the scale. final focalPointSceneScaled = _transformationController!.toScene( details.localFocalPoint, ); _transformationController!.value = _matrixTranslate( _transformationController!.value, focalPointSceneScaled - _referenceFocalPoint!, ); // details.localFocalPoint should now be at the same location as the // original _referenceFocalPoint point. If it's not, that's because // the translate came in contact with a boundary. In that case, update // _referenceFocalPoint so subsequent updates happen in relation to // the new effective focal point. final focalPointSceneCheck = _transformationController!.toScene( details.localFocalPoint, ); if (_round(_referenceFocalPoint!) != _round(focalPointSceneCheck)) { _referenceFocalPoint = focalPointSceneCheck; } case _GestureType.pan: assert(_referenceFocalPoint != null); // details may have a change in scale here when scaleEnabled is false. // In an effort to keep the behavior similar whether or not scaleEnabled // is true, these gestures are thrown away. if (details.scale != 1.0) { widget.onInteractionUpdate?.call(details); return; } _currentAxis ??= _getPanAxis(_referenceFocalPoint!, focalPointScene); // Translate so that the same point in the scene is underneath the // focal point before and after the movement. final translationChange = focalPointScene - _referenceFocalPoint!; _transformationController!.value = _matrixTranslate( _transformationController!.value, translationChange, ); _referenceFocalPoint = _transformationController!.toScene( details.localFocalPoint, ); } widget.onInteractionUpdate?.call(details); } // Handle the end of a gesture of _GestureType. All of pan, scale, and rotate // are handled with GestureDetector's scale gesture. void _onScaleEnd(ScaleEndDetails details) { widget.onInteractionEnd?.call(details); _scaleStart = null; _referenceFocalPoint = null; _animation?.removeListener(_onAnimate); _scaleAnimation?.removeListener(_onScaleAnimate); _controller.reset(); _scaleController.reset(); if (!_gestureIsSupported(_gestureType)) { _currentAxis = null; return; } switch (_gestureType) { case _GestureType.pan: if (details.velocity.pixelsPerSecond.distance < kMinFlingVelocity) { _currentAxis = null; return; } final translationVector = _transformationController!.value.getTranslation(); final translation = Offset(translationVector.x, translationVector.y); final frictionSimulationX = FrictionSimulation( widget.interactionEndFrictionCoefficient, translation.dx, details.velocity.pixelsPerSecond.dx, ); final frictionSimulationY = FrictionSimulation( widget.interactionEndFrictionCoefficient, translation.dy, details.velocity.pixelsPerSecond.dy, ); final tFinal = _getFinalTime( details.velocity.pixelsPerSecond.distance, widget.interactionEndFrictionCoefficient, ); _animation = Tween( begin: translation, end: Offset(frictionSimulationX.finalX, frictionSimulationY.finalX), ).animate( CurvedAnimation( parent: _controller, curve: Curves.decelerate, ), ); _controller.duration = Duration(milliseconds: (tFinal * 1000).round()); _animation!.addListener(_onAnimate); unawaited(_controller.forward()); case _GestureType.scale: if (details.scaleVelocity.abs() < 0.1) { _currentAxis = null; return; } final scale = _transformationController!.value.getMaxScaleOnAxis(); final frictionSimulation = FrictionSimulation( widget.interactionEndFrictionCoefficient * widget.scaleFactor, scale, details.scaleVelocity / 10, ); final tFinal = _getFinalTime( details.scaleVelocity.abs(), widget.interactionEndFrictionCoefficient, effectivelyMotionless: 0.1, ); _scaleAnimation = Tween(begin: scale, end: frictionSimulation.x(tFinal)) .animate( CurvedAnimation( parent: _scaleController, curve: Curves.decelerate, ), ); _scaleController.duration = Duration(milliseconds: (tFinal * 1000).round()); _scaleAnimation!.addListener(_onScaleAnimate); unawaited(_scaleController.forward()); case null: break; } } // Handle mouse wheel and web trackpad scroll events. void _receivedPointerSignal(PointerSignalEvent event) { final double scaleChange; if (event is PointerScrollEvent) { if (event.kind == PointerDeviceKind.trackpad && !widget.trackpadScrollCausesScale) { // Trackpad scroll, so treat it as a pan. widget.onInteractionStart?.call( ScaleStartDetails( focalPoint: event.position, localFocalPoint: event.localPosition, ), ); final localDelta = PointerEvent.transformDeltaViaPositions( untransformedEndPosition: event.position + event.scrollDelta, untransformedDelta: event.scrollDelta, transform: event.transform, ); if (!_gestureIsSupported(_GestureType.pan)) { widget.onInteractionUpdate?.call( ScaleUpdateDetails( focalPoint: event.position - event.scrollDelta, localFocalPoint: event.localPosition - event.scrollDelta, focalPointDelta: -localDelta, ), ); widget.onInteractionEnd?.call(ScaleEndDetails()); return; } final focalPointScene = _transformationController!.toScene( event.localPosition, ); final newFocalPointScene = _transformationController!.toScene( event.localPosition - localDelta, ); _transformationController!.value = _matrixTranslate( _transformationController!.value, newFocalPointScene - focalPointScene, ); widget.onInteractionUpdate?.call( ScaleUpdateDetails( focalPoint: event.position - event.scrollDelta, localFocalPoint: event.localPosition - localDelta, focalPointDelta: -localDelta, ), ); widget.onInteractionEnd?.call(ScaleEndDetails()); return; } // Ignore left and right mouse wheel scroll. if (event.scrollDelta.dy == 0.0) { return; } scaleChange = math.exp(-event.scrollDelta.dy / widget.scaleFactor); } else if (event is PointerScaleEvent) { scaleChange = event.scale; } else { return; } widget.onInteractionStart?.call( ScaleStartDetails( focalPoint: event.position, localFocalPoint: event.localPosition, ), ); if (!_gestureIsSupported(_GestureType.scale)) { widget.onInteractionUpdate?.call( ScaleUpdateDetails( focalPoint: event.position, localFocalPoint: event.localPosition, scale: scaleChange, ), ); widget.onInteractionEnd?.call(ScaleEndDetails()); return; } final focalPointScene = _transformationController!.toScene( event.localPosition, ); _transformationController!.value = _matrixScale( _transformationController!.value, scaleChange, ); // After scaling, translate such that the event's position is at the // same scene point before and after the scale. final focalPointSceneScaled = _transformationController!.toScene( event.localPosition, ); _transformationController!.value = _matrixTranslate( _transformationController!.value, focalPointSceneScaled - focalPointScene, ); widget.onInteractionUpdate?.call( ScaleUpdateDetails( focalPoint: event.position, localFocalPoint: event.localPosition, scale: scaleChange, ), ); widget.onInteractionEnd?.call(ScaleEndDetails()); } // Handle inertia drag animation. void _onAnimate() { if (!_controller.isAnimating) { _currentAxis = null; _animation?.removeListener(_onAnimate); _animation = null; _controller.reset(); return; } // Translate such that the resulting translation is _animation.value. final translationVector = _transformationController!.value.getTranslation(); final translation = Offset(translationVector.x, translationVector.y); final translationScene = _transformationController!.toScene( translation, ); final animationScene = _transformationController!.toScene( _animation!.value, ); final translationChangeScene = animationScene - translationScene; _transformationController!.value = _matrixTranslate( _transformationController!.value, translationChangeScene, ); } // Handle inertia scale animation. void _onScaleAnimate() { if (!_scaleController.isAnimating) { _currentAxis = null; _scaleAnimation?.removeListener(_onScaleAnimate); _scaleAnimation = null; _scaleController.reset(); return; } final desiredScale = _scaleAnimation!.value; final scaleChange = desiredScale / _transformationController!.value.getMaxScaleOnAxis(); final referenceFocalPoint = _transformationController!.toScene( _scaleAnimationFocalPoint, ); _transformationController!.value = _matrixScale( _transformationController!.value, scaleChange, ); // While scaling, translate such that the user's two fingers stay on // the same places in the scene. That means that the focal point of // the scale should be on the same place in the scene before and after // the scale. final focalPointSceneScaled = _transformationController!.toScene( _scaleAnimationFocalPoint, ); _transformationController!.value = _matrixTranslate( _transformationController!.value, focalPointSceneScaled - referenceFocalPoint, ); } void _onTransformationControllerChange() { // A change to the TransformationController's value is a change to the // state. setState(() {}); } @override void initState() { super.initState(); _transformationController = widget.transformationController ?? TransformationController(); _transformationController!.addListener(_onTransformationControllerChange); _controller = AnimationController( vsync: this, ); _scaleController = AnimationController(vsync: this); } @override void didUpdateWidget(CustomInteractiveViewer oldWidget) { super.didUpdateWidget(oldWidget); // Handle all cases of needing to dispose and initialize // transformationControllers. if (oldWidget.transformationController == null) { if (widget.transformationController != null) { _transformationController! .removeListener(_onTransformationControllerChange); _transformationController!.dispose(); _transformationController = widget.transformationController; _transformationController! .addListener(_onTransformationControllerChange); } } else { if (widget.transformationController == null) { _transformationController! .removeListener(_onTransformationControllerChange); _transformationController = TransformationController(); _transformationController! .addListener(_onTransformationControllerChange); } else if (widget.transformationController != oldWidget.transformationController) { _transformationController! .removeListener(_onTransformationControllerChange); _transformationController = widget.transformationController; _transformationController! .addListener(_onTransformationControllerChange); } } } @override void dispose() { _controller.dispose(); _scaleController.dispose(); _transformationController! .removeListener(_onTransformationControllerChange); if (widget.transformationController == null) { _transformationController!.dispose(); } super.dispose(); } @override Widget build(BuildContext context) { Widget child; if (widget.child != null) { child = _CustomInteractiveViewerBuilt( childKey: _childKey, clipBehavior: widget.clipBehavior, constrained: widget.constrained, matrix: _transformationController!.value, alignment: widget.alignment, child: widget.child!, ); } else { // When using CustomInteractiveViewer.builder, then constrained is false and the // viewport is the size of the constraints. assert(widget.builder != null); assert(!widget.constrained); child = LayoutBuilder( builder: (context, constraints) { final matrix = _transformationController!.value; return _CustomInteractiveViewerBuilt( childKey: _childKey, clipBehavior: widget.clipBehavior, constrained: widget.constrained, alignment: widget.alignment, matrix: matrix, child: widget.builder!( context, CustomInteractiveViewer.transformViewport( matrix, Offset.zero & constraints.biggest, ), ), ); }, ); } return Listener( key: _parentKey, onPointerSignal: _receivedPointerSignal, child: GestureDetector( behavior: HitTestBehavior.opaque, // Necessary when panning off screen. onScaleEnd: _onScaleEnd, onScaleStart: _onScaleStart, onScaleUpdate: _onScaleUpdate, trackpadScrollCausesScale: widget.trackpadScrollCausesScale, trackpadScrollToScaleFactor: Offset(0, -1 / widget.scaleFactor), child: child, ), ); } } // This widget allows us to easily swap in and out the LayoutBuilder in // CustomInteractiveViewer's depending on if it's using a builder or a child. class _CustomInteractiveViewerBuilt extends StatelessWidget { const _CustomInteractiveViewerBuilt({ required this.child, required this.childKey, required this.clipBehavior, required this.constrained, required this.matrix, required this.alignment, }); final Widget child; final GlobalKey childKey; final Clip clipBehavior; final bool constrained; final Matrix4 matrix; final Alignment? alignment; @override Widget build(BuildContext context) { Widget child = KeyedSubtree( key: childKey, child: this.child, ); if (!constrained) { child = OverflowBox( alignment: Alignment.topLeft, minWidth: 0, minHeight: 0, maxWidth: double.infinity, maxHeight: double.infinity, child: child, ); } return ClipRect( clipBehavior: clipBehavior, child: child, ); } } // A classification of relevant user gestures. Each contiguous user gesture is // represented by exactly one _GestureType. enum _GestureType { pan, scale, } // Given a velocity and drag, calculate the time at which motion will come to // a stop, within the margin of effectivelyMotionless. double _getFinalTime( double velocity, double drag, { double effectivelyMotionless = 10, }) { return math.log(effectivelyMotionless / velocity) / math.log(drag / 100); } // Return the translation from the given Matrix4 as an Offset. Offset _getMatrixTranslation(Matrix4 matrix) { final nextTranslation = matrix.getTranslation(); return Offset(nextTranslation.x, nextTranslation.y); } // Find the axis aligned bounding box for the rect rotated about its center by // the given amount. Quad _getAxisAlignedBoundingBoxWithRotation(Rect rect, double rotation) { final rotationMatrix = Matrix4.identity() ..translateByDouble(rect.size.width / 2, rect.size.height / 2, 0, 1) ..rotateZ(rotation) ..translateByDouble(-rect.size.width / 2, -rect.size.height / 2, 0, 1); final boundariesRotated = Quad.points( rotationMatrix.transform3(Vector3(rect.left, rect.top, 0)), rotationMatrix.transform3(Vector3(rect.right, rect.top, 0)), rotationMatrix.transform3(Vector3(rect.right, rect.bottom, 0)), rotationMatrix.transform3(Vector3(rect.left, rect.bottom, 0)), ); return CustomInteractiveViewer.getAxisAlignedBoundingBox(boundariesRotated); } // Return the amount that viewport lies outside of boundary. If the viewport // is completely contained within the boundary (inclusively), then returns // Offset.zero. Offset _exceedsBy(Quad boundary, Quad viewport) { final viewportPoints = [ viewport.point0, viewport.point1, viewport.point2, viewport.point3, ]; var largestExcess = Offset.zero; for (final point in viewportPoints) { final pointInside = CustomInteractiveViewer.getNearestPointInside(point, boundary); final excess = Offset( pointInside.x - point.x, pointInside.y - point.y, ); if (excess.dx.abs() > largestExcess.dx.abs()) { largestExcess = Offset(excess.dx, largestExcess.dy); } if (excess.dy.abs() > largestExcess.dy.abs()) { largestExcess = Offset(largestExcess.dx, excess.dy); } } return _round(largestExcess); } // Round the output values. This works around a precision problem where // values that should have been zero were given as within 10^-10 of zero. Offset _round(Offset offset) { return Offset( double.parse(offset.dx.toStringAsFixed(9)), double.parse(offset.dy.toStringAsFixed(9)), ); } // Align the given offset to the given axis by allowing movement only in the // axis direction. Offset _alignAxis(Offset offset, Axis axis) { return switch (axis) { Axis.horizontal => Offset(offset.dx, 0), Axis.vertical => Offset(0, offset.dy), }; } // Given two points, return the axis where the distance between the points is // greatest. If they are equal, return null. Axis? _getPanAxis(Offset point1, Offset point2) { if (point1 == point2) { return null; } final x = point2.dx - point1.dx; final y = point2.dy - point1.dy; return x.abs() > y.abs() ? Axis.horizontal : Axis.vertical; } ================================================ FILE: lib/src/chart/base/line.dart ================================================ import 'dart:math' as math; import 'package:flutter/painting.dart'; /// Describes a line model (contains [from], and end [to]) class Line { const Line(this.from, this.to); /// Start of the line final Offset from; /// End of the line final Offset to; /// Returns the length of line double magnitude() { final diff = to - from; final dx = diff.dx; final dy = diff.dy; return math.sqrt(dx * dx + dy * dy); } /// Returns angle of the line in radians double direction() { final diff = to - from; return math.atan(diff.dy / diff.dx); } /// Returns the line in magnitude of 1.0 Offset normalize() { final diffOffset = to - from; return diffOffset * (1.0 / magnitude()); } } ================================================ FILE: lib/src/chart/candlestick_chart/candlestick_chart.dart ================================================ import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/chart/base/axis_chart/axis_chart_scaffold_widget.dart'; import 'package:fl_chart/src/chart/candlestick_chart/candlestick_chart_renderer.dart'; import 'package:flutter/material.dart'; /// Renders a pie chart as a widget, using provided [CandlestickChartData]. class CandlestickChart extends ImplicitlyAnimatedWidget { /// [data] determines how the [CandlestickChart] should be look like, /// when you make any change in the [CandlestickChartData], it updates /// new values with animation, and duration is [duration]. /// also you can change the [curve] /// which default is [Curves.linear]. const CandlestickChart( this.data, { this.chartRendererKey, super.key, @Deprecated('Please use [duration] instead') Duration? swapAnimationDuration, Duration duration = const Duration(milliseconds: 150), @Deprecated('Please use [curve] instead') Curve? swapAnimationCurve, Curve curve = Curves.linear, this.transformationConfig = const FlTransformationConfig(), }) : super( duration: swapAnimationDuration ?? duration, curve: swapAnimationCurve ?? curve, ); /// Determines how the [CandlestickChart] should be look like. final CandlestickChartData data; /// {@macro fl_chart.AxisChartScaffoldWidget.transformationConfig} final FlTransformationConfig transformationConfig; /// We pass this key to our renderers which are responsible to /// render the chart itself (without anything around the chart). final Key? chartRendererKey; /// Creates a [_CandlestickChartState] @override _CandlestickChartState createState() => _CandlestickChartState(); } class _CandlestickChartState extends AnimatedWidgetBaseState { /// we handle under the hood animations (implicit animations) via this tween, /// it lerps between the old [CandlestickChartData] to the new one. CandlestickChartDataTween? _candlestickChartDataTween; /// If [CandlestickTouchData.handleBuiltInTouches] is true, we override the callback to handle touches internally, /// but we need to keep the provided callback to notify it too. BaseTouchCallback? _providedTouchCallback; ({ Offset axisCoordinate, int spotIndex, })? touchedSpots; @override Widget build(BuildContext context) { final showingData = _getData(); return AxisChartScaffoldWidget( data: showingData, transformationConfig: widget.transformationConfig, chartBuilder: (context, chartVirtualRect) => CandlestickChartLeaf( data: _withTouchedIndicators( _candlestickChartDataTween!.evaluate(animation), ), targetData: _withTouchedIndicators(showingData), key: widget.chartRendererKey, chartVirtualRect: chartVirtualRect, canBeScaled: widget.transformationConfig.scaleAxis != FlScaleAxis.none, ), ); } CandlestickChartData _withTouchedIndicators( CandlestickChartData candlestickChartData, ) { if (!candlestickChartData.candlestickTouchData.enabled || !candlestickChartData.candlestickTouchData.handleBuiltInTouches) { return candlestickChartData; } final spot = touchedSpots != null && touchedSpots!.spotIndex != -1 ? candlestickChartData.candlestickSpots[touchedSpots!.spotIndex] : null; final touchInsideChart = touchedSpots != null && touchedSpots!.axisCoordinate.dx >= candlestickChartData.minX && touchedSpots!.axisCoordinate.dy >= candlestickChartData.minY && touchedSpots!.axisCoordinate.dx <= candlestickChartData.maxX && touchedSpots!.axisCoordinate.dy <= candlestickChartData.maxY; final providedPainter = candlestickChartData.touchedPointIndicator?.painter; final providedX = candlestickChartData.touchedPointIndicator?.x; final providedY = candlestickChartData.touchedPointIndicator?.y; return candlestickChartData.copyWith( showingTooltipIndicators: touchedSpots != null ? [touchedSpots!.spotIndex] : [], touchedPointIndicator: touchedSpots != null ? AxisSpotIndicator( x: providedX ?? spot?.x, y: providedY ?? (touchInsideChart ? touchedSpots!.axisCoordinate.dy : null), painter: providedPainter ?? AxisLinesIndicatorPainter( horizontalLineProvider: (y) => HorizontalLine( y: y, color: Theme.of(context).colorScheme.outline.withValues( alpha: 0.5, ), strokeWidth: 1, ), verticalLineProvider: (x) => VerticalLine( x: x, color: Theme.of(context).colorScheme.outline.withValues( alpha: 0.5, ), strokeWidth: 1, ), ), ) : null, ); } CandlestickChartData _getData() { final candlestickTouchData = widget.data.candlestickTouchData; if (candlestickTouchData.enabled && candlestickTouchData.handleBuiltInTouches) { _providedTouchCallback = candlestickTouchData.touchCallback; return widget.data.copyWith( candlestickTouchData: widget.data.candlestickTouchData .copyWith(touchCallback: _handleBuiltInTouch), ); } return widget.data; } void _handleBuiltInTouch( FlTouchEvent event, CandlestickTouchResponse? touchResponse, ) { if (!mounted) { return; } _providedTouchCallback?.call(event, touchResponse); final desiredTouch = event.isInterestedForInteractions; if (!desiredTouch || touchResponse == null || touchResponse.touchedSpot == null) { setState(() { if (desiredTouch) { touchedSpots = ( axisCoordinate: touchResponse?.touchChartCoordinate ?? Offset.zero, spotIndex: -1, ); } else { touchedSpots = null; } }); return; } setState(() { touchedSpots = ( axisCoordinate: touchResponse.touchChartCoordinate, spotIndex: touchResponse.touchedSpot!.spotIndex, ); }); } @override void forEachTween(TweenVisitor visitor) { _candlestickChartDataTween = visitor( _candlestickChartDataTween, _getData(), (dynamic value) => CandlestickChartDataTween( begin: value as CandlestickChartData, end: widget.data, ), ) as CandlestickChartDataTween?; } } ================================================ FILE: lib/src/chart/candlestick_chart/candlestick_chart_data.dart ================================================ // coverage:ignore-file import 'dart:math'; import 'dart:ui'; import 'package:equatable/equatable.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/chart/candlestick_chart/candlestick_chart_helper.dart'; import 'package:fl_chart/src/extensions/color_extension.dart'; import 'package:fl_chart/src/utils/lerp.dart'; import 'package:flutter/material.dart'; /// [CandlestickChart] needs this class to render itself. /// /// It holds data needed to draw a candlestick chart, /// including background color, Candlestick spots, ... class CandlestickChartData extends AxisChartData with EquatableMixin { /// [CandlestickChart] draws some candlesticks on the chart based on /// the provided [candlestickSpots], /// /// It draws some titles on left, top, right, bottom sides per each axis number, /// you can modify [titlesData] to have your custom titles, /// /// It draws a color as a background behind everything you can set it using [backgroundColor], /// then a grid over it, you can customize it using [gridData], /// and it draws 4 borders around your chart, you can customize it using [borderData]. /// /// You can modify [candlestickTouchData] to customize touch behaviors and responses. /// /// You can show some tooltipIndicators (a popup with an information) /// on top of each [CandlestickChartData.candleSpots] using [showingTooltipIndicators], /// just put spot indices you want to show it on top of them. /// /// [clipData] forces the [CandlestickChart] to draw lines inside the chart bounding box. CandlestickChartData({ List? candlestickSpots, FlCandlestickPainter? candlestickPainter, FlTitlesData? titlesData, CandlestickTouchData? candlestickTouchData, List? showingTooltipIndicators, FlGridData? gridData, super.borderData, double? minX, double? maxX, super.baselineX, double? minY, double? maxY, super.baselineY, super.rangeAnnotations, FlClipData? clipData, super.backgroundColor, super.rotationQuarterTurns, this.touchedPointIndicator, }) : candlestickSpots = candlestickSpots ?? const [], candlestickPainter = candlestickPainter ?? DefaultCandlestickPainter(), candlestickTouchData = candlestickTouchData ?? CandlestickTouchData(), showingTooltipIndicators = showingTooltipIndicators ?? const [], super( gridData: gridData ?? const FlGridData(), titlesData: titlesData ?? const FlTitlesData(), clipData: clipData ?? const FlClipData.none(), minX: minX ?? CandlestickChartHelper.calculateMaxAxisValues( candlestickSpots ?? const [], ).$1, maxX: maxX ?? CandlestickChartHelper.calculateMaxAxisValues( candlestickSpots ?? const [], ).$2, minY: minY ?? CandlestickChartHelper.calculateMaxAxisValues( candlestickSpots ?? const [], ).$3, maxY: maxY ?? CandlestickChartHelper.calculateMaxAxisValues( candlestickSpots ?? const [], ).$4, ); /// Contains the data for the candlestick chart. /// /// Each [CandlestickSpot] represents a candlestick in the chart /// that contains [open, high, low, close] values. final List candlestickSpots; /// The painter used to draw the candlestick. /// You can use the [DefaultCandlestickPainter] or implement your own. final FlCandlestickPainter candlestickPainter; /// Handles touch behaviors and responses. final CandlestickTouchData candlestickTouchData; /// you can show some tooltipIndicators (a popup with an information) /// on top of each [CandlestickSpot] using [showingTooltipIndicators], /// just put indices you want to show it on top of them. /// /// An important point is that you have to disable the default touch behaviour /// to show the tooltip manually, see [CandlestickTouchData.handleBuiltInTouches]. final List showingTooltipIndicators; /// Shows an indicator on the touched / hovered point /// /// We manage to set it by default /// when [candlestickTouchData.handleBuiltInTouches] is true, /// so nothing happens if you change this property as long as /// the handleBuiltInTouches property is true. /// /// But you can set [candlestickTouchData.handleBuiltInTouches] to false /// if you want to have customized [touchedPointIndicator] final AxisSpotIndicator? touchedPointIndicator; /// Lerps a [CandlestickChartData] based on [t] value, check [Tween.lerp]. @override CandlestickChartData lerp(BaseChartData a, BaseChartData b, double t) { if (a is CandlestickChartData && b is CandlestickChartData) { return CandlestickChartData( candlestickSpots: lerpCandleSpotList(a.candlestickSpots, b.candlestickSpots, t), candlestickPainter: b.candlestickPainter.lerp( a.candlestickPainter, b.candlestickPainter, t, ), titlesData: FlTitlesData.lerp(a.titlesData, b.titlesData, t), candlestickTouchData: b.candlestickTouchData, showingTooltipIndicators: lerpIntList( a.showingTooltipIndicators, b.showingTooltipIndicators, t, ), gridData: FlGridData.lerp(a.gridData, b.gridData, t), borderData: FlBorderData.lerp(a.borderData, b.borderData, t), minX: lerpDouble(a.minX, b.minX, t), maxX: lerpDouble(a.maxX, b.maxX, t), baselineX: lerpDouble(a.baselineX, b.baselineX, t), minY: lerpDouble(a.minY, b.minY, t), maxY: lerpDouble(a.maxY, b.maxY, t), baselineY: lerpDouble(a.baselineY, b.baselineY, t), rangeAnnotations: RangeAnnotations.lerp( a.rangeAnnotations, b.rangeAnnotations, t, ), clipData: b.clipData, backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t), rotationQuarterTurns: b.rotationQuarterTurns, touchedPointIndicator: b.touchedPointIndicator, ); } else { throw Exception('Illegal State'); } } /// Copies current [CandlestickChartData] to a new [CandlestickChartData], /// and replaces provided values. CandlestickChartData copyWith({ List? candlestickSpots, FlCandlestickPainter? candlestickPainter, FlTitlesData? titlesData, CandlestickTouchData? candlestickTouchData, List? showingTooltipIndicators, FlGridData? gridData, FlBorderData? borderData, double? minX, double? maxX, double? baselineX, double? minY, double? maxY, double? baselineY, RangeAnnotations? rangeAnnotations, FlClipData? clipData, Color? backgroundColor, int? rotationQuarterTurns, AxisSpotIndicator? touchedPointIndicator, }) => CandlestickChartData( candlestickSpots: candlestickSpots ?? this.candlestickSpots, candlestickPainter: candlestickPainter ?? this.candlestickPainter, titlesData: titlesData ?? this.titlesData, candlestickTouchData: candlestickTouchData ?? this.candlestickTouchData, showingTooltipIndicators: showingTooltipIndicators ?? this.showingTooltipIndicators, gridData: gridData ?? this.gridData, borderData: borderData ?? this.borderData, minX: minX ?? this.minX, maxX: maxX ?? this.maxX, baselineX: baselineX ?? this.baselineX, minY: minY ?? this.minY, maxY: maxY ?? this.maxY, baselineY: baselineY ?? this.baselineY, rangeAnnotations: rangeAnnotations ?? this.rangeAnnotations, clipData: clipData ?? this.clipData, backgroundColor: backgroundColor ?? this.backgroundColor, rotationQuarterTurns: rotationQuarterTurns ?? this.rotationQuarterTurns, touchedPointIndicator: touchedPointIndicator ?? this.touchedPointIndicator, ); /// Used for equality check, see [EquatableMixin]. @override List get props => [ candlestickSpots, candlestickPainter, candlestickTouchData, showingTooltipIndicators, gridData, titlesData, minX, maxX, baselineX, minY, maxY, baselineY, rangeAnnotations, clipData, backgroundColor, borderData, rotationQuarterTurns, touchedPointIndicator, ]; } /// Defines information about a spot in the [CandlestickChart] class CandlestickSpot extends FlSpot with EquatableMixin { /// You can change [show] value to show or hide the spot, /// [x] determines the location of [CandlestickChart] in the x-axis, /// [open], [high], [low], and [close] defines the values of the spot /// based on the standard [OHLC chart](https://en.wikipedia.org/wiki/Open-high-low-close_chart). /// /// You can temporarily hide the spot by setting [show] to false. /// /// The [candlestickPainter] is used to customize the appearance of each candlestick. /// We use the [DefaultCandlestickPainter] by default, but you can implement /// your own painter with your shiny UI CandlestickSpot({ required double x, required this.open, required this.high, required this.low, required this.close, bool? show, }) : show = show ?? true, super(x, high); /// The open value of a specific candlestick. final double open; /// The high value of a specific candlestick. final double high; /// The low value of a specific candlestick. final double low; /// The close value of a specific candlestick. final double close; /// Determines show or hide the spot. final bool show; /// Checks if the candlestick is up (close > open). /// /// It is the same as bullish and bearish definitions in the stock market. bool get isUp => close > open; /// Returns the middle point of the candlestick double get midPoint => (open + close) / 2; @override CandlestickSpot copyWith({ double? x, double? y, FlErrorRange? xError, FlErrorRange? yError, double? open, double? high, double? low, double? close, bool? show, }) { if (y != null) { throw Exception( 'y value is not used in CandlestickSpot and it does not do anything. Please use open, high, low, close values.', ); } if (xError != null || yError != null) { throw Exception( 'xError and yError values are not used in CandlestickSpot and it does not do anything.', ); } return CandlestickSpot( x: x ?? this.x, open: open ?? this.open, high: high ?? this.high, low: low ?? this.low, close: close ?? this.close, show: show ?? this.show, ); } /// Lerps a [CandlestickSpot] based on [t] value, check [Tween.lerp]. static CandlestickSpot lerp(CandlestickSpot a, CandlestickSpot b, double t) => CandlestickSpot( x: lerpDouble(a.x, b.x, t)!, open: lerpDouble(a.open, b.open, t)!, high: lerpDouble(a.high, b.high, t)!, low: lerpDouble(a.low, b.low, t)!, close: lerpDouble(a.close, b.close, t)!, show: b.show, ); /// Used for equality check, see [EquatableMixin]. @override List get props => [ x, open, high, low, close, show, ]; } /// Holds data to handle touch events, and touch responses in the [CandlestickChart]. /// /// There is a touch flow, explained [here](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/handle_touches.md) /// in a simple way, each chart's renderer captures the touch events, and passes the pointerEvent /// to the painter, and gets touched spot, and wraps it into a concrete [CandlestickTouchResponse]. class CandlestickTouchData extends FlTouchData with EquatableMixin { /// You can disable or enable the touch system using [enabled] flag, /// /// [touchCallback] notifies you about the happened touch/pointer events. /// It gives you a [FlTouchEvent] which is the happened event such as [FlPointerHoverEvent], [FlTapUpEvent], ... /// It also gives you a [CandlestickTouchResponse] which contains information /// about the elements that has touched. /// /// Using [mouseCursorResolver] you can change the mouse cursor /// based on the provided [FlTouchEvent] and [CandlestickTouchResponse] /// /// if [handleBuiltInTouches] is true, [CandlestickChart] shows a tooltip popup on top of the spots if /// touch occurs (or you can show it manually using, [CandlestickChartData.showingTooltipIndicators]) /// You can customize this tooltip using [touchTooltipData], /// /// If you need to have a distance threshold for handling touches, use [touchSpotThreshold]. CandlestickTouchData({ bool? enabled, BaseTouchCallback? touchCallback, MouseCursorResolver? mouseCursorResolver, Duration? longPressDuration, CandlestickTouchTooltipData? touchTooltipData, bool? handleBuiltInTouches, double? touchSpotThreshold, }) : touchTooltipData = touchTooltipData ?? CandlestickTouchTooltipData(), handleBuiltInTouches = handleBuiltInTouches ?? true, touchSpotThreshold = touchSpotThreshold ?? 4, super( enabled ?? true, touchCallback, mouseCursorResolver, longPressDuration, ); /// show a tooltip on touched spots final CandlestickTouchTooltipData touchTooltipData; /// set this true if you want the built in touch handling /// (show a tooltip bubble and an indicator on touched spots) final bool handleBuiltInTouches; /// we find the nearest spots on touched position based on this threshold final double touchSpotThreshold; /// Copies current [CandlestickTouchData] to a new [CandlestickTouchData], /// and replaces provided values. CandlestickTouchData copyWith({ bool? enabled, BaseTouchCallback? touchCallback, MouseCursorResolver? mouseCursorResolver, Duration? longPressDuration, CandlestickTouchTooltipData? touchTooltipData, bool? handleBuiltInTouches, double? touchSpotThreshold, }) => CandlestickTouchData( enabled: enabled ?? this.enabled, touchCallback: touchCallback ?? this.touchCallback, mouseCursorResolver: mouseCursorResolver ?? this.mouseCursorResolver, longPressDuration: longPressDuration ?? this.longPressDuration, touchTooltipData: touchTooltipData ?? this.touchTooltipData, handleBuiltInTouches: handleBuiltInTouches ?? this.handleBuiltInTouches, touchSpotThreshold: touchSpotThreshold ?? this.touchSpotThreshold, ); /// Used for equality check, see [EquatableMixin]. @override List get props => [ enabled, touchCallback, mouseCursorResolver, longPressDuration, touchTooltipData, handleBuiltInTouches, touchSpotThreshold, ]; } /// [CandlestickChart]'s touch callback. typedef CandlestickTouchCallback = void Function(CandlestickTouchResponse); /// Holds information about touch response in the [CandlestickChart]. /// /// You can override [CandlestickTouchData.touchCallback] to handle touch events, /// it gives you a [CandlestickTouchResponse] and you can do whatever you want. class CandlestickTouchResponse extends AxisBaseTouchResponse { /// If touch happens, [CandlestickChart] processes it internally and /// passes out a [CandlestickTouchResponse], it gives you information about the touched spot. /// /// [touchedSpot] tells you /// in which spot (of [CandlestickChartData.candleSpots]) touch happened. CandlestickTouchResponse({ required super.touchLocation, required super.touchChartCoordinate, required this.touchedSpot, }); final CandlestickTouchedSpot? touchedSpot; /// Copies current [CandlestickTouchResponse] to a new [CandlestickTouchResponse], /// and replaces provided values. CandlestickTouchResponse copyWith({ Offset? touchLocation, Offset? touchChartCoordinate, CandlestickTouchedSpot? touchedSpot, }) => CandlestickTouchResponse( touchLocation: touchLocation ?? this.touchLocation, touchChartCoordinate: touchChartCoordinate ?? this.touchChartCoordinate, touchedSpot: touchedSpot ?? this.touchedSpot, ); } /// Holds the touched spot data class CandlestickTouchedSpot with EquatableMixin { /// [spot], and [spotIndex] tells you /// in which spot (of [CandlestickChartData.candleSpots]) touch happened. const CandlestickTouchedSpot(this.spot, this.spotIndex); /// Touch happened on this spot final CandlestickSpot spot; /// Touch happened on this spot index final int spotIndex; /// Used for equality check, see [EquatableMixin]. @override List get props => [ spot, spotIndex, ]; /// Copies current [CandlestickTouchedSpot] to a new [CandlestickTouchedSpot], /// and replaces provided values. CandlestickTouchedSpot copyWith({ CandlestickSpot? spot, int? spotIndex, }) => CandlestickTouchedSpot(spot ?? this.spot, spotIndex ?? this.spotIndex); } /// Holds representation data for showing tooltip popup on top of spots. class CandlestickTouchTooltipData with EquatableMixin { /// if [CandlestickTouchData.handleBuiltInTouches] is true, /// [CandlestickChart] shows a tooltip popup on top of spots automatically when touch happens, /// otherwise you can show it manually using [CandlestickChartData.showingTooltipIndicators]. /// Tooltip shows on top of rods, with [getTooltipColor] as a background color. /// You can set the corner radius using [tooltipBorderRadius]. /// If you want to have a padding inside the tooltip, fill [tooltipPadding]. /// Content of the tooltip will provide using [getTooltipItems] callback, you can override it /// and pass your custom data to show in the tooltip. /// You can restrict the tooltip's width using [maxContentWidth]. /// Sometimes, [CandlestickChart] shows the tooltip outside of the chart, /// you can set [fitInsideHorizontally] true to force it to shift inside the chart horizontally, /// also you can set [fitInsideVertically] true to force it to shift inside the chart vertically. CandlestickTouchTooltipData({ BorderRadius? tooltipBorderRadius, EdgeInsets? tooltipPadding, FLHorizontalAlignment? tooltipHorizontalAlignment, double? tooltipHorizontalOffset, double? maxContentWidth, GetCandlestickTooltipItems? getTooltipItems, bool? fitInsideHorizontally, bool? fitInsideVertically, bool? showOnTopOfTheChartBoxArea, double? rotateAngle, BorderSide? tooltipBorder, GetCandlestickTooltipColor? getTooltipColor, }) : tooltipBorderRadius = tooltipBorderRadius ?? BorderRadius.circular(4), tooltipPadding = tooltipPadding ?? const EdgeInsets.symmetric(horizontal: 16, vertical: 8), tooltipHorizontalAlignment = tooltipHorizontalAlignment ?? FLHorizontalAlignment.center, tooltipHorizontalOffset = tooltipHorizontalOffset ?? 0, maxContentWidth = maxContentWidth ?? 120, getTooltipItems = getTooltipItems ?? defaultCandlestickTooltipItem, fitInsideHorizontally = fitInsideHorizontally ?? false, fitInsideVertically = fitInsideVertically ?? false, showOnTopOfTheChartBoxArea = showOnTopOfTheChartBoxArea ?? false, rotateAngle = rotateAngle ?? 0.0, tooltipBorder = tooltipBorder ?? BorderSide.none, getTooltipColor = getTooltipColor ?? defaultCandlestickTooltipColor, super(); /// Sets a border radius for the tooltip. final BorderRadius tooltipBorderRadius; /// Applies a padding for showing contents inside the tooltip. final EdgeInsets tooltipPadding; /// Controls showing tooltip on left side, right side or center aligned with spot, default is center final FLHorizontalAlignment tooltipHorizontalAlignment; /// Applies horizontal offset for showing tooltip, default is zero. final double tooltipHorizontalOffset; /// Restricts the tooltip's width. final double maxContentWidth; /// Retrieves data for showing content inside the tooltip. final GetCandlestickTooltipItems getTooltipItems; /// Forces the tooltip to shift horizontally inside the chart, if overflow happens. final bool fitInsideHorizontally; /// Forces the tooltip to shift vertically inside the chart, if overflow happens. final bool fitInsideVertically; /// Forces the tooltip container to top of the line, default 'false' final bool showOnTopOfTheChartBoxArea; /// Controls the rotation of the tooltip. final double rotateAngle; /// The tooltip border color. final BorderSide tooltipBorder; /// Retrieves data for showing content inside the tooltip. final GetCandlestickTooltipColor getTooltipColor; /// Used for equality check, see [EquatableMixin]. @override List get props => [ tooltipBorderRadius, tooltipPadding, tooltipHorizontalAlignment, tooltipHorizontalOffset, maxContentWidth, getTooltipItems, fitInsideHorizontally, fitInsideVertically, showOnTopOfTheChartBoxArea, rotateAngle, tooltipBorder, getTooltipColor, ]; /// Copies current [CandlestickTouchTooltipData] to a new [CandlestickTouchTooltipData], /// and replaces provided values. CandlestickTouchTooltipData copyWith({ BorderRadius? tooltipBorderRadius, EdgeInsets? tooltipPadding, FLHorizontalAlignment? tooltipHorizontalAlignment, double? tooltipHorizontalOffset, double? maxContentWidth, GetCandlestickTooltipItems? getTooltipItems, bool? fitInsideHorizontally, bool? fitInsideVertically, double? rotateAngle, BorderSide? tooltipBorder, GetCandlestickTooltipColor? getTooltipColor, }) => CandlestickTouchTooltipData( tooltipBorderRadius: tooltipBorderRadius ?? this.tooltipBorderRadius, tooltipPadding: tooltipPadding ?? this.tooltipPadding, tooltipHorizontalAlignment: tooltipHorizontalAlignment ?? this.tooltipHorizontalAlignment, tooltipHorizontalOffset: tooltipHorizontalOffset ?? this.tooltipHorizontalOffset, maxContentWidth: maxContentWidth ?? this.maxContentWidth, getTooltipItems: getTooltipItems ?? this.getTooltipItems, fitInsideHorizontally: fitInsideHorizontally ?? this.fitInsideHorizontally, fitInsideVertically: fitInsideVertically ?? this.fitInsideVertically, rotateAngle: rotateAngle ?? this.rotateAngle, tooltipBorder: tooltipBorder ?? this.tooltipBorder, getTooltipColor: getTooltipColor ?? this.getTooltipColor, ); } /// Provides a [CandlestickTooltipItem] for showing content inside the [CandlestickTouchTooltipData]. /// /// You can override [CandlestickTouchTooltipData.getTooltipItems], it gives you /// [touchedSpot] that touch happened on, /// then you should and pass your custom [CandlestickTooltipItem] /// to show it inside the tooltip popup. typedef GetCandlestickTooltipItems = CandlestickTooltipItem? Function( FlCandlestickPainter painter, CandlestickSpot touchedSpot, int spotIndex, ); /// Default implementation for [CandlestickTouchTooltipData.getTooltipItems]. CandlestickTooltipItem? defaultCandlestickTooltipItem( FlCandlestickPainter painter, CandlestickSpot touchedSpot, int spotIndex, ) { final textStyle = TextStyle( color: painter.getMainColor( spot: touchedSpot, spotIndex: spotIndex, ), fontSize: 14, ); final valueStyle = TextStyle( color: painter.getMainColor( spot: touchedSpot, spotIndex: spotIndex, ), fontWeight: FontWeight.bold, fontSize: 14, ); return CandlestickTooltipItem( '', textStyle: textStyle, children: [ TextSpan( text: 'open: ', style: textStyle, ), TextSpan( text: '${touchedSpot.open.toInt()}\n', style: valueStyle, ), TextSpan( text: 'high: ', style: textStyle, ), TextSpan( text: '${touchedSpot.high.toInt()}\n', style: valueStyle, ), TextSpan( text: 'low: ', style: textStyle, ), TextSpan( text: '${touchedSpot.low.toInt()}\n', style: valueStyle, ), TextSpan( text: 'close: ', style: textStyle, ), TextSpan( text: '${touchedSpot.close.toInt()}', style: valueStyle, ), ], ); } /// Provides a [Color] to show different background color inside the [CandlestickTouchTooltipData]. /// /// You can override [CandlestickTouchTooltipData.getTooltipColor], it gives you /// [touchedSpot] that touch happened on, /// then you should and pass your custom [Color] /// to show it inside the tooltip popup. typedef GetCandlestickTooltipColor = Color Function( CandlestickSpot touchedSpot, ); /// Default implementation for [CandlestickTouchTooltipData.getTooltipItems]. Color defaultCandlestickTooltipColor(CandlestickSpot touchedSpot) => Colors.blueGrey.darken(80); /// Holds data of showing each item in the tooltip popup. class CandlestickTooltipItem with EquatableMixin { /// Shows a [text] with [textStyle], [textDirection], and optional [children] in the tooltip popup, /// [bottomMargin] is the bottom space from spot. CandlestickTooltipItem( this.text, { this.textStyle, double? bottomMargin, TextAlign? textAlign, TextDirection? textDirection, this.children, }) : bottomMargin = bottomMargin ?? 8, textAlign = textAlign ?? TextAlign.center, textDirection = textDirection ?? TextDirection.ltr; /// Showing text. final String text; /// Style of showing text. final TextStyle? textStyle; /// Defines bottom space from spot. final double bottomMargin; /// TextAlign of the showing content. final TextAlign textAlign; /// Direction of showing text. final TextDirection textDirection; /// Add further style and format to the text of the tooltip final List? children; /// Used for equality check, see [EquatableMixin]. @override List get props => [ text, textStyle, bottomMargin, textAlign, textDirection, children, ]; /// Copies current [CandlestickTooltipItem] to a new [CandlestickTooltipItem], /// and replaces provided values. CandlestickTooltipItem copyWith({ String? text, TextStyle? textStyle, double? bottomMargin, TextAlign? textAlign, TextDirection? textDirection, List? children, }) => CandlestickTooltipItem( text ?? this.text, textStyle: textStyle ?? this.textStyle, bottomMargin: bottomMargin ?? this.bottomMargin, textAlign: textAlign ?? this.textAlign, textDirection: textDirection ?? this.textDirection, children: children ?? this.children, ); } /// This class contains the interface for drawing the candlestick shape. abstract class FlCandlestickPainter with EquatableMixin { const FlCandlestickPainter(); /// This method should be overridden to draw the candlestick shape void paint( Canvas canvas, ValueInCanvasProvider xInCanvasProvider, ValueInCanvasProvider yInCanvasProvider, CandlestickSpot spot, int spotIndex, ); /// Used to show default UIs, for example [defaultCandlestickTooltipItem] Color getMainColor({ required CandlestickSpot spot, required int spotIndex, }); FlCandlestickPainter lerp( FlCandlestickPainter a, FlCandlestickPainter b, double t, ); /// Used to implement touch behaviour of this dot, for example, /// it behaves like a square of [getSize] /// Check [DefaultCandlestickPainter.hitTest] for an example of an implementation (bool, double) hitTest( CandlestickSpot spot, double touchedX, double spotX, double extraTouchThreshold, ) { final distance = (touchedX - spotX).abs(); final hit = distance <= extraTouchThreshold; return (hit, distance); } } /// [CandlestickChart]'s touch callback. typedef CandlestickStyleProvider = CandlestickStyle Function( CandlestickSpot spot, int index, ); CandlestickStyleProvider get _defaultStrokeColorProvider => (spot, _) { final generalColor = spot.isUp ? const Color(0xFF4CAF50) : const Color(0xFFEF5350); return CandlestickStyle( lineColor: generalColor, lineWidth: 1.5, bodyStrokeColor: generalColor, bodyStrokeWidth: 0, bodyFillColor: generalColor, bodyWidth: 4, bodyRadius: 0, ); }; /// Default implementation of [FlCandlestickPainter]. /// /// It draws the candlestick shape with a line and a body (just like a standard /// candlestick chart). /// /// You can customize the appearance of the candlestick /// using [CandlestickStyleProvider]. class DefaultCandlestickPainter extends FlCandlestickPainter { DefaultCandlestickPainter({ CandlestickStyleProvider? candlestickStyleProvider, }) : candlestickStyleProvider = candlestickStyleProvider ?? _defaultStrokeColorProvider; final CandlestickStyleProvider candlestickStyleProvider; final _linePainter = Paint(); final _bodyPainter = Paint(); final _bodyStrokePainter = Paint(); @override void paint( Canvas canvas, ValueInCanvasProvider xInCanvasProvider, ValueInCanvasProvider yInCanvasProvider, CandlestickSpot spot, int spotIndex, ) { final style = candlestickStyleProvider(spot, spotIndex); final xOffsetInCanvas = xInCanvasProvider(spot.x); final openYOffsetInCanvas = yInCanvasProvider(spot.open); final highYOffsetInCanvas = yInCanvasProvider(spot.high); final lowOYOffsetInCanvas = yInCanvasProvider(spot.low); final closeYOffsetInCanvas = yInCanvasProvider(spot.close); final bodyHighCanvas = min(openYOffsetInCanvas, closeYOffsetInCanvas); final bodyLowCanvas = max(openYOffsetInCanvas, closeYOffsetInCanvas); if (style.lineWidth > 0 && style.lineColor.a > 0) { canvas // Bottom line ..drawLine( Offset(xOffsetInCanvas, lowOYOffsetInCanvas), Offset(xOffsetInCanvas, bodyLowCanvas), _linePainter ..color = style.lineColor ..strokeWidth = style.lineWidth, ) // Top line ..drawLine( Offset(xOffsetInCanvas, highYOffsetInCanvas), Offset(xOffsetInCanvas, bodyHighCanvas), _linePainter ..color = style.lineColor ..strokeWidth = style.lineWidth, ); } // Body final bodyRect = Rect.fromLTRB( xOffsetInCanvas - style.bodyWidth / 2, bodyHighCanvas, xOffsetInCanvas + style.bodyWidth / 2, bodyLowCanvas, ); if (style.bodyFillColor.a > 0 && style.bodyWidth > 0) { canvas.drawRRect( RRect.fromRectAndRadius( bodyRect, Radius.circular(style.bodyRadius), ), _bodyPainter ..color = style.bodyFillColor ..style = PaintingStyle.fill, ); } if (style.bodyStrokeWidth > 0 && style.bodyStrokeColor.a > 0) { canvas.drawRRect( RRect.fromRectAndRadius( bodyRect, Radius.circular(style.bodyRadius), ), _bodyStrokePainter ..color = style.bodyStrokeColor ..strokeWidth = style.bodyStrokeWidth ..style = PaintingStyle.stroke, ); } } @override FlCandlestickPainter lerp( FlCandlestickPainter a, FlCandlestickPainter b, double t, ) { if (a is! DefaultCandlestickPainter || b is! DefaultCandlestickPainter) { return b; } return DefaultCandlestickPainter( candlestickStyleProvider: b.candlestickStyleProvider, ); } @override Color getMainColor({ required CandlestickSpot spot, required int spotIndex, }) => candlestickStyleProvider(spot, spotIndex).lineColor; @override List get props => [ candlestickStyleProvider, ]; } /// Holds data for drawing each candlestick shape. class CandlestickStyle with EquatableMixin { const CandlestickStyle({ required this.lineColor, required this.lineWidth, required this.bodyStrokeColor, required this.bodyStrokeWidth, required this.bodyFillColor, required this.bodyWidth, required this.bodyRadius, }); /// The color of the candlestick line. final Color lineColor; /// The width of the candlestick line. final double lineWidth; /// The color of the candlestick body stroke. final Color bodyStrokeColor; /// The width of the candlestick body stroke. final double bodyStrokeWidth; /// The fill color of the candlestick body. final Color bodyFillColor; /// The width of the candlestick body. final double bodyWidth; /// The radius of the corners of the candlestick body. final double bodyRadius; /// Lerps a [CandlestickStyle] based on [t] value, check [Tween.lerp]. static CandlestickStyle lerp( CandlestickStyle a, CandlestickStyle b, double t, ) => CandlestickStyle( lineColor: Color.lerp(a.lineColor, b.lineColor, t)!, lineWidth: lerpDouble(a.lineWidth, b.lineWidth, t)!, bodyStrokeColor: Color.lerp(a.bodyStrokeColor, b.bodyStrokeColor, t)!, bodyStrokeWidth: lerpDouble(a.bodyStrokeWidth, b.bodyStrokeWidth, t)!, bodyFillColor: Color.lerp(a.bodyFillColor, b.bodyFillColor, t)!, bodyWidth: lerpDouble(a.bodyWidth, b.bodyWidth, t)!, bodyRadius: lerpDouble(a.bodyRadius, b.bodyRadius, t)!, ); @override List get props => [ lineColor, lineWidth, bodyStrokeColor, bodyStrokeWidth, bodyFillColor, bodyWidth, bodyRadius, ]; } /// It lerps a [CandlestickChartData] to another [CandlestickChartData] (handles animation for updating values) class CandlestickChartDataTween extends Tween { CandlestickChartDataTween({ required CandlestickChartData begin, required CandlestickChartData end, }) : super(begin: begin, end: end); /// Lerps a [CandlestickChartData] based on [t] value, check [Tween.lerp]. @override CandlestickChartData lerp(double t) => begin!.lerp(begin!, end!, t); } ================================================ FILE: lib/src/chart/candlestick_chart/candlestick_chart_helper.dart ================================================ import 'package:fl_chart/src/chart/candlestick_chart/candlestick_chart_data.dart'; /// Contains anything that helps CandlestickChart works class CandlestickChartHelper { /// Calculates minX, maxX, minY, and maxY based on [candleSpots], /// returns cached values, to prevent redundant calculations. static ( double minX, double maxX, double minY, double maxY, ) calculateMaxAxisValues( List candleSpots, ) { if (candleSpots.isEmpty) { return (0, 0, 0, 0); } var minX = candleSpots[0].x; var maxX = candleSpots[0].x; var minY = candleSpots[0].low; var maxY = candleSpots[0].high; for (var j = 0; j < candleSpots.length; j++) { final spot = candleSpots[j]; if (spot.x > maxX) { maxX = spot.x; } if (spot.x < minX) { minX = spot.x; } if (spot.high > maxY) { maxY = spot.high; } if (spot.low < minY) { minY = spot.low; } } return (minX, maxX, minY, maxY); } } ================================================ FILE: lib/src/chart/candlestick_chart/candlestick_chart_painter.dart ================================================ import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/chart/base/axis_chart/axis_chart_painter.dart'; import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart'; import 'package:fl_chart/src/utils/canvas_wrapper.dart'; import 'package:fl_chart/src/utils/utils.dart'; import 'package:flutter/material.dart'; /// Paints [CandlestickChartData] in the canvas, it can be used in a [CustomPainter] class CandlestickChartPainter extends AxisChartPainter { /// Paints [CandlestickChartData] in the canvas CandlestickChartPainter() : super() { _bgTouchTooltipPaint = Paint() ..style = PaintingStyle.fill ..color = Colors.white; _borderTouchTooltipPaint = Paint() ..style = PaintingStyle.stroke ..color = Colors.transparent ..strokeWidth = 1.0; _clipPaint = Paint(); } late Paint _bgTouchTooltipPaint; late Paint _borderTouchTooltipPaint; late Paint _clipPaint; /// Paints [CandlestickChartData] into the provided canvas. @override void paint( BuildContext context, CanvasWrapper canvasWrapper, PaintHolder holder, ) { if (holder.chartVirtualRect != null) { canvasWrapper ..saveLayer( Offset.zero & canvasWrapper.size, _clipPaint, ) ..clipRect(Offset.zero & canvasWrapper.size); } super.paint(context, canvasWrapper, holder); drawAxisSpotIndicator(context, canvasWrapper, holder); drawCandlesticks(context, canvasWrapper, holder); if (holder.chartVirtualRect != null) { canvasWrapper.restore(); } drawTouchTooltips(context, canvasWrapper, holder); } @visibleForTesting void drawCandlesticks( BuildContext context, CanvasWrapper canvasWrapper, PaintHolder holder, ) { final data = holder.data; final viewSize = canvasWrapper.size; final clip = data.clipData; final border = data.borderData.show ? data.borderData.border : null; if (data.clipData.any) { canvasWrapper.saveLayer( Rect.fromLTRB( 0, 0, canvasWrapper.size.width, canvasWrapper.size.height, ), _clipPaint, ); var left = 0.0; var top = 0.0; var right = viewSize.width; var bottom = viewSize.height; if (clip.left) { final borderWidth = border?.left.width ?? 0; left = borderWidth / 2; } if (clip.top) { final borderWidth = border?.top.width ?? 0; top = borderWidth / 2; } if (clip.right) { final borderWidth = border?.right.width ?? 0; right = viewSize.width - (borderWidth / 2); } if (clip.bottom) { final borderWidth = border?.bottom.width ?? 0; bottom = viewSize.height - (borderWidth / 2); } canvasWrapper.clipRect(Rect.fromLTRB(left, top, right, bottom)); } for (var i = 0; i < data.candlestickSpots.length; i++) { final candlestickSpot = data.candlestickSpots[i]; if (!candlestickSpot.show) { continue; } holder.data.candlestickPainter.paint( canvasWrapper.canvas, (x) => getPixelX(x, viewSize, holder), (y) => getPixelY(y, viewSize, holder), candlestickSpot, i, ); } if (data.clipData.any) { canvasWrapper.restore(); } } @visibleForTesting void drawTouchTooltips( BuildContext context, CanvasWrapper canvasWrapper, PaintHolder holder, ) { final targetData = holder.targetData; for (var i = 0; i < targetData.candlestickSpots.length; i++) { if (!targetData.showingTooltipIndicators.contains(i)) { continue; } final candlestickSpot = targetData.candlestickSpots[i]; drawTouchTooltip( context, canvasWrapper, targetData.candlestickTouchData.touchTooltipData, candlestickSpot, i, holder, ); } } @visibleForTesting void drawTouchTooltip( BuildContext context, CanvasWrapper canvasWrapper, CandlestickTouchTooltipData tooltipData, CandlestickSpot showOnSpot, int spotIndex, PaintHolder holder, ) { final viewSize = canvasWrapper.size; final tooltipItem = tooltipData.getTooltipItems( holder.data.candlestickPainter, showOnSpot, spotIndex, ); if (tooltipItem == null) { return; } final span = TextSpan( style: Utils().getThemeAwareTextStyle(context, tooltipItem.textStyle), text: tooltipItem.text, children: tooltipItem.children, ); final drawingTextPainter = TextPainter( text: span, textAlign: tooltipItem.textAlign, textDirection: tooltipItem.textDirection, textScaler: holder.textScaler, )..layout(maxWidth: tooltipData.maxContentWidth); final width = drawingTextPainter.width; final height = drawingTextPainter.height; final tooltipOriginPoint = Offset( getPixelX(showOnSpot.x, viewSize, holder), getPixelY( showOnSpot.high, viewSize, holder, ), ); final tooltipWidth = width + tooltipData.tooltipPadding.horizontal; final tooltipHeight = height + tooltipData.tooltipPadding.vertical; double tooltipTopPosition; if (tooltipData.showOnTopOfTheChartBoxArea) { tooltipTopPosition = 0 - tooltipHeight - tooltipItem.bottomMargin; } else { tooltipTopPosition = tooltipOriginPoint.dy - tooltipHeight - tooltipItem.bottomMargin; } final tooltipLeftPosition = getTooltipLeft( tooltipOriginPoint.dx, tooltipWidth, tooltipData.tooltipHorizontalAlignment, tooltipData.tooltipHorizontalOffset, ); /// draw the background rect with rounded radius var rect = Rect.fromLTWH( tooltipLeftPosition, tooltipTopPosition, tooltipWidth, tooltipHeight, ); if (tooltipData.fitInsideHorizontally) { if (rect.left < 0) { final shiftAmount = 0 - rect.left; rect = Rect.fromLTRB( rect.left + shiftAmount, rect.top, rect.right + shiftAmount, rect.bottom, ); } if (rect.right > viewSize.width) { final shiftAmount = rect.right - viewSize.width; rect = Rect.fromLTRB( rect.left - shiftAmount, rect.top, rect.right - shiftAmount, rect.bottom, ); } } if (tooltipData.fitInsideVertically) { if (rect.top < 0) { final shiftAmount = 0 - rect.top; rect = Rect.fromLTRB( rect.left, rect.top + shiftAmount, rect.right, rect.bottom + shiftAmount, ); } if (rect.bottom > viewSize.height) { final shiftAmount = rect.bottom - viewSize.height; rect = Rect.fromLTRB( rect.left, rect.top - shiftAmount, rect.right, rect.bottom - shiftAmount, ); } } final roundedRect = RRect.fromRectAndCorners( rect, topLeft: tooltipData.tooltipBorderRadius.topLeft, topRight: tooltipData.tooltipBorderRadius.topRight, bottomLeft: tooltipData.tooltipBorderRadius.bottomLeft, bottomRight: tooltipData.tooltipBorderRadius.bottomRight, ); _bgTouchTooltipPaint.color = tooltipData.getTooltipColor(showOnSpot); final rotateAngle = tooltipData.rotateAngle; final rectRotationOffset = Offset(0, Utils().calculateRotationOffset(rect.size, rotateAngle).dy); final rectDrawOffset = Offset(roundedRect.left, roundedRect.top); final textRotationOffset = Utils().calculateRotationOffset(drawingTextPainter.size, rotateAngle); final drawOffset = Offset( rect.center.dx - (drawingTextPainter.width / 2), rect.topCenter.dy + tooltipData.tooltipPadding.top - textRotationOffset.dy + rectRotationOffset.dy, ); if (tooltipData.tooltipBorder != BorderSide.none) { _borderTouchTooltipPaint ..color = tooltipData.tooltipBorder.color ..strokeWidth = tooltipData.tooltipBorder.width; } final reverseQuarterTurnsAngle = -holder.data.rotationQuarterTurns * 90; canvasWrapper.drawRotated( size: rect.size, rotationOffset: rectRotationOffset, drawOffset: rectDrawOffset, angle: reverseQuarterTurnsAngle + rotateAngle, drawCallback: () { canvasWrapper ..drawRRect(roundedRect, _bgTouchTooltipPaint) ..drawRRect(roundedRect, _borderTouchTooltipPaint) ..drawText(drawingTextPainter, drawOffset); }, ); } @visibleForTesting void drawAxisSpotIndicator( BuildContext context, CanvasWrapper canvasWrapper, PaintHolder holder, ) { final pointIndicator = holder.data.touchedPointIndicator; if (pointIndicator == null) { return; } final viewSize = canvasWrapper.size; pointIndicator.painter.paint( context, canvasWrapper.canvas, canvasWrapper.size, pointIndicator, (x) => getPixelX(x, viewSize, holder), (y) => getPixelY(y, viewSize, holder), holder.data, ); } /// Makes a [CandlestickTouchedSpot] based on the provided [localPosition] /// /// Processes [localPosition] and checks /// the elements of the chart that are near the offset, /// then makes a [CandlestickTouchedSpot] from the elements that has been touched. /// /// Returns null if finds nothing! CandlestickTouchedSpot? handleTouch( Offset localPosition, Size viewSize, PaintHolder holder, ) { final data = holder.data; final touchedSpots = <({CandlestickSpot spot, int index, double distance})>[]; for (var i = data.candlestickSpots.length - 1; i >= 0; i--) { // Reverse the loop to check the topmost spot first final spot = data.candlestickSpots[i]; if (!spot.show) { continue; } final spotPixelX = getPixelX(spot.x, viewSize, holder); final (hit, distance) = holder.targetData.candlestickPainter.hitTest( spot, spotPixelX, localPosition.dx, holder.data.candlestickTouchData.touchSpotThreshold, ); if (hit) { touchedSpots.add( ( spot: spot, index: i, distance: distance, ), ); } } if (touchedSpots.isEmpty) { return null; } // Sort the touched spots by distance touchedSpots.sort((a, b) => a.distance.compareTo(b.distance)); final closestSpot = touchedSpots.first; return CandlestickTouchedSpot(closestSpot.spot, closestSpot.index); } } ================================================ FILE: lib/src/chart/candlestick_chart/candlestick_chart_renderer.dart ================================================ import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart'; import 'package:fl_chart/src/chart/base/base_chart/render_base_chart.dart'; import 'package:fl_chart/src/chart/candlestick_chart/candlestick_chart_painter.dart'; import 'package:fl_chart/src/utils/canvas_wrapper.dart'; import 'package:flutter/cupertino.dart'; // coverage:ignore-start /// Low level ScatterChart Widget. class CandlestickChartLeaf extends LeafRenderObjectWidget { const CandlestickChartLeaf({ super.key, required this.data, required this.targetData, required this.chartVirtualRect, required this.canBeScaled, }); final CandlestickChartData data; final CandlestickChartData targetData; final Rect? chartVirtualRect; final bool canBeScaled; @override RenderCandlestickChart createRenderObject(BuildContext context) => RenderCandlestickChart( context, data, targetData, MediaQuery.of(context).textScaler, chartVirtualRect, canBeScaled: canBeScaled, ); @override void updateRenderObject( BuildContext context, RenderCandlestickChart renderObject, ) { renderObject ..data = data ..targetData = targetData ..textScaler = MediaQuery.of(context).textScaler ..buildContext = context ..chartVirtualRect = chartVirtualRect ..canBeScaled = canBeScaled; } } // coverage:ignore-end /// Renders our ScatterChart, also handles hitTest. class RenderCandlestickChart extends RenderBaseChart { RenderCandlestickChart( BuildContext context, CandlestickChartData data, CandlestickChartData targetData, TextScaler textScaler, Rect? chartVirtualRect, { required bool canBeScaled, }) : _data = data, _targetData = targetData, _textScaler = textScaler, _chartVirtualRect = chartVirtualRect, super( targetData.candlestickTouchData, context, canBeScaled: canBeScaled, ); CandlestickChartData get data => _data; CandlestickChartData _data; set data(CandlestickChartData value) { if (_data == value) return; _data = value; markNeedsPaint(); } CandlestickChartData get targetData => _targetData; CandlestickChartData _targetData; set targetData(CandlestickChartData value) { if (_targetData == value) return; _targetData = value; super.updateBaseTouchData(_targetData.candlestickTouchData); markNeedsPaint(); } TextScaler get textScaler => _textScaler; TextScaler _textScaler; set textScaler(TextScaler value) { if (_textScaler == value) return; _textScaler = value; markNeedsPaint(); } Rect? get chartVirtualRect => _chartVirtualRect; Rect? _chartVirtualRect; set chartVirtualRect(Rect? value) { if (_chartVirtualRect == value) return; _chartVirtualRect = value; markNeedsPaint(); } // We couldn't mock [size] property of this class, that's why we have this @visibleForTesting Size? mockTestSize; @visibleForTesting CandlestickChartPainter painter = CandlestickChartPainter(); PaintHolder get paintHolder => PaintHolder(data, targetData, textScaler, chartVirtualRect); @override void paint(PaintingContext context, Offset offset) { final canvas = context.canvas ..save() ..translate(offset.dx, offset.dy); painter.paint( buildContext, CanvasWrapper(canvas, mockTestSize ?? size), paintHolder, ); canvas.restore(); } @override bool hitTestSelf(Offset position) { if (!targetData.candlestickTouchData.enabled) { return false; } return super.hitTestSelf(position); } @override CandlestickTouchResponse getResponseAtLocation(Offset localPosition) { final chartSize = mockTestSize ?? size; return CandlestickTouchResponse( touchLocation: localPosition, touchChartCoordinate: painter.getChartCoordinateFromPixel( localPosition, chartSize, paintHolder, ), touchedSpot: painter.handleTouch( localPosition, chartSize, paintHolder, ), ); } } ================================================ FILE: lib/src/chart/line_chart/line_chart.dart ================================================ import 'package:fl_chart/src/chart/base/axis_chart/axis_chart_scaffold_widget.dart'; import 'package:fl_chart/src/chart/base/axis_chart/scale_axis.dart'; import 'package:fl_chart/src/chart/base/axis_chart/transformation_config.dart'; import 'package:fl_chart/src/chart/base/base_chart/base_chart_data.dart'; import 'package:fl_chart/src/chart/base/base_chart/fl_touch_event.dart'; import 'package:fl_chart/src/chart/line_chart/line_chart_data.dart'; import 'package:fl_chart/src/chart/line_chart/line_chart_helper.dart'; import 'package:fl_chart/src/chart/line_chart/line_chart_renderer.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; /// Renders a line chart as a widget, using provided [LineChartData]. class LineChart extends ImplicitlyAnimatedWidget { /// [data] determines how the [LineChart] should be look like, /// when you make any change in the [LineChartData], it updates /// new values with animation, and duration is [duration]. /// also you can change the [curve] /// which default is [Curves.linear]. const LineChart( this.data, { this.chartRendererKey, super.key, super.duration = const Duration(milliseconds: 150), super.curve = Curves.linear, this.transformationConfig = const FlTransformationConfig(), }); /// Determines how the [LineChart] should be look like. final LineChartData data; /// {@macro fl_chart.AxisChartScaffoldWidget.transformationConfig} final FlTransformationConfig transformationConfig; /// We pass this key to our renderers which are supposed to /// render the chart itself (without anything around the chart). final Key? chartRendererKey; /// Creates a [_LineChartState] @override _LineChartState createState() => _LineChartState(); } class _LineChartState extends AnimatedWidgetBaseState { /// we handle under the hood animations (implicit animations) via this tween, /// it lerps between the old [LineChartData] to the new one. LineChartDataTween? _lineChartDataTween; /// If [LineTouchData.handleBuiltInTouches] is true, we override the callback to handle touches internally, /// but we need to keep the provided callback to notify it too. BaseTouchCallback? _providedTouchCallback; final List _showingTouchedTooltips = []; final Map> _showingTouchedIndicators = {}; final _lineChartHelper = LineChartHelper(); @override Widget build(BuildContext context) { final showingData = _getData(); return AxisChartScaffoldWidget( transformationConfig: widget.transformationConfig, chartBuilder: (context, chartVirtualRect) => LineChartLeaf( data: _withTouchedIndicators( _lineChartDataTween!.evaluate(animation), ), targetData: _withTouchedIndicators(showingData), key: widget.chartRendererKey, chartVirtualRect: chartVirtualRect, canBeScaled: widget.transformationConfig.scaleAxis != FlScaleAxis.none, ), data: showingData, ); } LineChartData _withTouchedIndicators(LineChartData lineChartData) { if (!lineChartData.lineTouchData.enabled || !lineChartData.lineTouchData.handleBuiltInTouches) { return lineChartData; } return lineChartData.copyWith( showingTooltipIndicators: _showingTouchedTooltips, lineBarsData: lineChartData.lineBarsData.map((barData) { final index = lineChartData.lineBarsData.indexOf(barData); return barData.copyWith( showingIndicators: _showingTouchedIndicators[index] ?? [], ); }).toList(), ); } LineChartData _getData() { var newData = widget.data; /// Calculate minX, maxX, minY, maxY for [LineChartData] if they are null, /// it is necessary to render the chart correctly. if (newData.minX.isNaN || newData.maxX.isNaN || newData.minY.isNaN || newData.maxY.isNaN) { final (minX, maxX, minY, maxY) = _lineChartHelper.calculateMaxAxisValues( newData.lineBarsData, ); newData = newData.copyWith( minX: newData.minX.isNaN ? minX : newData.minX, maxX: newData.maxX.isNaN ? maxX : newData.maxX, minY: newData.minY.isNaN ? minY : newData.minY, maxY: newData.maxY.isNaN ? maxY : newData.maxY, ); } final lineTouchData = newData.lineTouchData; if (lineTouchData.enabled && lineTouchData.handleBuiltInTouches) { _providedTouchCallback = lineTouchData.touchCallback; newData = newData.copyWith( lineTouchData: newData.lineTouchData.copyWith(touchCallback: _handleBuiltInTouch), ); } return newData; } void _handleBuiltInTouch( FlTouchEvent event, LineTouchResponse? touchResponse, ) { if (!mounted) { return; } _providedTouchCallback?.call(event, touchResponse); if (!event.isInterestedForInteractions || touchResponse?.lineBarSpots == null || touchResponse!.lineBarSpots!.isEmpty) { setState(() { _showingTouchedTooltips.clear(); _showingTouchedIndicators.clear(); }); return; } setState(() { final sortedLineSpots = List.of(touchResponse.lineBarSpots!) ..sort((spot1, spot2) => spot2.y.compareTo(spot1.y)); _showingTouchedIndicators.clear(); for (var i = 0; i < touchResponse.lineBarSpots!.length; i++) { final touchedBarSpot = touchResponse.lineBarSpots![i]; final barPos = touchedBarSpot.barIndex; _showingTouchedIndicators[barPos] = [touchedBarSpot.spotIndex]; } _showingTouchedTooltips ..clear() ..add(ShowingTooltipIndicators(sortedLineSpots)); }); } @override void forEachTween(TweenVisitor visitor) { _lineChartDataTween = visitor( _lineChartDataTween, _getData(), (dynamic value) => LineChartDataTween(begin: value as LineChartData, end: widget.data), ) as LineChartDataTween?; } } ================================================ FILE: lib/src/chart/line_chart/line_chart_data.dart ================================================ // coverage:ignore-file import 'dart:ui'; import 'package:equatable/equatable.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/extensions/color_extension.dart'; import 'package:fl_chart/src/extensions/gradient_extension.dart'; import 'package:fl_chart/src/utils/lerp.dart'; import 'package:flutter/material.dart' hide Image; /// [LineChart] needs this class to render itself. /// /// It holds data needed to draw a line chart, /// including bar lines, spots, colors, touches, ... class LineChartData extends AxisChartData with EquatableMixin { /// [LineChart] draws some lines in various shapes and overlaps them. /// lines are defined in [lineBarsData], sometimes you need to fill space between two bars /// with a color or gradient, you can use [betweenBarsData] to achieve that. /// /// It draws some titles on left, top, right, bottom sides per each axis number, /// you can modify [titlesData] to have your custom titles, /// also you can define the axis title (one text per axis) for each side /// using [axisTitleData], you can restrict the y axis using [minY] and [maxY] value, /// and restrict x axis using [minX] and [maxX]. /// /// It draws a color as a background behind everything you can set it using [backgroundColor], /// then a grid over it, you can customize it using [gridData], /// and it draws 4 borders around your chart, you can customize it using [borderData]. /// /// You can annotate some regions with a highlight color using [rangeAnnotations]. /// /// You can modify [lineTouchData] to customize touch behaviors and responses. /// /// you can show some tooltipIndicators (a popup with an information) /// on top of each [LineChartBarData.spots] using [showingTooltipIndicators], /// just put line indicator number and spots indices you want to show it on top of them. /// /// [LineChart] draws some horizontal or vertical lines on above or below of everything, /// they are useful in some scenarios, for example you can show average line, you can fill /// [extraLinesData] property to have your extra lines. /// /// [clipData] forces the [LineChart] to draw lines inside the chart bounding box. LineChartData({ this.lineBarsData = const [], this.betweenBarsData = const [], super.titlesData = const FlTitlesData(), super.extraLinesData = const ExtraLinesData(), this.lineTouchData = const LineTouchData(), this.showingTooltipIndicators = const [], super.gridData = const FlGridData(), super.borderData, super.rangeAnnotations = const RangeAnnotations(), double? minX, double? maxX, super.baselineX, double? minY, double? maxY, super.baselineY, super.clipData = const FlClipData.none(), super.backgroundColor, super.rotationQuarterTurns, }) : super( minX: minX ?? double.nan, maxX: maxX ?? double.nan, minY: minY ?? double.nan, maxY: maxY ?? double.nan, ); /// [LineChart] draws some lines in various shapes and overlaps them. final List lineBarsData; /// Fills area between two [LineChartBarData] with a color or gradient. final List betweenBarsData; /// Handles touch behaviors and responses. final LineTouchData lineTouchData; /// You can show some tooltipIndicators (a popup with an information) /// on top of each [LineChartBarData.spots] using [showingTooltipIndicators], /// just put line indicator number and spots indices you want to show it on top of them. /// /// An important point is that you have to disable the default touch behaviour /// to show the tooltip manually, see [LineTouchData.handleBuiltInTouches]. final List showingTooltipIndicators; /// Lerps a [BaseChartData] based on [t] value, check [Tween.lerp]. @override LineChartData lerp(BaseChartData a, BaseChartData b, double t) { if (a is LineChartData && b is LineChartData) { return LineChartData( minX: lerpDouble(a.minX, b.minX, t), maxX: lerpDouble(a.maxX, b.maxX, t), baselineX: lerpDouble(a.baselineX, b.baselineX, t), minY: lerpDouble(a.minY, b.minY, t), maxY: lerpDouble(a.maxY, b.maxY, t), baselineY: lerpDouble(a.baselineY, b.baselineY, t), backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t), borderData: FlBorderData.lerp(a.borderData, b.borderData, t), clipData: b.clipData, extraLinesData: ExtraLinesData.lerp(a.extraLinesData, b.extraLinesData, t), gridData: FlGridData.lerp(a.gridData, b.gridData, t), titlesData: FlTitlesData.lerp(a.titlesData, b.titlesData, t), rangeAnnotations: RangeAnnotations.lerp(a.rangeAnnotations, b.rangeAnnotations, t), lineBarsData: lerpLineChartBarDataList(a.lineBarsData, b.lineBarsData, t)!, betweenBarsData: lerpBetweenBarsDataList(a.betweenBarsData, b.betweenBarsData, t)!, lineTouchData: b.lineTouchData, showingTooltipIndicators: b.showingTooltipIndicators, rotationQuarterTurns: b.rotationQuarterTurns, ); } else { throw Exception('Illegal State'); } } /// Copies current [LineChartData] to a new [LineChartData], /// and replaces provided values. LineChartData copyWith({ List? lineBarsData, List? betweenBarsData, FlTitlesData? titlesData, RangeAnnotations? rangeAnnotations, ExtraLinesData? extraLinesData, LineTouchData? lineTouchData, List? showingTooltipIndicators, FlGridData? gridData, FlBorderData? borderData, double? minX, double? maxX, double? baselineX, double? minY, double? maxY, double? baselineY, FlClipData? clipData, Color? backgroundColor, int? rotationQuarterTurns, }) => LineChartData( lineBarsData: lineBarsData ?? this.lineBarsData, betweenBarsData: betweenBarsData ?? this.betweenBarsData, titlesData: titlesData ?? this.titlesData, rangeAnnotations: rangeAnnotations ?? this.rangeAnnotations, extraLinesData: extraLinesData ?? this.extraLinesData, lineTouchData: lineTouchData ?? this.lineTouchData, showingTooltipIndicators: showingTooltipIndicators ?? this.showingTooltipIndicators, gridData: gridData ?? this.gridData, borderData: borderData ?? this.borderData, minX: minX ?? this.minX, maxX: maxX ?? this.maxX, baselineX: baselineX ?? this.baselineX, minY: minY ?? this.minY, maxY: maxY ?? this.maxY, baselineY: baselineY ?? this.baselineY, clipData: clipData ?? this.clipData, backgroundColor: backgroundColor ?? this.backgroundColor, rotationQuarterTurns: rotationQuarterTurns ?? this.rotationQuarterTurns, ); /// Used for equality check, see [EquatableMixin]. @override List get props => [ lineBarsData, betweenBarsData, titlesData, extraLinesData, lineTouchData, showingTooltipIndicators, gridData, borderData, rangeAnnotations, minX, maxX, baselineX, minY, maxY, baselineY, clipData, backgroundColor, rotationQuarterTurns, ]; } enum LineChartGradientArea { /// The gradient area will be around the line only, meaning /// the gradient will exactly wrap around the curve. rectAroundTheLine, /// The entire chart area will be used as the gradient area for the curve. wholeChart; } /// Holds data for drawing each individual line in the [LineChart] class LineChartBarData with EquatableMixin { /// [BarChart] draws some lines and overlaps them in the chart's view, /// You can have multiple lines by splitting them, /// put a [FlSpot.nullSpot] between each section. /// each line passes through [spots], with hard edges by default, /// [isCurved] makes it curve for drawing, and [curveSmoothness] determines the curve smoothness. /// /// [show] determines the drawing, if set to false, it draws nothing. /// /// [mainColors] determines the color of drawing line, if one color provided it applies a solid color, /// otherwise it gradients between provided colors for drawing the line. /// Gradient happens using provided [colorStops], [gradientFrom], [gradientTo]. /// if you want it draw normally, don't touch them, /// check [LinearGradient] for understanding [colorStops] /// /// [barWidth] determines the thickness of drawing line, /// /// if [isCurved] is true, in some situations if the spots changes are in high values, /// an overshooting will happen, we don't have any idea to solve this at the moment, /// but you can set [preventCurveOverShooting] true, and update the threshold /// using [preventCurveOvershootingThreshold] to achieve an acceptable curve, /// check this [issue](https://github.com/imaNNeo/fl_chart/issues/25) /// to overshooting understand the problem. /// /// [isStrokeCapRound] determines the shape of line's cap. /// /// [isStrokeJoinRound] determines the shape of the line joins. /// /// [belowBarData], and [aboveBarData] used to fill the space below or above the drawn line, /// you can fill with a solid color or a linear gradient. /// /// [LineChart] draws points that the line is going through [spots], /// you can customize it's appearance using [dotData]. /// /// there are some indicators with a line and bold point on each spot, /// you can show them by filling [showingIndicators] with indices /// you want to show indicator on them. /// /// [LineChart] draws the lines with dashed effect if you fill [dashArray]. /// /// If you want to have a Step Line Chart style, just set [isStepLineChart] true, /// also you can tweak the [LineChartBarData.lineChartStepData]. LineChartBarData({ this.spots = const [], this.show = true, Color? color, this.gradient, this.gradientArea = LineChartGradientArea.rectAroundTheLine, this.barWidth = 2.0, this.isCurved = false, this.curveSmoothness = 0.35, this.preventCurveOverShooting = false, this.preventCurveOvershootingThreshold = 10.0, this.isStrokeCapRound = false, this.isStrokeJoinRound = false, BarAreaData? belowBarData, BarAreaData? aboveBarData, this.dotData = const FlDotData(), this.errorIndicatorData = const FlErrorIndicatorData(), this.showingIndicators = const [], this.dashArray, this.shadow = const Shadow(color: Colors.transparent), this.isStepLineChart = false, this.lineChartStepData = const LineChartStepData(), }) : color = color ?? ((color == null && gradient == null) ? Colors.cyan : null), belowBarData = belowBarData ?? BarAreaData(), aboveBarData = aboveBarData ?? BarAreaData() { FlSpot? mostLeft; FlSpot? mostTop; FlSpot? mostRight; FlSpot? mostBottom; FlSpot? firstValidSpot; try { firstValidSpot = spots.firstWhere((element) => element != FlSpot.nullSpot); } catch (_) { // There is no valid spot } if (firstValidSpot != null) { for (final spot in spots) { if (spot.isNull()) { continue; } if (mostLeft == null || spot.x < mostLeft.x) { mostLeft = spot; } if (mostRight == null || spot.x > mostRight.x) { mostRight = spot; } if (mostTop == null || spot.y > mostTop.y) { mostTop = spot; } if (mostBottom == null || spot.y < mostBottom.y) { mostBottom = spot; } } mostLeftSpot = mostLeft!; mostTopSpot = mostTop!; mostRightSpot = mostRight!; mostBottomSpot = mostBottom!; } } /// This line goes through this spots. /// /// You can have multiple lines by splitting them, /// put a [FlSpot.nullSpot] between each section. final List spots; /// We keep the most left spot to prevent redundant calculations late final FlSpot mostLeftSpot; /// We keep the most top spot to prevent redundant calculations late final FlSpot mostTopSpot; /// We keep the most right spot to prevent redundant calculations late final FlSpot mostRightSpot; /// We keep the most bottom spot to prevent redundant calculations late final FlSpot mostBottomSpot; /// Determines to show or hide the line. final bool show; /// If provided, this [LineChartBarData] draws with this [color] /// Otherwise we use [gradient] to draw the background. /// It throws an exception if you provide both [color] and [gradient] final Color? color; /// If provided, this [LineChartBarData] draws with this [gradient]. /// Otherwise we use [color] to draw the background. /// It throws an exception if you provide both [color] and [gradient] final Gradient? gradient; /// Only effective if [gradient] is provided. /// /// It will be used to determine the area of the gradient. final LineChartGradientArea gradientArea; /// Determines thickness of drawing line. final double barWidth; /// If it's true, [LineChart] draws the line with curved edges, /// otherwise it draws line with hard edges. final bool isCurved; /// If [isCurved] is true, it determines smoothness of the curved edges. final double curveSmoothness; /// Prevent overshooting when draw curve line with high value changes. /// check this [issue](https://github.com/imaNNeo/fl_chart/issues/25) final bool preventCurveOverShooting; /// Applies threshold for [preventCurveOverShooting] algorithm. final double preventCurveOvershootingThreshold; /// Determines the style of line's cap. final bool isStrokeCapRound; /// Determines the style of line joins. final bool isStrokeJoinRound; /// Fills the space blow the line, using a color or gradient. final BarAreaData belowBarData; /// Fills the space above the line, using a color or gradient. final BarAreaData aboveBarData; /// Responsible to showing [spots] on the line as a circular point. final FlDotData dotData; /// Holds data for showing error indicators on the spots in this line. final FlErrorIndicatorData errorIndicatorData; /// Show indicators based on provided indexes final List showingIndicators; /// Determines the dash length and space respectively, fill it if you want to have dashed line. final List? dashArray; /// Drops a shadow behind the bar line. final Shadow shadow; /// If sets true, it draws the chart in Step Line Chart style, using [LineChartBarData.lineChartStepData]. final bool isStepLineChart; /// Holds data for representing a Step Line Chart, and works only if [isStepChart] is true. final LineChartStepData lineChartStepData; /// Lerps a [LineChartBarData] based on [t] value, check [Tween.lerp]. static LineChartBarData lerp( LineChartBarData a, LineChartBarData b, double t, ) => LineChartBarData( show: b.show, barWidth: lerpDouble(a.barWidth, b.barWidth, t)!, belowBarData: BarAreaData.lerp(a.belowBarData, b.belowBarData, t), aboveBarData: BarAreaData.lerp(a.aboveBarData, b.aboveBarData, t), curveSmoothness: b.curveSmoothness, isCurved: b.isCurved, isStrokeCapRound: b.isStrokeCapRound, isStrokeJoinRound: b.isStrokeJoinRound, preventCurveOverShooting: b.preventCurveOverShooting, preventCurveOvershootingThreshold: lerpDouble( a.preventCurveOvershootingThreshold, b.preventCurveOvershootingThreshold, t, )!, dotData: FlDotData.lerp(a.dotData, b.dotData, t), errorIndicatorData: FlErrorIndicatorData.lerp( a.errorIndicatorData, b.errorIndicatorData, t, ), dashArray: lerpIntList(a.dashArray, b.dashArray, t), color: Color.lerp(a.color, b.color, t), gradient: Gradient.lerp(a.gradient, b.gradient, t), gradientArea: b.gradientArea, spots: lerpFlSpotList(a.spots, b.spots, t)!, showingIndicators: b.showingIndicators, shadow: Shadow.lerp(a.shadow, b.shadow, t)!, isStepLineChart: b.isStepLineChart, lineChartStepData: LineChartStepData.lerp(a.lineChartStepData, b.lineChartStepData, t), ); /// Copies current [LineChartBarData] to a new [LineChartBarData], /// and replaces provided values. LineChartBarData copyWith({ List? spots, bool? show, Color? color, Gradient? gradient, LineChartGradientArea? gradientArea, double? barWidth, bool? isCurved, double? curveSmoothness, bool? preventCurveOverShooting, double? preventCurveOvershootingThreshold, bool? isStrokeCapRound, bool? isStrokeJoinRound, BarAreaData? belowBarData, BarAreaData? aboveBarData, FlDotData? dotData, FlErrorIndicatorData? errorIndicatorData, List? dashArray, List? showingIndicators, Shadow? shadow, bool? isStepLineChart, LineChartStepData? lineChartStepData, }) => LineChartBarData( spots: spots ?? this.spots, show: show ?? this.show, color: color ?? this.color, gradient: gradient ?? this.gradient, gradientArea: gradientArea ?? this.gradientArea, barWidth: barWidth ?? this.barWidth, isCurved: isCurved ?? this.isCurved, curveSmoothness: curveSmoothness ?? this.curveSmoothness, preventCurveOverShooting: preventCurveOverShooting ?? this.preventCurveOverShooting, preventCurveOvershootingThreshold: preventCurveOvershootingThreshold ?? this.preventCurveOvershootingThreshold, isStrokeCapRound: isStrokeCapRound ?? this.isStrokeCapRound, isStrokeJoinRound: isStrokeJoinRound ?? this.isStrokeJoinRound, belowBarData: belowBarData ?? this.belowBarData, aboveBarData: aboveBarData ?? this.aboveBarData, dashArray: dashArray ?? this.dashArray, dotData: dotData ?? this.dotData, errorIndicatorData: errorIndicatorData ?? this.errorIndicatorData, showingIndicators: showingIndicators ?? this.showingIndicators, shadow: shadow ?? this.shadow, isStepLineChart: isStepLineChart ?? this.isStepLineChart, lineChartStepData: lineChartStepData ?? this.lineChartStepData, ); /// Used for equality check, see [EquatableMixin]. @override List get props => [ spots, show, color, gradient, gradientArea, barWidth, isCurved, curveSmoothness, preventCurveOverShooting, preventCurveOvershootingThreshold, isStrokeCapRound, isStrokeJoinRound, belowBarData, aboveBarData, dotData, errorIndicatorData, showingIndicators, dashArray, shadow, isStepLineChart, lineChartStepData, ]; } /// Holds data for representing a Step Line Chart, and works only if [LineChartBarData.isStepChart] is true. class LineChartStepData with EquatableMixin { /// Determines the [stepDirection] of each step; const LineChartStepData({this.stepDirection = stepDirectionMiddle}); /// Go to the next spot directly, with the current point's y value. static const stepDirectionForward = 0.0; /// Go to the half with the current spot y, and with the next spot y for the rest. static const stepDirectionMiddle = 0.5; /// Go to the next spot y and direct line to the next spot. static const stepDirectionBackward = 1.0; /// Determines the direction of each step; final double stepDirection; /// Lerps a [LineChartStepData] based on [t] value, check [Tween.lerp]. static LineChartStepData lerp( LineChartStepData a, LineChartStepData b, double t, ) => LineChartStepData( stepDirection: lerpDouble(a.stepDirection, b.stepDirection, t)!, ); /// Used for equality check, see [EquatableMixin]. @override List get props => [stepDirection]; } /// Holds data for filling an area (above or below) of the line with a color or gradient. class BarAreaData with EquatableMixin { /// if [show] is true, [LineChart] fills above and below area of each line /// with a color or gradient. /// /// [color] determines the color of above or below space area, /// if one color provided it applies a solid color, /// otherwise it gradients between provided colors for drawing the line. /// Gradient happens using provided [gradientColorStops], [gradientFrom], [gradientTo]. /// if you want it draw normally, don't touch them, /// check [LinearGradient] for understanding [gradientColorStops] /// /// If [spotsLine] is provided, it draws some lines from each spot /// to the bottom or top of the chart. /// /// If [applyCutOffY] is true, it cuts the drawing by the [cutOffY] line. BarAreaData({ this.show = false, Color? color, this.gradient, this.spotsLine = const BarAreaSpotsLine(), this.cutOffY = 0, this.applyCutOffY = false, }) : color = color ?? ((color == null && gradient == null) ? Colors.blueGrey.withValues(alpha: 0.5) : null); final bool show; /// If provided, this [BarAreaData] draws with this [color] /// Otherwise we use [gradient] to draw the background. /// It throws an exception if you provide both [color] and [gradient] final Color? color; /// If provided, this [BarAreaData] draws with this [gradient]. /// Otherwise we use [color] to draw the background. /// It throws an exception if you provide both [color] and [gradient] final Gradient? gradient; /// holds data for drawing a line from each spot the the bottom, or top of the chart final BarAreaSpotsLine spotsLine; /// cut the drawing below or above area to this y value final double cutOffY; /// determines should or shouldn't apply cutOffY final bool applyCutOffY; /// Lerps a [BarAreaData] based on [t] value, check [Tween.lerp]. static BarAreaData lerp(BarAreaData a, BarAreaData b, double t) => BarAreaData( show: b.show, spotsLine: BarAreaSpotsLine.lerp(a.spotsLine, b.spotsLine, t), color: Color.lerp(a.color, b.color, t), gradient: Gradient.lerp(a.gradient, b.gradient, t), cutOffY: lerpDouble(a.cutOffY, b.cutOffY, t)!, applyCutOffY: b.applyCutOffY, ); /// Used for equality check, see [EquatableMixin]. @override List get props => [ show, color, gradient, spotsLine, cutOffY, applyCutOffY, ]; } /// Holds data about filling below or above space of the bar line, class BetweenBarsData with EquatableMixin { BetweenBarsData({ required this.fromIndex, required this.toIndex, Color? color, this.gradient, }) : color = color ?? ((color == null && gradient == null) ? Colors.blueGrey.withValues(alpha: 0.5) : null); /// The index of the lineBarsData from where the area has to be rendered final int fromIndex; /// The index of the lineBarsData until where the area has to be rendered final int toIndex; /// If provided, this [BetweenBarsData] draws with this [color] /// Otherwise we use [gradient] to draw the background. /// It throws an exception if you provide both [color] and [gradient] final Color? color; /// If provided, this [BetweenBarsData] draws with this [gradient]. /// Otherwise we use [color] to draw the background. /// It throws an exception if you provide both [color] and [gradient] final Gradient? gradient; /// Lerps a [BetweenBarsData] based on [t] value, check [Tween.lerp]. static BetweenBarsData lerp(BetweenBarsData a, BetweenBarsData b, double t) { return BetweenBarsData( fromIndex: b.fromIndex, toIndex: b.toIndex, color: Color.lerp(a.color, b.color, t), gradient: Gradient.lerp(a.gradient, b.gradient, t), ); } /// Used for equality check, see [EquatableMixin]. @override List get props => [ fromIndex, toIndex, color, gradient, ]; } /// Holds data for drawing line on the spots under the [BarAreaData]. class BarAreaSpotsLine with EquatableMixin { /// If [show] is true, [LineChart] draws some lines on above or below the spots, /// you can customize the appearance of the lines using [flLineStyle] /// and you can decide to show or hide the lines on each spot using [checkToShowSpotLine]. const BarAreaSpotsLine({ this.show = false, this.flLineStyle = const FlLine(), this.checkToShowSpotLine = showAllSpotsBelowLine, this.applyCutOffY = true, }); /// Determines to show or hide all the lines. final bool show; /// Holds appearance of drawing line on the spots. final FlLine flLineStyle; /// Checks to show or hide lines on the spots. final CheckToShowSpotLine checkToShowSpotLine; /// Determines to inherit the cutOff properties from its parent [BarAreaData] final bool applyCutOffY; /// Lerps a [BarAreaSpotsLine] based on [t] value, check [Tween.lerp]. static BarAreaSpotsLine lerp( BarAreaSpotsLine a, BarAreaSpotsLine b, double t, ) => BarAreaSpotsLine( show: b.show, checkToShowSpotLine: b.checkToShowSpotLine, flLineStyle: FlLine.lerp(a.flLineStyle, b.flLineStyle, t), applyCutOffY: b.applyCutOffY, ); /// Used for equality check, see [EquatableMixin]. @override List get props => [ show, flLineStyle, checkToShowSpotLine, applyCutOffY, ]; } /// It used for determine showing or hiding [BarAreaSpotsLine]s /// /// Gives you the checking spot, and you have to decide to /// show or not show the line on the provided spot. typedef CheckToShowSpotLine = bool Function(FlSpot spot); /// Shows all spot lines. bool showAllSpotsBelowLine(FlSpot spot) => true; /// The callback passed to get the color of a [FlSpot] /// /// The callback receives [FlSpot], which is the target spot, /// [double] is the percentage of spot along the bar line, /// [LineChartBarData] is the chart's bar. /// It should return a [Color] that needs to be used for drawing target. typedef GetDotColorCallback = Color Function(FlSpot, double, LineChartBarData); /// If there is one color in [LineChartBarData.mainColors], it returns that color, /// otherwise it returns the color along the gradient colors based on the [xPercentage]. Color _defaultGetDotColor(FlSpot _, double xPercentage, LineChartBarData bar) { if (bar.gradient != null && bar.gradient is LinearGradient) { return lerpGradient( bar.gradient!.colors, bar.gradient!.getSafeColorStops(), xPercentage / 100, ); } return bar.gradient?.colors.first ?? bar.color ?? Colors.blueGrey; } /// If there is one color in [LineChartBarData.mainColors], it returns that color in a darker mode, /// otherwise it returns the color along the gradient colors based on the [xPercentage] in a darker mode. Color _defaultGetDotStrokeColor( FlSpot spot, double xPercentage, LineChartBarData bar, ) { Color color; if (bar.gradient != null && bar.gradient is LinearGradient) { color = lerpGradient( bar.gradient!.colors, bar.gradient!.getSafeColorStops(), xPercentage / 100, ); } else { color = bar.gradient?.colors.first ?? bar.color ?? Colors.blueGrey; } return color.darken(); } /// The callback passed to get the painter of a [FlSpot] /// /// The callback receives [FlSpot], which is the target spot, /// [LineChartBarData] is the chart's bar. /// [int] is the index position of the spot. /// It should return a [FlDotPainter] that needs to be used for drawing target. typedef GetDotPainterCallback = FlDotPainter Function( FlSpot, double, LineChartBarData, int, ); FlDotPainter _defaultGetDotPainter( FlSpot spot, double xPercentage, LineChartBarData bar, int index, { double? size, }) => FlDotCirclePainter( radius: size, color: _defaultGetDotColor(spot, xPercentage, bar), strokeColor: _defaultGetDotStrokeColor(spot, xPercentage, bar), ); /// This class holds data about drawing spot dots on the drawing bar line. class FlDotData with EquatableMixin { /// set [show] false to prevent dots from drawing, /// if you want to show or hide dots in some spots, /// override [checkToShowDot] to handle it in your way. const FlDotData({ this.show = true, this.checkToShowDot = showAllDots, this.getDotPainter = _defaultGetDotPainter, }); /// Determines show or hide all dots. final bool show; /// Checks to show or hide an individual dot. final CheckToShowDot checkToShowDot; /// Callback which is called to set the painter of the given [FlSpot]. /// The [FlSpot] is provided as parameter to this callback final GetDotPainterCallback getDotPainter; /// Lerps a [FlDotData] based on [t] value, check [Tween.lerp]. static FlDotData lerp(FlDotData a, FlDotData b, double t) => FlDotData( show: b.show, checkToShowDot: b.checkToShowDot, getDotPainter: b.getDotPainter, ); /// Used for equality check, see [EquatableMixin]. @override List get props => [ show, checkToShowDot, getDotPainter, ]; } /// It determines showing or hiding [FlDotData] on the spots. /// /// It gives you the checking [FlSpot] and you should decide to /// show or hide the dot on this spot by returning true or false. typedef CheckToShowDot = bool Function(FlSpot spot, LineChartBarData barData); /// Shows all dots on spots. bool showAllDots(FlSpot spot, LineChartBarData barData) => true; enum LabelDirection { horizontal, vertical, horizontalMirrored, verticalMirrored } /// Shows a text label abstract class FlLineLabel with EquatableMixin { /// Draws a title on the line, align it with [alignment] over the line, /// applies [padding] for spaces, and applies [style] for changing color, /// size, ... of the text. /// [show] determines showing label or not. /// [direction] determines if the direction of the text should be horizontal or vertical. const FlLineLabel({ required this.show, required this.padding, required this.style, required this.alignment, required this.direction, }); /// Determines showing label or not. final bool show; /// Inner spaces around the drawing text. final EdgeInsetsGeometry padding; /// Sets style of the drawing text. final TextStyle? style; /// Aligns the text on the line. final Alignment alignment; /// Determines the direction of the text. final LabelDirection direction; /// Used for equality check, see [EquatableMixin]. @override List get props => [ show, padding, style, alignment, direction, ]; } /// Holds data to handle touch events, and touch responses in the [LineChart]. /// /// There is a touch flow, explained [here](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/handle_touches.md) /// in a simple way, each chart's renderer captures the touch events, and passes the pointerEvent /// to the painter, and gets touched spot, and wraps it into a concrete [LineTouchResponse]. class LineTouchData extends FlTouchData with EquatableMixin { /// You can disable or enable the touch system using [enabled] flag, /// /// [touchCallback] notifies you about the happened touch/pointer events. /// It gives you a [FlTouchEvent] which is the happened event such as [FlPointerHoverEvent], [FlTapUpEvent], ... /// It also gives you a [LineTouchResponse] which contains information /// about the elements that has touched. /// /// Using [mouseCursorResolver] you can change the mouse cursor /// based on the provided [FlTouchEvent] and [LineTouchResponse] /// /// if [handleBuiltInTouches] is true, [LineChart] shows a tooltip popup on top of the spots if /// touch occurs (or you can show it manually using, [LineChartData.showingTooltipIndicators]) /// and also it shows an indicator (contains a thicker line and larger dot on the targeted spot), /// You can define how this indicator looks like through [getTouchedSpotIndicator] callback, /// You can customize this tooltip using [touchTooltipData], indicator lines starts from position /// controlled by [getTouchLineStart] and ends at position controlled by [getTouchLineEnd]. /// If you need to have a distance threshold for handling touches, use [touchSpotThreshold]. const LineTouchData({ bool enabled = true, BaseTouchCallback? touchCallback, MouseCursorResolver? mouseCursorResolver, Duration? longPressDuration, this.touchTooltipData = const LineTouchTooltipData(), this.getTouchedSpotIndicator = defaultTouchedIndicators, this.touchSpotThreshold = 10, this.distanceCalculator = _xDistance, this.handleBuiltInTouches = true, this.getTouchLineStart = defaultGetTouchLineStart, this.getTouchLineEnd = defaultGetTouchLineEnd, }) : super( enabled, touchCallback, mouseCursorResolver, longPressDuration, ); /// Configs of how touch tooltip popup. final LineTouchTooltipData touchTooltipData; /// Configs of how touch indicator looks like. final GetTouchedSpotIndicator getTouchedSpotIndicator; /// Distance threshold to handle the touch event. final double touchSpotThreshold; /// Distance function used when finding closest points to touch point final CalculateTouchDistance distanceCalculator; /// Determines to handle default built-in touch responses, /// [LineTouchResponse] shows a tooltip popup above the touched spot. final bool handleBuiltInTouches; /// The starting point on y axis of the touch line. By default, line starts on the bottom of /// the chart. final GetTouchLineY getTouchLineStart; /// The end point on y axis of the touch line. By default, line ends at the touched point. /// If line end is overlap with the dot, it will be automatically adjusted to the edge of the dot. final GetTouchLineY getTouchLineEnd; /// Copies current [LineTouchData] to a new [LineTouchData], /// and replaces provided values. LineTouchData copyWith({ bool? enabled, BaseTouchCallback? touchCallback, MouseCursorResolver? mouseCursorResolver, Duration? longPressDuration, LineTouchTooltipData? touchTooltipData, GetTouchedSpotIndicator? getTouchedSpotIndicator, double? touchSpotThreshold, CalculateTouchDistance? distanceCalculator, GetTouchLineY? getTouchLineStart, GetTouchLineY? getTouchLineEnd, bool? handleBuiltInTouches, }) => LineTouchData( enabled: enabled ?? this.enabled, touchCallback: touchCallback ?? this.touchCallback, mouseCursorResolver: mouseCursorResolver ?? this.mouseCursorResolver, longPressDuration: longPressDuration ?? this.longPressDuration, touchTooltipData: touchTooltipData ?? this.touchTooltipData, getTouchedSpotIndicator: getTouchedSpotIndicator ?? this.getTouchedSpotIndicator, touchSpotThreshold: touchSpotThreshold ?? this.touchSpotThreshold, distanceCalculator: distanceCalculator ?? this.distanceCalculator, getTouchLineStart: getTouchLineStart ?? this.getTouchLineStart, getTouchLineEnd: getTouchLineEnd ?? this.getTouchLineEnd, handleBuiltInTouches: handleBuiltInTouches ?? this.handleBuiltInTouches, ); /// Used for equality check, see [EquatableMixin]. @override List get props => [ enabled, touchCallback, mouseCursorResolver, longPressDuration, touchTooltipData, getTouchedSpotIndicator, touchSpotThreshold, distanceCalculator, handleBuiltInTouches, getTouchLineStart, getTouchLineEnd, ]; } /// Used for showing touch indicators (a thicker line and larger dot on the targeted spot). /// /// It gives you the [spotIndexes] that touch happened, or manually targeted, /// in the given [barData], you should return a list of [TouchedSpotIndicatorData], /// length of this list should be equal to the [spotIndexes.length], /// each [TouchedSpotIndicatorData] determines the look of showing indicator. typedef GetTouchedSpotIndicator = List Function( LineChartBarData barData, List spotIndexes, ); /// Used for determine the touch indicator line's starting/end point. typedef GetTouchLineY = double Function( LineChartBarData barData, int spotIndex, ); /// Used to calculate the distance between coordinates of a touch event and a spot typedef CalculateTouchDistance = double Function( Offset touchPoint, Offset spotPixelCoordinates, ); /// Default distanceCalculator only considers distance on x axis double _xDistance(Offset touchPoint, Offset spotPixelCoordinates) => (touchPoint.dx - spotPixelCoordinates.dx).abs(); /// Default presentation of touched indicators. List defaultTouchedIndicators( LineChartBarData barData, List indicators, ) => indicators.map((index) { /// Indicator Line var lineColor = barData.gradient?.colors.first ?? barData.color; if (barData.dotData.show) { lineColor = _defaultGetDotColor(barData.spots[index], 0, barData); } const lineStrokeWidth = 4.0; final flLine = FlLine(color: lineColor, strokeWidth: lineStrokeWidth); var dotSize = 10.0; if (barData.dotData.show) { dotSize = 4.0 * 1.8; } final dotData = FlDotData( getDotPainter: (spot, percent, bar, index) => _defaultGetDotPainter(spot, percent, bar, index, size: dotSize), ); return TouchedSpotIndicatorData(flLine, dotData); }).toList(); /// By default line starts from the bottom of the chart. double defaultGetTouchLineStart(LineChartBarData barData, int spotIndex) { return -double.infinity; } /// By default line ends at the touched point. double defaultGetTouchLineEnd(LineChartBarData barData, int spotIndex) => barData.spots[spotIndex].y; /// Holds representation data for showing tooltip popup on top of spots. class LineTouchTooltipData with EquatableMixin { /// if [LineTouchData.handleBuiltInTouches] is true, /// [LineChart] shows a tooltip popup on top of spots automatically when touch happens, /// otherwise you can show it manually using [LineChartData.showingTooltipIndicators]. /// Tooltip shows on top of rods, with [getTooltipColor] as a background color. /// You can set the corner radius using [tooltipBorderRadius], /// If you want to have a padding inside the tooltip, fill [tooltipPadding], /// or If you want to have a bottom margin, set [tooltipMargin]. /// Content of the tooltip will provide using [getTooltipItems] callback, you can override it /// and pass your custom data to show in the tooltip. /// You can restrict the tooltip's width using [maxContentWidth]. /// Sometimes, [LineChart] shows the tooltip outside of the chart, /// you can set [fitInsideHorizontally] true to force it to shift inside the chart horizontally, /// also you can set [fitInsideVertically] true to force it to shift inside the chart vertically. const LineTouchTooltipData({ BorderRadius? tooltipBorderRadius, this.tooltipPadding = const EdgeInsets.symmetric(horizontal: 16, vertical: 8), this.tooltipMargin = 16, this.tooltipHorizontalAlignment = FLHorizontalAlignment.center, this.tooltipHorizontalOffset = 0, this.maxContentWidth = 120, this.getTooltipItems = defaultLineTooltipItem, this.getTooltipColor = defaultLineTooltipColor, this.fitInsideHorizontally = false, this.fitInsideVertically = false, this.showOnTopOfTheChartBoxArea = false, this.rotateAngle = 0.0, this.tooltipBorder = BorderSide.none, }) : _tooltipBorderRadius = tooltipBorderRadius; /// Sets a rounded radius for the tooltip. final BorderRadius? _tooltipBorderRadius; /// Sets a rounded radius for the tooltip. BorderRadius get tooltipBorderRadius => _tooltipBorderRadius ?? BorderRadius.circular(4); /// Applies a padding for showing contents inside the tooltip. final EdgeInsets tooltipPadding; /// Applies a bottom margin for showing tooltip on top of rods. final double tooltipMargin; /// Controls showing tooltip on left side, right side or center aligned with spot, default is center final FLHorizontalAlignment tooltipHorizontalAlignment; /// Applies horizontal offset for showing tooltip, default is zero. final double tooltipHorizontalOffset; /// Restricts the tooltip's width. final double maxContentWidth; /// Retrieves data for showing content inside the tooltip. final GetLineTooltipItems getTooltipItems; /// Forces the tooltip to shift horizontally inside the chart, if overflow happens. final bool fitInsideHorizontally; /// Forces the tooltip to shift vertically inside the chart, if overflow happens. final bool fitInsideVertically; /// Forces the tooltip container to top of the line, default 'false' final bool showOnTopOfTheChartBoxArea; /// Controls the rotation of the tooltip. final double rotateAngle; /// The tooltip border color. final BorderSide tooltipBorder; // /// Retrieves data for setting background color of the tooltip. final GetLineTooltipColor getTooltipColor; /// Used for equality check, see [EquatableMixin]. @override List get props => [ _tooltipBorderRadius, tooltipPadding, tooltipMargin, tooltipHorizontalAlignment, tooltipHorizontalOffset, maxContentWidth, getTooltipItems, fitInsideHorizontally, fitInsideVertically, showOnTopOfTheChartBoxArea, rotateAngle, tooltipBorder, getTooltipColor, ]; } /// Provides a [LineTooltipItem] for showing content inside the [LineTouchTooltipData]. /// /// You can override [LineTouchTooltipData.getTooltipItems], it gives you /// [touchedSpots] list that touch happened on, /// then you should and pass your custom [LineTooltipItem] list /// (length should be equal to the [touchedSpots.length]), /// to show inside the tooltip popup. typedef GetLineTooltipItems = List Function( List touchedSpots, ); /// Default implementation for [LineTouchTooltipData.getTooltipItems]. List defaultLineTooltipItem(List touchedSpots) => touchedSpots.map((touchedSpot) { final textStyle = TextStyle( color: touchedSpot.bar.gradient?.colors.first ?? touchedSpot.bar.color ?? Colors.blueGrey, fontWeight: FontWeight.bold, fontSize: 14, ); return LineTooltipItem(touchedSpot.y.toString(), textStyle); }).toList(); //// Provides a [Color] to show different background color for each touched spot /// /// You can override [LineTouchTooltipData.getTooltipColor], it gives you /// [touchedSpot] object that touch happened on, then you should and pass your custom [Color] list /// (length should be equal to the [touchedSpots.length]), to set background color /// of tooltip popup. typedef GetLineTooltipColor = Color Function( LineBarSpot touchedSpot, ); /// Default implementation for [LineTouchTooltipData.getTooltipColor]. Color defaultLineTooltipColor(LineBarSpot touchedSpot) => Colors.blueGrey.darken(15); /// Represent a targeted spot inside a line bar. class LineBarSpot extends FlSpot with EquatableMixin { /// [bar] is the [LineChartBarData] that this spot is inside of, /// [barIndex] is the index of our [bar], in the [LineChartData.lineBarsData] list, /// [spot] is the targeted spot. /// [spotIndex] is the index this [FlSpot], in the [LineChartBarData.spots] list. LineBarSpot( this.bar, this.barIndex, FlSpot spot, ) : spotIndex = bar.spots.indexOf(spot), super(spot.x, spot.y); /// Is the [LineChartBarData] that this spot is inside of. final LineChartBarData bar; /// Is the index of our [bar], in the [LineChartData.lineBarsData] list, final int barIndex; /// Is the index of our [super.spot], in the [LineChartBarData.spots] list. final int spotIndex; /// Used for equality check, see [EquatableMixin]. @override List get props => [ bar, barIndex, spotIndex, x, y, ]; } /// A [LineBarSpot] that holds information about the event that selected it class TouchLineBarSpot extends LineBarSpot { TouchLineBarSpot( super.bar, super.barIndex, super.spot, this.distance, ); /// Distance in pixels from where the user taped final double distance; } /// Holds data of showing each row item in the tooltip popup. class LineTooltipItem with EquatableMixin { /// Shows a [text] with [textStyle], [textDirection], /// and optional [children] as a row in the tooltip popup. const LineTooltipItem( this.text, this.textStyle, { this.textAlign = TextAlign.center, this.textDirection = TextDirection.ltr, this.children, }); /// Showing text. final String text; /// Style of showing text. final TextStyle textStyle; /// Align of showing text. final TextAlign textAlign; /// Direction of showing text. final TextDirection textDirection; /// Add further style and format to the text of the tooltip final List? children; /// Used for equality check, see [EquatableMixin]. @override List get props => [ text, textStyle, textAlign, textDirection, children, ]; } /// details of showing indicator when touch happened on [LineChart] /// [indicatorBelowLine] we draw a vertical line below of the touched spot /// [touchedSpotDotData] we draw a larger dot on the touched spot to bold it class TouchedSpotIndicatorData with EquatableMixin { /// if [LineTouchData.handleBuiltInTouches] is true, /// [LineChart] shows a thicker line and larger spot as indicator automatically when touch happens, /// otherwise you can show it manually using [LineChartBarData.showingIndicators]. /// [indicatorBelowLine] determines line's style, and /// [touchedSpotDotData] determines dot's style. const TouchedSpotIndicatorData( this.indicatorBelowLine, this.touchedSpotDotData, ); /// Determines line's style. final FlLine indicatorBelowLine; /// Determines dot's style. final FlDotData touchedSpotDotData; /// Used for equality check, see [EquatableMixin]. @override List get props => [ indicatorBelowLine, touchedSpotDotData, ]; } /// Holds data for showing tooltips over a line class ShowingTooltipIndicators with EquatableMixin { /// [LineChart] shows some tooltips over each [LineChartBarData], /// and [showingSpots] determines in which spots this tooltip should be shown. const ShowingTooltipIndicators(this.showingSpots); /// Determines the spots that each tooltip should be shown. final List showingSpots; /// Used for equality check, see [EquatableMixin]. @override List get props => [showingSpots]; } /// Holds information about touch response in the [LineChart]. /// /// You can override [LineTouchData.touchCallback] to handle touch events, /// it gives you a [LineTouchResponse] and you can do whatever you want. class LineTouchResponse extends AxisBaseTouchResponse { /// If touch happens, [LineChart] processes it internally and /// passes out a list of [lineBarSpots] it gives you information about the touched spot. /// They are sorted based on their distance to the touch event LineTouchResponse({ required super.touchLocation, required super.touchChartCoordinate, this.lineBarSpots, }); /// touch happened on these spots /// (if a single line provided on the chart, [lineBarSpots]'s length will be 1 always) final List? lineBarSpots; /// Copies current [LineTouchResponse] to a new [LineTouchResponse], /// and replaces provided values. LineTouchResponse copyWith({ Offset? touchLocation, Offset? touchChartCoordinate, List? lineBarSpots, }) => LineTouchResponse( touchLocation: touchLocation ?? this.touchLocation, touchChartCoordinate: touchChartCoordinate ?? this.touchChartCoordinate, lineBarSpots: lineBarSpots ?? this.lineBarSpots, ); } /// It is the input of the [GetSpotRangeErrorPainter] callback in /// the [LineChartData.errorIndicatorData] /// /// So it contains the information about the spot, and the bar that the spot /// is in. The callback should return a [FlSpotErrorRangePainter] that will draw /// the error bars class LineChartSpotErrorRangeCallbackInput extends FlSpotErrorRangeCallbackInput { LineChartSpotErrorRangeCallbackInput({ required this.spot, required this.bar, required this.spotIndex, }); final FlSpot spot; final LineChartBarData bar; final int spotIndex; @override List get props => [ spot, bar, spotIndex, ]; } /// It lerps a [LineChartData] to another [LineChartData] (handles animation for updating values) class LineChartDataTween extends Tween { LineChartDataTween({required super.begin, required super.end}); /// Lerps a [LineChartData] based on [t] value, check [Tween.lerp]. @override LineChartData lerp(double t) => begin!.lerp(begin!, end!, t); } ================================================ FILE: lib/src/chart/line_chart/line_chart_helper.dart ================================================ import 'package:fl_chart/fl_chart.dart'; /// Contains anything that helps LineChart works class LineChartHelper { /// Calculates the [minX], [maxX], [minY], and [maxY] values of /// the provided [lineBarsData]. (double minX, double maxX, double minY, double maxY) calculateMaxAxisValues( List lineBarsData, ) { if (lineBarsData.isEmpty) { return (0, 0, 0, 0); } final LineChartBarData lineBarData; try { lineBarData = lineBarsData.firstWhere((element) => element.spots.isNotEmpty); } catch (_) { // There is no lineBarData with at least one spot return (0, 0, 0, 0); } final FlSpot firstValidSpot; try { firstValidSpot = lineBarData.spots.firstWhere((element) => element != FlSpot.nullSpot); } catch (_) { // There is no valid spot return (0, 0, 0, 0); } var minX = firstValidSpot.x; var maxX = firstValidSpot.x; var minY = firstValidSpot.y; var maxY = firstValidSpot.y; for (final barData in lineBarsData) { if (barData.spots.isEmpty) { continue; } if (barData.mostRightSpot.x > maxX) { maxX = barData.mostRightSpot.x; } if (barData.mostLeftSpot.x < minX) { minX = barData.mostLeftSpot.x; } if (barData.mostTopSpot.y > maxY) { maxY = barData.mostTopSpot.y; } if (barData.mostBottomSpot.y < minY) { minY = barData.mostBottomSpot.y; } } return (minX, maxX, minY, maxY); } } ================================================ FILE: lib/src/chart/line_chart/line_chart_painter.dart ================================================ import 'dart:math'; import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/chart/base/axis_chart/axis_chart_extensions.dart'; import 'package:fl_chart/src/chart/base/axis_chart/axis_chart_painter.dart'; import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart'; import 'package:fl_chart/src/extensions/paint_extension.dart'; import 'package:fl_chart/src/extensions/path_extension.dart'; import 'package:fl_chart/src/extensions/text_align_extension.dart'; import 'package:fl_chart/src/utils/canvas_wrapper.dart'; import 'package:fl_chart/src/utils/utils.dart'; import 'package:flutter/material.dart'; /// Paints [LineChartData] in the canvas, it can be used in a [CustomPainter] class LineChartPainter extends AxisChartPainter { /// Paints [dataList] into canvas, it is the animating [LineChartData], /// [targetData] is the animation's target and remains the same /// during animation, then we should use it when we need to show /// tooltips or something like that, because [dataList] is changing constantly. /// /// [textScale] used for scaling texts inside the chart, /// parent can use [MediaQuery.textScaleFactor] to respect /// the system's font size. LineChartPainter() : super() { _barPaint = Paint()..style = PaintingStyle.stroke; _barAreaPaint = Paint()..style = PaintingStyle.fill; _barAreaLinesPaint = Paint()..style = PaintingStyle.stroke; _clearBarAreaPaint = Paint() ..style = PaintingStyle.fill ..color = const Color(0x00000000) ..blendMode = BlendMode.dstIn; _touchLinePaint = Paint() ..style = PaintingStyle.stroke ..color = Colors.black; _bgTouchTooltipPaint = Paint() ..style = PaintingStyle.fill ..color = Colors.white; _borderTouchTooltipPaint = Paint() ..style = PaintingStyle.stroke ..color = Colors.transparent ..strokeWidth = 1.0; _clipPaint = Paint(); } late Paint _barPaint; late Paint _barAreaPaint; late Paint _barAreaLinesPaint; late Paint _clearBarAreaPaint; late Paint _touchLinePaint; late Paint _bgTouchTooltipPaint; late Paint _borderTouchTooltipPaint; late Paint _clipPaint; /// Paints [LineChartData] into the provided canvas. @override void paint( BuildContext context, CanvasWrapper canvasWrapper, PaintHolder holder, ) { final data = holder.data; if (holder.chartVirtualRect != null) { canvasWrapper ..saveLayer( Offset.zero & canvasWrapper.size, _clipPaint, ) ..clipRect(Offset.zero & canvasWrapper.size); } super.paint(context, canvasWrapper, holder); if (data.lineBarsData.isEmpty) { return; } if (data.clipData.any && holder.chartVirtualRect == null) { canvasWrapper.saveLayer( Rect.fromLTWH( 0, -40, canvasWrapper.size.width + 40, canvasWrapper.size.height + 40, ), _clipPaint, ); clipToBorder(canvasWrapper, holder); } for (final betweenBarsData in data.betweenBarsData) { drawBetweenBarsArea(canvasWrapper, data, betweenBarsData, holder); } if (!data.extraLinesData.extraLinesOnTop) { super.drawExtraLines(context, canvasWrapper, holder); } final lineIndexDrawingInfo = []; /// draw each line independently on the chart for (var i = 0; i < data.lineBarsData.length; i++) { final barData = data.lineBarsData[i]; if (!barData.show) { continue; } drawBarLine(canvasWrapper, barData, holder); drawDots(canvasWrapper, barData, holder); if (data.extraLinesData.extraLinesOnTop) { super.drawExtraLines(context, canvasWrapper, holder); } final indicatorsData = data.lineTouchData .getTouchedSpotIndicator(barData, barData.showingIndicators); if (indicatorsData.length != barData.showingIndicators.length) { throw Exception( 'indicatorsData and touchedSpotOffsets size should be same', ); } for (var j = 0; j < barData.showingIndicators.length; j++) { final indicatorData = indicatorsData[j]; final index = barData.showingIndicators[j]; if (index < 0 || index >= barData.spots.length) { continue; } final spot = barData.spots[index]; if (indicatorData == null) { continue; } lineIndexDrawingInfo.add( LineIndexDrawingInfo(barData, i, spot, index, indicatorData), ); } } drawTouchedSpotsIndicator(canvasWrapper, lineIndexDrawingInfo, holder); if (data.clipData.any || holder.chartVirtualRect != null) { canvasWrapper.restore(); } // Draw error indicators for (var i = 0; i < data.lineBarsData.length; i++) { final barData = data.lineBarsData[i]; if (!barData.show) { continue; } drawErrorIndicatorData( canvasWrapper, barData, holder, ); } // Draw touch tooltip on most top spot for (var i = 0; i < data.showingTooltipIndicators.length; i++) { var tooltipSpots = data.showingTooltipIndicators[i]; final showingBarSpots = tooltipSpots.showingSpots; if (showingBarSpots.isEmpty) { continue; } final barSpots = List.of(showingBarSpots); FlSpot topSpot = barSpots[0]; for (final barSpot in barSpots) { if (barSpot.y > topSpot.y) { topSpot = barSpot; } } tooltipSpots = ShowingTooltipIndicators(barSpots); drawTouchTooltip( context, canvasWrapper, data.lineTouchData.touchTooltipData, topSpot, tooltipSpots, holder, ); } } @visibleForTesting void clipToBorder( CanvasWrapper canvasWrapper, PaintHolder holder, ) { final data = holder.data; final viewSize = canvasWrapper.size; final clip = data.clipData; final border = data.borderData.show ? data.borderData.border : null; var left = 0.0; var top = 0.0; var right = viewSize.width; var bottom = viewSize.height; if (clip.left) { final borderWidth = border?.left.width ?? 0; left = borderWidth / 2; } if (clip.top) { final borderWidth = border?.top.width ?? 0; top = borderWidth / 2; } if (clip.right) { final borderWidth = border?.right.width ?? 0; right = viewSize.width - (borderWidth / 2); } if (clip.bottom) { final borderWidth = border?.bottom.width ?? 0; bottom = viewSize.height - (borderWidth / 2); } canvasWrapper.clipRect(Rect.fromLTRB(left, top, right, bottom)); } @visibleForTesting void drawBarLine( CanvasWrapper canvasWrapper, LineChartBarData barData, PaintHolder holder, ) { final viewSize = holder.getChartUsableSize(canvasWrapper.size); final barList = barData.spots.splitByNullSpots(); // paint each sublist that was built above // bar is passed in separately from barData // because barData is the whole line // and bar is a piece of that line for (final bar in barList) { final barPath = generateBarPath(viewSize, barData, bar, holder); final belowBarPath = generateBelowBarPath(viewSize, barData, barPath, bar, holder); final completelyFillBelowBarPath = generateBelowBarPath( viewSize, barData, barPath, bar, holder, fillCompletely: true, ); final aboveBarPath = generateAboveBarPath(viewSize, barData, barPath, bar, holder); final completelyFillAboveBarPath = generateAboveBarPath( viewSize, barData, barPath, bar, holder, fillCompletely: true, ); drawBelowBar( canvasWrapper, belowBarPath, completelyFillAboveBarPath, holder, barData, ); drawAboveBar( canvasWrapper, aboveBarPath, completelyFillBelowBarPath, holder, barData, ); drawBarShadow(canvasWrapper, barPath, barData); drawBar(canvasWrapper, barPath, barData, holder); } } @visibleForTesting void drawBetweenBarsArea( CanvasWrapper canvasWrapper, LineChartData data, BetweenBarsData betweenBarsData, PaintHolder holder, ) { final viewSize = canvasWrapper.size; final fromBarData = data.lineBarsData[betweenBarsData.fromIndex]; final toBarData = data.lineBarsData[betweenBarsData.toIndex]; final fromBarSplitLines = fromBarData.spots.splitByNullSpots(); final toBarSplitLines = toBarData.spots.splitByNullSpots(); if (fromBarSplitLines.length != toBarSplitLines.length) { throw ArgumentError( 'Cannot draw betWeenBarsArea when null spots are inconsistent.', ); } for (var i = 0; i < fromBarSplitLines.length; i++) { final fromSpots = fromBarSplitLines[i]; final toSpots = toBarSplitLines[i].reversed.toList(); final fromBarPath = generateBarPath( viewSize, fromBarData, fromSpots, holder, ); final barPath = generateBarPath( viewSize, toBarData.copyWith(spots: toSpots), toSpots, holder, appendToPath: fromBarPath, ); final left = min(fromBarData.mostLeftSpot.x, toBarData.mostLeftSpot.x); final top = max(fromBarData.mostTopSpot.y, toBarData.mostTopSpot.y); final right = max(fromBarData.mostRightSpot.x, toBarData.mostRightSpot.x); final bottom = min( fromBarData.mostBottomSpot.y, toBarData.mostBottomSpot.y, ); final aroundRect = Rect.fromLTRB( getPixelX(left, viewSize, holder), getPixelY(top, viewSize, holder), getPixelX(right, viewSize, holder), getPixelY(bottom, viewSize, holder), ); drawBetweenBar( canvasWrapper, barPath, betweenBarsData, aroundRect, holder, ); } } @visibleForTesting void drawDots( CanvasWrapper canvasWrapper, LineChartBarData barData, PaintHolder holder, ) { if (!barData.dotData.show || barData.spots.isEmpty) { return; } final viewSize = canvasWrapper.size; final barXDelta = getBarLineXLength(barData, viewSize, holder); for (var i = 0; i < barData.spots.length; i++) { final spot = barData.spots[i]; if (spot.isNotNull() && barData.dotData.checkToShowDot(spot, barData)) { final x = getPixelX(spot.x, viewSize, holder); final y = getPixelY(spot.y, viewSize, holder); final xPercentInLine = (x / barXDelta) * 100; final painter = barData.dotData.getDotPainter(spot, xPercentInLine, barData, i); canvasWrapper.drawDot(painter, spot, Offset(x, y)); } } } @visibleForTesting void drawErrorIndicatorData( CanvasWrapper canvasWrapper, LineChartBarData barData, PaintHolder holder, ) { final errorIndicatorData = barData.errorIndicatorData; if (!errorIndicatorData.show) { return; } final viewSize = canvasWrapper.size; for (var i = 0; i < barData.spots.length; i++) { final spot = barData.spots[i]; if (spot.isNotNull()) { final x = getPixelX(spot.x, viewSize, holder); final y = getPixelY(spot.y, viewSize, holder); if (spot.xError == null && spot.yError == null) { continue; } var left = 0.0; var right = 0.0; if (spot.xError != null) { left = getPixelX(spot.x - spot.xError!.lowerBy, viewSize, holder) - x; right = getPixelX(spot.x + spot.xError!.upperBy, viewSize, holder) - x; } var top = 0.0; var bottom = 0.0; if (spot.yError != null) { top = getPixelY(spot.y + spot.yError!.upperBy, viewSize, holder) - y; bottom = getPixelY(spot.y - spot.yError!.lowerBy, viewSize, holder) - y; } final relativeErrorPixelsRect = Rect.fromLTRB( left, top, right, bottom, ); final painter = errorIndicatorData.painter( LineChartSpotErrorRangeCallbackInput( spot: spot, bar: barData, spotIndex: i, ), ); canvasWrapper.drawErrorIndicator( painter, spot, Offset(x, y), relativeErrorPixelsRect, holder.data, ); } } } @visibleForTesting void drawTouchedSpotsIndicator( CanvasWrapper canvasWrapper, List lineIndexDrawingInfo, PaintHolder holder, ) { if (lineIndexDrawingInfo.isEmpty) { return; } final viewSize = canvasWrapper.size; lineIndexDrawingInfo.sort((a, b) => b.spot.y.compareTo(a.spot.y)); for (final info in lineIndexDrawingInfo) { final barData = info.line; final barXDelta = getBarLineXLength(barData, viewSize, holder); final data = holder.data; final index = info.spotIndex; final spot = info.spot; final indicatorData = info.indicatorData; final touchedSpot = Offset( getPixelX(spot.x, viewSize, holder), getPixelY(spot.y, viewSize, holder), ); /// For drawing the dot final showingDots = indicatorData.touchedSpotDotData.show; var dotHeight = 0.0; late FlDotPainter dotPainter; if (showingDots) { final xPercentInLine = (touchedSpot.dx / barXDelta) * 100; dotPainter = indicatorData.touchedSpotDotData .getDotPainter(spot, xPercentInLine, barData, index); dotHeight = dotPainter.getSize(spot).height; } /// For drawing the indicator line final lineStartY = min( data.maxY, max(data.minY, data.lineTouchData.getTouchLineStart(barData, index)), ); final lineEndY = min( data.maxY, max(data.minY, data.lineTouchData.getTouchLineEnd(barData, index)), ); final lineStart = Offset(touchedSpot.dx, getPixelY(lineStartY, viewSize, holder)); var lineEnd = Offset(touchedSpot.dx, getPixelY(lineEndY, viewSize, holder)); /// If line end is inside the dot, adjust it so that it doesn't overlap with the dot. final dotMinY = touchedSpot.dy - dotHeight / 2; final dotMaxY = touchedSpot.dy + dotHeight / 2; if (lineEnd.dy > dotMinY && lineEnd.dy < dotMaxY) { if (lineStart.dy < lineEnd.dy) { lineEnd -= Offset(0, lineEnd.dy - dotMinY); } else { lineEnd += Offset(0, dotMaxY - lineEnd.dy); } } final indicatorLine = indicatorData.indicatorBelowLine; _touchLinePaint ..setColorOrGradientForLine( indicatorLine.color, indicatorLine.gradient, from: lineStart, to: lineEnd, ) ..strokeWidth = indicatorLine.strokeWidth ..transparentIfWidthIsZero(); canvasWrapper.drawDashedLine( lineStart, lineEnd, _touchLinePaint, indicatorLine.dashArray, ); /// Draw the indicator dot if (showingDots) { canvasWrapper.drawDot(dotPainter, spot, touchedSpot); } } } /// Generates a path, based on [LineChartBarData.isStepChart] for step style, and normal style. @visibleForTesting Path generateBarPath( Size viewSize, LineChartBarData barData, List barSpots, PaintHolder holder, { Path? appendToPath, }) { if (barData.isStepLineChart) { return generateStepBarPath( viewSize, barData, barSpots, holder, appendToPath: appendToPath, ); } else { return generateNormalBarPath( viewSize, barData, barSpots, holder, appendToPath: appendToPath, ); } } /// firstly we generate the bar line that we should draw, /// then we reuse it to fill below bar space. /// there is two type of barPath that generate here, /// first one is the sharp corners line on spot connections /// second one is curved corners line on spot connections, /// and we use isCurved to find out how we should generate it, /// If you want to concatenate paths together for creating an area between /// multiple bars for example, you can pass the appendToPath @visibleForTesting Path generateNormalBarPath( Size viewSize, LineChartBarData barData, List barSpots, PaintHolder holder, { Path? appendToPath, }) { final path = appendToPath ?? Path(); final size = barSpots.length; var temp = Offset.zero; final x = getPixelX(barSpots[0].x, viewSize, holder); final y = getPixelY(barSpots[0].y, viewSize, holder); if (appendToPath == null) { path.moveTo(x, y); if (size == 1) { path.lineTo(x, y); } } else { path.lineTo(x, y); } for (var i = 1; i < size; i++) { /// CurrentSpot final current = Offset( getPixelX(barSpots[i].x, viewSize, holder), getPixelY(barSpots[i].y, viewSize, holder), ); /// previous spot final previous = Offset( getPixelX(barSpots[i - 1].x, viewSize, holder), getPixelY(barSpots[i - 1].y, viewSize, holder), ); /// next point final next = Offset( getPixelX(barSpots[i + 1 < size ? i + 1 : i].x, viewSize, holder), getPixelY(barSpots[i + 1 < size ? i + 1 : i].y, viewSize, holder), ); final controlPoint1 = previous + temp; /// if the isCurved is false, we set 0 for smoothness, /// it means we should not have any smoothness then we face with /// the sharped corners line final smoothness = barData.isCurved ? barData.curveSmoothness : 0.0; temp = ((next - previous) / 2) * smoothness; if (barData.preventCurveOverShooting) { if ((next - current).dy <= barData.preventCurveOvershootingThreshold || (current - previous).dy <= barData.preventCurveOvershootingThreshold) { temp = Offset(temp.dx, 0); } if ((next - current).dx <= barData.preventCurveOvershootingThreshold || (current - previous).dx <= barData.preventCurveOvershootingThreshold) { temp = Offset(0, temp.dy); } } final controlPoint2 = current - temp; path.cubicTo( controlPoint1.dx, controlPoint1.dy, controlPoint2.dx, controlPoint2.dy, current.dx, current.dy, ); } return path; } /// generates a `Step Line Chart` bar style path. @visibleForTesting Path generateStepBarPath( Size viewSize, LineChartBarData barData, List barSpots, PaintHolder holder, { Path? appendToPath, }) { final path = appendToPath ?? Path(); final size = barSpots.length; final x = getPixelX(barSpots[0].x, viewSize, holder); final y = getPixelY(barSpots[0].y, viewSize, holder); if (appendToPath == null) { path.moveTo(x, y); } else { path.lineTo(x, y); } for (var i = 0; i < size; i++) { /// CurrentSpot final current = Offset( getPixelX(barSpots[i].x, viewSize, holder), getPixelY(barSpots[i].y, viewSize, holder), ); /// next point final next = Offset( getPixelX(barSpots[i + 1 < size ? i + 1 : i].x, viewSize, holder), getPixelY(barSpots[i + 1 < size ? i + 1 : i].y, viewSize, holder), ); final stepDirection = barData.lineChartStepData.stepDirection; // middle if (current.dy == next.dy) { path.lineTo(next.dx, next.dy); } else { final deltaX = next.dx - current.dx; path ..lineTo(current.dx + deltaX - (deltaX * stepDirection), current.dy) ..lineTo(current.dx + deltaX - (deltaX * stepDirection), next.dy) ..lineTo(next.dx, next.dy); } } return path; } /// it generates below area path using a copy of [barPath], /// if cutOffY is provided by the [BarAreaData], it cut the area to the provided cutOffY value, /// if [fillCompletely] is true, the cutOffY will be ignored, /// and a completely filled path will return, @visibleForTesting Path generateBelowBarPath( Size viewSize, LineChartBarData barData, Path barPath, List barSpots, PaintHolder holder, { bool fillCompletely = false, }) { final belowBarPath = Path.from(barPath); /// Line To Bottom Right var x = getPixelX(barSpots[barSpots.length - 1].x, viewSize, holder); double y; if (!fillCompletely && barData.belowBarData.applyCutOffY) { y = getPixelY(barData.belowBarData.cutOffY, viewSize, holder); } else { y = viewSize.height; } belowBarPath.lineTo(x, y); /// Line To Bottom Left x = getPixelX(barSpots[0].x, viewSize, holder); if (!fillCompletely && barData.belowBarData.applyCutOffY) { y = getPixelY(barData.belowBarData.cutOffY, viewSize, holder); } else { y = viewSize.height; } belowBarPath.lineTo(x, y); /// Line To Top Left x = getPixelX(barSpots[0].x, viewSize, holder); y = getPixelY(barSpots[0].y, viewSize, holder); belowBarPath ..lineTo(x, y) ..close(); return belowBarPath; } /// it generates above area path using a copy of [barPath], /// if cutOffY is provided by the [BarAreaData], it cut the area to the provided cutOffY value, /// if [fillCompletely] is true, the cutOffY will be ignored, /// and a completely filled path will return, @visibleForTesting Path generateAboveBarPath( Size viewSize, LineChartBarData barData, Path barPath, List barSpots, PaintHolder holder, { bool fillCompletely = false, }) { final aboveBarPath = Path.from(barPath); /// Line To Top Right var x = getPixelX(barSpots[barSpots.length - 1].x, viewSize, holder); double y; if (!fillCompletely && barData.aboveBarData.applyCutOffY) { y = getPixelY(barData.aboveBarData.cutOffY, viewSize, holder); } else { y = 0.0; } aboveBarPath.lineTo(x, y); /// Line To Top Left x = getPixelX(barSpots[0].x, viewSize, holder); if (!fillCompletely && barData.aboveBarData.applyCutOffY) { y = getPixelY(barData.aboveBarData.cutOffY, viewSize, holder); } else { y = 0.0; } aboveBarPath.lineTo(x, y); /// Line To Bottom Left x = getPixelX(barSpots[0].x, viewSize, holder); y = getPixelY(barSpots[0].y, viewSize, holder); aboveBarPath ..lineTo(x, y) ..close(); return aboveBarPath; } /// firstly we draw [belowBarPath], then if cutOffY value is provided in [BarAreaData], /// [belowBarPath] maybe draw over the main bar line, /// then to fix the problem we use [filledAboveBarPath] to clear the above section from this draw. @visibleForTesting void drawBelowBar( CanvasWrapper canvasWrapper, Path belowBarPath, Path filledAboveBarPath, PaintHolder holder, LineChartBarData barData, ) { if (!barData.belowBarData.show) { return; } final viewSize = canvasWrapper.size; final belowBarLargestRect = Rect.fromLTRB( getPixelX(barData.mostLeftSpot.x, viewSize, holder), getPixelY(barData.mostTopSpot.y, viewSize, holder), getPixelX(barData.mostRightSpot.x, viewSize, holder), viewSize.height, ); final belowBar = barData.belowBarData; _barAreaPaint.setColorOrGradient( belowBar.color, belowBar.gradient, belowBarLargestRect, ); if (barData.belowBarData.applyCutOffY) { canvasWrapper.saveLayer( Rect.fromLTWH(0, 0, viewSize.width, viewSize.height), _clipPaint, ); } canvasWrapper.drawPath(belowBarPath, _barAreaPaint); // clear the above area that get out of the bar line if (barData.belowBarData.applyCutOffY) { canvasWrapper ..drawPath(filledAboveBarPath, _clearBarAreaPaint) ..restore(); } /// draw below spots line if (barData.belowBarData.spotsLine.show) { for (final spot in barData.spots) { if (barData.belowBarData.spotsLine.checkToShowSpotLine(spot)) { final from = Offset( getPixelX(spot.x, viewSize, holder), getPixelY(spot.y, viewSize, holder), ); Offset to; // Check applyCutOffY if (barData.belowBarData.spotsLine.applyCutOffY && barData.belowBarData.applyCutOffY) { to = Offset( getPixelX(spot.x, viewSize, holder), getPixelY(barData.belowBarData.cutOffY, viewSize, holder), ); } else { to = Offset( getPixelX(spot.x, viewSize, holder), viewSize.height, ); } final lineStyle = barData.belowBarData.spotsLine.flLineStyle; _barAreaLinesPaint ..setColorOrGradientForLine( lineStyle.color, lineStyle.gradient, from: from, to: to, ) ..strokeWidth = lineStyle.strokeWidth ..transparentIfWidthIsZero(); canvasWrapper.drawDashedLine( from, to, _barAreaLinesPaint, lineStyle.dashArray, ); } } } } /// firstly we draw [aboveBarPath], then if cutOffY value is provided in [BarAreaData], /// [aboveBarPath] maybe draw over the main bar line, /// then to fix the problem we use [filledBelowBarPath] to clear the above section from this draw. @visibleForTesting void drawAboveBar( CanvasWrapper canvasWrapper, Path aboveBarPath, Path filledBelowBarPath, PaintHolder holder, LineChartBarData barData, ) { if (!barData.aboveBarData.show) { return; } final viewSize = canvasWrapper.size; final aboveBarLargestRect = Rect.fromLTRB( getPixelX(barData.mostLeftSpot.x, viewSize, holder), 0, getPixelX(barData.mostRightSpot.x, viewSize, holder), getPixelY(barData.mostBottomSpot.y, viewSize, holder), ); final aboveBar = barData.aboveBarData; _barAreaPaint.setColorOrGradient( aboveBar.color, aboveBar.gradient, aboveBarLargestRect, ); if (barData.aboveBarData.applyCutOffY) { canvasWrapper.saveLayer( Rect.fromLTWH(0, 0, viewSize.width, viewSize.height), _clipPaint, ); } canvasWrapper.drawPath(aboveBarPath, _barAreaPaint); // clear the above area that get out of the bar line if (barData.aboveBarData.applyCutOffY) { canvasWrapper ..drawPath(filledBelowBarPath, _clearBarAreaPaint) ..restore(); } /// draw above spots line if (barData.aboveBarData.spotsLine.show) { for (final spot in barData.spots) { if (barData.aboveBarData.spotsLine.checkToShowSpotLine(spot)) { final from = Offset( getPixelX(spot.x, viewSize, holder), getPixelY(spot.y, viewSize, holder), ); Offset to; // Check applyCutOffY if (barData.aboveBarData.spotsLine.applyCutOffY && barData.aboveBarData.applyCutOffY) { to = Offset( getPixelX(spot.x, viewSize, holder), getPixelY(barData.aboveBarData.cutOffY, viewSize, holder), ); } else { to = Offset( getPixelX(spot.x, viewSize, holder), 0, ); } final lineStyle = barData.aboveBarData.spotsLine.flLineStyle; _barAreaLinesPaint ..setColorOrGradientForLine( lineStyle.color, lineStyle.gradient, from: from, to: to, ) ..strokeWidth = lineStyle.strokeWidth ..transparentIfWidthIsZero(); canvasWrapper.drawDashedLine( from, to, _barAreaLinesPaint, lineStyle.dashArray, ); } } } } @visibleForTesting void drawBetweenBar( CanvasWrapper canvasWrapper, Path barPath, BetweenBarsData betweenBarsData, Rect aroundRect, PaintHolder holder, ) { final viewSize = canvasWrapper.size; _barAreaPaint.setColorOrGradient( betweenBarsData.color, betweenBarsData.gradient, aroundRect, ); canvasWrapper ..saveLayer( Rect.fromLTWH(0, 0, viewSize.width, viewSize.height), _clipPaint, ) ..drawPath(barPath, _barAreaPaint) ..restore(); // clear the above area that get out of the bar line } /// draw the main bar line's shadow by the [barPath] @visibleForTesting void drawBarShadow( CanvasWrapper canvasWrapper, Path barPath, LineChartBarData barData, ) { if (!barData.show || barData.shadow.color.a == 0.0) { return; } if (barPath.computeMetrics().isEmpty) { return; } _barPaint ..strokeCap = barData.isStrokeCapRound ? StrokeCap.round : StrokeCap.butt ..strokeJoin = barData.isStrokeJoinRound ? StrokeJoin.round : StrokeJoin.miter ..color = barData.shadow.color ..shader = null ..strokeWidth = barData.barWidth ..color = barData.shadow.color ..maskFilter = MaskFilter.blur( BlurStyle.normal, Utils().convertRadiusToSigma(barData.shadow.blurRadius), ); barPath = barPath.toDashedPath(barData.dashArray); barPath = barPath.shift(barData.shadow.offset); canvasWrapper.drawPath( barPath, _barPaint, ); } /// draw the main bar line by the [barPath] @visibleForTesting void drawBar( CanvasWrapper canvasWrapper, Path barPath, LineChartBarData barData, PaintHolder holder, ) { if (!barData.show) { return; } final viewSize = canvasWrapper.size; _barPaint ..strokeCap = barData.isStrokeCapRound ? StrokeCap.round : StrokeCap.butt ..strokeJoin = barData.isStrokeJoinRound ? StrokeJoin.round : StrokeJoin.miter; final rectAroundTheLine = Rect.fromLTRB( getPixelX(barData.mostLeftSpot.x, viewSize, holder), getPixelY(barData.mostTopSpot.y, viewSize, holder), getPixelX(barData.mostRightSpot.x, viewSize, holder), getPixelY(barData.mostBottomSpot.y, viewSize, holder), ); _barPaint ..setColorOrGradient( barData.color, barData.gradient, barData.gradientArea == LineChartGradientArea.wholeChart ? Offset.zero & viewSize : rectAroundTheLine, ) ..maskFilter = null ..strokeWidth = barData.barWidth ..transparentIfWidthIsZero(); barPath = barPath.toDashedPath(barData.dashArray); canvasWrapper.drawPath(barPath, _barPaint); } @visibleForTesting void drawTouchTooltip( BuildContext context, CanvasWrapper canvasWrapper, LineTouchTooltipData tooltipData, FlSpot showOnSpot, ShowingTooltipIndicators showingTooltipSpots, PaintHolder holder, ) { final viewSize = canvasWrapper.size; const textsBelowMargin = 4; // Get the dot height if available final dotHeight = _getDotHeight( viewSize: viewSize, holder: holder, showingTooltipSpots: showingTooltipSpots.showingSpots, ); /// creating TextPainters to calculate the width and height of the tooltip final drawingTextPainters = []; final tooltipItems = tooltipData.getTooltipItems(showingTooltipSpots.showingSpots); if (tooltipItems.length != showingTooltipSpots.showingSpots.length) { throw Exception('tooltipItems and touchedSpots size should be same'); } for (var i = 0; i < showingTooltipSpots.showingSpots.length; i++) { var tooltipItem = tooltipItems[i]; if (holder.data.rotationQuarterTurns % 4 == 2) { tooltipItem = tooltipItems[tooltipItems.length - 1 - i]; } if (tooltipItem == null) { continue; } final span = TextSpan( style: Utils().getThemeAwareTextStyle(context, tooltipItem.textStyle), text: tooltipItem.text, children: tooltipItem.children, ); final tp = TextPainter( text: span, textAlign: tooltipItem.textAlign, textDirection: tooltipItem.textDirection, textScaler: holder.textScaler, )..layout(maxWidth: tooltipData.maxContentWidth); drawingTextPainters.add(tp); } if (drawingTextPainters.isEmpty) { return; } /// biggerWidth /// some texts maybe larger, then we should /// draw the tooltip' width as wide as biggerWidth /// /// sumTextsHeight /// sum up all Texts height, then we should /// draw the tooltip's height as tall as sumTextsHeight var biggerWidth = 0.0; var sumTextsHeight = 0.0; for (final tp in drawingTextPainters) { if (tp.width > biggerWidth) { biggerWidth = tp.width; } sumTextsHeight += tp.height; } sumTextsHeight += (drawingTextPainters.length - 1) * textsBelowMargin; /// if we have multiple bar lines, /// there are more than one FlCandidate on touch area, /// we should get the most top FlSpot Offset to draw the tooltip on top of it final mostTopOffset = Offset( getPixelX(showOnSpot.x, viewSize, holder), getPixelY(showOnSpot.y, viewSize, holder), ); // Create an extended boundary that includes the center of the dot final extendedBoundary = (Offset.zero & viewSize).inflate(dotHeight / 2); final isZoomed = holder.chartVirtualRect != null; if (isZoomed && !extendedBoundary.contains(mostTopOffset)) { return; } final tooltipWidth = biggerWidth + tooltipData.tooltipPadding.horizontal; final tooltipHeight = sumTextsHeight + tooltipData.tooltipPadding.vertical; double tooltipTopPosition; if (tooltipData.showOnTopOfTheChartBoxArea) { tooltipTopPosition = 0 - tooltipHeight - tooltipData.tooltipMargin; } else { tooltipTopPosition = mostTopOffset.dy - tooltipHeight - tooltipData.tooltipMargin; } final tooltipLeftPosition = getTooltipLeft( mostTopOffset.dx, tooltipWidth, tooltipData.tooltipHorizontalAlignment, tooltipData.tooltipHorizontalOffset, ); /// draw the background rect with rounded radius var rect = Rect.fromLTWH( tooltipLeftPosition, tooltipTopPosition, tooltipWidth, tooltipHeight, ); if (tooltipData.fitInsideHorizontally) { if (rect.left < 0) { final shiftAmount = 0 - rect.left; rect = Rect.fromLTRB( rect.left + shiftAmount, rect.top, rect.right + shiftAmount, rect.bottom, ); } if (rect.right > viewSize.width) { final shiftAmount = rect.right - viewSize.width; rect = Rect.fromLTRB( rect.left - shiftAmount, rect.top, rect.right - shiftAmount, rect.bottom, ); } } if (tooltipData.fitInsideVertically) { if (rect.top < 0) { final shiftAmount = 0 - rect.top; rect = Rect.fromLTRB( rect.left, rect.top + shiftAmount, rect.right, rect.bottom + shiftAmount, ); } if (rect.bottom > viewSize.height) { final shiftAmount = rect.bottom - viewSize.height; rect = Rect.fromLTRB( rect.left, rect.top - shiftAmount, rect.right, rect.bottom - shiftAmount, ); } } final roundedRect = RRect.fromRectAndCorners( rect, topLeft: tooltipData.tooltipBorderRadius.topLeft, topRight: tooltipData.tooltipBorderRadius.topRight, bottomLeft: tooltipData.tooltipBorderRadius.bottomLeft, bottomRight: tooltipData.tooltipBorderRadius.bottomRight, ); var topSpot = showingTooltipSpots.showingSpots[0]; for (final barSpot in showingTooltipSpots.showingSpots) { if (barSpot.y > topSpot.y) { topSpot = barSpot; } } _bgTouchTooltipPaint.color = tooltipData.getTooltipColor(topSpot); final rotateAngle = tooltipData.rotateAngle; final rectRotationOffset = Offset(0, Utils().calculateRotationOffset(rect.size, rotateAngle).dy); final rectDrawOffset = Offset(roundedRect.left, roundedRect.top); final textRotationOffset = Utils().calculateRotationOffset(rect.size, rotateAngle); if (tooltipData.tooltipBorder != BorderSide.none) { _borderTouchTooltipPaint ..color = tooltipData.tooltipBorder.color ..strokeWidth = tooltipData.tooltipBorder.width; } final reverseQuarterTurnsAngle = -holder.data.rotationQuarterTurns * 90; canvasWrapper.drawRotated( size: rect.size, rotationOffset: rectRotationOffset, drawOffset: rectDrawOffset, angle: reverseQuarterTurnsAngle + rotateAngle, drawCallback: () { canvasWrapper ..drawRRect(roundedRect, _bgTouchTooltipPaint) ..drawRRect(roundedRect, _borderTouchTooltipPaint); }, ); /// draw the texts one by one in below of each other var topPosSeek = tooltipData.tooltipPadding.top; for (final tp in drawingTextPainters) { final yOffset = rect.topCenter.dy + topPosSeek - textRotationOffset.dy + rectRotationOffset.dy; final align = tp.textAlign.getFinalHorizontalAlignment(tp.textDirection); final xOffset = switch (align) { HorizontalAlignment.left => rect.left + tooltipData.tooltipPadding.left, HorizontalAlignment.right => rect.right - tooltipData.tooltipPadding.right - tp.width, _ => rect.center.dx - (tp.width / 2), }; final drawOffset = Offset( xOffset, yOffset, ); final reverseQuarterTurnsAngle = -holder.data.rotationQuarterTurns * 90; canvasWrapper.drawRotated( size: rect.size, rotationOffset: rectRotationOffset, drawOffset: rectDrawOffset, angle: reverseQuarterTurnsAngle + rotateAngle, drawCallback: () { canvasWrapper.drawText(tp, drawOffset); }, ); topPosSeek += tp.height; topPosSeek += textsBelowMargin; } } @visibleForTesting double getBarLineXLength( LineChartBarData barData, Size chartUsableSize, PaintHolder holder, ) { if (barData.spots.isEmpty) { return 0; } final firstSpot = barData.spots[0]; final firstSpotX = getPixelX(firstSpot.x, chartUsableSize, holder); final lastSpot = barData.spots[barData.spots.length - 1]; final lastSpotX = getPixelX(lastSpot.x, chartUsableSize, holder); return lastSpotX - firstSpotX; } /// Makes a [LineTouchResponse] based on the provided [localPosition] /// /// Processes [localPosition] and checks /// the elements of the chart that are near the offset, /// then makes a [LineTouchResponse] from the elements that has been touched. List? handleTouch( Offset localPosition, Size size, PaintHolder holder, ) { final data = holder.data; final viewSize = holder.getChartUsableSize(size); final isZoomed = holder.chartVirtualRect != null; if (isZoomed && !size.contains(localPosition)) { return null; } /// it holds list of nearest touched spots of each line /// and we use it to draw touch stuff on them final touchedSpots = []; /// draw each line independently on the chart for (var i = 0; i < data.lineBarsData.length; i++) { final barData = data.lineBarsData[i]; // find the nearest spot on touch area in this bar line final foundTouchedSpot = getNearestTouchedSpot( viewSize, localPosition, barData, i, holder, ); if (foundTouchedSpot != null) { touchedSpots.add(foundTouchedSpot); } } touchedSpots.sort((a, b) => a.distance.compareTo(b.distance)); return touchedSpots.isEmpty ? null : touchedSpots; } /// find the nearest spot base on the touched offset @visibleForTesting TouchLineBarSpot? getNearestTouchedSpot( Size viewSize, Offset touchedPoint, LineChartBarData barData, int barDataPosition, PaintHolder holder, ) { final data = holder.data; if (!barData.show) { return null; } /// Find the nearest spot (based on distanceCalculator) final sortedSpots = []; double? smallestDistance; for (final spot in barData.spots) { if (spot.isNull()) continue; final distance = data.lineTouchData.distanceCalculator( touchedPoint, Offset( getPixelX(spot.x, viewSize, holder), getPixelY(spot.y, viewSize, holder), ), ); if (distance <= data.lineTouchData.touchSpotThreshold) { smallestDistance ??= distance; if (distance < smallestDistance) { sortedSpots.insert(0, spot); smallestDistance = distance; } else { sortedSpots.add(spot); } } } if (sortedSpots.isNotEmpty) { return TouchLineBarSpot( barData, barDataPosition, sortedSpots.first, smallestDistance!, ); } else { return null; } } // Get the height of the dot for the given showingTooltipSpots double _getDotHeight({ required Size viewSize, required PaintHolder holder, required List showingTooltipSpots, }) { double? dotHeight; for (final info in showingTooltipSpots) { // Find the corresponding indicator data for this spot final lineData = holder.data.lineBarsData.elementAtOrNull(info.barIndex); if (lineData == null) continue; final indicators = holder.data.lineTouchData .getTouchedSpotIndicator(lineData, [info.spotIndex]); final indicatorData = indicators.elementAtOrNull(0); if (indicatorData != null && indicatorData.touchedSpotDotData.show) { final xPercentInLine = (getPixelX(info.x, viewSize, holder) / getBarLineXLength(lineData, viewSize, holder)) * 100; final dotPainter = indicatorData.touchedSpotDotData .getDotPainter(info, xPercentInLine, lineData, info.spotIndex); final currentDotHeight = dotPainter.getSize(info).height; // Keep the largest dot height if (dotHeight == null || currentDotHeight > dotHeight) { dotHeight = currentDotHeight; } } } return dotHeight ?? 0; } } @visibleForTesting class LineIndexDrawingInfo { LineIndexDrawingInfo( this.line, this.lineIndex, this.spot, this.spotIndex, this.indicatorData, ); final LineChartBarData line; final int lineIndex; final FlSpot spot; final int spotIndex; final TouchedSpotIndicatorData indicatorData; } ================================================ FILE: lib/src/chart/line_chart/line_chart_renderer.dart ================================================ import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart'; import 'package:fl_chart/src/chart/base/base_chart/render_base_chart.dart'; import 'package:fl_chart/src/chart/line_chart/line_chart_painter.dart'; import 'package:fl_chart/src/utils/canvas_wrapper.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; // coverage:ignore-start /// Low level LineChart Widget. class LineChartLeaf extends LeafRenderObjectWidget { const LineChartLeaf({ super.key, required this.data, required this.targetData, required this.canBeScaled, required this.chartVirtualRect, }); final LineChartData data; final LineChartData targetData; final Rect? chartVirtualRect; final bool canBeScaled; @override RenderLineChart createRenderObject(BuildContext context) => RenderLineChart( context, data, targetData, MediaQuery.of(context).textScaler, chartVirtualRect, canBeScaled: canBeScaled, ); @override void updateRenderObject(BuildContext context, RenderLineChart renderObject) { renderObject ..data = data ..targetData = targetData ..textScaler = MediaQuery.of(context).textScaler ..buildContext = context ..chartVirtualRect = chartVirtualRect ..canBeScaled = canBeScaled; } } // coverage:ignore-end /// Renders our LineChart, also handles hitTest. class RenderLineChart extends RenderBaseChart { RenderLineChart( BuildContext context, LineChartData data, LineChartData targetData, TextScaler textScaler, Rect? chartVirtualRect, { required bool canBeScaled, }) : _data = data, _targetData = targetData, _textScaler = textScaler, _chartVirtualRect = chartVirtualRect, super( targetData.lineTouchData, context, canBeScaled: canBeScaled, ); LineChartData get data => _data; LineChartData _data; set data(LineChartData value) { if (_data == value) return; _data = value; markNeedsPaint(); } LineChartData get targetData => _targetData; LineChartData _targetData; set targetData(LineChartData value) { if (_targetData == value) return; _targetData = value; super.updateBaseTouchData(_targetData.lineTouchData); markNeedsPaint(); } TextScaler get textScaler => _textScaler; TextScaler _textScaler; set textScaler(TextScaler value) { if (_textScaler == value) return; _textScaler = value; markNeedsPaint(); } Rect? get chartVirtualRect => _chartVirtualRect; Rect? _chartVirtualRect; set chartVirtualRect(Rect? value) { if (_chartVirtualRect == value) return; _chartVirtualRect = value; markNeedsPaint(); } // We couldn't mock [size] property of this class, that's why we have this @visibleForTesting Size? mockTestSize; @visibleForTesting LineChartPainter painter = LineChartPainter(); PaintHolder get paintHolder => PaintHolder(data, targetData, textScaler, chartVirtualRect); @override void paint(PaintingContext context, Offset offset) { final canvas = context.canvas ..save() ..translate(offset.dx, offset.dy); painter.paint( buildContext, CanvasWrapper(canvas, mockTestSize ?? size), paintHolder, ); canvas.restore(); } @override bool hitTestSelf(Offset position) { if (!targetData.lineTouchData.enabled) { return false; } return super.hitTestSelf(position); } @override LineTouchResponse getResponseAtLocation(Offset localPosition) { final chartSize = mockTestSize ?? size; return LineTouchResponse( touchLocation: localPosition, touchChartCoordinate: painter.getChartCoordinateFromPixel( localPosition, chartSize, paintHolder, ), lineBarSpots: painter.handleTouch( localPosition, chartSize, paintHolder, ), ); } } ================================================ FILE: lib/src/chart/pie_chart/pie_chart.dart ================================================ import 'package:fl_chart/src/chart/pie_chart/pie_chart_data.dart'; import 'package:fl_chart/src/chart/pie_chart/pie_chart_renderer.dart'; import 'package:flutter/material.dart'; /// Renders a pie chart as a widget, using provided [PieChartData]. class PieChart extends ImplicitlyAnimatedWidget { /// [data] determines how the [PieChart] should be look like, /// when you make any change in the [PieChartData], it updates /// new values with animation, and duration is [duration]. /// also you can change the [curve] /// which default is [Curves.linear]. const PieChart( this.data, { super.key, @Deprecated('Please use [duration] instead') Duration? swapAnimationDuration, Duration duration = const Duration(milliseconds: 150), @Deprecated('Please use [curve] instead') Curve? swapAnimationCurve, Curve curve = Curves.linear, }) : super( duration: swapAnimationDuration ?? duration, curve: swapAnimationCurve ?? curve, ); /// Default duration to reuse externally. static const defaultDuration = Duration(milliseconds: 150); /// Determines how the [PieChart] should be look like. final PieChartData data; /// Creates a [_PieChartState] @override _PieChartState createState() => _PieChartState(); } class _PieChartState extends AnimatedWidgetBaseState { /// We handle under the hood animations (implicit animations) via this tween, /// it lerps between the old [PieChartData] to the new one. PieChartDataTween? _pieChartDataTween; @override void initState() { /// Make sure that [_widgetsPositionHandler] is updated. WidgetsBinding.instance.addPostFrameCallback((timeStamp) { if (mounted) { setState(() {}); } }); super.initState(); } @override Widget build(BuildContext context) { final showingData = _getData(); return PieChartLeaf( data: _pieChartDataTween!.evaluate(animation), targetData: showingData, ); } /// if builtIn touches are enabled, we should recreate our [pieChartData] /// to handle built in touches PieChartData _getData() { return widget.data; } @override void forEachTween(TweenVisitor visitor) { _pieChartDataTween = visitor( _pieChartDataTween, widget.data, (dynamic value) => PieChartDataTween(begin: value as PieChartData, end: widget.data), ) as PieChartDataTween?; } } /// Positions the badge widgets on their respective sections. class BadgeWidgetsDelegate extends MultiChildLayoutDelegate { BadgeWidgetsDelegate({ required this.badgeWidgetsCount, required this.badgeWidgetsOffsets, }); final int badgeWidgetsCount; final Map badgeWidgetsOffsets; @override void performLayout(Size size) { for (var index = 0; index < badgeWidgetsCount; index++) { final key = badgeWidgetsOffsets.keys.elementAt(index); final finalSize = layoutChild( key, BoxConstraints( maxWidth: size.width, maxHeight: size.height, ), ); positionChild( key, Offset( badgeWidgetsOffsets[key]!.dx - (finalSize.width / 2), badgeWidgetsOffsets[key]!.dy - (finalSize.height / 2), ), ); } } @override bool shouldRelayout(BadgeWidgetsDelegate oldDelegate) { return oldDelegate.badgeWidgetsOffsets != badgeWidgetsOffsets; } } ================================================ FILE: lib/src/chart/pie_chart/pie_chart_data.dart ================================================ // coverage:ignore-file import 'dart:ui'; import 'package:equatable/equatable.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/utils/lerp.dart'; import 'package:flutter/material.dart'; /// [PieChart] needs this class to render itself. /// /// It holds data needed to draw a pie chart, /// including pie sections, colors, ... class PieChartData extends BaseChartData with EquatableMixin { /// [PieChart] draws some [sections] in a circle, /// and applies free space with radius [centerSpaceRadius], /// and color [centerSpaceColor] in the center of the circle, /// if you don't want it, set [centerSpaceRadius] to zero. /// /// It draws [sections] from zero degree (right side of the circle) clockwise, /// you can change the starting point, by changing [startDegreeOffset] (in degrees). /// /// You can define a gap between [sections] by setting [sectionsSpace]. /// /// You can modify [pieTouchData] to customize touch behaviors and responses. PieChartData({ List? sections, double? centerSpaceRadius, Color? centerSpaceColor, double? sectionsSpace, double? startDegreeOffset, PieTouchData? pieTouchData, FlBorderData? borderData, bool? titleSunbeamLayout, }) : sections = sections ?? const [], centerSpaceRadius = centerSpaceRadius ?? double.infinity, centerSpaceColor = centerSpaceColor ?? Colors.transparent, sectionsSpace = sectionsSpace ?? 2, startDegreeOffset = startDegreeOffset ?? 0, pieTouchData = pieTouchData ?? PieTouchData(), titleSunbeamLayout = titleSunbeamLayout ?? false, super( borderData: borderData ?? FlBorderData(show: false), ); /// Defines showing sections of the [PieChart]. final List sections; /// Radius of free space in center of the circle. final double centerSpaceRadius; /// Color of free space in center of the circle. final Color centerSpaceColor; /// Defines gap between sections. /// /// Does not work on html-renderer, /// https://github.com/imaNNeo/fl_chart/issues/955 final double sectionsSpace; /// [PieChart] draws [sections] from zero degree (right side of the circle) clockwise. final double startDegreeOffset; /// Handles touch behaviors and responses. final PieTouchData pieTouchData; /// Whether to rotate the titles on each section of the chart final bool titleSunbeamLayout; /// We hold this value to determine weight of each [PieChartSectionData.value]. double get sumValue => sections .map((data) => data.value) .reduce((first, second) => first + second); /// Copies current [PieChartData] to a new [PieChartData], /// and replaces provided values. PieChartData copyWith({ List? sections, double? centerSpaceRadius, Color? centerSpaceColor, double? sectionsSpace, double? startDegreeOffset, PieTouchData? pieTouchData, FlBorderData? borderData, bool? titleSunbeamLayout, }) => PieChartData( sections: sections ?? this.sections, centerSpaceRadius: centerSpaceRadius ?? this.centerSpaceRadius, centerSpaceColor: centerSpaceColor ?? this.centerSpaceColor, sectionsSpace: sectionsSpace ?? this.sectionsSpace, startDegreeOffset: startDegreeOffset ?? this.startDegreeOffset, pieTouchData: pieTouchData ?? this.pieTouchData, borderData: borderData ?? this.borderData, titleSunbeamLayout: titleSunbeamLayout ?? this.titleSunbeamLayout, ); /// Lerps a [BaseChartData] based on [t] value, check [Tween.lerp]. @override PieChartData lerp(BaseChartData a, BaseChartData b, double t) { if (a is PieChartData && b is PieChartData) { return PieChartData( borderData: FlBorderData.lerp(a.borderData, b.borderData, t), centerSpaceColor: Color.lerp(a.centerSpaceColor, b.centerSpaceColor, t), centerSpaceRadius: lerpDoubleAllowInfinity( a.centerSpaceRadius, b.centerSpaceRadius, t, ), pieTouchData: b.pieTouchData, sectionsSpace: lerpDouble(a.sectionsSpace, b.sectionsSpace, t), startDegreeOffset: lerpDouble(a.startDegreeOffset, b.startDegreeOffset, t), sections: lerpPieChartSectionDataList(a.sections, b.sections, t), titleSunbeamLayout: b.titleSunbeamLayout, ); } else { throw Exception('Illegal State'); } } /// Used for equality check, see [EquatableMixin]. @override List get props => [ sections, centerSpaceRadius, centerSpaceColor, pieTouchData, sectionsSpace, startDegreeOffset, borderData, titleSunbeamLayout, ]; } /// Holds data related to drawing each [PieChart] section. class PieChartSectionData with EquatableMixin { /// [PieChart] draws section from right side of the circle (0 degrees), /// each section have a [value] that determines how much it should occupy, /// this is depends on sum of all sections, each section should /// occupy ([value] / sumValues) * 360 degrees. /// /// It draws this section with filled [color], and [radius]. /// /// If [showTitle] is true, it draws a title at the middle of section, /// you can set the text using [title], and set the style using [titleStyle], /// by default it draws texts at the middle of section, but you can change the /// [titlePositionPercentageOffset] to have your desire design, /// it should be between 0.0 to 1.0, /// 0.0 means near the center, /// 1.0 means near the outside of the [PieChart]. /// /// If [badgeWidget] is not null, it draws a widget at the middle of section, /// by default it draws the widget at the middle of section, but you can change the /// [badgePositionPercentageOffset] to have your desire design, /// the value works the same way as [titlePositionPercentageOffset]. PieChartSectionData({ double? value, Color? color, this.gradient, double? radius, bool? showTitle, this.titleStyle, String? title, BorderSide? borderSide, double? cornerRadius, this.badgeWidget, double? titlePositionPercentageOffset, double? badgePositionPercentageOffset, }) : value = value ?? 10, color = color ?? Colors.cyan, radius = (radius ?? 40).clamp(0, double.infinity).toDouble(), showTitle = showTitle ?? true, title = title ?? (value == null ? '' : value.toString()), borderSide = borderSide ?? const BorderSide(width: 0), cornerRadius = (cornerRadius ?? 0.0).clamp(0, double.infinity).toDouble(), titlePositionPercentageOffset = titlePositionPercentageOffset ?? 0.5, badgePositionPercentageOffset = badgePositionPercentageOffset ?? 0.5; /// It determines how much space it should occupy around the circle. /// /// This is depends on sum of all sections, each section should /// occupy ([value] / sumValues) * 360 degrees. /// /// value can not be null. final double value; /// Defines the color of section. final Color color; /// Defines the gradient of section. If specified, overrides the color setting. final Gradient? gradient; /// Defines the radius of section. final double radius; /// Defines show or hide the title of section. final bool showTitle; /// Defines style of showing title of section. final TextStyle? titleStyle; /// Defines text of showing title at the middle of section. final String title; /// Defines border stroke around the section final BorderSide borderSide; /// Defines corner radius for rounded edges (applies to all corners) final double cornerRadius; /// Defines a widget that represents the section. /// /// This can be anything from a text, an image, an animation, and even a combination of widgets. /// Use AnimatedWidgets to animate this widget. final Widget? badgeWidget; /// Defines position of showing title in the section. /// /// It should be between 0.0 to 1.0, /// 0.0 means near the center, /// 1.0 means near the outside of the [PieChart]. final double titlePositionPercentageOffset; /// Defines position of badge widget in the section. /// /// It should be between 0.0 to 1.0, /// 0.0 means near the center, /// 1.0 means near the outside of the [PieChart]. final double badgePositionPercentageOffset; /// Copies current [PieChartSectionData] to a new [PieChartSectionData], /// and replaces provided values. PieChartSectionData copyWith({ double? value, Color? color, Gradient? gradient, double? radius, bool? showTitle, TextStyle? titleStyle, String? title, BorderSide? borderSide, double? cornerRadius, Widget? badgeWidget, double? titlePositionPercentageOffset, double? badgePositionPercentageOffset, }) => PieChartSectionData( value: value ?? this.value, color: color ?? this.color, gradient: gradient ?? this.gradient, radius: radius ?? this.radius, showTitle: showTitle ?? this.showTitle, titleStyle: titleStyle ?? this.titleStyle, title: title ?? this.title, borderSide: borderSide ?? this.borderSide, cornerRadius: cornerRadius ?? this.cornerRadius, badgeWidget: badgeWidget ?? this.badgeWidget, titlePositionPercentageOffset: titlePositionPercentageOffset ?? this.titlePositionPercentageOffset, badgePositionPercentageOffset: badgePositionPercentageOffset ?? this.badgePositionPercentageOffset, ); /// Lerps a [PieChartSectionData] based on [t] value, check [Tween.lerp]. static PieChartSectionData lerp( PieChartSectionData a, PieChartSectionData b, double t, ) => PieChartSectionData( value: lerpDouble(a.value, b.value, t), color: Color.lerp(a.color, b.color, t), gradient: Gradient.lerp(a.gradient, b.gradient, t), radius: lerpDouble(a.radius, b.radius, t), showTitle: b.showTitle, titleStyle: TextStyle.lerp(a.titleStyle, b.titleStyle, t), title: b.title, borderSide: BorderSide.lerp(a.borderSide, b.borderSide, t), cornerRadius: lerpDouble(a.cornerRadius, b.cornerRadius, t), badgeWidget: b.badgeWidget, titlePositionPercentageOffset: lerpDouble( a.titlePositionPercentageOffset, b.titlePositionPercentageOffset, t, ), badgePositionPercentageOffset: lerpDouble( a.badgePositionPercentageOffset, b.badgePositionPercentageOffset, t, ), ); /// Used for equality check, see [EquatableMixin]. @override List get props => [ value, color, gradient, radius, showTitle, titleStyle, title, borderSide, cornerRadius, badgeWidget, titlePositionPercentageOffset, badgePositionPercentageOffset, ]; } /// Holds data to handle touch events, and touch responses in the [PieChart]. /// /// There is a touch flow, explained [here](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/handle_touches.md) /// in a simple way, each chart's renderer captures the touch events, and passes the pointerEvent /// to the painter, and gets touched spot, and wraps it into a concrete [PieTouchResponse]. class PieTouchData extends FlTouchData with EquatableMixin { /// You can disable or enable the touch system using [enabled] flag, /// /// [touchCallback] notifies you about the happened touch/pointer events. /// It gives you a [FlTouchEvent] which is the happened event such as [FlPointerHoverEvent], [FlTapUpEvent], ... /// It also gives you a [PieTouchResponse] which contains information /// about the elements that has touched. /// /// Using [mouseCursorResolver] you can change the mouse cursor /// based on the provided [FlTouchEvent] and [PieTouchResponse] PieTouchData({ bool? enabled, BaseTouchCallback? touchCallback, MouseCursorResolver? mouseCursorResolver, Duration? longPressDuration, }) : super( enabled ?? true, touchCallback, mouseCursorResolver, longPressDuration, ); /// Used for equality check, see [EquatableMixin]. @override List get props => [ enabled, touchCallback, mouseCursorResolver, longPressDuration, ]; } class PieTouchedSection with EquatableMixin { /// This class Contains [touchedSection], [touchedSectionIndex] that tells /// you touch happened on which section, /// [touchAngle] gives you angle of touch, /// and [touchRadius] gives you radius of the touch. PieTouchedSection( this.touchedSection, this.touchedSectionIndex, this.touchAngle, this.touchRadius, ); /// touch happened on this section final PieChartSectionData? touchedSection; /// touch happened on this position final int touchedSectionIndex; /// touch happened with this angle on the [PieChart] final double touchAngle; /// touch happened with this radius on the [PieChart] final double touchRadius; /// Used for equality check, see [EquatableMixin]. @override List get props => [ touchedSection, touchedSectionIndex, touchAngle, touchRadius, ]; } /// Holds information about touch response in the [PieChart]. /// /// You can override [PieTouchData.touchCallback] to handle touch events, /// it gives you a [PieTouchResponse] and you can do whatever you want. class PieTouchResponse extends BaseTouchResponse { /// If touch happens, [PieChart] processes it internally and passes out a [PieTouchResponse] PieTouchResponse({ required super.touchLocation, required this.touchedSection, }); /// Contains information about touched section, like index, angle, radius, ... final PieTouchedSection? touchedSection; /// Copies current [PieTouchResponse] to a new [PieTouchResponse], /// and replaces provided values. PieTouchResponse copyWith({ Offset? touchLocation, PieTouchedSection? touchedSection, }) => PieTouchResponse( touchLocation: touchLocation ?? this.touchLocation, touchedSection: touchedSection ?? this.touchedSection, ); } /// It lerps a [PieChartData] to another [PieChartData] (handles animation for updating values) class PieChartDataTween extends Tween { PieChartDataTween({required PieChartData begin, required PieChartData end}) : super(begin: begin, end: end); /// Lerps a [PieChartData] based on [t] value, check [Tween.lerp]. @override PieChartData lerp(double t) => begin!.lerp(begin!, end!, t); } ================================================ FILE: lib/src/chart/pie_chart/pie_chart_helper.dart ================================================ import 'package:fl_chart/src/chart/pie_chart/pie_chart_data.dart'; import 'package:flutter/widgets.dart'; extension PieChartSectionDataListExtension on List { List toWidgets() { final widgets = List.filled(length, Container()); var allWidgetsAreNull = true; asMap().entries.forEach((e) { final index = e.key; final section = e.value; if (section.badgeWidget != null) { widgets[index] = section.badgeWidget!; allWidgetsAreNull = false; } }); if (allWidgetsAreNull) { return List.empty(); } return widgets; } } ================================================ FILE: lib/src/chart/pie_chart/pie_chart_painter.dart ================================================ import 'dart:math' as math; import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart'; import 'package:fl_chart/src/chart/base/line.dart'; import 'package:fl_chart/src/chart/pie_chart/pie_chart_data.dart'; import 'package:fl_chart/src/extensions/paint_extension.dart'; import 'package:fl_chart/src/utils/canvas_wrapper.dart'; import 'package:fl_chart/src/utils/utils.dart'; import 'package:flutter/material.dart'; /// Paints [PieChartData] in the canvas, it can be used in a [CustomPainter] class PieChartPainter extends BaseChartPainter { /// Paints [dataList] into canvas, it is the animating [PieChartData], /// [targetData] is the animation's target and remains the same /// during animation, then we should use it when we need to show /// tooltips or something like that, because [dataList] is changing constantly. /// /// [textScale] used for scaling texts inside the chart, /// parent can use [MediaQuery.textScaleFactor] to respect /// the system's font size. PieChartPainter() : super() { _sectionPaint = Paint()..style = PaintingStyle.stroke; _sectionSaveLayerPaint = Paint(); _sectionStrokePaint = Paint()..style = PaintingStyle.stroke; _centerSpacePaint = Paint()..style = PaintingStyle.fill; _clipPaint = Paint(); } static const double _kRadiusSafetyMargin = 0.499; late Paint _sectionPaint; late Paint _sectionSaveLayerPaint; late Paint _sectionStrokePaint; late Paint _centerSpacePaint; late Paint _clipPaint; /// Paints [PieChartData] into the provided canvas. @override void paint( BuildContext context, CanvasWrapper canvasWrapper, PaintHolder holder, ) { super.paint(context, canvasWrapper, holder); final data = holder.data; if (data.sections.isEmpty) { return; } final sectionsAngle = calculateSectionsAngle(data.sections, data.sumValue); final centerRadius = calculateCenterRadius(canvasWrapper.size, holder); drawCenterSpace(canvasWrapper, centerRadius, holder); drawSections(canvasWrapper, sectionsAngle, centerRadius, holder); drawTexts(context, canvasWrapper, holder, centerRadius); } @visibleForTesting List calculateSectionsAngle( List sections, double sumValue, ) { if (sumValue == 0) { return List.filled(sections.length, 0); } return sections.map((section) { return 360 * (section.value / sumValue); }).toList(); } @visibleForTesting void drawCenterSpace( CanvasWrapper canvasWrapper, double centerRadius, PaintHolder holder, ) { final data = holder.data; final viewSize = canvasWrapper.size; final centerX = viewSize.width / 2; final centerY = viewSize.height / 2; _centerSpacePaint.color = data.centerSpaceColor; canvasWrapper.drawCircle( Offset(centerX, centerY), centerRadius, _centerSpacePaint, ); } @visibleForTesting void drawSections( CanvasWrapper canvasWrapper, List sectionsAngle, double centerRadius, PaintHolder holder, ) { final data = holder.data; final viewSize = canvasWrapper.size; final center = Offset(viewSize.width / 2, viewSize.height / 2); var tempAngle = data.startDegreeOffset; for (var i = 0; i < data.sections.length; i++) { final section = data.sections[i]; if (section.value == 0) { continue; } final sectionDegree = sectionsAngle[i]; if (sectionDegree == 360) { final radius = centerRadius + section.radius / 2; final rect = Rect.fromCircle(center: center, radius: radius); _sectionPaint ..setColorOrGradient( section.color, section.gradient, rect, ) ..strokeWidth = section.radius ..style = PaintingStyle.fill; final bounds = Rect.fromCircle( center: center, radius: centerRadius + section.radius, ); canvasWrapper ..saveLayer(bounds, _sectionSaveLayerPaint) ..drawCircle( center, centerRadius + section.radius, _sectionPaint..blendMode = BlendMode.srcOver, ) ..drawCircle( center, centerRadius, _sectionPaint..blendMode = BlendMode.srcOut, ) ..restore(); _sectionPaint.blendMode = BlendMode.srcOver; if (section.borderSide.width != 0.0 && section.borderSide.color.a != 0.0) { _sectionStrokePaint ..strokeWidth = section.borderSide.width ..color = section.borderSide.color; // Outer canvasWrapper ..drawCircle( center, centerRadius + section.radius - (section.borderSide.width / 2), _sectionStrokePaint, ) // Inner ..drawCircle( center, centerRadius + (section.borderSide.width / 2), _sectionStrokePaint, ); } return; } final sectionPath = generateSectionPath( section, data.sectionsSpace, tempAngle, sectionDegree, center, centerRadius, ); drawSection(section, sectionPath, canvasWrapper); drawSectionStroke(section, sectionPath, canvasWrapper, viewSize); tempAngle += sectionDegree; } } /// Generates a path around a section @visibleForTesting Path generateSectionPath( PieChartSectionData section, double sectionSpace, double tempAngle, double sectionDegree, Offset center, double centerRadius, ) { final sectionRadiusRect = Rect.fromCircle( center: center, radius: centerRadius + section.radius, ); final centerRadiusRect = Rect.fromCircle( center: center, radius: centerRadius, ); final startRadians = Utils().radians(tempAngle); final sweepRadians = Utils().radians(sectionDegree); final endRadians = startRadians + sweepRadians; final startLineDirection = Offset(math.cos(startRadians), math.sin(startRadians)); final startLineFrom = center + startLineDirection * centerRadius; final startLineTo = startLineFrom + startLineDirection * section.radius; final startLine = Line(startLineFrom, startLineTo); final endLineDirection = Offset(math.cos(endRadians), math.sin(endRadians)); final endLineFrom = center + endLineDirection * centerRadius; final endLineTo = endLineFrom + endLineDirection * section.radius; final endLine = Line(endLineFrom, endLineTo); Path sectionPath; if (section.cornerRadius > 0) { sectionPath = generateRoundedSectionPath( section, startRadians, sweepRadians, center, centerRadius, sectionRadiusRect, centerRadiusRect, ); } else { sectionPath = Path() ..moveTo(startLine.from.dx, startLine.from.dy) ..lineTo(startLine.to.dx, startLine.to.dy) ..arcTo(sectionRadiusRect, startRadians, sweepRadians, false) ..lineTo(endLine.from.dx, endLine.from.dy) ..arcTo(centerRadiusRect, endRadians, -sweepRadians, false) ..moveTo(startLine.from.dx, startLine.from.dy) ..close(); } /// Apply section-space separators as parallel radial cuts. if (sectionSpace != 0) { final startLineSeparatorPath = createRectPathAroundLine( Line(startLineFrom, startLineTo), sectionSpace, ); try { sectionPath = Path.combine( PathOperation.difference, sectionPath, startLineSeparatorPath, ); } catch (_) { /// It's a flutter engine issue with [Path.combine] in web-html renderer /// https://github.com/imaNNeo/fl_chart/issues/955 } final endLineSeparatorPath = createRectPathAroundLine(Line(endLineFrom, endLineTo), sectionSpace); try { sectionPath = Path.combine( PathOperation.difference, sectionPath, endLineSeparatorPath, ); } catch (_) { /// It's a flutter engine issue with [Path.combine] in web-html renderer /// https://github.com/imaNNeo/fl_chart/issues/955 } } return sectionPath; } /// Generates a Path for a pie-section with rounded corners. /// /// This method builds a path that rounds both the outer and inner /// corners of a pie section (when `centerRadius > 0`). It clamps the /// requested `section.cornerRadius` separately for the outer and inner /// edges to avoid geometric overlap when the section is narrow or the /// radii would be too large for the available arc length. /// /// Important behaviors / notes: /// - If `cornerRadius <= 1` the method returns a standard (non-rounded) /// section path for performance and to avoid tiny visual artifacts. /// - Outer and inner corner radii are clamped independently (`clampedOuterRadius` /// and `clampedInnerRadius`) to reasonable maxima based on section size /// and sweep angle. /// - The code supports `centerRadius == 0` (fully filled pie) and /// `centerRadius > 0` (donut). When `centerRadius > 0` the inner /// corners are rounded as well. /// - `sectionsSpace` trimming is applied later by subtracting separator /// rectangles from the resulting path (see `generateSectionPath`). /// - There are known platform/engine caveats when using `Path.combine` on /// web-html renderer; the subtraction steps are guarded with try/catch /// where used. @visibleForTesting Path generateRoundedSectionPath( PieChartSectionData section, double startRadians, double sweepRadians, Offset center, double centerRadius, Rect sectionRadiusRect, Rect centerRadiusRect, ) { final endRadians = startRadians + sweepRadians; final outerRadius = centerRadius + section.radius; // User-provided corner radius (applies uniformly to this section). final cornerRadius = section.cornerRadius; final path = Path(); if (cornerRadius <= 1) { // if corner radius is too small, return standard section path final innerStart = center + Offset(math.cos(startRadians), math.sin(startRadians)) * centerRadius; final outerStart = center + Offset(math.cos(startRadians), math.sin(startRadians)) * outerRadius; final innerEnd = center + Offset(math.cos(endRadians), math.sin(endRadians)) * centerRadius; path ..moveTo(innerStart.dx, innerStart.dy) ..lineTo(outerStart.dx, outerStart.dy) ..arcTo(sectionRadiusRect, startRadians, sweepRadians, false) ..lineTo(innerEnd.dx, innerEnd.dy) ..arcTo(centerRadiusRect, endRadians, -sweepRadians, false) ..close(); } else { // Clamp requested radii to avoid overlaps. We compute a separate // maximum for the outer arc (based on section radius and sweep angle) // and for the inner arc (based on centerRadius). This keeps rounding // visually stable across different section sizes. final maxRadiusForSection = section.radius * _kRadiusSafetyMargin; final maxRadiusForOuterArc = sweepRadians * outerRadius * _kRadiusSafetyMargin; final maxRadiusForInnerArc = centerRadius > 0 ? sweepRadians * centerRadius * _kRadiusSafetyMargin : 0.0; final clampedOuterRadius = math.min( cornerRadius, math.min(maxRadiusForSection, maxRadiusForOuterArc), ); final clampedInnerRadius = math.min( cornerRadius, math.min(maxRadiusForSection, maxRadiusForInnerArc), ); // Compute angular offsets that correspond to the linear corner radii. // These are used to trim the sweep angles so the rounded joins fit // cleanly along the arc. final outerAngleOffset = outerRadius > 0 ? clampedOuterRadius / outerRadius : 0.0; final innerAngleOffset = centerRadius > 0 ? clampedInnerRadius / centerRadius : 0.0; // Tight angles for outside corners final outerStartAngle = startRadians + outerAngleOffset; final outerEndAngle = endRadians - outerAngleOffset; final outerSweepAngle = sweepRadians - (2 * outerAngleOffset); // Tight angles for inside corners final innerStartAngle = startRadians + innerAngleOffset; final innerEndAngle = endRadians - innerAngleOffset; final innerSweepAngle = sweepRadians - (2 * innerAngleOffset); // Points of the outer corners final outerStartPoint = center + Offset(math.cos(startRadians), math.sin(startRadians)) * outerRadius; final outerEndPoint = center + Offset(math.cos(endRadians), math.sin(endRadians)) * outerRadius; final outerStartRounded = center + Offset(math.cos(outerStartAngle), math.sin(outerStartAngle)) * outerRadius; final outerEndRounded = center + Offset(math.cos(outerEndAngle), math.sin(outerEndAngle)) * outerRadius; // Points of the inner corners final innerStartPoint = center + Offset(math.cos(startRadians), math.sin(startRadians)) * centerRadius; final innerEndPoint = center + Offset(math.cos(endRadians), math.sin(endRadians)) * centerRadius; final innerStartRounded = center + Offset(math.cos(innerStartAngle), math.sin(innerStartAngle)) * centerRadius; final innerEndRounded = center + Offset(math.cos(innerEndAngle), math.sin(innerEndAngle)) * centerRadius; // Control points used to connect the rounded corner bezier segments to // the inner/outer arcs. They lie along the original radial directions // but offset inward/outward by the clamped radii. final startOuterControl = center + Offset(math.cos(startRadians), math.sin(startRadians)) * (outerRadius - clampedOuterRadius); final endOuterControl = center + Offset(math.cos(endRadians), math.sin(endRadians)) * (outerRadius - clampedOuterRadius); final startInnerControl = center + Offset(math.cos(startRadians), math.sin(startRadians)) * (centerRadius + clampedInnerRadius); final endInnerControl = center + Offset(math.cos(endRadians), math.sin(endRadians)) * (centerRadius + clampedInnerRadius); // Build the rounded path step by step if (centerRadius > 0) { // Start from the inner rounded corner path.moveTo(innerStartRounded.dx, innerStartRounded.dy); // Inner starting rounded corner (quadratic join). If the inner // radius is small we fall back to a straight line to avoid tiny // bezier segments. if (clampedInnerRadius > 1) { path.quadraticBezierTo( innerStartPoint.dx, innerStartPoint.dy, startInnerControl.dx, startInnerControl.dy, ); } else { path.lineTo(innerStartPoint.dx, innerStartPoint.dy); } // Straight line to the outer edge path.lineTo(startOuterControl.dx, startOuterControl.dy); // Outer starting rounded corner (quadratic join). if (clampedOuterRadius > 1) { path.quadraticBezierTo( outerStartPoint.dx, outerStartPoint.dy, outerStartRounded.dx, outerStartRounded.dy, ); } else { path.lineTo(outerStartPoint.dx, outerStartPoint.dy); } } else { // If there is no centerRadius, start from the center path ..moveTo(center.dx, center.dy) ..lineTo(startOuterControl.dx, startOuterControl.dy); if (clampedOuterRadius > 1) { path.quadraticBezierTo( outerStartPoint.dx, outerStartPoint.dy, outerStartRounded.dx, outerStartRounded.dy, ); } else { path.lineTo(outerStartPoint.dx, outerStartPoint.dy); } } // Draw the outer arc between the two rounded outer corner points. if (outerSweepAngle > 0) { path.arcTo(sectionRadiusRect, outerStartAngle, outerSweepAngle, false); } // Outer ending rounded corner (quadratic join). if (clampedOuterRadius > 1) { path ..lineTo(outerEndRounded.dx, outerEndRounded.dy) ..quadraticBezierTo( outerEndPoint.dx, outerEndPoint.dy, endOuterControl.dx, endOuterControl.dy, ); } else { path.lineTo(outerEndPoint.dx, outerEndPoint.dy); } if (centerRadius > 0) { // Straight line to the inner edge path.lineTo(endInnerControl.dx, endInnerControl.dy); // Inner ending rounded corner (quadratic join). if (clampedInnerRadius > 1) { path.quadraticBezierTo( innerEndPoint.dx, innerEndPoint.dy, innerEndRounded.dx, innerEndRounded.dy, ); } else { path.lineTo(innerEndPoint.dx, innerEndPoint.dy); } // Draw the inner arc between the two rounded inner corner points. if (innerSweepAngle > 0) { path.arcTo(centerRadiusRect, innerEndAngle, -innerSweepAngle, false); } } else { // If there is no centerRadius, close towards the center path.lineTo(center.dx, center.dy); } path.close(); } return path; } /// Creates a rect around a narrow line @visibleForTesting Path createRectPathAroundLine(Line line, double width) { width = width / 2; final normalized = line.normalize(); final verticalAngle = line.direction() + (math.pi / 2); final verticalDirection = Offset(math.cos(verticalAngle), math.sin(verticalAngle)); final startPoint1 = Offset( line.from.dx - (normalized * (width / 2)).dx - (verticalDirection * width).dx, line.from.dy - (normalized * (width / 2)).dy - (verticalDirection * width).dy, ); final startPoint2 = Offset( line.to.dx + (normalized * (width / 2)).dx - (verticalDirection * width).dx, line.to.dy + (normalized * (width / 2)).dy - (verticalDirection * width).dy, ); final startPoint3 = Offset( startPoint2.dx + (verticalDirection * (width * 2)).dx, startPoint2.dy + (verticalDirection * (width * 2)).dy, ); final startPoint4 = Offset( startPoint1.dx + (verticalDirection * (width * 2)).dx, startPoint1.dy + (verticalDirection * (width * 2)).dy, ); return Path() ..moveTo(startPoint1.dx, startPoint1.dy) ..lineTo(startPoint2.dx, startPoint2.dy) ..lineTo(startPoint3.dx, startPoint3.dy) ..lineTo(startPoint4.dx, startPoint4.dy) ..lineTo(startPoint1.dx, startPoint1.dy); } @visibleForTesting void drawSection( PieChartSectionData section, Path sectionPath, CanvasWrapper canvasWrapper, ) { _sectionPaint ..setColorOrGradient( section.color, section.gradient, sectionPath.getBounds(), ) ..style = PaintingStyle.fill; canvasWrapper.drawPath(sectionPath, _sectionPaint); } @visibleForTesting void drawSectionStroke( PieChartSectionData section, Path sectionPath, CanvasWrapper canvasWrapper, Size viewSize, ) { if (section.borderSide.width != 0.0 && section.borderSide.color.a != 0.0) { canvasWrapper ..saveLayer( Rect.fromLTWH(0, 0, viewSize.width, viewSize.height), _clipPaint, ) ..clipPath(sectionPath); _sectionStrokePaint ..strokeWidth = section.borderSide.width * 2 ..color = section.borderSide.color; canvasWrapper ..drawPath( sectionPath, _sectionStrokePaint, ) ..restore(); } } /// Calculates layout of overlaying elements, includes: /// - title text /// - badge widget positions @visibleForTesting void drawTexts( BuildContext context, CanvasWrapper canvasWrapper, PaintHolder holder, double centerRadius, ) { final data = holder.data; final viewSize = canvasWrapper.size; final center = Offset(viewSize.width / 2, viewSize.height / 2); var tempAngle = data.startDegreeOffset; for (var i = 0; i < data.sections.length; i++) { final section = data.sections[i]; if (section.value == 0) { continue; } final startAngle = tempAngle; final sweepAngle = 360 * (section.value / data.sumValue); final sectionCenterAngle = startAngle + (sweepAngle / 2); double? rotateAngle; if (data.titleSunbeamLayout) { if (sectionCenterAngle >= 90 && sectionCenterAngle <= 270) { rotateAngle = sectionCenterAngle - 180; } else { rotateAngle = sectionCenterAngle; } } Offset sectionCenter(double percentageOffset) => center + Offset( math.cos(Utils().radians(sectionCenterAngle)) * (centerRadius + (section.radius * percentageOffset)), math.sin(Utils().radians(sectionCenterAngle)) * (centerRadius + (section.radius * percentageOffset)), ); final sectionCenterOffsetTitle = sectionCenter(section.titlePositionPercentageOffset); if (section.showTitle) { final span = TextSpan( style: Utils().getThemeAwareTextStyle(context, section.titleStyle), text: section.title, ); final tp = TextPainter( text: span, textAlign: TextAlign.center, textDirection: TextDirection.ltr, textScaler: holder.textScaler, )..layout(); canvasWrapper.drawText( tp, sectionCenterOffsetTitle - Offset(tp.width / 2, tp.height / 2), rotateAngle, ); } tempAngle += sweepAngle; } } /// Calculates center radius based on the provided sections radius @visibleForTesting double calculateCenterRadius( Size viewSize, PaintHolder holder, ) { final data = holder.data; if (data.centerSpaceRadius.isFinite) { return data.centerSpaceRadius; } final maxRadius = data.sections.reduce((a, b) => a.radius > b.radius ? a : b).radius; return (viewSize.shortestSide - (maxRadius * 2)) / 2; } /// Makes a [PieTouchedSection] based on the provided [localPosition] /// /// Processes [localPosition] and checks /// the elements of the chart that are near the offset, /// then makes a [PieTouchedSection] from the elements that has been touched. PieTouchedSection handleTouch( Offset localPosition, Size viewSize, PaintHolder holder, ) { final data = holder.data; final sectionsAngle = calculateSectionsAngle(data.sections, data.sumValue); final centerRadius = calculateCenterRadius(viewSize, holder); final center = Offset(viewSize.width / 2, viewSize.height / 2); final touchedPoint2 = localPosition - center; final touchX = touchedPoint2.dx; final touchY = touchedPoint2.dy; final touchR = math.sqrt(math.pow(touchX, 2) + math.pow(touchY, 2)); var touchAngle = Utils().degrees(math.atan2(touchY, touchX)); touchAngle = touchAngle < 0 ? (180 - touchAngle.abs()) + 180 : touchAngle; PieChartSectionData? foundSectionData; var foundSectionDataPosition = -1; var tempAngle = data.startDegreeOffset; for (var i = 0; i < data.sections.length; i++) { final section = data.sections[i]; final sectionAngle = sectionsAngle[i]; if (sectionAngle == 360) { final distance = math.sqrt( math.pow(localPosition.dx - center.dx, 2) + math.pow(localPosition.dy - center.dy, 2), ); if (distance >= centerRadius && distance <= section.radius + centerRadius) { foundSectionData = section; foundSectionDataPosition = i; } break; } final sectionPath = generateSectionPath( section, data.sectionsSpace, tempAngle, sectionAngle, center, centerRadius, ); if (sectionPath.contains(localPosition)) { foundSectionData = section; foundSectionDataPosition = i; break; } tempAngle += sectionAngle; } return PieTouchedSection( foundSectionData, foundSectionDataPosition, touchAngle, touchR, ); } /// Exposes offset for laying out the badge widgets upon the chart. Map getBadgeOffsets( Size viewSize, PaintHolder holder, ) { final data = holder.data; final center = viewSize.center(Offset.zero); final badgeWidgetsOffsets = {}; if (data.sections.isEmpty) { return badgeWidgetsOffsets; } var tempAngle = data.startDegreeOffset; final sectionsAngle = calculateSectionsAngle(data.sections, data.sumValue); for (var i = 0; i < data.sections.length; i++) { final section = data.sections[i]; final startAngle = tempAngle; final sweepAngle = sectionsAngle[i]; final sectionCenterAngle = startAngle + (sweepAngle / 2); final centerRadius = calculateCenterRadius(viewSize, holder); Offset sectionCenter(double percentageOffset) => center + Offset( math.cos(Utils().radians(sectionCenterAngle)) * (centerRadius + (section.radius * percentageOffset)), math.sin(Utils().radians(sectionCenterAngle)) * (centerRadius + (section.radius * percentageOffset)), ); final sectionCenterOffsetBadgeWidget = sectionCenter(section.badgePositionPercentageOffset); badgeWidgetsOffsets[i] = sectionCenterOffsetBadgeWidget; tempAngle += sweepAngle; } return badgeWidgetsOffsets; } } ================================================ FILE: lib/src/chart/pie_chart/pie_chart_renderer.dart ================================================ import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart'; import 'package:fl_chart/src/chart/base/base_chart/render_base_chart.dart'; import 'package:fl_chart/src/chart/pie_chart/pie_chart_helper.dart'; import 'package:fl_chart/src/chart/pie_chart/pie_chart_painter.dart'; import 'package:fl_chart/src/utils/canvas_wrapper.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; // coverage:ignore-start /// Low level PieChart Widget. class PieChartLeaf extends MultiChildRenderObjectWidget { PieChartLeaf({ super.key, required this.data, required this.targetData, }) : super(children: targetData.sections.toWidgets()); final PieChartData data; final PieChartData targetData; @override RenderPieChart createRenderObject(BuildContext context) => RenderPieChart( context, data, targetData, MediaQuery.of(context).textScaler, ); @override void updateRenderObject(BuildContext context, RenderPieChart renderObject) { renderObject ..data = data ..targetData = targetData ..textScaler = MediaQuery.of(context).textScaler ..buildContext = context; } } // coverage:ignore-end /// Renders our PieChart, also handles hitTest. class RenderPieChart extends RenderBaseChart with ContainerRenderObjectMixin, RenderBoxContainerDefaultsMixin implements MouseTrackerAnnotation { RenderPieChart( BuildContext context, PieChartData data, PieChartData targetData, TextScaler textScaler, ) : _data = data, _targetData = targetData, _textScaler = textScaler, super(targetData.pieTouchData, context, canBeScaled: false); PieChartData get data => _data; PieChartData _data; set data(PieChartData value) { if (_data == value) return; _data = value; // We must update layout to draw badges correctly! markNeedsLayout(); } PieChartData get targetData => _targetData; PieChartData _targetData; set targetData(PieChartData value) { if (_targetData == value) return; _targetData = value; super.updateBaseTouchData(_targetData.pieTouchData); // We must update layout to draw badges correctly! markNeedsLayout(); } TextScaler get textScaler => _textScaler; TextScaler _textScaler; set textScaler(TextScaler value) { if (_textScaler == value) return; _textScaler = value; markNeedsPaint(); } // We couldn't mock [size] property of this class, that's why we have this @visibleForTesting Size? mockTestSize; @visibleForTesting PieChartPainter painter = PieChartPainter(); PaintHolder get paintHolder => PaintHolder(data, targetData, textScaler); @override void setupParentData(RenderBox child) { if (child.parentData is! MultiChildLayoutParentData) { child.parentData = MultiChildLayoutParentData(); } } @override void performLayout() { var child = firstChild; size = computeDryLayout(constraints); final childConstraints = constraints.loosen(); var counter = 0; final badgeOffsets = painter.getBadgeOffsets( mockTestSize ?? size, paintHolder, ); while (child != null) { if (counter >= badgeOffsets.length) { break; } child.layout(childConstraints, parentUsesSize: true); final childParentData = child.parentData! as MultiChildLayoutParentData; final sizeOffset = Offset(child.size.width / 2, child.size.height / 2); childParentData.offset = badgeOffsets[counter]! - sizeOffset; child = childParentData.nextSibling; counter++; } } @override bool hitTestChildren(BoxHitTestResult result, {required Offset position}) => defaultHitTestChildren(result, position: position); @override void paint(PaintingContext context, Offset offset) { final canvas = context.canvas ..save() ..translate(offset.dx, offset.dy); painter.paint( buildContext, CanvasWrapper(canvas, mockTestSize ?? size), paintHolder, ); canvas.restore(); badgeWidgetPaint(context, offset); } void badgeWidgetPaint(PaintingContext context, Offset offset) { RenderObject? child = firstChild; var counter = 0; while (child != null) { final childParentData = child.parentData! as MultiChildLayoutParentData; if (data.sections[counter].value > 0) { context.paintChild(child, childParentData.offset + offset); } child = childParentData.nextSibling; counter++; } } @override bool hitTestSelf(Offset position) { if (!targetData.pieTouchData.enabled) { return false; } return super.hitTestSelf(position); } @override PieTouchResponse getResponseAtLocation(Offset localPosition) { return PieTouchResponse( touchLocation: localPosition, touchedSection: painter.handleTouch( localPosition, mockTestSize ?? size, paintHolder, ), ); } @override void visitChildrenForSemantics(RenderObjectVisitor visitor) { /// It produces an error when we change the sections list, Check this issue: /// https://github.com/imaNNeo/fl_chart/issues/861 /// /// Below is the error message: /// Updated layout information required for RenderSemanticsAnnotations#f3b96 NEEDS-LAYOUT NEEDS-PAINT to calculate semantics. /// /// I don't know how to solve this error. That's why we disabled semantics for now. } } ================================================ FILE: lib/src/chart/radar_chart/radar_chart.dart ================================================ import 'package:fl_chart/src/chart/radar_chart/radar_chart_data.dart'; import 'package:fl_chart/src/chart/radar_chart/radar_chart_renderer.dart'; import 'package:flutter/material.dart'; /// Renders a radar chart as a widget, using provided [RadarChartData]. class RadarChart extends ImplicitlyAnimatedWidget { /// [data] determines how the [RadarChart] should be look like, /// when you make any change in the [RadarChart], it updates /// new values with animation, and duration is [duration]. /// also you can change the [curve] /// which default is [Curves.linear]. const RadarChart( this.data, { super.key, @Deprecated('Please use [duration] instead') Duration? swapAnimationDuration, Duration duration = const Duration(milliseconds: 150), @Deprecated('Please use [curve] instead') Curve? swapAnimationCurve, Curve curve = Curves.linear, }) : super( duration: swapAnimationDuration ?? duration, curve: swapAnimationCurve ?? curve, ); /// Determines how the [RadarChart] should be look like. final RadarChartData data; @override _RadarChartState createState() => _RadarChartState(); } class _RadarChartState extends AnimatedWidgetBaseState { /// we handle under the hood animations (implicit animations) via this tween, /// it lerps between the old [RadarChartData] to the new one. RadarChartDataTween? _radarChartDataTween; @override Widget build(BuildContext context) { final showingData = _getDate(); return RadarChartLeaf( data: _radarChartDataTween!.evaluate(animation), targetData: showingData, ); } RadarChartData _getDate() => widget.data; @override void forEachTween(TweenVisitor visitor) { _radarChartDataTween = visitor( _radarChartDataTween, widget.data, (dynamic value) => RadarChartDataTween(begin: value as RadarChartData, end: widget.data), ) as RadarChartDataTween?; } } ================================================ FILE: lib/src/chart/radar_chart/radar_chart_data.dart ================================================ // coverage:ignore-file import 'dart:ui'; import 'package:equatable/equatable.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/chart/radar_chart/radar_extension.dart'; import 'package:fl_chart/src/utils/lerp.dart'; import 'package:flutter/material.dart'; typedef GetTitleByIndexFunction = RadarChartTitle Function( int index, double angle, ); enum RadarShape { circle, polygon, } class RadarChartTitle { const RadarChartTitle({ required this.text, this.children, this.angle = 0, this.positionPercentageOffset, }); /// [text] is used to draw titles outside the [RadarChart] final String text; /// [children] is used to draw additional titles outside the [RadarChart] final List? children; /// [angle] is used to rotate the title final double angle; /// [positionPercentageOffset] is the place of showing title on the [RadarChart] /// The higher the value of this field, the more titles move away from the chart. /// The value of [positionPercentageOffset] takes precedence over the value of /// [RadarChartData.titlePositionPercentageOffset], even if it is set. final double? positionPercentageOffset; } /// [RadarChart] needs this class to render itself. /// /// It holds data needed to draw a radar chart, /// including radar dataSets, colors, ... class RadarChartData extends BaseChartData with EquatableMixin { /// [RadarChart] draws some [dataSets] in a radar-shaped chart. /// it fills the radar area with [radarBackgroundColor] /// and draws radar border with [radarBorderData] /// then draws a grid over it, you can customize it using [gridBorderData]. /// /// it draws some titles based on the number of [dataSets] values. /// the titles are shown near each radar grid or line. /// for changing the titles you can modify the [getTitle] field. /// and for styling the titles you can use [titleTextStyle]. /// /// it draws some ticks. and you can customize the number of ticks by modifying the [titleCount] /// and style the ticks titles with [ticksTextStyle]. /// for changing the ticks color and border width you can use [tickBorderData]. /// /// You can modify [radarTouchData] to customize touch behaviors and responses. RadarChartData({ required List? dataSets, Color? radarBackgroundColor, BorderSide? radarBorderData, RadarShape? radarShape, this.getTitle, this.titleTextStyle, double? titlePositionPercentageOffset, int? tickCount, this.ticksTextStyle, BorderSide? tickBorderData, BorderSide? gridBorderData, RadarTouchData? radarTouchData, this.isMinValueAtCenter = false, super.borderData, }) : assert(dataSets != null && dataSets.hasEqualDataEntriesLength), assert( tickCount == null || tickCount >= 1, "RadarChart need's at least 1 tick", ), assert( titlePositionPercentageOffset == null || titlePositionPercentageOffset >= 0 && titlePositionPercentageOffset <= 1, 'titlePositionPercentageOffset must be something between 0 and 1 ', ), dataSets = dataSets ?? const [], radarBackgroundColor = radarBackgroundColor ?? Colors.transparent, radarBorderData = radarBorderData ?? const BorderSide(width: 2), radarShape = radarShape ?? RadarShape.circle, radarTouchData = radarTouchData ?? RadarTouchData(), titlePositionPercentageOffset = titlePositionPercentageOffset ?? 0.2, tickCount = tickCount ?? 1, tickBorderData = tickBorderData ?? const BorderSide(width: 2), gridBorderData = gridBorderData ?? const BorderSide(width: 2), super(); /// [RadarChart] draw [dataSets] that each of them showing a list of [RadarEntry] final List dataSets; /// [radarBackgroundColor] draw the background color of the [RadarChart] final Color radarBackgroundColor; /// [radarBorderData] is used to draw [RadarChart] border final BorderSide radarBorderData; /// [radarShape] is used to draw [RadarChart] border and background final RadarShape radarShape; /// [getTitle] is used to draw titles outside the [RadarChart] /// [getTitle] is type of [GetTitleByIndexFunction] so you should return a valid [RadarChartTitle] /// for each [index] (we provide a default [angle] = index * 360 / titleCount) /// /// ```dart /// getTitle: (index, angle) { /// switch (index) { /// case 0: /// return RadarChartTitle(text: 'Mobile or Tablet', angle: angle); /// case 2: /// return RadarChartTitle(text: 'Desktop', angle: angle); /// case 1: /// return RadarChartTitle(text: 'TV', angle: angle); /// default: /// return const RadarChartTitle(text: ''); /// } /// } /// ``` final GetTitleByIndexFunction? getTitle; /// Defines style of showing [RadarChart] titles. final TextStyle? titleTextStyle; /// the [titlePositionPercentageOffset] is the place of showing title on the [RadarChart] /// The higher the value of this field, the more titles move away from the chart. /// this field should be between 0 and 1, /// if it is 0 the title will be drawn near the inside section, /// if it is 1 the title will be drawn near the outside of section, /// the default value is 0.2. final double titlePositionPercentageOffset; /// Defines the number of ticks that should be paint in [RadarChart] /// the default & minimum value of this field is 1. final int tickCount; /// Defines style of showing [RadarChart] tick titles. final TextStyle? ticksTextStyle; /// Defines style of showing [RadarChart] tick borders. final BorderSide tickBorderData; /// Defines style of showing [RadarChart] grid borders. final BorderSide gridBorderData; /// Handles touch behaviors and responses. final RadarTouchData radarTouchData; /// If [isMinValueAtCenter] is true, the minimum value of the [RadarChart] will be at the center of the chart. final bool isMinValueAtCenter; /// [titleCount] we use this value to determine number of [RadarChart] grid or lines. int get titleCount => dataSets[0].dataEntries.length; /// defines the maximum [RadarEntry] value in all [dataSets] /// we use this value to calculate the maximum value of ticks. RadarEntry get maxEntry { var maximum = dataSets.first.dataEntries.first; for (final dataSet in dataSets) { for (final entry in dataSet.dataEntries) { if (entry.value > maximum.value) maximum = entry; } } return maximum; } /// defines the minimum [RadarEntry] value in all [dataSets] /// we use this value to calculate the minimum value of ticks. RadarEntry get minEntry { var minimum = dataSets.first.dataEntries.first; for (final dataSet in dataSets) { for (final entry in dataSet.dataEntries) { if (entry.value < minimum.value) minimum = entry; } } return minimum; } /// Copies current [RadarChartData] to a new [RadarChartData], /// and replaces provided values. RadarChartData copyWith({ List? dataSets, Color? radarBackgroundColor, BorderSide? radarBorderData, RadarShape? radarShape, GetTitleByIndexFunction? getTitle, TextStyle? titleTextStyle, double? titlePositionPercentageOffset, int? tickCount, TextStyle? ticksTextStyle, BorderSide? tickBorderData, BorderSide? gridBorderData, RadarTouchData? radarTouchData, bool? isMinValueAtCenter, FlBorderData? borderData, }) => RadarChartData( dataSets: dataSets ?? this.dataSets, radarBackgroundColor: radarBackgroundColor ?? this.radarBackgroundColor, radarBorderData: radarBorderData ?? this.radarBorderData, radarShape: radarShape ?? this.radarShape, getTitle: getTitle ?? this.getTitle, titleTextStyle: titleTextStyle ?? this.titleTextStyle, titlePositionPercentageOffset: titlePositionPercentageOffset ?? this.titlePositionPercentageOffset, tickCount: tickCount ?? this.tickCount, ticksTextStyle: ticksTextStyle ?? this.ticksTextStyle, tickBorderData: tickBorderData ?? this.tickBorderData, gridBorderData: gridBorderData ?? this.gridBorderData, radarTouchData: radarTouchData ?? this.radarTouchData, isMinValueAtCenter: isMinValueAtCenter ?? this.isMinValueAtCenter, borderData: borderData ?? this.borderData, ); /// Lerps a [BaseChartData] based on [t] value, check [Tween.lerp]. @override RadarChartData lerp(BaseChartData a, BaseChartData b, double t) { if (a is RadarChartData && b is RadarChartData) { return RadarChartData( dataSets: lerpRadarDataSetList(a.dataSets, b.dataSets, t), radarBackgroundColor: Color.lerp(a.radarBackgroundColor, b.radarBackgroundColor, t), getTitle: b.getTitle, titleTextStyle: TextStyle.lerp(a.titleTextStyle, b.titleTextStyle, t), titlePositionPercentageOffset: lerpDouble( a.titlePositionPercentageOffset, b.titlePositionPercentageOffset, t, ), tickCount: lerpInt(a.tickCount, b.tickCount, t), ticksTextStyle: TextStyle.lerp(a.ticksTextStyle, b.ticksTextStyle, t), gridBorderData: BorderSide.lerp(a.gridBorderData, b.gridBorderData, t), radarBorderData: BorderSide.lerp(a.radarBorderData, b.radarBorderData, t), radarShape: b.radarShape, tickBorderData: BorderSide.lerp(a.tickBorderData, b.tickBorderData, t), borderData: FlBorderData.lerp(a.borderData, b.borderData, t), isMinValueAtCenter: b.isMinValueAtCenter, radarTouchData: b.radarTouchData, ); } else { throw Exception('Illegal State'); } } /// Used for equality check, see [EquatableMixin]. @override List get props => [ borderData, dataSets, radarBackgroundColor, radarBorderData, radarShape, getTitle, titleTextStyle, titlePositionPercentageOffset, tickCount, ticksTextStyle, tickBorderData, gridBorderData, radarTouchData, isMinValueAtCenter, ]; } /// the data values for drawing [RadarChart] sections class RadarDataSet with EquatableMixin { /// [RadarChart] can contain multiple [RadarDataSet] And it shows them on top of each other. /// each [RadarDataSet] has a set of [dataEntries] /// and the [RadarChart] uses this [dataEntries] to draw the chart. /// /// it fill dataSets with [fillColor]. /// /// the [RadarDataSet] can have custom border. for changing border of [RadarDataSet] /// you can modify the [borderColor] and [borderWidth]. RadarDataSet({ List? dataEntries, Color? fillColor, this.fillGradient, Color? borderColor, double? borderWidth, double? entryRadius, }) : assert( dataEntries == null || dataEntries.isEmpty || dataEntries.length >= 3, 'Radar needs at least 3 RadarEntry', ), dataEntries = dataEntries ?? const [], fillColor = fillColor ?? Colors.cyan, borderColor = borderColor ?? Colors.cyan, borderWidth = borderWidth ?? 2.0, entryRadius = entryRadius ?? 5.0; /// each section or dataSets consists of a set of [dataEntries]. final List dataEntries; /// defines the color that fills the [RadarDataSet]. final Color fillColor; // defines the gradient color that fills the [RadarDataSet]. final Gradient? fillGradient; /// defines the border color of the [RadarDataSet]. /// if [borderColor] is not defined it will replaced with [fillColor]. final Color borderColor; /// defines the width of [RadarDataSet] border. /// the default value of this field is 2.0 final double borderWidth; /// defines the radius of each entry /// the default value of this field is 5.0 final double entryRadius; /// Copies current [RadarDataSet] to a new [RadarDataSet], /// and replaces provided values. RadarDataSet copyWith({ List? dataEntries, Color? fillColor, Gradient? fillGradient, Color? borderColor, double? borderWidth, double? entryRadius, }) => RadarDataSet( dataEntries: dataEntries ?? this.dataEntries, fillColor: fillColor ?? this.fillColor, fillGradient: fillGradient, borderColor: borderColor ?? this.borderColor, borderWidth: borderWidth ?? this.borderWidth, entryRadius: entryRadius ?? this.entryRadius, ); /// Lerps a [RadarDataSet] based on [t] value, check [Tween.lerp]. static RadarDataSet lerp(RadarDataSet a, RadarDataSet b, double t) => RadarDataSet( dataEntries: lerpRadarEntryList(a.dataEntries, b.dataEntries, t), fillColor: Color.lerp(a.fillColor, b.fillColor, t), fillGradient: Gradient.lerp(a.fillGradient, b.fillGradient, t), borderColor: Color.lerp(a.borderColor, b.borderColor, t), borderWidth: lerpDouble(a.borderWidth, b.borderWidth, t), entryRadius: lerpDouble(a.entryRadius, b.entryRadius, t), ); /// Used for equality check, see [EquatableMixin]. @override List get props => [ dataEntries, fillColor, fillGradient, borderColor, borderWidth, entryRadius, ]; } /// holds the data about each entry or point in [RadarChart] class RadarEntry with EquatableMixin { /// [RadarChart] draws every point or entry with [RadarEntry] const RadarEntry({required this.value}); /// [RadarChart] uses this field to render every point in chart. final double value; /// Lerps a [RadarEntry] based on [t] value, check [Tween.lerp]. RadarEntry copyWith({double? value}) => RadarEntry(value: value ?? this.value); /// Lerps a [RadarDataSet] based on [t] value, check [Tween.lerp]. static RadarEntry lerp(RadarEntry a, RadarEntry b, double t) => RadarEntry(value: lerpDouble(a.value, b.value, t)!); /// Used for equality check, see [EquatableMixin]. @override List get props => [value]; } /// Holds data to handle touch events, and touch responses in the [RadarChart]. /// /// There is a touch flow, explained [here](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/handle_touches.md) /// in a simple way, each chart's renderer captures the touch events, and passes the pointerEvent /// to the painter, and gets touched spot, and wraps it into a concrete [RadarTouchResponse]. class RadarTouchData extends FlTouchData with EquatableMixin { /// You can disable or enable the touch system using [enabled] flag, /// /// [touchCallback] notifies you about the happened touch/pointer events. /// It gives you a [FlTouchEvent] which is the happened event such as [FlPointerHoverEvent], [FlTapUpEvent], ... /// It also gives you a [RadarTouchResponse] which contains information /// about the elements that has touched. /// /// Using [mouseCursorResolver] you can change the mouse cursor /// based on the provided [FlTouchEvent] and [RadarTouchResponse] RadarTouchData({ bool? enabled, BaseTouchCallback? touchCallback, MouseCursorResolver? mouseCursorResolver, Duration? longPressDuration, double? touchSpotThreshold, }) : touchSpotThreshold = touchSpotThreshold ?? 10, super( enabled ?? true, touchCallback, mouseCursorResolver, longPressDuration, ); /// we find the nearest spots on touched position based on this threshold final double touchSpotThreshold; /// Used for equality check, see [EquatableMixin]. @override List get props => [ enabled, touchCallback, mouseCursorResolver, longPressDuration, touchSpotThreshold, ]; } /// Holds information about touch response in the [RadarTouchResponse]. /// /// You can override [RadarTouchData.touchCallback] to handle touch events, /// it gives you a [RadarTouchResponse] and you can do whatever you want. class RadarTouchResponse extends BaseTouchResponse { /// If touch happens, [RadarChart] processes it internally and passes out a [RadarTouchResponse] /// that contains a [touchedSpot], it gives you information about the touched spot. RadarTouchResponse({ required super.touchLocation, required this.touchedSpot, }); /// touch happened on this spot. this spot has useful information about spot or entry final RadarTouchedSpot? touchedSpot; /// Copies current [RadarTouchResponse] to a new [RadarTouchResponse], /// and replaces provided values. RadarTouchResponse copyWith({ Offset? touchLocation, RadarTouchedSpot? touchedSpot, }) => RadarTouchResponse( touchLocation: touchLocation ?? this.touchLocation, touchedSpot: touchedSpot ?? this.touchedSpot, ); } /// It gives you information about the touched spot. class RadarTouchedSpot extends TouchedSpot with EquatableMixin { /// When touch happens, a [RadarTouchedSpot] returns as a output, /// it tells you where the touch happened. /// [touchedDataSet], and [touchedDataSetIndex] tell you in which dataSet touch happened, /// [touchedRadarEntry], and [touchedRadarEntryIndex] tell you in which entry touch happened, /// You can also have the touched x and y in the chart as a [FlSpot] using [spot] value, /// and you can have the local touch coordinates on the screen as a [Offset] using [offset] value. RadarTouchedSpot( this.touchedDataSet, this.touchedDataSetIndex, this.touchedRadarEntry, this.touchedRadarEntryIndex, FlSpot spot, Offset offset, ) : super(spot, offset); final RadarDataSet touchedDataSet; final int touchedDataSetIndex; final RadarEntry touchedRadarEntry; final int touchedRadarEntryIndex; /// Used for equality check, see [EquatableMixin]. @override List get props => [ spot, offset, touchedDataSet, touchedDataSetIndex, touchedRadarEntry, touchedRadarEntryIndex, ]; } /// It lerps a [RadarChartData] to another [RadarChartData] (handles animation for updating values) class RadarChartDataTween extends Tween { RadarChartDataTween({ required RadarChartData begin, required RadarChartData end, }) : super(begin: begin, end: end); /// Lerps a [RadarChartData] based on [t] value, check [Tween.lerp]. @override RadarChartData lerp(double t) => begin!.lerp(begin!, end!, t); } ================================================ FILE: lib/src/chart/radar_chart/radar_chart_painter.dart ================================================ import 'dart:math' show cos, min, pi, sin; import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart'; import 'package:fl_chart/src/utils/canvas_wrapper.dart'; import 'package:fl_chart/src/utils/utils.dart'; import 'package:flutter/material.dart'; /// Paints [RadarChartData] in the canvas, it can be used in a [CustomPainter] class RadarChartPainter extends BaseChartPainter { /// Paints [dataList] into canvas, it is the animating [RadarChartData], /// [targetData] is the animation's target and remains the same /// during animation, then we should use it when we need to show /// tooltips or something like that, because [dataList] is changing constantly. /// /// [textScale] used for scaling texts inside the chart, /// parent can use [MediaQuery.textScaleFactor] to respect /// the system's font size. RadarChartPainter() : super() { _backgroundPaint = Paint() ..style = PaintingStyle.fill ..isAntiAlias = true; _borderPaint = Paint()..style = PaintingStyle.stroke; _gridPaint = Paint()..style = PaintingStyle.stroke; _tickPaint = Paint()..style = PaintingStyle.stroke; _graphPaint = Paint(); _graphBorderPaint = Paint(); _graphPointPaint = Paint(); _ticksTextPaint = TextPainter(); _titleTextPaint = TextPainter(); } late Paint _borderPaint; late Paint _backgroundPaint; late Paint _gridPaint; late Paint _tickPaint; late Paint _graphPaint; late Paint _graphBorderPaint; late Paint _graphPointPaint; late TextPainter _ticksTextPaint; late TextPainter _titleTextPaint; List? dataSetsPosition; /// Paints [RadarChartData] into the provided canvas. @override void paint( BuildContext context, CanvasWrapper canvasWrapper, PaintHolder holder, ) { super.paint(context, canvasWrapper, holder); final data = holder.data; if (data.dataSets.isEmpty) { return; } dataSetsPosition = calculateDataSetsPosition(canvasWrapper.size, holder); drawGrids(canvasWrapper, holder); drawTicks(context, canvasWrapper, holder); drawTitles(context, canvasWrapper, holder); drawDataSets(canvasWrapper, holder); } @visibleForTesting double getDefaultChartCenterValue() => 0; double getChartCenterValue(RadarChartData data) { final dataSetMaxValue = data.maxEntry.value; final dataSetMinValue = data.minEntry.value; if (data.isMinValueAtCenter) { return dataSetMinValue; } final tickSpace = getSpaceBetweenTicks(data); final centerValue = dataSetMinValue - tickSpace; return dataSetMaxValue == dataSetMinValue ? getDefaultChartCenterValue() : centerValue; } @visibleForTesting double getScaledPoint(RadarEntry point, double radius, RadarChartData data) { final centerValue = getChartCenterValue(data); final distanceFromPointToCenter = point.value - centerValue; final distanceFromMaxToCenter = data.maxEntry.value - centerValue; if (distanceFromMaxToCenter == 0) { return radius * distanceFromPointToCenter / 0.001; } return radius * distanceFromPointToCenter / distanceFromMaxToCenter; } @visibleForTesting double getFirstTickValue(RadarChartData data) { final defaultCenterValue = getDefaultChartCenterValue(); if (data.isMinValueAtCenter) { return defaultCenterValue; } final dataSetMaxValue = data.maxEntry.value; final dataSetMinValue = data.minEntry.value; return dataSetMaxValue == dataSetMinValue ? (dataSetMaxValue - defaultCenterValue) / (data.tickCount + 1) + defaultCenterValue : dataSetMinValue; } @visibleForTesting double getSpaceBetweenTicks(RadarChartData data) { final dataSetMaxValue = data.maxEntry.value; final dataSetMinValue = data.minEntry.value; if (data.isMinValueAtCenter) { return (dataSetMaxValue - dataSetMinValue) / (data.tickCount); } final defaultCenterValue = getDefaultChartCenterValue(); final tickSpace = (dataSetMaxValue - dataSetMinValue) / data.tickCount; final defaultTickSpace = (dataSetMaxValue - defaultCenterValue) / (data.tickCount + 1); return dataSetMaxValue == dataSetMinValue ? defaultTickSpace : tickSpace; } @visibleForTesting void drawTicks( BuildContext context, CanvasWrapper canvasWrapper, PaintHolder holder, ) { final data = holder.data; final size = canvasWrapper.size; final centerX = radarCenterX(size); final centerY = radarCenterY(size); final centerOffset = Offset(centerX, centerY); /// controls Radar chart size final radius = radarRadius(size); _backgroundPaint.color = data.radarBackgroundColor; _borderPaint ..color = data.radarBorderData.color ..strokeWidth = data.radarBorderData.width; if (data.radarShape == RadarShape.circle) { /// draw radar background canvasWrapper ..drawCircle(centerOffset, radius, _backgroundPaint) /// draw radar border ..drawCircle(centerOffset, radius, _borderPaint); } else { final path = _generatePolygonPath(centerX, centerY, radius, data.titleCount); /// draw radar background canvasWrapper ..drawPath(path, _backgroundPaint) /// draw radar border ..drawPath(path, _borderPaint); } final tickSpace = getSpaceBetweenTicks(data); final ticks = []; var tickValue = getFirstTickValue(data); for (var i = 0; i <= data.tickCount; i++) { ticks.add(tickValue); tickValue += tickSpace; } final tickDistance = data.isMinValueAtCenter ? radius / (ticks.length - 1) : radius / ticks.length; _tickPaint ..color = data.tickBorderData.color ..strokeWidth = data.tickBorderData.width; /// draw radar ticks ticks.sublist(0, ticks.length - 1).asMap().forEach( (index, tick) { final tickRadius = tickDistance * (index + (data.isMinValueAtCenter ? 0 : 1)); if (data.radarShape == RadarShape.circle) { canvasWrapper.drawCircle(centerOffset, tickRadius, _tickPaint); } else { canvasWrapper.drawPath( _generatePolygonPath(centerX, centerY, tickRadius, data.titleCount), _tickPaint, ); } _ticksTextPaint ..text = TextSpan( text: tick.toStringAsFixed(1), style: Utils().getThemeAwareTextStyle(context, data.ticksTextStyle), ) ..textDirection = TextDirection.ltr ..layout(maxWidth: size.width); canvasWrapper.drawText( _ticksTextPaint, Offset(centerX + 5, centerY - tickRadius - _ticksTextPaint.height), ); }, ); } Path _generatePolygonPath( double centerX, double centerY, double radius, int count, ) { final path = Path()..moveTo(centerX, centerY - radius); final angle = (2 * pi) / count; for (var index = 0; index < count; index++) { final xAngle = cos(angle * index - pi / 2); final yAngle = sin(angle * index - pi / 2); path.lineTo(centerX + radius * xAngle, centerY + radius * yAngle); } path.lineTo(centerX, centerY - radius); return path; } void drawGrids( CanvasWrapper canvasWrapper, PaintHolder holder, ) { final data = holder.data; final size = canvasWrapper.size; final centerX = radarCenterX(size); final centerY = radarCenterY(size); final centerOffset = Offset(centerX, centerY); /// controls Radar chart size final radius = radarRadius(size); final angle = (2 * pi) / data.titleCount; /// drawing grids for (var index = 0; index < data.titleCount; index++) { final endX = centerX + radius * cos(angle * index - pi / 2); final endY = centerY + radius * sin(angle * index - pi / 2); final gridOffset = Offset(endX, endY); _gridPaint ..color = data.gridBorderData.color ..strokeWidth = data.gridBorderData.width; canvasWrapper.drawLine(centerOffset, gridOffset, _gridPaint); } } @visibleForTesting void drawTitles( BuildContext context, CanvasWrapper canvasWrapper, PaintHolder holder, ) { final data = holder.data; if (data.getTitle == null) return; final size = canvasWrapper.size; final centerX = radarCenterX(size); final centerY = radarCenterY(size); /// controls Radar chart size final radius = radarRadius(size); final diffAngle = (2 * pi) / data.titleCount; final style = Utils().getThemeAwareTextStyle(context, data.titleTextStyle); _titleTextPaint ..textAlign = TextAlign.center ..textDirection = TextDirection.ltr ..textScaler = holder.textScaler; for (var index = 0; index < data.titleCount; index++) { final baseTitleAngle = Utils().degrees(diffAngle * index); final title = data.getTitle!(index, baseTitleAngle); final span = TextSpan(text: title.text, children: title.children, style: style); _titleTextPaint ..text = span ..layout(); final angle = diffAngle * index - pi / 2; final threshold = 1.0 + (title.positionPercentageOffset ?? data.titlePositionPercentageOffset); final titleX = centerX + cos(angle) * (radius * threshold + (_titleTextPaint.height / 2)); final titleY = centerY + sin(angle) * (radius * threshold + (_titleTextPaint.height / 2)); final rect = Rect.fromLTWH( titleX, titleY, _titleTextPaint.width, _titleTextPaint.height, ); final rectDrawOffset = Offset(rect.left, rect.top); final drawTitleDegrees = (angle * 180 / pi) + 90; canvasWrapper.drawRotated( size: rect.size, rotationOffset: Offset( -rect.width / 2, -rect.height / 2, ), drawOffset: rectDrawOffset, angle: drawTitleDegrees, drawCallback: () { canvasWrapper.drawText( _titleTextPaint, rect.topLeft, title.angle - baseTitleAngle, ); }, ); } } @visibleForTesting void drawDataSets( CanvasWrapper canvasWrapper, PaintHolder holder, ) { final data = holder.data; // we will use dataSetsPosition to draw the graphs dataSetsPosition ??= calculateDataSetsPosition(canvasWrapper.size, holder); final size = canvasWrapper.size; final centerX = radarCenterX(size); final centerY = radarCenterY(size); final centerOffset = Offset(centerX, centerY); final radius = radarRadius(size); dataSetsPosition!.asMap().forEach((index, dataSetOffset) { final graph = data.dataSets[index]; // if fillGradient exists if (graph.fillGradient != null) { // Create the shader final rect = Rect.fromCircle(center: centerOffset, radius: radius); _graphPaint ..shader = graph.fillGradient!.createShader(rect) ..style = PaintingStyle.fill; } else { // else solid fill color _graphPaint ..color = graph.fillColor ..style = PaintingStyle.fill; } _graphBorderPaint ..color = graph.borderColor ..style = PaintingStyle.stroke ..strokeWidth = graph.borderWidth; _graphPointPaint ..color = _graphBorderPaint.color ..style = PaintingStyle.fill; final path = Path(); final firstOffset = Offset( dataSetOffset.entriesOffset.first.dx, dataSetOffset.entriesOffset.first.dy, ); path.moveTo(firstOffset.dx, firstOffset.dy); canvasWrapper.drawCircle( firstOffset, graph.entryRadius, _graphPointPaint, ); dataSetOffset.entriesOffset.asMap().forEach((index, pointOffset) { if (index == 0) return; path.lineTo(pointOffset.dx, pointOffset.dy); canvasWrapper.drawCircle( pointOffset, graph.entryRadius, _graphPointPaint, ); }); path.close(); canvasWrapper ..drawPath(path, _graphPaint) ..drawPath(path, _graphBorderPaint); }); } RadarTouchedSpot? handleTouch( Offset touchedPoint, Size viewSize, PaintHolder holder, ) { final targetData = holder.targetData; dataSetsPosition ??= calculateDataSetsPosition(viewSize, holder); for (var i = 0; i < dataSetsPosition!.length; i++) { final dataSetPosition = dataSetsPosition![i]; for (var j = 0; j < dataSetPosition.entriesOffset.length; j++) { final entryOffset = dataSetPosition.entriesOffset[j]; if ((touchedPoint.dx - entryOffset.dx).abs() <= targetData.radarTouchData.touchSpotThreshold && (touchedPoint.dy - entryOffset.dy).abs() <= targetData.radarTouchData.touchSpotThreshold) { return RadarTouchedSpot( targetData.dataSets[i], i, targetData.dataSets[i].dataEntries[j], j, FlSpot(entryOffset.dx, entryOffset.dy), entryOffset, ); } } } return null; } @visibleForTesting double radarCenterY(Size size) => size.height / 2.0; @visibleForTesting double radarCenterX(Size size) => size.width / 2.0; @visibleForTesting double radarRadius(Size size) => min(radarCenterX(size), radarCenterY(size)) * 0.8; @visibleForTesting List calculateDataSetsPosition( Size viewSize, PaintHolder holder, ) { final data = holder.data; final centerX = radarCenterX(viewSize); final centerY = radarCenterY(viewSize); final radius = radarRadius(viewSize); final angle = (2 * pi) / data.titleCount; final dataSetsPosition = List.filled( data.dataSets.length, const RadarDataSetsPosition([]), ); for (var i = 0; i < data.dataSets.length; i++) { final dataSet = data.dataSets[i]; final entriesOffset = List.filled(dataSet.dataEntries.length, Offset.zero); for (var j = 0; j < dataSet.dataEntries.length; j++) { final point = dataSet.dataEntries[j]; final xAngle = cos(angle * j - pi / 2); final yAngle = sin(angle * j - pi / 2); final scaledPoint = getScaledPoint(point, radius, data); final entryOffset = Offset( centerX + scaledPoint * xAngle, centerY + scaledPoint * yAngle, ); entriesOffset[j] = entryOffset; } dataSetsPosition[i] = RadarDataSetsPosition(entriesOffset); } return dataSetsPosition; } } class RadarDataSetsPosition { const RadarDataSetsPosition(this.entriesOffset); final List entriesOffset; } ================================================ FILE: lib/src/chart/radar_chart/radar_chart_renderer.dart ================================================ import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart'; import 'package:fl_chart/src/chart/base/base_chart/render_base_chart.dart'; import 'package:fl_chart/src/chart/radar_chart/radar_chart_painter.dart'; import 'package:fl_chart/src/utils/canvas_wrapper.dart'; import 'package:flutter/cupertino.dart'; // coverage:ignore-start /// Low level RadarChart Widget. class RadarChartLeaf extends LeafRenderObjectWidget { const RadarChartLeaf({ super.key, required this.data, required this.targetData, }); final RadarChartData data; final RadarChartData targetData; @override RenderRadarChart createRenderObject(BuildContext context) => RenderRadarChart( context, data, targetData, MediaQuery.of(context).textScaler, ); @override void updateRenderObject(BuildContext context, RenderRadarChart renderObject) { renderObject ..data = data ..targetData = targetData ..textScaler = MediaQuery.of(context).textScaler ..buildContext = context; } } // coverage:ignore-end /// Renders our RadarChart, also handles hitTest. class RenderRadarChart extends RenderBaseChart { RenderRadarChart( BuildContext context, RadarChartData data, RadarChartData targetData, TextScaler textScaler, ) : _data = data, _targetData = targetData, _textScaler = textScaler, super(targetData.radarTouchData, context, canBeScaled: false); RadarChartData get data => _data; RadarChartData _data; set data(RadarChartData value) { if (_data == value) return; _data = value; markNeedsPaint(); } RadarChartData get targetData => _targetData; RadarChartData _targetData; set targetData(RadarChartData value) { if (_targetData == value) return; _targetData = value; super.updateBaseTouchData(_targetData.radarTouchData); markNeedsPaint(); } TextScaler get textScaler => _textScaler; TextScaler _textScaler; set textScaler(TextScaler value) { if (_textScaler == value) return; _textScaler = value; markNeedsPaint(); } // We couldn't mock [size] property of this class, that's why we have this @visibleForTesting Size? mockTestSize; @visibleForTesting RadarChartPainter painter = RadarChartPainter(); PaintHolder get paintHolder => PaintHolder(data, targetData, textScaler); @override void paint(PaintingContext context, Offset offset) { final canvas = context.canvas ..save() ..translate(offset.dx, offset.dy); painter.paint( buildContext, CanvasWrapper(canvas, mockTestSize ?? size), paintHolder, ); canvas.restore(); } @override bool hitTestSelf(Offset position) { if (!targetData.radarTouchData.enabled) { return false; } return super.hitTestSelf(position); } @override RadarTouchResponse getResponseAtLocation(Offset localPosition) { return RadarTouchResponse( touchLocation: localPosition, touchedSpot: painter.handleTouch( localPosition, mockTestSize ?? size, paintHolder, ), ); } } ================================================ FILE: lib/src/chart/radar_chart/radar_extension.dart ================================================ import 'package:fl_chart/src/chart/radar_chart/radar_chart_data.dart'; /// Defines extensions on the [List] extension DashedPath on List { /// check all the [RadarDataSet] has a same [dataEntries] length bool get hasEqualDataEntriesLength { if (length == 0) return false; final firstDataEntriesLength = this[0].dataEntries.length; return every( (element) => element.dataEntries.length == firstDataEntriesLength, ); } } ================================================ FILE: lib/src/chart/scatter_chart/scatter_chart.dart ================================================ import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/chart/base/axis_chart/axis_chart_scaffold_widget.dart'; import 'package:fl_chart/src/chart/scatter_chart/scatter_chart_renderer.dart'; import 'package:flutter/cupertino.dart'; /// Renders a pie chart as a widget, using provided [ScatterChartData]. class ScatterChart extends ImplicitlyAnimatedWidget { /// [data] determines how the [ScatterChart] should be look like, /// when you make any change in the [ScatterChartData], it updates /// new values with animation, and duration is [duration]. /// also you can change the [curve] /// which default is [Curves.linear]. const ScatterChart( this.data, { this.chartRendererKey, super.key, @Deprecated('Please use [duration] instead') Duration? swapAnimationDuration, Duration duration = const Duration(milliseconds: 150), @Deprecated('Please use [curve] instead') Curve? swapAnimationCurve, Curve curve = Curves.linear, this.transformationConfig = const FlTransformationConfig(), }) : super( duration: swapAnimationDuration ?? duration, curve: swapAnimationCurve ?? curve, ); /// Determines how the [ScatterChart] should be look like. final ScatterChartData data; /// {@macro fl_chart.AxisChartScaffoldWidget.transformationConfig} final FlTransformationConfig transformationConfig; /// We pass this key to our renderers which are responsible to /// render the chart itself (without anything around the chart). final Key? chartRendererKey; /// Creates a [_ScatterChartState] @override _ScatterChartState createState() => _ScatterChartState(); } class _ScatterChartState extends AnimatedWidgetBaseState { /// we handle under the hood animations (implicit animations) via this tween, /// it lerps between the old [ScatterChartData] to the new one. ScatterChartDataTween? _scatterChartDataTween; /// If [ScatterTouchData.handleBuiltInTouches] is true, we override the callback to handle touches internally, /// but we need to keep the provided callback to notify it too. BaseTouchCallback? _providedTouchCallback; List touchedSpots = []; @override Widget build(BuildContext context) { final showingData = _getData(); return AxisChartScaffoldWidget( data: showingData, transformationConfig: widget.transformationConfig, chartBuilder: (context, chartVirtualRect) => ScatterChartLeaf( data: _withTouchedIndicators(_scatterChartDataTween!.evaluate(animation)), targetData: _withTouchedIndicators(showingData), key: widget.chartRendererKey, chartVirtualRect: chartVirtualRect, canBeScaled: widget.transformationConfig.scaleAxis != FlScaleAxis.none, ), ); } ScatterChartData _withTouchedIndicators(ScatterChartData scatterChartData) { if (!scatterChartData.scatterTouchData.enabled || !scatterChartData.scatterTouchData.handleBuiltInTouches) { return scatterChartData; } return scatterChartData.copyWith( showingTooltipIndicators: touchedSpots, ); } ScatterChartData _getData() { final scatterTouchData = widget.data.scatterTouchData; if (scatterTouchData.enabled && scatterTouchData.handleBuiltInTouches) { _providedTouchCallback = scatterTouchData.touchCallback; return widget.data.copyWith( scatterTouchData: widget.data.scatterTouchData .copyWith(touchCallback: _handleBuiltInTouch), ); } return widget.data; } void _handleBuiltInTouch( FlTouchEvent event, ScatterTouchResponse? touchResponse, ) { if (!mounted) { return; } _providedTouchCallback?.call(event, touchResponse); final desiredTouch = event.isInterestedForInteractions; if (!desiredTouch || touchResponse == null || touchResponse.touchedSpot == null) { setState(() { touchedSpots = []; }); return; } setState(() { touchedSpots = [touchResponse.touchedSpot!.spotIndex]; }); } @override void forEachTween(TweenVisitor visitor) { _scatterChartDataTween = visitor( _scatterChartDataTween, _getData(), (dynamic value) => ScatterChartDataTween( begin: value as ScatterChartData, end: widget.data, ), ) as ScatterChartDataTween?; } } ================================================ FILE: lib/src/chart/scatter_chart/scatter_chart_data.dart ================================================ // coverage:ignore-file import 'dart:ui'; import 'package:equatable/equatable.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/chart/scatter_chart/scatter_chart_helper.dart'; import 'package:fl_chart/src/extensions/color_extension.dart'; import 'package:fl_chart/src/utils/lerp.dart'; import 'package:flutter/material.dart'; /// [ScatterChart] needs this class to render itself. /// /// It holds data needed to draw a scatter chart, /// including background color, scatter spots, ... class ScatterChartData extends AxisChartData with EquatableMixin { /// [ScatterChart] draws some points in a square space, /// points are defined by [scatterSpots], /// /// It draws some titles on left, top, right, bottom sides per each axis number, /// you can modify [titlesData] to have your custom titles, /// also you can define the axis title (one text per axis) for each side /// using [axisTitleData], you can restrict the y axis using [minY] and [maxY] value, /// and restrict x axis using [minX] and [maxX]. /// /// It draws a color as a background behind everything you can set it using [backgroundColor], /// then a grid over it, you can customize it using [gridData], /// and it draws 4 borders around your chart, you can customize it using [borderData]. /// /// You can modify [scatterTouchData] to customize touch behaviors and responses. /// /// You can show some tooltipIndicators (a popup with an information) /// on top of each [ScatterChartData.scatterSpots] using [showingTooltipIndicators], /// just put spot indices you want to show it on top of them. /// /// [clipData] forces the [LineChart] to draw lines inside the chart bounding box. ScatterChartData({ List? scatterSpots, FlTitlesData? titlesData, ScatterTouchData? scatterTouchData, List? showingTooltipIndicators, FlGridData? gridData, super.borderData, double? minX, double? maxX, super.baselineX, double? minY, double? maxY, super.baselineY, FlClipData? clipData, super.backgroundColor, ScatterLabelSettings? scatterLabelSettings, super.rotationQuarterTurns, this.errorIndicatorData = const FlErrorIndicatorData(), }) : scatterSpots = scatterSpots ?? const [], scatterTouchData = scatterTouchData ?? ScatterTouchData(), showingTooltipIndicators = showingTooltipIndicators ?? const [], scatterLabelSettings = scatterLabelSettings ?? ScatterLabelSettings(), super( gridData: gridData ?? const FlGridData(), titlesData: titlesData ?? const FlTitlesData(), clipData: clipData ?? const FlClipData.none(), minX: minX ?? ScatterChartHelper.calculateMaxAxisValues( scatterSpots ?? const [], ).$1, maxX: maxX ?? ScatterChartHelper.calculateMaxAxisValues( scatterSpots ?? const [], ).$2, minY: minY ?? ScatterChartHelper.calculateMaxAxisValues( scatterSpots ?? const [], ).$3, maxY: maxY ?? ScatterChartHelper.calculateMaxAxisValues( scatterSpots ?? const [], ).$4, ); final List scatterSpots; final ScatterTouchData scatterTouchData; /// you can show some tooltipIndicators (a popup with an information) /// on top of each [ScatterSpot] using [showingTooltipIndicators], /// just put indices you want to show it on top of them. /// /// An important point is that you have to disable the default touch behaviour /// to show the tooltip manually, see [ScatterTouchData.handleBuiltInTouches]. final List showingTooltipIndicators; final ScatterLabelSettings scatterLabelSettings; /// Holds data for showing error indicators on the [scatterSpots] final FlErrorIndicatorData errorIndicatorData; /// Lerps a [ScatterChartData] based on [t] value, check [Tween.lerp]. @override ScatterChartData lerp(BaseChartData a, BaseChartData b, double t) { if (a is ScatterChartData && b is ScatterChartData) { return ScatterChartData( scatterSpots: lerpScatterSpotList(a.scatterSpots, b.scatterSpots, t), titlesData: FlTitlesData.lerp(a.titlesData, b.titlesData, t), scatterTouchData: b.scatterTouchData, showingTooltipIndicators: lerpIntList( a.showingTooltipIndicators, b.showingTooltipIndicators, t, ), gridData: FlGridData.lerp(a.gridData, b.gridData, t), borderData: FlBorderData.lerp(a.borderData, b.borderData, t), minX: lerpDouble(a.minX, b.minX, t), maxX: lerpDouble(a.maxX, b.maxX, t), baselineX: lerpDouble(a.baselineX, b.baselineX, t), minY: lerpDouble(a.minY, b.minY, t), maxY: lerpDouble(a.maxY, b.maxY, t), baselineY: lerpDouble(a.baselineY, b.baselineY, t), clipData: b.clipData, backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t), scatterLabelSettings: ScatterLabelSettings.lerp( a.scatterLabelSettings, b.scatterLabelSettings, t, ), rotationQuarterTurns: b.rotationQuarterTurns, errorIndicatorData: FlErrorIndicatorData.lerp( a.errorIndicatorData, b.errorIndicatorData, t, ), ); } else { throw Exception('Illegal State'); } } /// Copies current [ScatterChartData] to a new [ScatterChartData], /// and replaces provided values. ScatterChartData copyWith({ List? scatterSpots, FlTitlesData? titlesData, ScatterTouchData? scatterTouchData, List? showingTooltipIndicators, FlGridData? gridData, FlBorderData? borderData, double? minX, double? maxX, double? baselineX, double? minY, double? maxY, double? baselineY, FlClipData? clipData, Color? backgroundColor, ScatterLabelSettings? scatterLabelSettings, int? rotationQuarterTurns, FlErrorIndicatorData? errorIndicatorData, }) => ScatterChartData( scatterSpots: scatterSpots ?? this.scatterSpots, titlesData: titlesData ?? this.titlesData, scatterTouchData: scatterTouchData ?? this.scatterTouchData, showingTooltipIndicators: showingTooltipIndicators ?? this.showingTooltipIndicators, gridData: gridData ?? this.gridData, borderData: borderData ?? this.borderData, minX: minX ?? this.minX, maxX: maxX ?? this.maxX, baselineX: baselineX ?? this.baselineX, minY: minY ?? this.minY, maxY: maxY ?? this.maxY, baselineY: baselineY ?? this.baselineY, clipData: clipData ?? this.clipData, backgroundColor: backgroundColor ?? this.backgroundColor, scatterLabelSettings: scatterLabelSettings ?? this.scatterLabelSettings, rotationQuarterTurns: rotationQuarterTurns ?? this.rotationQuarterTurns, errorIndicatorData: errorIndicatorData ?? this.errorIndicatorData, ); /// Used for equality check, see [EquatableMixin]. @override List get props => [ scatterSpots, scatterTouchData, showingTooltipIndicators, gridData, titlesData, rangeAnnotations, minX, maxX, baselineX, minY, maxY, baselineY, rangeAnnotations, scatterLabelSettings, clipData, backgroundColor, borderData, rotationQuarterTurns, errorIndicatorData, ]; } /// Defines information about a spot in the [ScatterChart] class ScatterSpot extends FlSpot with EquatableMixin { /// You can change [show] value to show or hide the spot, /// [x], and [y] defines the location of spot in the [ScatterChart], /// [radius] defines the size of spot, and [color] defines the color of it. ScatterSpot( super.x, super.y, { bool? show, int? renderPriority, FlDotPainter? dotPainter, super.xError, super.yError, }) : show = show ?? true, renderPriority = renderPriority ?? 0, dotPainter = dotPainter ?? FlDotCirclePainter( radius: 6, color: Colors.primaries[((x * y) % Colors.primaries.length).toInt()], ); /// Determines show or hide the spot. final bool show; // Determines Z-Index of the spot final int renderPriority; /// Determines shape of the spot final FlDotPainter dotPainter; Size get size => dotPainter.getSize(this); String get defaultLabel { if (dotPainter is FlDotCirclePainter) { return '${(dotPainter as FlDotCirclePainter).radius.toInt()}'; } else { return '${x.toInt()}, ${y.toInt()}'; } } @override ScatterSpot copyWith({ double? x, double? y, bool? show, int? renderPriority, FlDotPainter? dotPainter, FlErrorRange? xError, FlErrorRange? yError, }) => ScatterSpot( x ?? this.x, y ?? this.y, show: show ?? this.show, renderPriority: renderPriority ?? this.renderPriority, dotPainter: dotPainter ?? this.dotPainter, xError: xError ?? this.xError, yError: yError ?? this.yError, ); /// Lerps a [ScatterSpot] based on [t] value, check [Tween.lerp]. static ScatterSpot lerp(ScatterSpot a, ScatterSpot b, double t) => ScatterSpot( lerpDouble(a.x, b.x, t)!, lerpDouble(a.y, b.y, t)!, show: b.show, renderPriority: a.renderPriority + (t * (b.renderPriority - a.renderPriority)).round(), dotPainter: a.dotPainter.lerp(a.dotPainter, b.dotPainter, t), xError: FlErrorRange.lerp(a.xError, b.xError, t), yError: FlErrorRange.lerp(a.yError, b.yError, t), ); /// Used for equality check, see [EquatableMixin]. @override List get props => [ x, y, show, renderPriority, dotPainter, xError, yError, ]; } /// Holds data to handle touch events, and touch responses in the [ScatterChart]. /// /// There is a touch flow, explained [here](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/handle_touches.md) /// in a simple way, each chart's renderer captures the touch events, and passes the pointerEvent /// to the painter, and gets touched spot, and wraps it into a concrete [ScatterTouchResponse]. class ScatterTouchData extends FlTouchData with EquatableMixin { /// You can disable or enable the touch system using [enabled] flag, /// /// [touchCallback] notifies you about the happened touch/pointer events. /// It gives you a [FlTouchEvent] which is the happened event such as [FlPointerHoverEvent], [FlTapUpEvent], ... /// It also gives you a [ScatterTouchResponse] which contains information /// about the elements that has touched. /// /// Using [mouseCursorResolver] you can change the mouse cursor /// based on the provided [FlTouchEvent] and [ScatterTouchResponse] /// /// if [handleBuiltInTouches] is true, [ScatterChart] shows a tooltip popup on top of the spots if /// touch occurs (or you can show it manually using, [ScatterChartData.showingTooltipIndicators]) /// You can customize this tooltip using [touchTooltipData], /// /// If you need to have a distance threshold for handling touches, use [touchSpotThreshold]. ScatterTouchData({ bool? enabled, BaseTouchCallback? touchCallback, MouseCursorResolver? mouseCursorResolver, Duration? longPressDuration, ScatterTouchTooltipData? touchTooltipData, double? touchSpotThreshold, bool? handleBuiltInTouches, }) : touchTooltipData = touchTooltipData ?? const ScatterTouchTooltipData(), touchSpotThreshold = touchSpotThreshold ?? 0, handleBuiltInTouches = handleBuiltInTouches ?? true, super( enabled ?? true, touchCallback, mouseCursorResolver, longPressDuration, ); /// show a tooltip on touched spots final ScatterTouchTooltipData touchTooltipData; /// we find the nearest spots on touched position based on this threshold final double touchSpotThreshold; /// set this true if you want the built in touch handling /// (show a tooltip bubble and an indicator on touched spots) final bool handleBuiltInTouches; /// Copies current [ScatterTouchData] to a new [ScatterTouchData], /// and replaces provided values. ScatterTouchData copyWith({ bool? enabled, BaseTouchCallback? touchCallback, MouseCursorResolver? mouseCursorResolver, Duration? longPressDuration, ScatterTouchTooltipData? touchTooltipData, double? touchSpotThreshold, bool? handleBuiltInTouches, }) => ScatterTouchData( enabled: enabled ?? this.enabled, touchCallback: touchCallback ?? this.touchCallback, mouseCursorResolver: mouseCursorResolver ?? this.mouseCursorResolver, longPressDuration: longPressDuration ?? this.longPressDuration, touchTooltipData: touchTooltipData ?? this.touchTooltipData, handleBuiltInTouches: handleBuiltInTouches ?? this.handleBuiltInTouches, touchSpotThreshold: touchSpotThreshold ?? this.touchSpotThreshold, ); /// Used for equality check, see [EquatableMixin]. @override List get props => [ enabled, touchCallback, mouseCursorResolver, longPressDuration, touchTooltipData, touchSpotThreshold, handleBuiltInTouches, ]; } /// [ScatterChart]'s touch callback. typedef ScatterTouchCallback = void Function(ScatterTouchResponse); /// Holds information about touch response in the [ScatterChart]. /// /// You can override [ScatterTouchData.touchCallback] to handle touch events, /// it gives you a [ScatterTouchResponse] and you can do whatever you want. class ScatterTouchResponse extends AxisBaseTouchResponse { /// If touch happens, [ScatterChart] processes it internally and /// passes out a [ScatterTouchResponse], it gives you information about the touched spot. /// /// [touchedSpot] tells you /// in which spot (of [ScatterChartData.scatterSpots]) touch happened. ScatterTouchResponse({ required super.touchLocation, required super.touchChartCoordinate, required this.touchedSpot, }); final ScatterTouchedSpot? touchedSpot; /// Copies current [ScatterTouchResponse] to a new [ScatterTouchResponse], /// and replaces provided values. ScatterTouchResponse copyWith({ Offset? touchLocation, Offset? touchChartCoordinate, ScatterTouchedSpot? touchedSpot, }) => ScatterTouchResponse( touchLocation: touchLocation ?? this.touchLocation, touchChartCoordinate: touchChartCoordinate ?? this.touchChartCoordinate, touchedSpot: touchedSpot ?? this.touchedSpot, ); } /// Holds the touched spot data class ScatterTouchedSpot with EquatableMixin { /// [spot], and [spotIndex] tells you /// in which spot (of [ScatterChartData.scatterSpots]) touch happened. const ScatterTouchedSpot(this.spot, this.spotIndex); /// Touch happened on this spot final ScatterSpot spot; /// Touch happened on this spot index final int spotIndex; /// Used for equality check, see [EquatableMixin]. @override List get props => [ spot, spotIndex, ]; /// Copies current [ScatterTouchedSpot] to a new [ScatterTouchedSpot], /// and replaces provided values. ScatterTouchedSpot copyWith({ ScatterSpot? spot, int? spotIndex, }) => ScatterTouchedSpot(spot ?? this.spot, spotIndex ?? this.spotIndex); } /// Holds representation data for showing tooltip popup on top of spots. class ScatterTouchTooltipData with EquatableMixin { /// if [ScatterTouchData.handleBuiltInTouches] is true, /// [ScatterChart] shows a tooltip popup on top of spots automatically when touch happens, /// otherwise you can show it manually using [ScatterChartData.showingTooltipIndicators]. /// Tooltip shows on top of rods, with [getTooltipColor] as a background color. /// You can set the corner radius using [tooltipBorderRadius], /// If you want to have a padding inside the tooltip, fill [tooltipPadding]. /// Content of the tooltip will provide using [getTooltipItems] callback, you can override it /// and pass your custom data to show in the tooltip. /// You can restrict the tooltip's width using [maxContentWidth]. /// Sometimes, [ScatterChart] shows the tooltip outside of the chart, /// you can set [fitInsideHorizontally] true to force it to shift inside the chart horizontally, /// also you can set [fitInsideVertically] true to force it to shift inside the chart vertically. const ScatterTouchTooltipData({ BorderRadius? tooltipBorderRadius, EdgeInsets? tooltipPadding, FLHorizontalAlignment? tooltipHorizontalAlignment, double? tooltipHorizontalOffset, double? maxContentWidth, GetScatterTooltipItems? getTooltipItems, bool? fitInsideHorizontally, bool? fitInsideVertically, double? rotateAngle, BorderSide? tooltipBorder, GetScatterTooltipColor? getTooltipColor, }) : _tooltipBorderRadius = tooltipBorderRadius, tooltipPadding = tooltipPadding ?? const EdgeInsets.symmetric(horizontal: 16, vertical: 8), tooltipHorizontalAlignment = tooltipHorizontalAlignment ?? FLHorizontalAlignment.center, tooltipHorizontalOffset = tooltipHorizontalOffset ?? 0, maxContentWidth = maxContentWidth ?? 120, getTooltipItems = getTooltipItems ?? defaultScatterTooltipItem, fitInsideHorizontally = fitInsideHorizontally ?? false, fitInsideVertically = fitInsideVertically ?? false, rotateAngle = rotateAngle ?? 0.0, tooltipBorder = tooltipBorder ?? BorderSide.none, getTooltipColor = getTooltipColor ?? defaultScatterTooltipColor, super(); /// Sets a rounded radius for the tooltip. final BorderRadius? _tooltipBorderRadius; /// Sets a rounded radius for the tooltip. BorderRadius get tooltipBorderRadius => _tooltipBorderRadius ?? BorderRadius.circular(4); /// Applies a padding for showing contents inside the tooltip. final EdgeInsets tooltipPadding; /// Controls showing tooltip on left side, right side or center aligned with spot, default is center final FLHorizontalAlignment tooltipHorizontalAlignment; /// Applies horizontal offset for showing tooltip, default is zero. final double tooltipHorizontalOffset; /// Restricts the tooltip's width. final double maxContentWidth; /// Retrieves data for showing content inside the tooltip. final GetScatterTooltipItems getTooltipItems; /// Forces the tooltip to shift horizontally inside the chart, if overflow happens. final bool fitInsideHorizontally; /// Forces the tooltip to shift vertically inside the chart, if overflow happens. final bool fitInsideVertically; /// Controls the rotation of the tooltip. final double rotateAngle; /// The tooltip border color. final BorderSide tooltipBorder; /// Retrieves data for showing content inside the tooltip. final GetScatterTooltipColor getTooltipColor; /// Used for equality check, see [EquatableMixin]. @override List get props => [ _tooltipBorderRadius, tooltipPadding, tooltipHorizontalAlignment, tooltipHorizontalOffset, maxContentWidth, getTooltipItems, fitInsideHorizontally, fitInsideVertically, rotateAngle, tooltipBorder, getTooltipColor, ]; /// Copies current [ScatterTouchTooltipData] to a new [ScatterTouchTooltipData], /// and replaces provided values. ScatterTouchTooltipData copyWith({ EdgeInsets? tooltipPadding, FLHorizontalAlignment? tooltipHorizontalAlignment, double? tooltipHorizontalOffset, double? maxContentWidth, GetScatterTooltipItems? getTooltipItems, bool? fitInsideHorizontally, bool? fitInsideVertically, double? rotateAngle, BorderSide? tooltipBorder, GetScatterTooltipColor? getTooltipColor, }) => ScatterTouchTooltipData( tooltipPadding: tooltipPadding ?? this.tooltipPadding, tooltipHorizontalAlignment: tooltipHorizontalAlignment ?? this.tooltipHorizontalAlignment, tooltipHorizontalOffset: tooltipHorizontalOffset ?? this.tooltipHorizontalOffset, maxContentWidth: maxContentWidth ?? this.maxContentWidth, getTooltipItems: getTooltipItems ?? this.getTooltipItems, fitInsideHorizontally: fitInsideHorizontally ?? this.fitInsideHorizontally, fitInsideVertically: fitInsideVertically ?? this.fitInsideVertically, rotateAngle: rotateAngle ?? this.rotateAngle, tooltipBorder: tooltipBorder ?? this.tooltipBorder, getTooltipColor: getTooltipColor ?? this.getTooltipColor, ); } /// Provides a [ScatterTooltipItem] for showing content inside the [ScatterTouchTooltipData]. /// /// You can override [ScatterTouchTooltipData.getTooltipItems], it gives you /// [touchedSpot] that touch happened on, /// then you should and pass your custom [ScatterTooltipItem] /// to show it inside the tooltip popup. typedef GetScatterTooltipItems = ScatterTooltipItem? Function( ScatterSpot touchedSpot, ); /// Default implementation for [ScatterTouchTooltipData.getTooltipItems]. ScatterTooltipItem? defaultScatterTooltipItem(ScatterSpot touchedSpot) { final textStyle = TextStyle( color: touchedSpot.dotPainter.mainColor, fontWeight: FontWeight.bold, fontSize: 14, ); String text; if (touchedSpot.dotPainter is FlDotCirclePainter) { text = '${(touchedSpot.dotPainter as FlDotCirclePainter).radius.toInt()}'; } else { text = '${touchedSpot.x.toInt()}, ${touchedSpot.y.toInt()}'; } return ScatterTooltipItem( text, textStyle: textStyle, ); } /// Provides a [Color] to show different background color inside the [ScatterTouchTooltipData]. /// /// You can override [ScatterTouchTooltipData.getTooltipColor], it gives you /// [touchedSpot] that touch happened on, /// then you should and pass your custom [Color] /// to show it inside the tooltip popup. typedef GetScatterTooltipColor = Color Function( ScatterSpot touchedSpot, ); /// Default implementation for [ScatterTouchTooltipData.getTooltipItems]. Color defaultScatterTooltipColor(ScatterSpot touchedSpot) => Colors.blueGrey.darken(15); /// Holds data of showing each item in the tooltip popup. class ScatterTooltipItem with EquatableMixin { /// Shows a [text] with [textStyle], [textDirection], and optional [children] in the tooltip popup, /// [bottomMargin] is the bottom space from spot. ScatterTooltipItem( this.text, { this.textStyle, double? bottomMargin, TextAlign? textAlign, TextDirection? textDirection, this.children, }) : bottomMargin = bottomMargin ?? 8, textAlign = textAlign ?? TextAlign.center, textDirection = textDirection ?? TextDirection.ltr; /// Showing text. final String text; /// Style of showing text. final TextStyle? textStyle; /// Defines bottom space from spot. final double bottomMargin; /// TextAlign of the showing content. final TextAlign textAlign; /// Direction of showing text. final TextDirection textDirection; /// Add further style and format to the text of the tooltip final List? children; /// Used for equality check, see [EquatableMixin]. @override List get props => [ text, textStyle, bottomMargin, textAlign, textDirection, children, ]; /// Copies current [ScatterTooltipItem] to a new [ScatterTooltipItem], /// and replaces provided values. ScatterTooltipItem copyWith({ String? text, TextStyle? textStyle, double? bottomMargin, TextAlign? textAlign, TextDirection? textDirection, List? children, }) => ScatterTooltipItem( text ?? this.text, textStyle: textStyle ?? this.textStyle, bottomMargin: bottomMargin ?? this.bottomMargin, textAlign: textAlign ?? this.textAlign, textDirection: textDirection ?? this.textDirection, children: children ?? this.children, ); } /// It lerps a [ScatterChartData] to another [ScatterChartData] (handles animation for updating values) class ScatterChartDataTween extends Tween { ScatterChartDataTween({ required ScatterChartData begin, required ScatterChartData end, }) : super(begin: begin, end: end); /// Lerps a [ScatterChartData] based on [t] value, check [Tween.lerp]. @override ScatterChartData lerp(double t) => begin!.lerp(begin!, end!, t); } /// It gives you the index value as well as the spot and gets the text style of the label. typedef GetLabelTextStyleFunction = TextStyle? Function( int spotIndex, ScatterSpot spot, ); /// It gives you the index value as well as the spot and returns the label of the spot. typedef GetLabelFunction = String Function( int spotIndex, ScatterSpot spot, ); /// It gives you the default text style of the label for a spot. TextStyle? getDefaultLabelTextStyleFunction( int spotIndex, ScatterSpot spot, ) { return null; } /// It gives you the default label of the spot. String getDefaultLabelFunction( int spotIndex, ScatterSpot spot, ) => spot.defaultLabel; /// Defines information about the labels in the [ScatterChart] class ScatterLabelSettings with EquatableMixin { /// You can change [showLabel] value to show or hide the label, /// [textStyle] defines the style of label in the [ScatterChart]. ScatterLabelSettings({ bool? showLabel, GetLabelTextStyleFunction? getLabelTextStyleFunction, GetLabelFunction? getLabelFunction, TextDirection? textDirection, }) : showLabel = showLabel ?? false, getLabelTextStyleFunction = getLabelTextStyleFunction ?? getDefaultLabelTextStyleFunction, getLabelFunction = getLabelFunction ?? getDefaultLabelFunction, textDirection = textDirection ?? TextDirection.ltr; /// Determines whether to show or hide the labels. final bool showLabel; /// This function gives you the index value of the spot in the list and returns the text style. final GetLabelTextStyleFunction getLabelTextStyleFunction; /// This function gives you the index value of the spot in the list and returns the label. final GetLabelFunction getLabelFunction; /// Determines the direction of the text for the labels. final TextDirection textDirection; ScatterLabelSettings copyWith({ bool? showLabel, GetLabelTextStyleFunction? getLabelTextStyleFunction, GetLabelFunction? getLabelFunction, TextDirection? textDirection, }) { return ScatterLabelSettings( showLabel: showLabel ?? this.showLabel, getLabelTextStyleFunction: getLabelTextStyleFunction ?? this.getLabelTextStyleFunction, getLabelFunction: getLabelFunction ?? this.getLabelFunction, textDirection: textDirection ?? this.textDirection, ); } /// Lerps a [ScatterLabelSettings] based on [t] value, check [Tween.lerp]. static ScatterLabelSettings lerp( ScatterLabelSettings a, ScatterLabelSettings b, double t, ) => ScatterLabelSettings( showLabel: b.showLabel, getLabelTextStyleFunction: b.getLabelTextStyleFunction, getLabelFunction: b.getLabelFunction, textDirection: b.textDirection, ); /// Used for equality check, see [EquatableMixin]. @override List get props => [ showLabel, getLabelTextStyleFunction, getLabelFunction, textDirection, ]; } /// It is the input of the [GetSpotRangeErrorPainter] callback in /// the [ScatterChartData.errorIndicatorData] /// /// It contains the [spot] and [spotIndex] that the error range /// should be drawn for. /// It works based on the [ScatterSpot.xError] and [ScatterSpot.yError] values. class ScatterChartSpotErrorRangeCallbackInput extends FlSpotErrorRangeCallbackInput { ScatterChartSpotErrorRangeCallbackInput({ required this.spot, required this.spotIndex, }); final ScatterSpot spot; final int spotIndex; @override List get props => [ spot, spotIndex, ]; } ================================================ FILE: lib/src/chart/scatter_chart/scatter_chart_helper.dart ================================================ import 'package:fl_chart/src/chart/scatter_chart/scatter_chart_data.dart'; /// Contains anything that helps ScatterChart works class ScatterChartHelper { /// Calculates minX, maxX, minY, and maxY based on [scatterSpots], /// returns cached values, to prevent redundant calculations. static ( double minX, double maxX, double minY, double maxY, ) calculateMaxAxisValues( List scatterSpots, ) { if (scatterSpots.isEmpty) { return (0, 0, 0, 0); } var minX = scatterSpots[0].x; var maxX = scatterSpots[0].x; var minY = scatterSpots[0].y; var maxY = scatterSpots[0].y; for (var j = 0; j < scatterSpots.length; j++) { final spot = scatterSpots[j]; if (spot.x > maxX) { maxX = spot.x; } if (spot.x < minX) { minX = spot.x; } if (spot.y > maxY) { maxY = spot.y; } if (spot.y < minY) { minY = spot.y; } } return (minX, maxX, minY, maxY); } } ================================================ FILE: lib/src/chart/scatter_chart/scatter_chart_painter.dart ================================================ import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/chart/base/axis_chart/axis_chart_painter.dart'; import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart'; import 'package:fl_chart/src/utils/canvas_wrapper.dart'; import 'package:fl_chart/src/utils/utils.dart'; import 'package:flutter/material.dart'; /// Paints [ScatterChartData] in the canvas, it can be used in a [CustomPainter] class ScatterChartPainter extends AxisChartPainter { /// Paints [dataList] into canvas, it is the animating [ScatterChartData], /// [targetData] is the animation's target and remains the same /// during animation, then we should use it when we need to show /// tooltips or something like that, because [dataList] is changing constantly. /// /// [textScale] used for scaling texts inside the chart, /// parent can use [MediaQuery.textScaleFactor] to respect /// the system's font size. ScatterChartPainter() : super() { _bgTouchTooltipPaint = Paint() ..style = PaintingStyle.fill ..color = Colors.white; _borderTouchTooltipPaint = Paint() ..style = PaintingStyle.stroke ..color = Colors.transparent ..strokeWidth = 1.0; _clipPaint = Paint(); } late Paint _bgTouchTooltipPaint; late Paint _borderTouchTooltipPaint; late Paint _clipPaint; /// Paints [ScatterChartData] into the provided canvas. @override void paint( BuildContext context, CanvasWrapper canvasWrapper, PaintHolder holder, ) { if (holder.chartVirtualRect != null) { canvasWrapper ..saveLayer( Offset.zero & canvasWrapper.size, _clipPaint, ) ..clipRect(Offset.zero & canvasWrapper.size); } super.paint(context, canvasWrapper, holder); drawSpots(context, canvasWrapper, holder); if (holder.chartVirtualRect != null) { canvasWrapper.restore(); } drawTouchTooltips(context, canvasWrapper, holder); } @visibleForTesting void drawSpots( BuildContext context, CanvasWrapper canvasWrapper, PaintHolder holder, ) { final data = holder.data; final viewSize = canvasWrapper.size; final clip = data.clipData; final border = data.borderData.show ? data.borderData.border : null; if (data.clipData.any) { canvasWrapper.saveLayer( Rect.fromLTRB( 0, 0, canvasWrapper.size.width, canvasWrapper.size.height, ), _clipPaint, ); var left = 0.0; var top = 0.0; var right = viewSize.width; var bottom = viewSize.height; if (clip.left) { final borderWidth = border?.left.width ?? 0; left = borderWidth / 2; } if (clip.top) { final borderWidth = border?.top.width ?? 0; top = borderWidth / 2; } if (clip.right) { final borderWidth = border?.right.width ?? 0; right = viewSize.width - (borderWidth / 2); } if (clip.bottom) { final borderWidth = border?.bottom.width ?? 0; bottom = viewSize.height - (borderWidth / 2); } canvasWrapper.clipRect(Rect.fromLTRB(left, top, right, bottom)); } for (final scatterSpot in data.scatterSpots) { if (!scatterSpot.show) { continue; } final pixelX = getPixelX(scatterSpot.x, viewSize, holder); final pixelY = getPixelY(scatterSpot.y, viewSize, holder); canvasWrapper.drawDot( scatterSpot.dotPainter, scatterSpot, Offset(pixelX, pixelY), ); } drawScatterErrorBars(canvasWrapper, holder); if (data.scatterLabelSettings.showLabel) { for (var i = 0; i < data.scatterSpots.length; i++) { final scatterSpot = data.scatterSpots[i]; final spotIndex = i; final label = data.scatterLabelSettings.getLabelFunction(spotIndex, scatterSpot); if (label.isEmpty || !scatterSpot.show) { continue; } final span = TextSpan( text: label, style: Utils().getThemeAwareTextStyle( context, data.scatterLabelSettings.getLabelTextStyleFunction( spotIndex, scatterSpot, ), ), ); final tp = TextPainter( text: span, textAlign: TextAlign.center, textDirection: holder.data.scatterLabelSettings.textDirection, textScaler: holder.textScaler, )..layout(maxWidth: viewSize.width); final pixelX = getPixelX(scatterSpot.x, viewSize, holder); final pixelY = getPixelY(scatterSpot.y, viewSize, holder); double newPixelY; /// To ensure the label is centered horizontally with respect to the spot. final newPixelX = pixelX - tp.width / 2; final centerChartY = viewSize.height / 2; final radius = scatterSpot.dotPainter.getSize(scatterSpot).width / 2; /// if the spot is in the lower half of the chart, then draw the label either in the center or above the spot, /// if the spot is in upper half of the chart, then draw the label either in the center or below the spot. if (pixelY > centerChartY) { /// if either the height or the width of the spot is greater than the radius of the spot, then draw the label above the bubble, /// else draw the label inside the bubble. final off = (radius * 1.5 < tp.height || radius * 1.5 < tp.width) ? radius + tp.height : tp.height / 2; newPixelY = pixelY - off; } else { /// if either the height or the width of the spot is greater than the radius of the spot, then draw the label below the bubble, /// else draw the label inside the bubble. final off = (radius * 1.5 < tp.height || radius * 1.5 < tp.width) ? radius : -tp.height / 2; newPixelY = pixelY + off; } canvasWrapper.drawText( tp, Offset(newPixelX, newPixelY), ); } } if (data.clipData.any) { canvasWrapper.restore(); } } @visibleForTesting void drawScatterErrorBars( CanvasWrapper canvasWrapper, PaintHolder holder, ) { final data = holder.data; final viewSize = canvasWrapper.size; final errorIndicatorData = data.errorIndicatorData; if (!errorIndicatorData.show) { return; } for (var i = 0; i < data.scatterSpots.length; i++) { final spot = data.scatterSpots[i]; if (!spot.show || spot.isNull()) { continue; } final x = getPixelX(spot.x, viewSize, holder); final y = getPixelY(spot.y, viewSize, holder); if (spot.xError == null && spot.yError == null) { continue; } var left = 0.0; var right = 0.0; if (spot.xError != null) { left = getPixelX(spot.x - spot.xError!.lowerBy, viewSize, holder) - x; right = getPixelX(spot.x + spot.xError!.upperBy, viewSize, holder) - x; } var top = 0.0; var bottom = 0.0; if (spot.yError != null) { top = getPixelY(spot.y + spot.yError!.upperBy, viewSize, holder) - y; bottom = getPixelY(spot.y - spot.yError!.lowerBy, viewSize, holder) - y; } final relativeErrorPixelsRect = Rect.fromLTRB( left, top, right, bottom, ); final painter = errorIndicatorData.painter( ScatterChartSpotErrorRangeCallbackInput( spot: spot, spotIndex: i, ), ); canvasWrapper.drawErrorIndicator( painter, spot, Offset(x, y), relativeErrorPixelsRect, holder.data, ); } } @visibleForTesting void drawTouchTooltips( BuildContext context, CanvasWrapper canvasWrapper, PaintHolder holder, ) { final targetData = holder.targetData; for (var i = 0; i < targetData.scatterSpots.length; i++) { if (!targetData.showingTooltipIndicators.contains(i)) { continue; } final scatterSpot = targetData.scatterSpots[i]; drawTouchTooltip( context, canvasWrapper, targetData.scatterTouchData.touchTooltipData, scatterSpot, holder, ); } } @visibleForTesting void drawTouchTooltip( BuildContext context, CanvasWrapper canvasWrapper, ScatterTouchTooltipData tooltipData, ScatterSpot showOnSpot, PaintHolder holder, ) { final viewSize = canvasWrapper.size; final tooltipItem = tooltipData.getTooltipItems(showOnSpot); if (tooltipItem == null) { return; } final span = TextSpan( style: Utils().getThemeAwareTextStyle(context, tooltipItem.textStyle), text: tooltipItem.text, children: tooltipItem.children, ); final drawingTextPainter = TextPainter( text: span, textAlign: tooltipItem.textAlign, textDirection: tooltipItem.textDirection, textScaler: holder.textScaler, )..layout(maxWidth: tooltipData.maxContentWidth); final width = drawingTextPainter.width; final height = drawingTextPainter.height; final tooltipOriginPoint = Offset( getPixelX(showOnSpot.x, viewSize, holder), getPixelY(showOnSpot.y, viewSize, holder), ); // Get the dot size to create an extended boundary final dotSize = showOnSpot.dotPainter.getSize(showOnSpot); final dotRadius = dotSize.width / 2; final viewRect = Offset.zero & viewSize; final extendedBoundary = viewRect.inflate(dotRadius); // Check if any part of the dot is within the extended boundary if (!extendedBoundary.contains(tooltipOriginPoint)) { return; } final tooltipWidth = width + tooltipData.tooltipPadding.horizontal; final tooltipHeight = height + tooltipData.tooltipPadding.vertical; final tooltipLeftPosition = getTooltipLeft( tooltipOriginPoint.dx, tooltipWidth, tooltipData.tooltipHorizontalAlignment, tooltipData.tooltipHorizontalOffset, ); /// draw the background rect with rounded radius var rect = Rect.fromLTWH( tooltipLeftPosition, tooltipOriginPoint.dy - tooltipHeight - (showOnSpot.size.height / 2) - tooltipItem.bottomMargin, tooltipWidth, tooltipHeight, ); if (tooltipData.fitInsideHorizontally) { if (rect.left < 0) { final shiftAmount = 0 - rect.left; rect = Rect.fromLTRB( rect.left + shiftAmount, rect.top, rect.right + shiftAmount, rect.bottom, ); } if (rect.right > viewSize.width) { final shiftAmount = rect.right - viewSize.width; rect = Rect.fromLTRB( rect.left - shiftAmount, rect.top, rect.right - shiftAmount, rect.bottom, ); } } if (tooltipData.fitInsideVertically) { if (rect.top < 0) { final shiftAmount = 0 - rect.top; rect = Rect.fromLTRB( rect.left, rect.top + shiftAmount, rect.right, rect.bottom + shiftAmount, ); } if (rect.bottom > viewSize.height) { final shiftAmount = rect.bottom - viewSize.height; rect = Rect.fromLTRB( rect.left, rect.top - shiftAmount, rect.right, rect.bottom - shiftAmount, ); } } final roundedRect = RRect.fromRectAndCorners( rect, topLeft: tooltipData.tooltipBorderRadius.topLeft, topRight: tooltipData.tooltipBorderRadius.topRight, bottomLeft: tooltipData.tooltipBorderRadius.bottomLeft, bottomRight: tooltipData.tooltipBorderRadius.bottomRight, ); _bgTouchTooltipPaint.color = tooltipData.getTooltipColor(showOnSpot); final rotateAngle = tooltipData.rotateAngle; final rectRotationOffset = Offset(0, Utils().calculateRotationOffset(rect.size, rotateAngle).dy); final rectDrawOffset = Offset(roundedRect.left, roundedRect.top); final textRotationOffset = Utils().calculateRotationOffset(drawingTextPainter.size, rotateAngle); final drawOffset = Offset( rect.center.dx - (drawingTextPainter.width / 2), rect.topCenter.dy + tooltipData.tooltipPadding.top - textRotationOffset.dy + rectRotationOffset.dy, ); if (tooltipData.tooltipBorder != BorderSide.none) { _borderTouchTooltipPaint ..color = tooltipData.tooltipBorder.color ..strokeWidth = tooltipData.tooltipBorder.width; } final reverseQuarterTurnsAngle = -holder.data.rotationQuarterTurns * 90; canvasWrapper.drawRotated( size: rect.size, rotationOffset: rectRotationOffset, drawOffset: rectDrawOffset, angle: reverseQuarterTurnsAngle + rotateAngle, drawCallback: () { canvasWrapper ..drawRRect(roundedRect, _bgTouchTooltipPaint) ..drawRRect(roundedRect, _borderTouchTooltipPaint) ..drawText(drawingTextPainter, drawOffset); }, ); } /// Makes a [ScatterTouchedSpot] based on the provided [localPosition] /// /// Processes [localPosition] and checks /// the elements of the chart that are near the offset, /// then makes a [ScatterTouchedSpot] from the elements that has been touched. /// /// Returns null if finds nothing! ScatterTouchedSpot? handleTouch( Offset localPosition, Size viewSize, PaintHolder holder, ) { final data = holder.data; for (var i = data.scatterSpots.length - 1; i >= 0; i--) { // Reverse the loop to check the topmost spot first final spot = data.scatterSpots[i]; final spotPixelX = getPixelX(spot.x, viewSize, holder); final spotPixelY = getPixelY(spot.y, viewSize, holder); final center = Offset(spotPixelX, spotPixelY); final touched = spot.dotPainter.hitTest( spot, localPosition, center, data.scatterTouchData.touchSpotThreshold, ); if (touched) { return ScatterTouchedSpot(spot, i); } } return null; } } ================================================ FILE: lib/src/chart/scatter_chart/scatter_chart_renderer.dart ================================================ import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart'; import 'package:fl_chart/src/chart/base/base_chart/render_base_chart.dart'; import 'package:fl_chart/src/chart/scatter_chart/scatter_chart_painter.dart'; import 'package:fl_chart/src/utils/canvas_wrapper.dart'; import 'package:flutter/cupertino.dart'; // coverage:ignore-start /// Low level ScatterChart Widget. class ScatterChartLeaf extends LeafRenderObjectWidget { const ScatterChartLeaf({ super.key, required this.data, required this.targetData, required this.chartVirtualRect, required this.canBeScaled, }); final ScatterChartData data; final ScatterChartData targetData; final Rect? chartVirtualRect; final bool canBeScaled; @override RenderScatterChart createRenderObject(BuildContext context) => RenderScatterChart( context, data, targetData, MediaQuery.of(context).textScaler, chartVirtualRect, canBeScaled: canBeScaled, ); @override void updateRenderObject( BuildContext context, RenderScatterChart renderObject, ) { renderObject ..data = data ..targetData = targetData ..textScaler = MediaQuery.of(context).textScaler ..buildContext = context ..chartVirtualRect = chartVirtualRect ..canBeScaled = canBeScaled; } } // coverage:ignore-end /// Renders our ScatterChart, also handles hitTest. class RenderScatterChart extends RenderBaseChart { RenderScatterChart( BuildContext context, ScatterChartData data, ScatterChartData targetData, TextScaler textScaler, Rect? chartVirtualRect, { required bool canBeScaled, }) : _data = data, _targetData = targetData, _textScaler = textScaler, _chartVirtualRect = chartVirtualRect, super(targetData.scatterTouchData, context, canBeScaled: canBeScaled); ScatterChartData get data => _data; ScatterChartData _data; set data(ScatterChartData value) { if (_data == value) return; _data = value; markNeedsPaint(); } ScatterChartData get targetData => _targetData; ScatterChartData _targetData; set targetData(ScatterChartData value) { if (_targetData == value) return; _targetData = value; super.updateBaseTouchData(_targetData.scatterTouchData); markNeedsPaint(); } TextScaler get textScaler => _textScaler; TextScaler _textScaler; set textScaler(TextScaler value) { if (_textScaler == value) return; _textScaler = value; markNeedsPaint(); } Rect? get chartVirtualRect => _chartVirtualRect; Rect? _chartVirtualRect; set chartVirtualRect(Rect? value) { if (_chartVirtualRect == value) return; _chartVirtualRect = value; markNeedsPaint(); } // We couldn't mock [size] property of this class, that's why we have this @visibleForTesting Size? mockTestSize; @visibleForTesting ScatterChartPainter painter = ScatterChartPainter(); PaintHolder get paintHolder => PaintHolder(data, targetData, textScaler, chartVirtualRect); @override void paint(PaintingContext context, Offset offset) { final canvas = context.canvas ..save() ..translate(offset.dx, offset.dy); painter.paint( buildContext, CanvasWrapper(canvas, mockTestSize ?? size), paintHolder, ); canvas.restore(); } @override bool hitTestSelf(Offset position) { if (!targetData.scatterTouchData.enabled) { return false; } return super.hitTestSelf(position); } @override ScatterTouchResponse getResponseAtLocation(Offset localPosition) { final chartSize = mockTestSize ?? size; return ScatterTouchResponse( touchLocation: localPosition, touchChartCoordinate: painter.getChartCoordinateFromPixel( localPosition, chartSize, paintHolder, ), touchedSpot: painter.handleTouch( localPosition, chartSize, paintHolder, ), ); } } ================================================ FILE: lib/src/extensions/bar_chart_data_extension.dart ================================================ import 'package:fl_chart/src/chart/bar_chart/bar_chart_data.dart'; extension BarChartDataExtension on BarChartData { List calculateGroupsX(double viewWidth) { assert(barGroups.isNotEmpty); final groupsX = List.filled(barGroups.length, 0); var sumWidth = barGroups.map((group) => group.width).reduce((a, b) => a + b); final spaceAvailable = viewWidth - sumWidth; void spaceEvenly() { final eachSpace = spaceAvailable / (barGroups.length + 1); var tempX = 0.0; barGroups.asMap().forEach((i, group) { tempX += eachSpace; tempX += group.width / 2; groupsX[i] = tempX; tempX += group.width / 2; }); } switch (alignment) { case BarChartAlignment.start: var tempX = 0.0; for (var i = 0; i < barGroups.length; i++) { final group = barGroups[i]; groupsX[i] = tempX + group.width / 2; final groupSpace = i == barGroups.length - 1 ? 0 : groupsSpace; tempX += group.width + groupSpace; } if (tempX > viewWidth) { spaceEvenly(); } case BarChartAlignment.end: sumWidth += groupsSpace * (barGroups.length - 1); final horizontalMargin = viewWidth - sumWidth; var tempX = 0.0; for (var i = 0; i < barGroups.length; i++) { final group = barGroups[i]; groupsX[i] = horizontalMargin + tempX + group.width / 2; final groupSpace = i == barGroups.length - 1 ? 0 : groupsSpace; tempX += group.width + groupSpace; } if (tempX > viewWidth) { spaceEvenly(); } case BarChartAlignment.center: sumWidth += groupsSpace * (barGroups.length - 1); final horizontalMargin = (viewWidth - sumWidth) / 2; var tempX = 0.0; for (var i = 0; i < barGroups.length; i++) { final group = barGroups[i]; groupsX[i] = horizontalMargin + tempX + group.width / 2; final groupSpace = i == barGroups.length - 1 ? 0 : groupsSpace; tempX += group.width + groupSpace; } if (tempX > viewWidth) { spaceEvenly(); } case BarChartAlignment.spaceBetween: final eachSpace = spaceAvailable / (barGroups.length - 1); var tempX = 0.0; barGroups.asMap().forEach((index, group) { tempX += group.width / 2; if (index != 0) { tempX += eachSpace; } groupsX[index] = tempX; tempX += group.width / 2; }); case BarChartAlignment.spaceAround: final eachSpace = spaceAvailable / (barGroups.length * 2); var tempX = 0.0; barGroups.asMap().forEach((i, group) { tempX += eachSpace; tempX += group.width / 2; groupsX[i] = tempX; tempX += group.width / 2; tempX += eachSpace; }); case BarChartAlignment.spaceEvenly: spaceEvenly(); } return groupsX; } } ================================================ FILE: lib/src/extensions/border_extension.dart ================================================ import 'package:flutter/widgets.dart'; extension BorderExtension on Border { bool isVisible() { if (left.width == 0 && top.width == 0 && right.width == 0 && bottom.width == 0) { return false; } if (left.color.a == 0.0 && top.color.a == 0.0 && right.color.a == 0.0 && bottom.color.a == 0.0) { return false; } return true; } } ================================================ FILE: lib/src/extensions/color_extension.dart ================================================ import 'package:flutter/material.dart'; extension ColorExtension on Color { /// Convert the color to a darken color based on the [percent] Color darken([int percent = 40]) { assert(1 <= percent && percent <= 100); final value = 1 - percent / 100; return Color.fromARGB( _floatToInt8(a), (_floatToInt8(r) * value).round(), (_floatToInt8(g) * value).round(), (_floatToInt8(b) * value).round(), ); } // Int color components were deprecated in Flutter 3.27.0. // This method is used to convert the new double color components to the // old int color components. // // Taken from the Color class. int _floatToInt8(double x) { return (x * 255.0).round() & 0xff; } } ================================================ FILE: lib/src/extensions/edge_insets_extension.dart ================================================ import 'package:flutter/widgets.dart'; extension EdgeInsetsExtension on EdgeInsets { EdgeInsets get onlyTopBottom => EdgeInsets.only( top: top, bottom: bottom, ); EdgeInsets get onlyLeftRight => EdgeInsets.only( left: left, right: right, ); } ================================================ FILE: lib/src/extensions/fl_border_data_extension.dart ================================================ import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/widgets.dart'; extension FlBorderDataExtension on FlBorderData { EdgeInsets get allSidesPadding => EdgeInsets.only( left: show ? border.left.width : 0.0, top: show ? border.top.width : 0.0, right: show ? border.right.width : 0.0, bottom: show ? border.bottom.width : 0.0, ); } ================================================ FILE: lib/src/extensions/fl_titles_data_extension.dart ================================================ import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/extensions/side_titles_extension.dart'; import 'package:flutter/widgets.dart'; extension FlTitlesDataExtension on FlTitlesData { EdgeInsets get allSidesPadding => EdgeInsets.only( left: _getPadding( leftTitles.sideTitleAlignment, leftTitles.totalReservedSize, ), top: _getPadding( topTitles.sideTitleAlignment, topTitles.totalReservedSize, ), right: _getPadding( rightTitles.sideTitleAlignment, rightTitles.totalReservedSize, ), bottom: _getPadding( bottomTitles.sideTitleAlignment, bottomTitles.totalReservedSize, ), ); double _getPadding(SideTitleAlignment alignment, double reservedSize) { if (!show || alignment == SideTitleAlignment.inside) { return 0; } else if (alignment == SideTitleAlignment.border) { return reservedSize / 2; } else { return reservedSize; } } } ================================================ FILE: lib/src/extensions/gradient_extension.dart ================================================ import 'package:flutter/painting.dart'; /// Extensions on [Gradient] extension GradientExtension on Gradient { /// Returns color stops. /// /// If [stops] has the same length as [colors], returns it directly. /// Otherwise, calculates stops linearly between 0.0 and 1.0. /// /// Throws [ArgumentError] if [colors] has less than 2 colors. List getSafeColorStops() { if (stops?.length == colors.length) { return stops!; } if (colors.length <= 1) { throw ArgumentError('"colors" must have length > 1.'); } final stopsStep = 1.0 / (colors.length - 1); return [ for (var index = 0; index < colors.length; index++) index * stopsStep, ]; } } ================================================ FILE: lib/src/extensions/paint_extension.dart ================================================ import 'package:flutter/material.dart'; extension PaintExtension on Paint { /// Hides the paint's color, if strokeWidth is zero void transparentIfWidthIsZero() { if (strokeWidth == 0) { shader = null; color = color.withValues(alpha: 0); } } void setColorOrGradient(Color? color, Gradient? gradient, Rect rect) { if (gradient != null) { this.color = Colors.black; shader = gradient.createShader(rect); } else { this.color = color ?? Colors.transparent; shader = null; } } void setColorOrGradientForLine( Color? color, Gradient? gradient, { required Offset from, required Offset to, }) { final rect = Rect.fromPoints(from, to); setColorOrGradient(color, gradient, rect); } } ================================================ FILE: lib/src/extensions/path_extension.dart ================================================ import 'dart:ui'; import 'package:fl_chart/src/utils/path_drawing/dash_path.dart'; /// Defines extensions on the [Path] extension DashedPath on Path { /// Returns a dashed path based on [dashArray]. /// /// it is a circular array of dash offsets and lengths. /// For example, the array `[5, 10]` would result in dashes 5 pixels long /// followed by blank spaces 10 pixels long. Path toDashedPath(List? dashArray) { if (dashArray != null) { final castedArray = dashArray.map((value) => value.toDouble()).toList(); final dashedPath = dashPath(this, dashArray: CircularIntervalList(castedArray)); return dashedPath; } else { return this; } } } ================================================ FILE: lib/src/extensions/rrect_extension.dart ================================================ import 'package:flutter/cupertino.dart'; /// Defines extensions on the [RRect] extension RRectExtension on RRect { /// Return [Rect] from [RRect] Rect getRect() => Rect.fromLTRB( left, top, right, bottom, ); } ================================================ FILE: lib/src/extensions/side_titles_extension.dart ================================================ import 'package:fl_chart/src/chart/base/axis_chart/axis_chart_data.dart'; extension SideTitlesExtension on AxisTitles { double get totalReservedSize { var size = 0.0; if (showAxisTitles) { size += axisNameSize; } if (showSideTitles) { size += sideTitles.reservedSize; } return size; } } ================================================ FILE: lib/src/extensions/size_extension.dart ================================================ import 'dart:ui'; extension SizeExtension on Size { Size rotateByQuarterTurns(int quarterTurns) { if (quarterTurns < 0) { throw ArgumentError('quarterTurns must be greater than or equal to 0.'); } return switch (quarterTurns % 4) { 0 || 2 => this, _ /*2 || 3*/ => Size(height, width), }; } } ================================================ FILE: lib/src/extensions/text_align_extension.dart ================================================ import 'package:flutter/material.dart'; enum HorizontalAlignment { left, center, right } extension TextAlignExtension on TextAlign { HorizontalAlignment getFinalHorizontalAlignment(TextDirection? direction) { if ((this == TextAlign.left) || (this == TextAlign.start && direction == TextDirection.ltr) || (this == TextAlign.end && direction == TextDirection.rtl)) { return HorizontalAlignment.left; } else if ((this == TextAlign.right) || (this == TextAlign.end && direction == TextDirection.ltr) || (this == TextAlign.start && direction == TextDirection.rtl)) { return HorizontalAlignment.right; } else { return HorizontalAlignment.center; } } } ================================================ FILE: lib/src/utils/canvas_wrapper.dart ================================================ import 'dart:ui'; import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/extensions/path_extension.dart'; import 'package:fl_chart/src/utils/utils.dart'; import 'package:flutter/cupertino.dart' hide Image; typedef DrawCallback = void Function(); /// Proxies Canvas functions /// /// We wrapped the canvas here, because we needed to write tests for our drawing system. /// Now in tests we can verify that these functions called with a specific value. class CanvasWrapper { CanvasWrapper( this.canvas, this.size, ); final Canvas canvas; final Size size; /// Directly calls [Canvas.drawRRect] void drawRRect(RRect rrect, Paint paint) => canvas.drawRRect(rrect, paint); /// Directly calls [Canvas.save] void save() => canvas.save(); /// Directly calls [Canvas.restore] void restore() => canvas.restore(); /// Directly calls [Canvas.clipRect] void clipRect( Rect rect, { ClipOp clipOp = ClipOp.intersect, bool doAntiAlias = true, }) => canvas.clipRect(rect, clipOp: clipOp, doAntiAlias: doAntiAlias); /// Directly calls [Canvas.translate] void translate(double dx, double dy) => canvas.translate(dx, dy); /// Directly calls [Canvas.rotate] void rotate(double radius) => canvas.rotate(radius); /// Directly calls [Canvas.drawPath] void drawPath(Path path, Paint paint) => canvas.drawPath(path, paint); /// Directly calls [Canvas.saveLayer] void saveLayer(Rect bounds, Paint paint) => canvas.saveLayer(bounds, paint); /// Directly calls [Canvas.drawPicture] void drawPicture(Picture picture) => canvas.drawPicture(picture); /// Directly calls [Canvas.drawImage] void drawImage(Image image, Offset offset, Paint paint) => canvas.drawImage(image, offset, paint); /// Directly calls [Canvas.clipPath] void clipPath(Path path, {bool doAntiAlias = true}) => canvas.clipPath(path, doAntiAlias: doAntiAlias); /// Directly calls [Canvas.drawRect] void drawRect(Rect rect, Paint paint) => canvas.drawRect(rect, paint); /// Directly calls [Canvas.drawLine] void drawLine(Offset p1, Offset p2, Paint paint) => canvas.drawLine(p1, p2, paint); /// Directly calls [Canvas.drawCircle] void drawCircle(Offset center, double radius, Paint paint) => canvas.drawCircle(center, radius, paint); /// Directly calls [Canvas.drawCircle] void drawArc( Rect rect, double startAngle, double sweepAngle, bool useCenter, Paint paint, ) => canvas.drawArc(rect, startAngle, sweepAngle, useCenter, paint); /// Paints a text on the [Canvas] /// /// Gets a [TextPainter] and call its [TextPainter.paint] using our canvas void drawText(TextPainter tp, Offset offset, [double? rotateAngle]) { if (rotateAngle == null) { tp.paint(canvas, offset); } else { drawRotated( size: tp.size, drawOffset: offset, angle: rotateAngle, drawCallback: () { tp.paint(canvas, offset); }, ); } } /// Paints a vertical text on the [Canvas] /// /// Gets a [TextPainter] and call its [TextPainter.paint] using our canvas void drawVerticalText( TextPainter tp, Offset offset, [ double rotateAngle = 90, ]) { save(); translate(offset.dx, offset.dy); rotate(Utils().radians(rotateAngle)); translate(-offset.dx, -offset.dy); tp.paint(canvas, offset); restore(); } /// Paints a dot using customized [FlDotPainter] /// /// Paints a customized dot using [FlDotPainter] at the [spot]'s position, /// with the [offset] void drawDot(FlDotPainter painter, FlSpot spot, Offset offset) { painter.draw(canvas, spot, offset); } /// Paints a error indicator using the [painter] void drawErrorIndicator( FlSpotErrorRangePainter painter, FlSpot origin, Offset offset, Rect errorRelativeRect, AxisChartData axisData, ) { painter.draw(canvas, offset, origin, errorRelativeRect, axisData); } /// Handles performing multiple draw actions rotated. void drawRotated({ required Size size, Offset rotationOffset = Offset.zero, Offset drawOffset = Offset.zero, required double angle, required DrawCallback drawCallback, }) { save(); translate( rotationOffset.dx + drawOffset.dx + size.width / 2, rotationOffset.dy + drawOffset.dy + size.height / 2, ); rotate(Utils().radians(angle)); translate( -drawOffset.dx - size.width / 2, -drawOffset.dy - size.height / 2, ); drawCallback(); restore(); } /// Draws a dashed line from passed in offsets void drawDashedLine( Offset from, Offset to, Paint painter, List? dashArray, ) { var path = Path() ..moveTo(from.dx, from.dy) ..lineTo(to.dx, to.dy); path = path.toDashedPath(dashArray); drawPath(path, painter); } } ================================================ FILE: lib/src/utils/lerp.dart ================================================ import 'dart:ui'; import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; @visibleForTesting List? lerpList( List? a, List? b, double t, { required T Function(T, T, double) lerp, }) { if (a != null && b != null && a.length == b.length) { return List.generate(a.length, (i) { return lerp(a[i], b[i], t); }); } else if (a != null && b != null) { return List.generate(b.length, (i) { return lerp(i >= a.length ? b[i] : a[i], b[i], t); }); } else { return b; } } /// Lerps [Color] list based on [t] value, check [Tween.lerp]. List? lerpColorList(List? a, List? b, double t) => lerpList(a, b, t, lerp: lerpColor); /// Lerps [Color] based on [t] value, check [Color.lerp]. Color lerpColor(Color a, Color b, double t) => Color.lerp(a, b, t)!; /// Lerps [double] list based on [t] value, allows [double.infinity]. double? lerpDoubleAllowInfinity(double? a, double? b, double t) { if (a == b || (a?.isNaN == true) && (b?.isNaN == true)) { return a; } if (a!.isInfinite || b!.isInfinite) { return b; } assert(a.isFinite, 'Cannot interpolate between finite and non-finite values'); assert(b.isFinite, 'Cannot interpolate between finite and non-finite values'); assert(t.isFinite, 't must be finite when interpolating between values'); return a * (1.0 - t) + b * t; } /// Lerps [double] list based on [t] value, check [Tween.lerp]. List? lerpDoubleList(List? a, List? b, double t) => lerpList(a, b, t, lerp: lerpNonNullDouble); /// Lerps [int] list based on [t] value, check [Tween.lerp]. List? lerpIntList(List? a, List? b, double t) => lerpList(a, b, t, lerp: lerpInt); /// Lerps [int] list based on [t] value, check [Tween.lerp]. int lerpInt(int a, int b, double t) => (a + (b - a) * t).round(); @visibleForTesting double lerpNonNullDouble(double a, double b, double t) => lerpDouble(a, b, t)!; /// Lerps [FlSpot] list based on [t] value, check [Tween.lerp]. List? lerpFlSpotList(List? a, List? b, double t) => lerpList(a, b, t, lerp: FlSpot.lerp); /// Lerps [HorizontalLine] list based on [t] value, check [Tween.lerp]. List? lerpHorizontalLineList( List? a, List? b, double t, ) => lerpList(a, b, t, lerp: HorizontalLine.lerp); /// Lerps [VerticalLine] list based on [t] value, check [Tween.lerp]. List? lerpVerticalLineList( List? a, List? b, double t, ) => lerpList(a, b, t, lerp: VerticalLine.lerp); /// Lerps [HorizontalRangeAnnotation] list based on [t] value, check [Tween.lerp]. List? lerpHorizontalRangeAnnotationList( List? a, List? b, double t, ) => lerpList(a, b, t, lerp: HorizontalRangeAnnotation.lerp); /// Lerps [VerticalRangeAnnotation] list based on [t] value, check [Tween.lerp]. List? lerpVerticalRangeAnnotationList( List? a, List? b, double t, ) => lerpList(a, b, t, lerp: VerticalRangeAnnotation.lerp); /// Lerps [LineChartBarData] list based on [t] value, check [Tween.lerp]. List? lerpLineChartBarDataList( List? a, List? b, double t, ) => lerpList(a, b, t, lerp: LineChartBarData.lerp); /// Lerps [BetweenBarsData] list based on [t] value, check [Tween.lerp]. List? lerpBetweenBarsDataList( List? a, List? b, double t, ) => lerpList(a, b, t, lerp: BetweenBarsData.lerp); /// Lerps [BarChartGroupData] list based on [t] value, check [Tween.lerp]. List? lerpBarChartGroupDataList( List? a, List? b, double t, ) => lerpList(a, b, t, lerp: BarChartGroupData.lerp); /// Lerps [BarChartRodData] list based on [t] value, check [Tween.lerp]. List? lerpBarChartRodDataList( List? a, List? b, double t, ) => lerpList(a, b, t, lerp: BarChartRodData.lerp); /// Lerps [PieChartSectionData] list based on [t] value, check [Tween.lerp]. List? lerpPieChartSectionDataList( List? a, List? b, double t, ) => lerpList(a, b, t, lerp: PieChartSectionData.lerp); /// Lerps [ScatterSpot] list based on [t] value, check [Tween.lerp]. List? lerpScatterSpotList( List? a, List? b, double t, ) => lerpList(a, b, t, lerp: ScatterSpot.lerp); /// Lerps [CandlestickSpot] list based on [t] value, check [Tween.lerp]. List? lerpCandleSpotList( List? a, List? b, double t, ) => lerpList(a, b, t, lerp: CandlestickSpot.lerp); /// Lerps [BarChartRodStackItem] list based on [t] value, check [Tween.lerp]. List? lerpBarChartRodStackList( List? a, List? b, double t, ) => lerpList(a, b, t, lerp: BarChartRodStackItem.lerp); /// Lerps [RadarDataSet] list based on [t] value, check [Tween.lerp]. List? lerpRadarDataSetList( List? a, List? b, double t, ) => lerpList(a, b, t, lerp: RadarDataSet.lerp); /// Lerps [RadarEntry] list based on [t] value, check [Tween.lerp]. List? lerpRadarEntryList( List? a, List? b, double t, ) => lerpList(a, b, t, lerp: RadarEntry.lerp); /// Lerps between a [LinearGradient] colors, based on [t] Color lerpGradient(List colors, List stops, double t) { final length = colors.length; if (stops.length != length) { /// provided gradientColorStops is invalid and we calculate it here stops = List.generate(length, (i) => (i + 1) / length); } for (var s = 0; s < stops.length - 1; s++) { final leftStop = stops[s]; final rightStop = stops[s + 1]; final leftColor = colors[s]; final rightColor = colors[s + 1]; if (t <= leftStop) { return leftColor; } else if (t < rightStop) { final sectionT = (t - leftStop) / (rightStop - leftStop); return Color.lerp(leftColor, rightColor, sectionT)!; } } return colors.last; } ================================================ FILE: lib/src/utils/path_drawing/dash_path.dart ================================================ import 'dart:ui'; /// Came from [flutter_path_drawing](https://github.com/dnfield/flutter_path_drawing) library. /// Creates a new path that is drawn from the segments of `source`. /// /// Dash intervals are controlled by the `dashArray` - see [CircularIntervalList] /// for examples. /// /// `dashOffset` specifies an initial starting point for the dashing. /// /// Passing a `source` that is an empty path will return an empty path. Path dashPath( Path source, { required CircularIntervalList dashArray, DashOffset? dashOffset, }) { dashOffset = dashOffset ?? const DashOffset.absolute(0); // TODO(imaNNeo): Is there some way to determine how much of a path would be visible today? final dest = Path(); for (final metric in source.computeMetrics()) { var distance = dashOffset._calculate(metric.length); var draw = true; while (distance < metric.length) { final len = dashArray.next; if (draw) { dest.addPath(metric.extractPath(distance, distance + len), Offset.zero); } distance += len; draw = !draw; } } return dest; } enum _DashOffsetType { absolute, percentage } /// Specifies the starting position of a dash array on a path, either as a /// percentage or absolute value. /// /// The internal value will be guaranteed to not be null. class DashOffset { /// Create a DashOffset that will be measured as a percentage of the length /// of the segment being dashed. /// /// `percentage` will be clamped between 0.0 and 1.0. DashOffset.percentage(double percentage) : _rawVal = percentage.clamp(0.0, 1.0), _dashOffsetType = _DashOffsetType.percentage; /// Create a DashOffset that will be measured in terms of absolute pixels /// along the length of a [Path] segment. const DashOffset.absolute(double start) : _rawVal = start, _dashOffsetType = _DashOffsetType.absolute; final double _rawVal; final _DashOffsetType _dashOffsetType; double _calculate(double length) => _dashOffsetType == _DashOffsetType.absolute ? _rawVal : length * _rawVal; } /// A circular array of dash offsets and lengths. /// /// For example, the array `[5, 10]` would result in dashes 5 pixels long /// followed by blank spaces 10 pixels long. The array `[5, 10, 5]` would /// result in a 5 pixel dash, a 10 pixel gap, a 5 pixel dash, a 5 pixel gap, /// a 10 pixel dash, etc. /// /// Note that this does not quite conform to an [Iterable], because it does /// not have a moveNext. class CircularIntervalList { CircularIntervalList(this._values); final List _values; int _idx = 0; T get next { if (_idx >= _values.length) { _idx = 0; } return _values[_idx++]; } } ================================================ FILE: lib/src/utils/utils.dart ================================================ import 'dart:math' as math; import 'package:flutter/material.dart'; class Utils { factory Utils() { return _singleton; } Utils._internal(); static Utils _singleton = Utils._internal(); @visibleForTesting static void changeInstance(Utils val) => _singleton = val; static const double _degrees2Radians = math.pi / 180.0; /// Converts degrees to radians double radians(double degrees) => degrees * _degrees2Radians; static const double _radians2Degrees = 180.0 / math.pi; /// Converts radians to degrees double degrees(double radians) => radians * _radians2Degrees; /// Forward the view base on its degree double translateRotatedPosition(double size, double degree) { return (size / 4) * math.sin(radians(degree.abs())); } Offset calculateRotationOffset(Size size, double degree) { final rotatedHeight = (size.width * math.sin(radians(degree))).abs() + (size.height * math.cos(radians(degree))).abs(); final rotatedWidth = (size.width * math.cos(radians(degree))).abs() + (size.height * math.sin(radians(degree))).abs(); return Offset( (size.width - rotatedWidth) / 2, (size.height - rotatedHeight) / 2, ); } /// Decreases [borderRadius] to <= width / 2 BorderRadius? normalizeBorderRadius( BorderRadius? borderRadius, double width, ) { if (borderRadius == null) { return null; } Radius topLeft; if (borderRadius.topLeft.x > width / 2 || borderRadius.topLeft.y > width / 2) { topLeft = Radius.circular(width / 2); } else { topLeft = borderRadius.topLeft; } Radius topRight; if (borderRadius.topRight.x > width / 2 || borderRadius.topRight.y > width / 2) { topRight = Radius.circular(width / 2); } else { topRight = borderRadius.topRight; } Radius bottomLeft; if (borderRadius.bottomLeft.x > width / 2 || borderRadius.bottomLeft.y > width / 2) { bottomLeft = Radius.circular(width / 2); } else { bottomLeft = borderRadius.bottomLeft; } Radius bottomRight; if (borderRadius.bottomRight.x > width / 2 || borderRadius.bottomRight.y > width / 2) { bottomRight = Radius.circular(width / 2); } else { bottomRight = borderRadius.bottomRight; } return BorderRadius.only( topLeft: topLeft, topRight: topRight, bottomLeft: bottomLeft, bottomRight: bottomRight, ); } /// Default value for BorderSide where borderSide value is not exists static const BorderSide defaultBorderSide = BorderSide(width: 0); /// Decreases [borderSide] to <= width / 2 BorderSide normalizeBorderSide(BorderSide? borderSide, double width) { if (borderSide == null) { return defaultBorderSide; } double borderWidth; if (borderSide.width > width / 2) { borderWidth = width / 2.toDouble(); } else { borderWidth = borderSide.width; } return borderSide.copyWith(width: borderWidth); } /// Returns an efficient interval for showing axis titles, or grid lines or ... /// /// If there isn't any provided interval, we use this function to calculate an interval to apply, /// using [axisViewSize] / [pixelPerInterval], we calculate the allowedCount lines in the axis, /// then using [diffInAxis] / allowedCount, we can find out how much interval we need, /// then we round that number by finding nearest number in this pattern: /// 1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 5000, 10000,... double getEfficientInterval( double axisViewSize, double diffInAxis, { double pixelPerInterval = 40, }) { final allowedCount = math.max(axisViewSize ~/ pixelPerInterval, 1); if (diffInAxis == 0) { return 1; } final accurateInterval = diffInAxis == 0 ? axisViewSize : diffInAxis / allowedCount; if (allowedCount <= 2) { return accurateInterval; } return roundInterval(accurateInterval); } @visibleForTesting double roundInterval(double input) { if (input < 1) { return _roundIntervalBelowOne(input); } return _roundIntervalAboveOne(input); } double _roundIntervalBelowOne(double input) { assert(input < 1.0); if (input < 0.000001) { return input; } final inputString = input.toString(); var precisionCount = inputString.length - 2; var zeroCount = 0; for (var i = 2; i <= inputString.length; i++) { if (inputString[i] != '0') { break; } zeroCount++; } final afterZerosNumberLength = precisionCount - zeroCount; if (afterZerosNumberLength > 2) { final numbersToRemove = afterZerosNumberLength - 2; precisionCount -= numbersToRemove; } final pow10onPrecision = math.pow(10, precisionCount); input *= pow10onPrecision; return _roundIntervalAboveOne(input) / pow10onPrecision; } double _roundIntervalAboveOne(double input) { assert(input >= 1.0); final decimalCount = input.toInt().toString().length - 1; input /= math.pow(10, decimalCount); final scaled = input >= 10 ? input.round() / 10 : input; if (scaled >= 7.6) { return 10 * math.pow(10, decimalCount).toInt().toDouble(); } else if (scaled >= 2.6) { return 5 * math.pow(10, decimalCount).toInt().toDouble(); } else if (scaled >= 1.6) { return 2 * math.pow(10, decimalCount).toInt().toDouble(); } else { return 1 * math.pow(10, decimalCount).toInt().toDouble(); } } /// billion number /// in short scale (https://en.wikipedia.org/wiki/Billion) static const double billion = 1000000000; /// million number static const double million = 1000000; /// kilo (thousands) number static const double kilo = 1000; /// Returns count of fraction digits of a value int getFractionDigits(double value) { if (value >= 1) { return 1; } else if (value >= 0.1) { return 2; } else if (value >= 0.01) { return 3; } else if (value >= 0.001) { return 4; } else if (value >= 0.0001) { return 5; } else if (value >= 0.00001) { return 6; } else if (value >= 0.000001) { return 7; } else if (value >= 0.0000001) { return 8; } else if (value >= 0.00000001) { return 9; } else if (value >= 0.000000001) { return 10; } return 1; } /// Formats and add symbols (K, M, B) at the end of number. /// /// if number is larger than [billion], it returns a short number like 13.3B, /// if number is larger than [million], it returns a short number line 43M, /// if number is larger than [kilo], it returns a short number like 4K, /// otherwise it returns number itself. /// also it removes .0, at the end of number for simplicity. String formatNumber(double axisMin, double axisMax, double axisValue) { final isNegative = axisValue < 0; if (isNegative) { axisValue = axisValue.abs(); } String resultNumber; String symbol; if (axisValue >= billion) { resultNumber = (axisValue / billion).toStringAsFixed(1); symbol = 'B'; } else if (axisValue >= million) { resultNumber = (axisValue / million).toStringAsFixed(1); symbol = 'M'; } else if (axisValue >= kilo) { resultNumber = (axisValue / kilo).toStringAsFixed(1); symbol = 'K'; } else { final diff = (axisMin - axisMax).abs(); resultNumber = axisValue.toStringAsFixed( getFractionDigits(diff), ); symbol = ''; } if (resultNumber.endsWith('.0')) { resultNumber = resultNumber.substring(0, resultNumber.length - 2); } if (isNegative) { resultNumber = '-$resultNumber'; } if (resultNumber == '-0') { resultNumber = '0'; } return resultNumber + symbol; } /// Returns a TextStyle based on provided [context], if [providedStyle] provided we try to merge it. TextStyle getThemeAwareTextStyle( BuildContext context, TextStyle? providedStyle, ) { final defaultTextStyle = DefaultTextStyle.of(context); var effectiveTextStyle = providedStyle; if (providedStyle == null || providedStyle.inherit) { effectiveTextStyle = defaultTextStyle.style.merge(providedStyle); } if (MediaQuery.boldTextOf(context)) { effectiveTextStyle = effectiveTextStyle! .merge(const TextStyle(fontWeight: FontWeight.bold)); } return effectiveTextStyle!; } /// Finds the best initial interval value /// /// If there is a zero point in the axis, we want to have a value that passes through it. /// For example if we have -3 to +3, with interval 2. if we start from -3, we get something like this: -3, -1, +1, +3 /// But the most important point is zero in most cases. with this logic we get this: -2, 0, 2 double getBestInitialIntervalValue( double min, double max, double interval, { double baseline = 0.0, }) { final diff = baseline - min; final mod = diff % interval; if ((max - min).abs() <= mod) { return min; } if (mod == 0) { return min; } return min + mod; } /// Converts radius number to sigma for drawing shadows double convertRadiusToSigma(double radius) => radius * 0.57735 + 0.5; } ================================================ FILE: pubspec.yaml ================================================ name: fl_chart description: A highly customizable Flutter chart library that supports Line Chart, Bar Chart, Pie Chart, Scatter Chart, and Radar Chart. version: 1.2.0 homepage: https://flchart.dev/ repository: https://github.com/imaNNeo/fl_chart issue_tracker: https://github.com/imaNNeo/fl_chart/issues documentation: https://github.com/imaNNeo/fl_chart environment: sdk: ">=3.6.2 <4.0.0" flutter: ">=3.27.4" funding: - https://github.com/sponsors/imaNNeo - https://www.buymeacoffee.com/fl_chart dependencies: equatable: ^2.0.7 flutter: sdk: flutter vector_math: ^2.2.0 dev_dependencies: build_runner: ^2.8.0 flutter_test: sdk: flutter mockito: ^5.5.1 very_good_analysis: ^10.2.0 screenshots: - description: "FL Chart Logo" path: pub_screenshots/logo_1024.png - description: "LineChartSample1 and LineChartSample2" path: pub_screenshots/line_chart.jpg - description: "LineChartSample10" path: pub_screenshots/line_chart_sample_10.gif - description: "BarChartSample1 and BarChartSample2" path: pub_screenshots/bar_chart.jpg - description: "BarChartSample7" path: pub_screenshots/bar_chart_sample_7.gif - description: "PieChartSample1, PieChartSample2 and PieChartSample3" path: pub_screenshots/pie_chart.jpg - description: "ScatterChartSample2" path: pub_screenshots/scatter_chart_sample_2.gif - description: "RadarChartSample1" path: pub_screenshots/radar_chart_sample_1.jpg topics: - chart - charts - visualization - graph - diagram ================================================ FILE: repo_files/documentations/bar_chart.md ================================================ ### How to use ```dart BarChart( BarChartData( // read about it in the BarChartData section ), duration: Duration(milliseconds: 150), // Optional curve: Curves.linear, // Optional ); ``` ### Implicit Animations When you change the chart's state, it animates to the new state internally (using [implicit animations](https://flutter.dev/docs/development/ui/animations/implicit-animations)). You can control the animation [duration](https://api.flutter.dev/flutter/dart-core/Duration-class.html) and [curve](https://api.flutter.dev/flutter/animation/Curves-class.html) using optional `duration` and `curve` properties, respectively. ### BarChartData |PropName |Description |default value| |:---------------|:---------------|:-------| |barGroups| list of [BarChartGroupData ](#BarChartGroupData) to show the bar lines together, you can provide one item per group to show normal bar chart|[]| |groupsSpace| space between groups, it applies only when the [alignment](#BarChartAlignment) is `BarChartAlignment.start`, `BarChartAlignment.center` or `BarChartAlignment.end`|16| |alignment| a [BarChartAlignment](#BarChartAlignment) that determines the alignment of the barGroups, inspired by [Flutter MainAxisAlignment](https://docs.flutter.io/flutter/rendering/MainAxisAlignment-class.html)| BarChartAlignment.spaceEvenly| |titlesData| check the [FlTitlesData](base_chart.md#FlTitlesData)|FlTitlesData()| |axisTitleData| check the [FlAxisTitleData](base_chart.md#FlAxisTitleData)| FlAxisTitleData()| |rangeAnnotations| show range annotations behind the chart, check [RangeAnnotations](base_chart.md#RangeAnnotations) | RangeAnnotations()| |backgroundColor| a background color which is drawn behind the chart| null | |barTouchData| [BarTouchData](#bartouchdata-read-about-touch-handling) holds the touch interactivity details|BarTouchData()| |gridData| check the [FlGridData](base_chart.md#FlGridData)|FlGridData()| |borderData| check the [FlBorderData](base_chart.md#FlBorderData)|FlBorderData()| |maxY| gets maximum y of y axis, if null, value will be read from the input barGroups (But it is more performant if you provide them) | null| |minY| gets minimum y of y axis, if null, value will be read from the input barGroups (But it is more performant if you provide them) | null| |baselineY| defines the baseline of y-axis | 0| |extraLinesData| allows extra horizontal lines to be drawn on the chart. Vertical lines are ignored when used with BarChartData, please see [#1149](https://github.com/imaNNeo/fl_chart/issues/1149), check [ExtraLinesData](base_chart.md#ExtraLinesData)|ExtraLinesData()| |rotationQuarterTurns|Rotates the chart 90 degrees (clockwise) in every quarter turns. This feature works like the [RotatedBox](https://api.flutter.dev/flutter/widgets/RotatedBox-class.html) widget. You can have horizontal BarChart by changing this value to |0| |errorIndicatorData|Holds data for representing an error indicator (you see the error indicators if you provide the `toYErrorRange` in the [BarChartRodData](#BarChartRodData))|[ErrorIndicatorData()](base_chart.md#FlErrorIndicatorData)| ### BarChartGroupData |PropName |Description |default value| |:---------------|:---------------|:-------| |x| x position of the group on horizontal axis|null| |barRods| list of [BarChartRodData](#BarChartRodData) that are a bar line| [] |barsSpace| the space between barRods of the group|2| |showingTooltipIndicators| indexes of barRods to show the tooltip on top of them, The point is that you need to disable touches to show these tooltips manually | []| ### BarChartAlignment enum values {`start`, `end`, `center`, `spaceEvenly`, `spaceAround`, `spaceBetween`} ### BarChartRodData |PropName|Description|default value| |:-------|:----------|:------------| |fromY|Position that this bar starts from|0| |toY|This rod is from `fromY` to `toY` in the vertical axis|null| |color|color of the rod bar|[Colors.cyan]| |gradient| You can use any [Gradient](https://api.flutter.dev/flutter/dart-ui/Gradient-class.html) here. such as [LinearGradient](https://api.flutter.dev/flutter/painting/LinearGradient-class.html) or [RadialGradient](https://api.flutter.dev/flutter/painting/RadialGradient-class.html)|null| |width|stroke width of the rod bar|8| |borderRadius|Determines the edge rounding of the bar corners, see [BorderRadius](https://api.flutter.dev/flutter/painting/BorderRadius-class.html). When `null`, it defaults to completely round bars. |null| |borderDashArray|Determines wether the border stroke is dashed. It is a circular array of dash offsets and lengths. For example, the array `[5, 10]` would result in dashes 5 pixels long followed by blank spaces 10 pixels long. The array `[5, 10, 5]` would result in a 5 pixel dash, a 10 pixel gap, a 5 pixel dash, a 5 pixel gap, a 10 pixel dash, etc.|null| |borderSide|Determines the border stroke around of the bar, see [BorderSide](https://api.flutter.dev/flutter/painting/BorderSide-class.html). When `null`, it defaults to draw no stroke. |null| |backDrawRodData|if provided, draws a rod in the background of the line bar, check the [BackgroundBarChartRodData](#BackgroundBarChartRodData)|null| |rodStackItem|if you want to have stacked bar chart, provide a list of [BarChartRodStackItem](#BarChartRodStackItem), it will draw over your rod.|[]| |toYErrorRange|If you want to show an error range on the rod, provide [FlErrorRange](base_chart.md#FlErrorRange)|null| |label|a [BarChartRodLabel](#BarChartRodLabel) to show a text label on the rod|null| ### BackgroundBarChartRodData |PropName|Description|default value| |:-------|:----------|:------------| |fromY|same as [BarChartRodData](#BarChartRodData)'s fromY|0| |toY|same as [BarChartRodData](#BarChartRodData)'s y|8| |show|determines to show or hide this section|false| |color|same as [BarChartRodData](#BarChartRodData)'s colors|[Colors.blueGrey]| |gradient|same as [BarChartRodData](#BarChartRodData)'s gradient|null| ### BarChartRodStackItem |PropName|Description|default value| |:-------|:----------|:------------| |fromY|draw stack item from this value|null| |toY|draw stack item to this value|null| |color|color of the stack item|null| |gradient|gradient of the stack item|null| |label|optional text label for the stack item|null| |labelStyle|optional TextStyle for the label|null| |borderSide|draw border stroke for each stack item|null| ### BarChartRodLabel Extends [FlLabel](base_chart.md#FlLabel). |PropName|Description|default value| |:-------|:----------|:------------| |show|determines whether to show or hide the label (inherited from [FlLabel](base_chart.md#FlLabel))|true| |text|the text content of the label (inherited from [FlLabel](base_chart.md#FlLabel))|''| |style|[TextStyle](https://api.flutter.dev/flutter/dart-ui/TextStyle-class.html) of the label. When null or when `inherit` is true, the style is merged with the ambient [DefaultTextStyle](https://api.flutter.dev/flutter/widgets/DefaultTextStyle-class.html) and respects the platform's bold-text accessibility setting (inherited from [FlLabel](base_chart.md#FlLabel))|null| |angle|rotation angle of the label in degrees (inherited from [FlLabel](base_chart.md#FlLabel))|0| |textDirection|[TextDirection](https://api.flutter.dev/flutter/dart-ui/TextDirection-class.html) of the label text (inherited from [FlLabel](base_chart.md#FlLabel))|TextDirection.ltr| |offset|[Offset](https://api.flutter.dev/flutter/dart-ui/Offset-class.html) from the rod tip to position the label. `dx` shifts horizontally, `dy` shifts vertically|Offset(0, 8)| ### BarTouchData ([read about touch handling](handle_touches.md)) |PropName|Description|default value| |:-------|:----------|:------------| |enabled|determines to enable or disable touch behaviors|true| |mouseCursorResolver|you can change the mouse cursor based on the provided [FlTouchEvent](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/base_chart.md#fltouchevent) and [BarTouchResponse](#BarTouchResponse)|MouseCursor.defer| |touchTooltipData|a [BarTouchTooltipData](#BarTouchTooltipData), that determines how show the tooltip on top of touched spots (appearance of the showing tooltip bubble)|BarTouchTooltipData()| |touchExtraThreshold|an [EdgeInsets](https://api.flutter.dev/flutter/painting/EdgeInsets-class.html) class to hold a bounding threshold of touch accuracy|EdgeInsets.all(4)| |allowTouchBarBackDraw| if sets true, touch works on backdraw bar line| false | |handleBuiltInTouches| set this true if you want the built in touch handling (show a tooltip bubble and an indicator on touched spots) | true| |touchCallback| listen to this callback to retrieve touch/pointer events and responses, it gives you a [FlTouchEvent](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/base_chart.md#fltouchevent) and [BarTouchResponse](#BarTouchResponse)| null| |longPressDuration| allows to customize the duration of the longPress gesture. If null, the duration of the longPressGesture is [kLongPressTimeout](https://api.flutter.dev/flutter/gestures/kLongPressTimeout-constant.html)| null| ### BarTouchTooltipData |PropName|Description|default value| |:-------|:----------|:------------| |tooltipBorder|border of the tooltip bubble|BorderSide.none| |tooltipBorderRadius|background corner radius of the tooltip bubble|BorderRadius.circular(4)| |tooltipPadding|padding of the tooltip|EdgeInsets.symmetric(horizontal: 16, vertical: 8)| |tooltipMargin|margin between the tooltip and the touched spot|16| |tooltipHorizontalAlignment|horizontal alginment of tooltip relative to the bar|FLHorizontalAlignment.center| |tooltipHorizontalOffset|horizontal offset of tooltip|0| |maxContentWidth|maximum width of the tooltip (if a text row is wider than this, then the text breaks to a new line|120| |getTooltipItems|a callback that retrieve [BarTooltipItem](#BarTooltipItem) by the given [BarChartGroupData](#BarChartGroupData), groupIndex, [BarChartRodData](#BarChartRodData) and rodIndex |defaultBarTooltipItem| |fitInsideHorizontally| forces tooltip to horizontally shift inside the chart's bounding box| false| |fitInsideVertically| forces tooltip to vertically shift inside the chart's bounding box| false| |direction| Controls showing tooltip on top or bottom, default is auto.| auto| |getTooltipColor|a callback that retrieves the Color for each rod separately from the given [BarChartGroupData](#BarChartGroupData) to set the background color of the tooltip bubble|Colors.blueGrey.darken(15)| ### BarTooltipItem |PropName|Description|default value| |:-------|:----------|:------------| |text|text string of each row in the tooltip bubble|null| |textStyle|[TextStyle](https://api.flutter.dev/flutter/dart-ui/TextStyle-class.html) of the showing text row|null| |textAlign|[TextAlign](https://api.flutter.dev/flutter/dart-ui/TextAlign-class.html) of the showing text row|TextAlign.center| |textDirection|[TextDirection](https://api.flutter.dev/flutter/dart-ui/TextDirection-class.html) of the showing text row|TextDirection.ltr| |children|[List](https://api.flutter.dev/flutter/painting/InlineSpan-class.html) pass additional InlineSpan children for a more advance tooltip|null| ### BarTouchResponse |PropName|Description|default value| |:-------|:----------|:------------| |touchLocation|the location of the touch event in the device pixels coordinates|required| |touchChartCoordinate|the location of the touch event in the chart coordinates|required| |spot|a [BarTouchedSpot](#BarTouchedSpot) class to hold data about touched spot| null | ### BarTouchedSpot |PropName|Description|default value| |:-------|:----------|:------------| |touchedBarGroup|the [BarChartGroupData](#BarChartGroupData) that user touched its rod's spot| null | |touchedBarGroupIndex| index of touched barGroup| null| |touchedRodData|the [BarChartRodData](#BarChartRodData) that user touched its spot|null| |touchedRodDataIndex| index of touchedRod | null| |touchedStackItem| [BarChartRodStackItem](#BarChartRodStackItem) is the touched stack (if you have stacked bar chart) |null| |touchedStackItemIndex| index of barChartRodStackItem, -1 if nothing found | -1| ### Some Samples ---- ##### Sample 1 ([Source Code](/example/lib/presentation/samples/bar/bar_chart_sample1.dart)) ##### Sample 2 ([Source Code](/example/lib/presentation/samples/bar/bar_chart_sample2.dart)) ##### Sample 3 ([Source Code](/example/lib/presentation/samples/bar/bar_chart_sample3.dart)) ##### Sample 4 ([Source Code](/example/lib/presentation/samples/bar/bar_chart_sample4.dart)) ##### Sample 5 ([Source Code](/example/lib/presentation/samples/bar/bar_chart_sample5.dart)) ##### Sample 6 ([Source Code](/example/lib/presentation/samples/bar/bar_chart_sample6.dart)) ##### Sample 7 ([Source Code](/example/lib/presentation/samples/bar/bar_chart_sample7.dart)) ##### Gist - Toggleable Tooltip ([Source Code](https://gist.github.com/imaNNeo/bce3f0169ff3fd6c3f137cdeb5005c0e)) https://user-images.githubusercontent.com/7009300/156784816-53f95dd9-f387-4600-8a92-d05b1aeea3da.mov ================================================ FILE: repo_files/documentations/base_chart.md ================================================ # BaseChart ### FlBorderData |PropName |Description |default value| |:---------------|:---------------|:-------| |show| determines to show or hide the border |true| |border| [Border](https://api.flutter.dev/flutter/painting/Border-class.html) details that determines which border should be drawn with which color| Border.all(color: Colors.black, width: 1.0, style: BorderStyle.solid)| ### FlTitlesData |PropName |Description |default value| |:---------------|:---------------|:-------| |show| determines to show or hide the titles Around the chart|true| |leftTitles| an [AxisTitle](#AxisTitle) that holds data to draw left titles | AxisTitles(sideTitles: SideTitles(reservedSize: 40, showTitles: true))| |topTitles| an [AxisTitle](#AxisTitle) that holds data to draw top titles | AxisTitles(sideTitles: SideTitles(reservedSize: 6, showTitles: true))| |rightTitles| an [AxisTitle](#AxisTitle) that holds data to draw right titles | AxisTitles(sideTitles: SideTitles(reservedSize: 40, showTitles: true))| |bottomTitles| an [AxisTitle](#AxisTitle) that holds data to draw bottom titles | AxisTitles(sideTitles: SideTitles(reservedSize: 6, showTitles: true))| ### AxisTitle |PropName |Description |default value| |:---------------|:---------------|:-------| |axisNameSize| Determines the size of [axisName] | `16`| |axisNameWidget| It shows the name of axis (you can pass a Widget)| `null`| |sideTitles| It accepts a [SideTitles](#SideTitles) which is responsible to show your axis side titles| `SideTitles()`| |drawBehindEverything| If titles are showing on top of your tooltip, you can draw them behind everything.| `true`| |sideTitleAlignment| It accepts a [SideTitleAlignment](#SideTitleAlignment) which is responsible for positioning the axis labels inside, outside, or on the border of the chart| `SideTitleAlignment.outside` ### SideTitles |PropName |Description |default value| |:---------------|:---------------|:-------| |showTitles| determines whether to show or hide the titles | false| |getTitlesWidget| A function to retrieve the title widget with given value on the related axis.|defaultGetTitle| |reservedSize| It determines the maximum space that your titles need, |22| |interval| Texts are showing with provided `interval`. If you don't provide anything, we try to find a suitable value to set as `interval` under the hood. | null | |minIncluded| Determines whether to include title for minimum data value | true | |maxIncluded| Determines whether to include title for maximum data value | true | ### SideTitleFitInsideData |PropName |Description |default value| |:---------------|:---------------|:-------| |enabled| determines whether to enable fit inside to SideTitleWidget |true| |axisPosition| position (in pixel) that applied to the center of child widget along its corresponding axis |null| |parentAxisSize| child widget's corresponding axis maximum width/height |null| |distanceFromEdge| distance between child widget and its closest corresponding axis edge | 6 | ### FlGridData |PropName|Description|default value| |:-------|:----------|:------------| |show|determines to show or hide the background grid data|true| |drawHorizontalLine|determines to show or hide the horizontal grid lines|true| |horizontalInterval|interval space of grid, left it null to be calculate automatically |null| |getDrawingHorizontalLine|a function to get the line style of each grid line by giving the related axis value|defaultGridLine| |checkToShowHorizontalLine|a function to check whether to show or hide the horizontal grid by giving the related axis value |showAllGrids| |drawVerticalLine|determines to show or hide the vertical grid lines|true| |verticalInterval|interval space of grid, left it null to be calculate automatically |null| |getDrawingVerticalLine|a function to get the line style of each grid line by giving the related axis value|defaultGridLine| |checkToShowVerticalLine|a function to determine whether to show or hide the vertical grid by giving the related axis value |showAllGrids| ### FlLabel |PropName|Description|default value| |:-------|:----------|:------------| |show|determines whether to show or hide the label|true| |text|the text content of the label|''| |style|[TextStyle](https://api.flutter.dev/flutter/dart-ui/TextStyle-class.html) of the label. When null or when `inherit` is true, the style is merged with the ambient [DefaultTextStyle](https://api.flutter.dev/flutter/widgets/DefaultTextStyle-class.html) and respects the platform's bold-text accessibility setting|null| |angle|rotation angle of the label in degrees|0| |textDirection|[TextDirection](https://api.flutter.dev/flutter/dart-ui/TextDirection-class.html) of the label text|TextDirection.ltr| ### FlSpot |PropName|Description|default value| |:-------|:----------|:------------| |x|represents x on the coordinate system (x starts from left)|null| |y|represents y on the coordinate system (y starts from bottom)|null| |xError| Determines the error range of the data point using (FlErrorRange)[#FlErrorRange] (which ontains `lowerBy` and `upperValue`) for the x-axis|null| |yError| Determines the error range of the data point using (FlErrorRange)[#FlErrorRange] (which ontains `upperBy` and `upperValue`) for the y-axis|null| ### FlLine |propName|Description|default value| |:-------|:----------|:------------| |color|determines the color of line|Colors.black| |gradient|gradient of the line (you have to provide either `color` or `gradient`|null| |strokeWidth|determines the stroke width of the line|2| |dashArray|A circular array of dash offsets and lengths. For example, the array `[5, 10]` would result in dashes 5 pixels long followed by blank spaces 10 pixels long. The array `[5, 10, 5]` would result in a 5 pixel dash, a 10 pixel gap, a 5 pixel dash, a 5 pixel gap, a 10 pixel dash, etc.|null| ### TouchedSpot |PropName|Description|default value| |:-------|:----------|:------------| |spot|the touched [FlSpot](#FlSpot)|null| |offset|[Offset](https://api.flutter.dev/flutter/dart-ui/Offset-class.html) of the touched spot|null| ### RangeAnnotations |PropName|Description|default value| |:-------|:----------|:------------| |horizontalRangeAnnotations|list of [horizontalRangeAnnotation](#HorizontalRangeAnnotation) to draw on the chart|[]| |verticalRangeAnnotations|list of [VerticalRangeAnnotation](#VerticalRangeAnnotation) to draw on the chart|[]| ### HorizontalRangeAnnotation |PropName|Description|default value| |:-------|:----------|:------------| |y1|start interval of horizontal rectangle|null| |y2|end interval of horizontal rectangle|null| |color|color of the rectangle|Colors.white| |gradient|gradient of the rectangle|null| ### VerticalRangeAnnotation |PropName|Description|default value| |:-------|:----------|:------------| |x1|start interval of vertical rectangle|null| |x2|end interval of vertical rectangle|null| |color|color of the rectangle|Colors.white| |gradient|gradient of the rectangle|null| ### FlTouchEvent Base class for all supported touch/pointer events. |PropName|Description|Inspired from| |:-------|:----------|:----------| |FlPanDownEvent|Contains information of happened touch gesture|[GestureDragDownCallback](https://api.flutter.dev/flutter/gestures/GestureDragDownCallback.html)| |FlPanStartEvent|When a pointer has contacted the screen and has begun to move.|[GestureDragStartCallback](https://api.flutter.dev/flutter/gestures/GestureDragStartCallback.html)| |FlPanUpdateEvent|When a pointer that is in contact with the screen and moving has moved again.|[GestureDragUpdateCallback](https://api.flutter.dev/flutter/gestures/GestureDragUpdateCallback.html)| |FlPanCancelEvent|When the pointer that previously triggered a `FlPanStartEvent` did not complete.|[GestureDragCancelCallback](https://api.flutter.dev/flutter/gestures/GestureDragCancelCallback.html)| |FlPanEndEvent|When a pointer that was previously in contact with the screen and moving is no longer in contact with the screen.|[GestureDragEndCallback](https://api.flutter.dev/flutter/gestures/GestureDragEndCallback.html)| |FlTapDownEvent|When a pointer that might cause a tap has contacted the screen.|[GestureTapDownCallback](https://api.flutter.dev/flutter/gestures/GestureTapDownCallback.html)| |FlTapCancelEvent|When the pointer that previously triggered a `FlTapDownEvent` will not end up causing a tap.|[GestureTapCancelCallback](https://api.flutter.dev/flutter/gestures/GestureTapCancelCallback.html)| |FlTapUpEvent|When a pointer that will trigger a tap has stopped contacting the screen.|[GestureTapUpCallback](https://api.flutter.dev/flutter/gestures/GestureTapUpCallback.html)| |FlLongPressStart|Called When a pointer has remained in contact with the screen at the same location for a long period of time.|[GestureLongPressStartCallback](https://api.flutter.dev/flutter/gestures/GestureLongPressStartCallback.html)| |FlLongPressMoveUpdate|When a pointer is moving after being held in contact at the same location for a long period of time. Reports the new position and its offset from the original down position.|[GestureLongPressMoveUpdateCallback](https://api.flutter.dev/flutter/gestures/GestureLongPressMoveUpdateCallback.html)| |FlLongPressEnd|When a pointer stops contacting the screen after a long press gesture was detected. Also reports the position where the pointer stopped contacting the screen.|[GestureLongPressEndCallback](https://api.flutter.dev/flutter/gestures/GestureLongPressEndCallback.html)| |FlPointerEnterEvent|The pointer has moved with respect to the device while the pointer is or is not in contact with the device, and it has entered our chart.|[PointerEnterEventListener](https://api.flutter.dev/flutter/services/PointerEnterEventListener.html)| |FlPointerHoverEvent|The pointer has moved with respect to the device while the pointer is not in contact with the device.|[PointerHoverEventListener](https://api.flutter.dev/flutter/services/PointerHoverEventListener.html)| |FlPointerExitEvent|The pointer has moved with respect to the device while the pointer is or is not in contact with the device, and exited our chart.|[PointerExitEventListener](https://api.flutter.dev/flutter/services/PointerExitEventListener.html)| ### ExtraLinesData |PropName|Description|default value| |:-------|:----------|:------------| |extraLinesOnTop|determines to paint the extraLines over the trendline or below it|true| |horizontalLines|list of [HorizontalLine](#HorizontalLine) to draw on the chart|[]| |verticalLines|list of [VerticalLine](#VerticalLine) to draw on the chart|[]| ### HorizontalLine |PropName|Description|default value| |:-------|:----------|:------------| |y|draw straight line from left to right of the chart with dynamic y value|null| |color|color of the line|Colors.black| |gradient|gradient of the line (you have to provide either `color` or `gradient`|null| |strokeWidth|strokeWidth of the line|2| |strokeCap|strokeCap of the line,e.g. Setting to StrokeCap.round will draw the tow ends of line rounded. NOTE: this might not work on dash lines.|StrokeCap.butt| |image|image to annotate the line. the Future must be complete at the time this is received by the chart|null| |sizedPicture|[SizedPicture](#Sizedpicture) uses an svg to annotate the line with a picture. the Future must be complete at the time this is received by the chart|null| |label|a [HorizontalLineLabel](#HorizontalLineLabel) object with label parameters|null ### VerticalLine |PropName|Description|default value| |:-------|:----------|:------------| |x|draw straight line from bottom to top of the chart with dynamic x value|null| |color|color of the line|Colors.black| |gradient|gradient of the line (you have to provide either `color` or `gradient`|null| |strokeWidth|strokeWidth of the line|2| |strokeCap|strokeCap of the line,e.g. Setting to StrokeCap.round will draw the tow ends of line rounded. NOTE: this might not work on dash lines.|StrokeCap.butt| |image|image to annotate the line. the Future must be complete at the time this is received by the chart|null| |sizedPicture|[SizedPicture](#SizedPicture) uses an svg to annotate the line with a picture. the Future must be complete at the time this is received by the chart|null| |label|a [VerticalLineLabel](#VerticalLineLabel) object with label parameters|null ### SizedPicture |PropName|Description|default value| |:-------|:----------|:------------| |Picture|a Dart UI Picture which should be derived from the svg. see example for how to get a Picture from an svg.|null| |width|the width of the picture|null| |height|the height of the picture|null| ### HorizontalLineLabel |PropName|Description|default value| |:-------|:----------|:------------| |show| Determines showing or not showing label|false| |padding|[EdgeInsets](https://api.flutter.dev/flutter/painting/EdgeInsets-class.html) object with label padding configuration|EdgeInsets.zero| |style|[TextStyle](https://api.flutter.dev/flutter/dart-ui/TextStyle-class.html) which determines label text style|TextStyle(fontSize: 11, color: line.color)| |alignment|[Alignment](https://api.flutter.dev/flutter/painting/Alignment-class.html) with label position relative to line|Alignment.topLeft| |direction|Direction of the text (horizontal, vertical, horizontalMirrored, verticalMirrored)|LabelDirection.horizontal| |labelResolver|Getter function returning label title|defaultLineLabelResolver| ### VerticalLineLabel |PropName|Description|default value| |:-------|:----------|:------------| |show| Determines showing or not showing label|false| |padding|[EdgeInsets](https://api.flutter.dev/flutter/painting/EdgeInsets-class.html) object with label padding configuration|EdgeInsets.zero| |style|[TextStyle](https://api.flutter.dev/flutter/dart-ui/TextStyle-class.html) which determines label text style|TextStyle(fontSize: 11, color: line.color)| |alignment|[Alignment](https://api.flutter.dev/flutter/painting/Alignment-class.html) with label position relative to line|Alignment.topLeft| |direction|Direction of the text (horizontal, vertical, horizontalMirrored, verticalMirrored)|LabelDirection.vertical| |labelResolver|Getter function returning label title|defaultLineLabelResolver| ### FLHorizontalAlignment enum values {`center`, `left`, `right`} ### FlErrorIndicatorData |PropName| Description | default value | |:-------|:------------------------------------------------------------|:-----------------------| |show| Determines showing or not showing error indicator/threshold | true | |painter| A callback that allows you to provide a custom painter for the error indicator| FlSimpleErrorPainter() | ### FlErrorRange |PropName| Description | default value | |:-------|:-------------------------------------------------------------------------------|:-----------------------| |lowerBy| Lower value of the error range. It is subtracted from the spot value and shoul be positive| null| |upperBy| Upper value of the error range. It is added to the spot value and shoul be positive| null| ### AxisSpotIndicator |PropName|Description|default value| |:-------|:----------|:------------| |x|x value of the touched spot|required| |y|y value of the touched spot|required| |AxisSpotIndicatorPainter|a painter that is used to draw the touched spot indicator. You can use this to customize the appearance of the touched spot indicator (Or you can implement your own painter).|AxisLinesIndicatorPainter()| ### SideTitleAlignment |Option |Description | Is Default |:---------------|:---------------|:-------| |`SideTitleAlignment.outside`| Places the axis labels outside the chart area| Yes| |`SideTitleAlignment.border`| Places the axis labels along the border of the chart area| No| |`SideTitleAlignment.inside`| Places the axis labels inside the border of the chart area| No| ================================================ FILE: repo_files/documentations/candlestick_chart.md ================================================ # CandlestickChart ### How to use ```dart CandlestickChart( CandlestickChartData( // read about it in the CandlestickChartData section ), duration: Duration(milliseconds: 150), // Optional curve: Curves.linear, // Optional ); ``` ### Implicit Animations When you change the chart's state, it animates to the new state internally (using [implicit animations](https://flutter.dev/docs/development/ui/animations/implicit-animations)). You can control the animation [duration](https://api.flutter.dev/flutter/dart-core/Duration-class.html) and [curve](https://api.flutter.dev/flutter/animation/Curves-class.html) using optional `duration` and `curve` properties, respectively. ### CandlestickChartData |PropName |Description |default value| |:---------------|:---------------|:-------| |candlestickSpots| Holds the data for the candlestick chart, which is a list of [CandlestickSpot](#CandlestickSpot) objects. Each [CandlestickSpot](#CandlestickSpot) represents a single data point in the chart.|[]| |candlestickPainter| It is a painter that is used to draw each individual candlestick. You can use this to customize the appearance of the candlesticks (Or you can implement your own painter).|DefaultCandlestickPainter()| |titlesData| check the [FlAxisTitleData](base_chart.md#FlAxisTitleData)| FlAxisTitleData()| |candlestickTouchData| [CandlestickTouchData](#CandlestickTouchData) holds the touch interactivity details| CandlestickTouchData()| |showingTooltipIndicators| indices of showing tooltip, The point is that you need to disable touches to show these tooltips manually|[]| |gridData|check the [FlGridData](base_chart.md#FlGridData)|FlGridData()| |borderData|check the [FlBorderData](base_chart.md#FlBorderData)|FlBorderData()| |minX|gets minimum x of x axis, if null, value will read from the input lineBars (But it is more performant if you provide them)|null| |maxX|gets maximum x of x axis, if null, value will read from the input lineBars (But it is more performant if you provide them)| null| |baselineX|defines the baseline of x-axis | 0| |minY|gets minimum y of y axis, if null, value will read from the input lineBars (But it is more performant if you provide them)| null| |maxY|gets maximum y of y axis, if null, value will read from the input lineBars (But it is more performant if you provide them)| null| |baselineY|defines the baseline of y-axis | 0| |rangeAnnotations|show range annotations behind the chart, check [RangeAnnotations](base_chart.md#RangeAnnotations) | RangeAnnotations()| |clipData|clip the chart to the border (prevent drawing outside the border) | FlClipData.none()| |backgroundColor|a background color which is drawn behind th chart| null | |rotationQuarterTurns|Rotates the chart 90 degrees (clockwise) in every quarter turns. This feature works like the [RotatedBox](https://api.flutter.dev/flutter/widgets/RotatedBox-class.html) widget|0| |touchedPointIndicator|Shows the touched point in the chart, by default it shows a horizontal and vertical line exactly on the touched candle. If the `handleBuiltInTouches` is true in [CandlestickTouchData](#CandlestickTouchData), this parameter is used under the hood to highlight the selected point. But you can disable the `handleBuiltInTouches` and implement your own way to highlight the point. Look at [AxisSpotIndicator](base_chart.md#AxisSpotIndicator) for more information |null| ### CandlestickSpot |PropName |Description |default value| |:---------------|:---------------|:-------| |open| The open value of the candlestick (based on the [OHLC standard](https://en.wikipedia.org/wiki/Open-high-low-close_chart)|required| |high| The high value of the candlestick (based on the [OHLC standard](https://en.wikipedia.org/wiki/Open-high-low-close_chart))|required| |low| The low value of the candlestick (based on the [OHLC standard](https://en.wikipedia.org/wiki/Open-high-low-close_chart))|required| |close| The close value of the candlestick (based on the [OHLC standard](https://en.wikipedia.org/wiki/Open-high-low-close_chart))|required| |show| Determines to show or hide this individual candlestick|true| ### CandlestickTouchData ([read about touch handling](handle_touches.md)) |PropName|Description|default value| |:-------|:----------|:------------| |enabled|determines to enable or disable touch behaviors|true| |touchCallback| listen to this callback to retrieve touch/pointer events and responses, it gives you a [FlTouchEvent](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/base_chart.md#fltouchevent) and [CandlestickTouchResponse](#CandlestickTouchResponse)| null| |mouseCursorResolver|you can change the mouse cursor based on the provided [FlTouchEvent](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/base_chart.md#fltouchevent) and [CandlestickTouchResponse](#CandlestickTouchResponse)|MouseCursor.defer| |touchTooltipData|a [CandlestickTouchTooltipData](#CandlestickTouchTooltipData), that determines how show the tooltip on top of touched spot (appearance of the showing tooltip bubble)|CandlestickTouchTooltipData()| |touchSpotThreshold|the threshold of the touch accuracy|4| |handleBuiltInTouches| set this true if you want the built in touch handling (show a tooltip bubble and an indicator on touched/hovered spots) | true| |longPressDuration| allows to customize the duration of the longPress gesture. If null, the duration of the longPressGesture is [kLongPressTimeout](https://api.flutter.dev/flutter/gestures/kLongPressTimeout-constant.html)| null| ### CandlestickTouchTooltipData |PropName|Description|default value| |:-------|:----------|:------------| |tooltipBorder|border of the tooltip bubble|BorderSide.none| |tooltipBorderRadius|background corner radius of the tooltip bubble|BorderRadius.circular(4)| |tooltipPadding|padding of the tooltip|EdgeInsets.symmetric(horizontal: 16, vertical: 8)| |tooltipHorizontalAlignment|horizontal alginment of tooltip relative to the spot|FLHorizontalAlignment.center| |tooltipHorizontalOffset|horizontal offset of tooltip|0| |maxContentWidth|maximum width of the tooltip (if a text row is wider than this, then the text breaks to a new line|120| |getTooltipItems|a callback that retrieve a [CandlestickTooltipItem](#CandlestickTooltipItem) by the given [CandlestickSpot](#CandlestickSpot) |defaultCandlestickTooltipItem| |fitInsideHorizontally| forces tooltip to horizontally shift inside the chart's bounding box| false| |fitInsideVertically| forces tooltip to vertically shift inside the chart's bounding box| false| |showOnTopOfTheChartBoxArea| forces the tooltip container to top of the line| false| |getTooltipColor|a callback that retrieves the Color for each touched spots separately from the given [CandlestickSpot](#CandlestickSpot) to set the background color of the tooltip bubble|Colors.blueGrey.darken(80)| ### CandlestickTooltipItem |PropName|Description|default value| |:-------|:----------|:------------| |text|text string of each row in the tooltip bubble|null| |textStyle|[TextStyle](https://api.flutter.dev/flutter/dart-ui/TextStyle-class.html) of the showing text row|null| |textDirection|[TextDirection](https://api.flutter.dev/flutter/dart-ui/TextDirection-class.html) of the showing text row|TextDirection.ltr| |bottomMargin| bottom margin of the tooltip (to the top of most top spot) | 0| |children|[List](https://api.flutter.dev/flutter/painting/InlineSpan-class.html) pass additional InlineSpan children for a more advance tooltip|null| ### CandlestickTouchResponse ###### you can listen to touch behaviors callback and retrieve this object when any touch action happened. |PropName|Description|default value| |:-------|:----------|:------------| |touchLocation|the location of the touch event in the device pixels coordinates|required| |touchChartCoordinate|the location of the touch event in the chart coordinates|required| |touchedSpot|Instance of [CandlestickTouchedSpot](#CandlestickTouchedSpot) which holds data about the touched spot|null| ### CandlestickTouchedSpot |PropName|Description|default value| |:-------|:----------|:------------| |spot|touched [CandlestickSpot](#CandlestickSpot)|null| |spotIndex|index of touched [CandlestickSpot](#CandlestickSpot)|null| ### some samples ---- ##### Sample 1 ([Source Code](/example/lib/presentation/samples/candlestick/candlestick_chart_sample1.dart)) ================================================ FILE: repo_files/documentations/handle_animations.md ================================================ ### Animations |Sample1 |Sample2 |Sample3 | |:------------:|:------------:|:-------------:| | [![](https://github.com/imaNNeo/fl_chart/raw/main/repo_files/images/line_chart/line_chart_sample_1_anim.gif)](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/line_chart.md#sample-1-source-code) | [![](https://github.com/imaNNeo/fl_chart/raw/main/repo_files/images/line_chart/line_chart_sample_2_anim.gif)](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/line_chart.md#sample-2-source-code) | [![](https://github.com/imaNNeo/fl_chart/raw/main/repo_files/images/bar_chart/bar_chart_sample_1_anim.gif)](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/bar_chart.md#sample-1-source-code) | ##### How? We handle all animations Implicitly, This is power of the [ImplicitlyAnimatedWidget](https://api.flutter.dev/flutter/widgets/ImplicitlyAnimatedWidget-class.html), just like [AnimatedContainer](https://api.flutter.dev/flutter/widgets/AnimatedContainer-class.html). It means you don't need to do anything, just change any value and the animation is handled under the hood, if you are curious about it, check the source code, reading the source code is the best way to learn things. ##### Properties You can change the [Duration](https://api.flutter.dev/flutter/dart-core/Duration-class.html) and [Curve](https://api.flutter.dev/flutter/animation/Curves-class.html) of animation using `duration` and `curve` properties respectively. ```dart LineChart( duration: Duration(milliseconds: 150), curve: Curves.linear, LineChartData( isShowingMainData ? sampleData1() : sampleData2(), ), ) ``` ##### How to disable If you want to disable the animations, you can set `Duration.zero` as `duration`. ```dart LineChart( duration: Duration.zero, LineChartData( // Your chart data here ), ) ================================================ FILE: repo_files/documentations/handle_touches.md ================================================ ### Touch Interactivity |LineChart |BarChart |PieChart | |:------------:|:------------:|:-------------:| | [![](https://github.com/imaNNeo/fl_chart/raw/main/repo_files/images/line_chart/line_chart_sample_1.gif)](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/line_chart.md#sample-1-source-code) [![](https://github.com/imaNNeo/fl_chart/raw/main/repo_files/images/line_chart/line_chart_sample_2.gif)](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/line_chart.md#sample-2-source-code) | [![](https://github.com/imaNNeo/fl_chart/raw/main/repo_files/images/bar_chart/bar_chart_sample_1.gif)](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/bar_chart.md#sample-1-source-code) [![](https://github.com/imaNNeo/fl_chart/raw/main/repo_files/images/bar_chart/bar_chart_sample_2.gif)](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/bar_chart.md#sample-2-source-code) | [![](https://github.com/imaNNeo/fl_chart/raw/main/repo_files/images/pie_chart/pie_chart_sample_1.gif)](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/pie_chart.md#sample-1-source-code) [![](https://github.com/imaNNeo/fl_chart/raw/main/repo_files/images/pie_chart/pie_chart_sample_2.gif)](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/pie_chart.md#sample-2-source-code) | #### The Interaction Flow When an interaction happens, our renderers give us a [FlTouchEvent](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/base_chart.md#fltouchevent). We pass it to correspond painter class. Then it calculates and gives us a TouchResponse (per interaction). Then we call the touchCallback function that provided through the chart's data. If you set `handleBuiltInTouches` true, it will handle touch by showing a tooltip or an indicator on the touched spot (in the line, bar and scatter charts), you can also handle your own touch handling along with the built in touches. #### How to use? (for example in `LineChart`) ##### In the Line and Bar Charts we show a built in tooltip on the touched spots, then you just need to config how to show it, just fill the `touchTooltipData` in the `LineTouchData`. ##### ```dart LineChart( LineChartData( lineTouchData: LineTouchData( touchTooltipData: TouchTooltipData ( getTooltipColor: (touchedSpot) => Colors.blueGrey.withOpacity(0.8), . . . ) ) ) ) ``` ##### But if you want more customization on touch behaviors, implement the `touchCallback` and handle it. ```dart LineChart( LineChartData( lineTouchData: LineTouchData( touchCallback: (FlTouchEvent event, LineTouchResponse touchResponse) { if (event is FlTapUpEvent) { // handle tap here } }, . . . ) ) ) ``` ================================================ FILE: repo_files/documentations/handle_transformations.md ================================================ # FL Chart Transformation Guide The transformation feature in `fl_chart` allows users to interact with charts through scaling and panning, similar to Flutter's `InteractiveViewer` widget. ## Basic Usage To enable transformations, provide a `FlTransformationConfig` to your chart: ```dart LineChart( LineChartData(...), transformationConfig: FlTransformationConfig( scaleAxis: FlScaleAxis.horizontal, minScale: 1.0, maxScale: 2.5, ), ) ``` ### Configuration Options See [FlTransformationConfig](https://github.com/imaNNeo/fl_chart/blob/main/lib/src/chart/base/axis_chart/transformation_config.dart) for more information. ### Chart-Specific Limitations - **Bar Chart**: When using `BarChartAlignment.center`, `end`, or `start`, horizontal scaling is not supported - **Line Chart**: Supports all transformation types - **Scatter Chart**: Supports all transformation types ## Advanced Usage: Custom Transformation Controller For more control over transformations, you can provide a `TransformationController`. This allows you to: - Programmatically control the chart's transformation - Reset to initial state - Implement custom zoom/pan controls ### Limitations At this moment, transformations made with a custom `TransformationController` are not prevented from moving the chart out of the screen. Developers are responsible for ensuring that the chart remains within the visible area and within the transformation limits. See the implementation of [AxisChartScaffoldWidget](https://github.com/imaNNeo/fl_chart/blob/main/lib/src/chart/base/axis_chart/axis_chart_scaffold_widget.dart) for how to prevent the chart from moving out of the screen when using a custom `TransformationController`. ### Example Implementation ```dart class ChartWithControls extends StatefulWidget { @override State createState() => _ChartWithControlsState(); } class _ChartWithControlsState extends State { late TransformationController _controller; @override void initState() { super.initState(); _controller = TransformationController(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Column( children: [ AspectRatio( aspectRatio: 1.4, child: LineChart( LineChartData(...), transformationConfig: FlTransformationConfig( scaleAxis: FlScaleAxis.horizontal, minScale: 1.0, maxScale: 25.0, transformationController: _controller, ), ), ), Row( children: [ IconButton( icon: Icon(Icons.zoom_in), onPressed: () { _controller.value *= Matrix4.diagonal3Values(1.1, 1.1, 1); }, ), IconButton( icon: Icon(Icons.zoom_out), onPressed: () { _controller.value *= Matrix4.diagonal3Values(0.9, 0.9, 1); }, ), IconButton( icon: Icon(Icons.refresh), onPressed: () { _controller.value = Matrix4.identity(); }, ), ], ), ], ); } } ``` ### Common Transformation Operations See [Matrix4](https://pub.dev/documentation/vector_math/latest/vector_math_64/Matrix4-class.html) for more information on how to manipulate the matrix. ## Best Practices 1. Always dispose of the `TransformationController` when you're done with it 2. Set appropriate `minScale` and `maxScale` values to prevent excessive zooming 3. Consider your chart's alignment when choosing a `scaleAxis` 4. Provide visual feedback for transformation limits 5. Consider adding reset functionality for better user experience 6. If you have touch indicators, consider allowing users to disable panning when zoomed in. This way the touch indicators will be shown when users hold and drag to explore the chart's data, instead of panning the chart. Remember that transformations are purely visual and don't affect the underlying data. They're particularly useful for exploring detailed data sets or allowing users to focus on specific regions of interest in your charts. ================================================ FILE: repo_files/documentations/index.md ================================================ ## FL Chart Documentation FlChart allows you to draw your charts in Flutter. Currently, we support these types of charts, Click and learn more about them. - [LineChart](line_chart.md) - [BarChart](bar_chart.md) - [PieChart](pie_chart.md) - [ScatterChart](scatter_chart.md) - [RadarChart](radar_chart.md) - [CandlestickChart](candlestick_chart.md) ----------- - [Migration Guides](migration_guides/INDEX.md) - [Handle Touches](handle_touches.md) - [Handle Animations](handle_animations.md) - [Handle Transformations](handle_transformations.md) ================================================ FILE: repo_files/documentations/line_chart.md ================================================ ### How to use ```dart LineChart( LineChartData( // read about it in the LineChartData section ), duration: Duration(milliseconds: 150), // Optional curve: Curves.linear, // Optional ); ``` ### Implicit Animations When you change the chart's state, it animates to the new state internally (using [implicit animations](https://flutter.dev/docs/development/ui/animations/implicit-animations)). You can control the animation [duration](https://api.flutter.dev/flutter/dart-core/Duration-class.html) and [curve](https://api.flutter.dev/flutter/animation/Curves-class.html) using optional `swapAnimationDuration` and `swapAnimationCurve` properties, respectively. ### LineChartData |PropName |Description |default value| |:---------------|:---------------|:-------| |lineBarsData| list of [LineChartBarData ](#LineChartBarData ) to show the chart's lines, they stack and can be drawn on top of each other|[]| |betweenBarsData| list of [BetweenBarsData](#BetweenBarsData ) to fill the area between 2 chart lines|[]| |titlesData| check the [FlTitlesData](base_chart.md#FlTitlesData)| FlTitlesData()| |extraLinesData| [ExtraLinesData](base_chart.md#ExtraLinesData) object to hold drawing details of extra horizontal and vertical lines. Check [ExtraLinesData](base_chart.md#ExtraLinesData)|ExtraLinesData()| |lineTouchData| [LineTouchData](#linetouchdata-read-about-touch-handling) holds the touch interactivity details| LineTouchData()| |rangeAnnotations| show range annotations behind the chart, check [RangeAnnotations](base_chart.md#RangeAnnotations) | RangeAnnotations()| |showingTooltipIndicators| show the tooltip based on provided list of [LineBarSpot](#LineBarSpot), The point is that you need to disable touches to show these tooltips manually| [] | |gridData| check the [FlGridData](base_chart.md#FlGridData)|FlGridData()| |borderData| check the [FlBorderData](base_chart.md#FlBorderData)|FlBorderData()| |minX| gets minimum x of x axis, if null, value will read from the input lineBars (But it is more performant if you provide them)|null| |maxX| gets maximum x of x axis, if null, value will read from the input lineBars (But it is more performant if you provide them)| null| |baselineX| defines the baseline of x-axis | 0| |minY| gets minimum y of y axis, if null, value will read from the input lineBars (But it is more performant if you provide them)| null| |maxY| gets maximum y of y axis, if null, value will read from the input lineBars (But it is more performant if you provide them)| null| |baselineY| defines the baseline of y-axis | 0| |clipData| clip the chart to the border (prevent drawing outside the border) | FlClipData.none()| |backgroundColor| a background color which is drawn behind th chart| null | |rotationQuarterTurns|Rotates the chart 90 degrees (clockwise) in every quarter turns. This feature works like the [RotatedBox](https://api.flutter.dev/flutter/widgets/RotatedBox-class.html) widget|0| ### LineChartBarData |PropName |Description |default value| |:---------------|:---------------|:-------| |show| determines to show or hide the bar line|true| |spots| list of [FlSpot](base_chart.md#FlSpot)'s x and y coordinates that the line go through it| [] |color|color of the line|[Colors.redAccent]| |gradient| You can use any [Gradient](https://api.flutter.dev/flutter/dart-ui/Gradient-class.html) here. such as [LinearGradient](https://api.flutter.dev/flutter/painting/LinearGradient-class.html) or [RadialGradient](https://api.flutter.dev/flutter/painting/RadialGradient-class.html)|null| |gradientArea| determines the area where the gradient is applied |null| |barWidth| gets the stroke width of the line bar|2.0| |isCurved| curves the corners of the line on the spot's positions| false| |curveSmoothness| smoothness radius of the curve corners (works when isCurved is true) | 0.35| |preventCurveOverShooting|prevent overshooting when draw curve line on linear sequence spots, check this [issue](https://github.com/imaNNeo/fl_chart/issues/25)| false| |preventCurveOvershootingThreshold|threshold for applying prevent overshooting algorithm | 10.0| |isStrokeCapRound| determines whether start and end of the bar line is Qubic or Round | false| |isStrokeJoinRound| determines whether stroke joins have a round shape or a sharp edge | false| |belowBarData| check the [BarAreaData](#BarAreaData) |BarAreaData| |aboveBarData| check the [BarAreaData](#BarAreaData) |BarAreaData| |dotData| check the [FlDotData](#FlDotData) | FlDotData()| |showingIndicators| show indicators based on provided indexes | []| |dashArray|A circular array of dash offsets and lengths. For example, the array `[5, 10]` would result in dashes 5 pixels long followed by blank spaces 10 pixels long. The array `[5, 10, 5]` would result in a 5 pixel dash, a 10 pixel gap, a 5 pixel dash, a 5 pixel gap, a 10 pixel dash, etc.|null| |shadow|It drops a shadow behind your bar, see [Shadow](https://api.flutter.dev/flutter/dart-ui/Shadow-class.html).|Shadow()| |isStepLineChart|If sets true, it draws the chart in Step Line Chart style, using `lineChartStepData`.|false| |lineChartStepData|Holds data for representing a Step Line Chart, and works only if [isStepChart] is true.|[LineChartStepData](#LineChartStepData)()| |errorIndicatorData|Holds data for representing an error indicator (you see the error indicators if you provide the `xError` or `yError` in the [FlSpot](base_chart.md#FlSpot)).|[ErrorIndicatorData()](base_chart.md#FlErrorIndicatorData)| ### LineChartStepData |PropName|Description|default value| |:-------|:----------|:------------| |stepDirection|Determines the direction of each step, could be between 0.0 (forward), and 1.0 (backward)|LineChartStepData.stepDirectionMiddle| ### BetweenBarsData |PropName|Description|default value| |:-------|:----------|:------------| |fromIndex|index of the first LineChartBarData inside LineChartData (zero-based index)|required| |toIndex|index of the second LineChartBarData inside LineChartData (zero-based index)|required| |color|color of the area|[Colors.blueGrey]| |gradient| You can use any [Gradient](https://api.flutter.dev/flutter/dart-ui/Gradient-class.html) here. such as [LinearGradient](https://api.flutter.dev/flutter/painting/LinearGradient-class.html) or [RadialGradient](https://api.flutter.dev/flutter/painting/RadialGradient-class.html)|null| ### BarAreaData |PropName|Description|default value| |:-------|:----------|:------------| |show|determines to show or hide the below, or above bar area|false| |color|color of the below, or above bar area|[Colors.blueGrey]| |gradient| You can use any [Gradient](https://api.flutter.dev/flutter/dart-ui/Gradient-class.html) here. such as [LinearGradient](https://api.flutter.dev/flutter/painting/LinearGradient-class.html) or [RadialGradient](https://api.flutter.dev/flutter/painting/RadialGradient-class.html)|null| |spotsLine| draw a line from each spot the the bottom, or top of the chart|[BarAreaSpotsLine](#BarAreaSpotsLine)()| |cutOffY| cut the drawing below or above area to this y value (set `applyCutOffY` true if you want to set it)|null| |applyCutOffY| determines should or shouldn't apply cutOffY (`scutOffY` should be provided)|false| ### BarAreaSpotsLine |PropName|Description|default value| |:-------|:----------|:------------| |show|determines show or hide the below, or above spots line|true| |flLineStyle|a [FlLine](base_chart.md#FlLine) object that determines style of the line|[Colors.blueGrey]| |checkToShowSpotLine|a function to determine whether to show or hide the below or above line on the given spot|showAllSpotsBelowLine| |applyCutOffY|Determines to inherit the cutOff properties from its parent [BarAreaData](#BarAreaData)|true| ### FlDotData |PropName|Description|default value| |:-------|:----------|:------------| |show|determines to show or hide the dots|true| |checkToShowDot|a function to determine whether to show or hide the dot on the given spot|showAllDots| |getDotPainter|a function to determine how the dot is drawn on the given spot|_defaultGetDotPainter| ### LineTouchData ([read about touch handling](handle_touches.md)) |PropName|Description|default value| |:-------|:----------|:------------| |enabled|determines to enable or disable touch behaviors|true| |mouseCursorResolver|you can change the mouse cursor based on the provided [FlTouchEvent](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/base_chart.md#fltouchevent) and [LineTouchResponse](#LineTouchResponse)|MouseCursor.defer| |touchTooltipData|a [LineTouchTooltipData](#LineTouchTooltipData), that determines how show the tooltip on top of touched spots (appearance of the showing tooltip bubble)|LineTouchTooltipData| |getTouchedSpotIndicator| a callback that retrieves list of [TouchedSpotIndicatorData](#TouchedSpotIndicatorData) by the given list of [LineBarSpot](#LineBarSpot) for showing the indicators on touched spots|defaultTouchedIndicators| |touchSpotThreshold|the threshold of the touch accuracy|10| |distanceCalculator| a function to calculate the distance between a spot and a touch event| _xDistance| |handleBuiltInTouches| set this true if you want the built in touch handling (show a tooltip bubble and an indicator on touched spots) | true| |getTouchLineStart| controls where the line starts, default is bottom of the chart| defaultGetTouchLineStart| |getTouchLineEnd| controls where the line ends, default is the touch point| defaultGetTouchLineEnd| |touchCallback| listen to this callback to retrieve touch/pointer events and responses, it gives you a [FlTouchEvent](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/base_chart.md#fltouchevent) and [LineTouchResponse](#LineTouchResponse)| null| |longPressDuration| allows to customize the duration of the longPress gesture. If null, the duration of the longPressGesture is [kLongPressTimeout](https://api.flutter.dev/flutter/gestures/kLongPressTimeout-constant.html)| null| ### LineTouchTooltipData |PropName|Description|default value| |:-------|:----------|:------------| |tooltipBorder|border of the tooltip bubble|BorderSide.none| |tooltipBorderRadius|background corner radius of the tooltip bubble|BorderRadius.circular(4)| |tooltipPadding|padding of the tooltip|EdgeInsets.symmetric(horizontal: 16, vertical: 8)| |tooltipMargin|margin between the tooltip and the touched spot|16| |tooltipHorizontalAlignment|horizontal alginment of tooltip relative to the spot|FLHorizontalAlignment.center| |tooltipHorizontalOffset|horizontal offset of tooltip|0| |maxContentWidth|maximum width of the tooltip (if a text row is wider than this, then the text breaks to a new line|120| |getTooltipItems|a callback that retrieve list of [LineTooltipItem](#LineTooltipItem) by the given list of [LineBarSpot](#LineBarSpot) |defaultLineTooltipItem| |fitInsideHorizontally| forces tooltip to horizontally shift inside the chart's bounding box| false| |fitInsideVertically| forces tooltip to vertically shift inside the chart's bounding box| false| |showOnTopOfTheChartBoxArea| forces the tooltip container to top of the line| false| |getTooltipColor|a callback that retrieves the Color for each touched spots separately from the given [LineBarSpot](#LineBarSpot) to set the background color of the tooltip bubble|Colors.blueGrey.darken(15)| ### LineTooltipItem |PropName|Description|default value| |:-------|:----------|:------------| |text|text string of each row in the tooltip bubble|null| |textStyle|[TextStyle](https://api.flutter.dev/flutter/dart-ui/TextStyle-class.html) of the showing text row|null| |textAlign|[TextAlign](https://api.flutter.dev/flutter/dart-ui/TextAlign-class.html) of the showing text row|TextAlign.center| |textDirection|[TextDirection](https://api.flutter.dev/flutter/dart-ui/TextDirection-class.html) of the showing text row|TextDirection.ltr| |children|[List](https://api.flutter.dev/flutter/painting/InlineSpan-class.html) pass additional InlineSpan children for a more advance tooltip|null| ### TouchedSpotIndicatorData |PropName|Description|default value| |:-------|:----------|:------------| |indicatorBelowLine|a [FlLine](base_chart.md#FlLine) to show the below line indicator on the touched spot|null| |touchedSpotDotData|a [FlDotData](#FlDotData) to show a dot indicator on the touched spot|null| ### LineBarSpot |PropName|Description|default value| |:-------|:----------|:------------| |bar|the [LineChartBarData](#LineChartBarData) that contains a spot|null| |barIndex|index of the target [LineChartBarData](#LineChartBarData) inside [LineChartData](#LineChartData)|null| |spotIndex|index of the target [FlSpot](base_chart.md#FlSpot) inside [LineChartBarData](#LineChartBarData)|null| ### TouchLineBarSpot |PropName|Description|default value| |:-------|:----------|:------------| |bar|the [LineChartBarData](#LineChartBarData) that contains a spot|null| |barIndex|index of the target [LineChartBarData](#LineChartBarData) inside [LineChartData](#LineChartData)|null| |spotIndex|index of the target [FlSpot](base_chart.md#FlSpot) inside [LineChartBarData](#LineChartBarData)|null| |distance|distance to the touch event|null| ### LineTouchResponse |PropName|Description|default value| |:-------|:----------|:------------| |touchLocation|the location of the touch event in the device pixels coordinates|required| |touchChartCoordinate|the location of the touch event in the chart coordinates|required| |lineBarSpots|a list of [TouchLineBarSpot](#TouchLineBarSpot)|null| ### ShowingTooltipIndicators |PropName|Description|default value| |:-------|:----------|:------------| |showingSpots|Determines the spots that each tooltip should be shown.|null| ### some samples ---- ##### Sample 1 ([Source Code](/example/lib/presentation/samples/line/line_chart_sample1.dart)) ##### Sample 2 ([Source Code](/example/lib/presentation/samples/line/line_chart_sample2.dart)) ##### Sample 3 ([Source Code](/example/lib/presentation/samples/line/line_chart_sample3.dart)) ##### Sample 4 ([Source Code](/example/lib/presentation/samples/line/line_chart_sample4.dart)) ##### Sample 5 ([Source Code](/example/lib/presentation/samples/line/line_chart_sample5.dart)) ##### Sample 6 - Reversed ([Source Code](/example/lib/presentation/samples/line/line_chart_sample6.dart)) ##### Sample 7 ([Source Code](/example/lib/presentation/samples/line/line_chart_sample7.dart)) ##### Sample 8 ([Source Code](/example/lib/presentation/samples/line/line_chart_sample8.dart)) ##### Sample 9 ([Source Code](/example/lib/presentation/samples/line/line_chart_sample9.dart)) ##### Sample 10 ([Source Code](/example/lib/presentation/samples/line/line_chart_sample10.dart)) ##### Sample 11 ([Source Code](/example/lib/presentation/samples/line/line_chart_sample11.dart)) https://user-images.githubusercontent.com/7009300/152555425-3b53ac8c-257f-49b0-8d75-1a878c03ccaa.mp4 ================================================ FILE: repo_files/documentations/migration_guides/0.50.0/MIGRATION_00_50_00.md ================================================ # Migrate to 0.50.0 ## Widgets as titles (instead of boring strings) [#183](https://github.com/imaNNeo/fl_chart/issues/183) We did a lot of hard-work to bring widgets to our titles around the axis-based charts. It means that you can now put a widget as a title instead of a string. Look at the below samples: **LineChartSample 8** ([Source Code](https://github.com/imaNNeo/fl_chart/blob/main/example/lib/presentation/samples/line/line_chart_sample8.dart)) **BarChartSample 7** ([Source Code](https://github.com/imaNNeo/fl_chart/blob/main/example/lib/presentation/samples/bar/bar_chart_sample7.dart)) **Breaking:** Previously in [FlTitlesData](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/base_chart.md#FlTitlesData), there were four [SideTitles](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/base_chart.md#sidetitles). Now we have four [AxisTitle](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/base_chart.md#axistitle) instead and [SideTitles](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/base_chart.md#sidetitles) can be placed inside [AxisTitle](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/base_chart.md#AxisTitle). In fact, we removed `AxisTitlesData` class (which used to hold four `AxisTitle`). Now you can put them in [FlTitlesData](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/base_chart.md#fltitlesdata). bool? showTitle, String? titleText, double? reservedSize, TextStyle? textStyle, TextDirection? textDirection, TextAlign? textAlign, Look at the below sample. Previously: ```dart AxisBasedChartData( // Line, Bar and Scatter axisTitleData: FlAxisTitleData( bottomTitle: AxisTitle( showTitle: true, margin: 0, titleText: '2019', reservedSize: 80, textStyle: TextStyle(color: Colors.green), textAlign: TextAlign.right, textDirection: TextDirection.rtl, ), ), titlesData: FlTitlesData( bottomTitles: SideTitles( showTitles: true, getTitles: (value) { return 'My Text' }, reservedSize: 14, interval: 1, margin: 8, getTextStyles: (context, value) => TextStyle(color: Colors.red), textDirection: TextDirection.rtl, textAlign: TextAlign.center, ), ) ) ``` Now in `0.50.0`: ```dart AxisBasedChartData( // Line, Bar and Scatter titlesData: FlTitlesData( show: true, bottomTitles: AxisTitles( axisNameWidget: Text( // You can use any widget here '2019', style: TextStyle(color: Colors.green), textAlign: TextAlign.right, textDirection: TextDirection.rtl, ), axisNameSize: 80, sideTitles: SideTitles( showTitles: true, getTitlesWidget: (value, titleMeta) { return Padding( // You can use any widget here padding: EdgeInsets.only(top: 8.0), child: Text( 'My Text', style: TextStyle(color: Colors.red), textDirection: TextDirection.rtl, textAlign: TextAlign.center, ), ); }, reservedSize: 14, interval: 1, ), ), ) ) ``` * Instead of setting `rotateAngle` property, now you can wrap your widget with [RotatedBox](https://api.flutter.dev/flutter/widgets/RotatedBox-class.html) to rotate it. * Instead of setting `checkToShowTitle` property, you can pass an empty [SizedBox](https://api.flutter.dev/flutter/widgets/SizedBox-class.html) wherever you want to skip drawing a title. ----- ## Gradient and solid color #948 We made some changes on our approach for handling `solid` color and `gradient` colors. Previously, we had these properties to handle gradient or solid color: ```dart List colors, List stops, Offset gradientFrom, Offset gradientTo, ``` It was supposed to work on both solid color and gradient color. If you pass just one color in the `colors` property, it was a solid color. On the other hand, if you provide more than one color, it was a linear gradient. Now we are using a new approach with the properties below: ```dart Color? color, Gradient? gradient, ``` * If you fill `color` property, it will be a solid color. * If you fill `gradient` property, it would be any [Gradient](https://api.flutter.dev/flutter/dart-ui/Gradient-class.html) you want. Such as [LinearGradient](https://api.flutter.dev/flutter/painting/LinearGradient-class.html) and [RadialGradient](https://api.flutter.dev/flutter/painting/RadialGradient-class.html). * You need to fill one of them. These are the affected classes: * [BarChartRodData](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/bar_chart.md#barchartroddata) * [BackgroundBarChartRodData](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/bar_chart.md#backgroundbarchartroddata) * [BarAreaData](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/line_chart.md#BarAreaData) * [BetweenBarsData](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/line_chart.md#betweenbarsdata) * [LineChartBarData](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/line_chart.md#linechartbardata) Check the below sample: Previously: ```dart LineChartBarData( colors: [Colors.red] ) ``` Now in `0.50.0`: ```dart LineChartBarData( color: Colors.red ) ``` ----- Previously: ```dart LineChartBarData( colors: [Colors.green, Colors.blue], ) ``` Now in `0.50.0`: ```dart LineChartBarData( gradient: LinearGradient( colors: [Colors.green, Colors.blue], begin: Alignment.centerLeft, end: Alignment.centerRight, ) ) ``` ----- Previously: ```dart LineChartBarData( colors: [Colors.green, Colors.blue], colorStops: [0.1, 0.10], gradientFrom: Offset(0, 0), // topLeft gradientTo: Offset(1, 1), // bottomRight ) ``` Now in `0.50.0`: ```dart LineChartBarData( gradient: LinearGradient( colors: [Colors.green, Colors.blue], stops: [0.1, 0.10], begin: Alignment.topLeft, end: Alignment.bottomRight, ) ) ``` ================================================ FILE: repo_files/documentations/migration_guides/0.55.0/MIGRATION_00_55_00.md ================================================ # Migrate to new version ## The ability to rotate the RadarChart titles [#1057](https://github.com/imaNNeo/fl_chart/issues/1057) We added the ability to customize the rotation angles of the RadarChart titles. To do that we add to break one thing and added a new type. **Breaking:** We only changed [RadarChartData.getTitle](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/radar_chart.md#RadarChartData). Its type [GetTitleByIndexFunction] changed from `string Function(int index)` to `RadarChartTitle Function(int index, double angle)`. To reuse the example from the code: Previously: ```dart getTitle: (index) { switch (index) { case 0: return 'Mobile or Tablet'; case 2: return 'Desktop'; case 1: return 'TV'; default: return ''; } } ``` Now in new version: ```dart getTitle: (index, angle) { switch (index) { case 0: return RadarChartTitle(text: 'Mobile or Tablet', angle: angle); case 2: return RadarChartTitle(text: 'Desktop', angle: angle); case 1: return RadarChartTitle(text: 'TV', angle: angle); default: return const RadarChartTitle(text: ''); } } ``` If you take the provided `angle` and forward it to the [RadarChartTitle] it will behave like in previous versions. But you can now render all the titles horizontally by avoiding the [RadarChartTitle.angle] prop (`0` by default). Apply a relative angle, for example: `RadarChartTitle(text: 'Desktop', angle: angle + 90);` or an absolute angle, for example: `RadarChartTitle(text: 'Desktop', angle: 90);` ================================================ FILE: repo_files/documentations/migration_guides/0.67.0/MIGRATION_00_67_00.md ================================================ # Migrate to new version ## Replaced tooltipBgColor **Breaking: [#1595](https://github.com/imaNNeo/fl_chart/pull/1595)** We added the ability to customize the tooltip background color for each point. The property `Color tooltipBgColor` from Bar, Line and Scatter Charts is replaced with a callback `Color Function(spot) getTooltipColor` #### BarChartData Previously: ```dart BarChartData( barTouchData: BarTouchData( touchTooltipData: BarTouchTooltipData( tooltipBgColor: Colors.blueGrey, ) ) ) ``` Now in new version: ```dart BarChartData( barTouchData: BarTouchData( touchTooltipData: BarTouchTooltipData( getTooltipColor: (BarChartGroupData group) => Colors.blueGrey, ) ) ) ``` #### LineChartData Previously: ```dart LineChartData( lineTouchData: LineTouchData( touchTooltipData: LineTouchTooltipData( tooltipBgColor: Colors.blueGrey, ) ) ) ``` Now in new version: ```dart LineChartData( lineTouchData: LineTouchData( touchTooltipData: LineTouchTooltipData( getTooltipColor: (LineBarSpot touchedSpot) => Colors.blueGrey, ) ) ) ``` #### ScatterChartData Previously: ```dart ScatterChartData( scatterTouchData: ScatterTouchData( touchTooltipData: ScatterTouchTooltipData( tooltipBgColor: Colors.blueGrey, ) ) ) ``` Now in new version: ```dart ScatterChartData( scatterTouchData: ScatterTouchData( touchTooltipData: ScatterTouchTooltipData( getTooltipColor: (ScatterSpot touchedBarSpot) => Colors.blueGrey, ) ) ) ``` ================================================ FILE: repo_files/documentations/migration_guides/0.70.0/MIGRATION_00_70_00.md ================================================ # Migrate to version 0.70.0 ## Fixed the equatable functionality in our PieChartSectionData Please check any code that compares `PieChartSectionData` classes or other objects containing `PieChartSectionData` and make sure it is not affected by this change. ## `BarChart` is not const anymore We added an assert to check if transformations are allowed depending on the `BarChartData.alignment` property. If you are using `BarChart` as a const, you need to remove the const keyword from the `BarChart` constructor. The compiler will show you an error if you try to use `BarChart` as a const. ================================================ FILE: repo_files/documentations/migration_guides/INDEX.md ================================================ Here are fl_chart's migration guides: #### [Migrate to 0.50.0](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/migration_guides/0.50.0/MIGRATION_00_50_00.md) #### [Migrate to 0.55.0](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/migration_guides/0.55.0/MIGRATION_00_55_00.md) #### [Migrate to 0.67.0](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/migration_guides/0.67.0/MIGRATION_00_67_00.md) ================================================ FILE: repo_files/documentations/pie_chart.md ================================================ ### How to use ```dart PieChart( PieChartData( // read about it in the PieChartData section ), duration: Duration(milliseconds: 150), // Optional curve: Curves.linear, // Optional ); ``` **If you have a padding widget around the PieChart, make sure to set `PieChartData.centerSpaceRadius` to `double.infinity`** ### Implicit Animations When you change the chart's state, it animates to the new state internally (using [implicit animations](https://flutter.dev/docs/development/ui/animations/implicit-animations)). You can control the animation [duration](https://api.flutter.dev/flutter/dart-core/Duration-class.html) and [curve](https://api.flutter.dev/flutter/animation/Curves-class.html) using optional `duration` and `curve` properties, respectively. ### PieChartData |PropName |Description |default value| |:---------------|:---------------|:-------| |sections| list of [PieChartSectionData ](#PieChartSectionData) that is shown on the pie chart|[]| |centerSpaceRadius| free space in the middle of the PieChart, set `double.infinity` if you want it to be calculated according to the view size| double.nan| |centerSpaceColor| colors the free space in the middle of the PieChart|Colors.transparent| |sectionsSpace| space between the sections (margin of them). It does not work on html-rendere, read more about it [here](https://github.com/imaNNeo/fl_chart/issues/955) |2| |startDegreeOffset| degree offset of the sections around the pie chart, should be between 0 and 360|0| |pieTouchData| [PieTouchData](#pietouchdata-read-about-touch-handling) holds the touch interactivity details| PieTouchData()| |borderData| shows a border around the chart, check the [FlBorderData](base_chart.md#FlBorderData)|FlBorderData()| |titleSunbeamLayout| whether to rotate the titles on each section of the chart|false| ### PieChartSectionData |PropName |Description |default value| |:---------------|:---------------|:-------| |value| value is the weight of each section, for example if all values is 25, and we have 4 section, then the sum is 100 and each section takes 1/4 of the whole circle (360/4) degree|10| |color| colors the section| Colors.red |gradient| You can use any [Gradient](https://api.flutter.dev/flutter/dart-ui/Gradient-class.html) here. such as [LinearGradient](https://api.flutter.dev/flutter/painting/LinearGradient-class.html) or [RadialGradient](https://api.flutter.dev/flutter/painting/RadialGradient-class.html) (you have to provide either `color` or `gradient`)|null| |radius| the width radius of each section|40| |showTitle| determines to show or hide the titles on each section|true| |titleStyle| TextStyle of the titles| TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold)| |title| title of the section| value| |borderSide| Defines border stroke around the section | BorderSide(width: 0)| |cornerRadius| defines corner radius for rounded section edges |0| |badgeWidget| badge component of the section| null| |titlePositionPercentageOffset|the place of the title in the section, this field should be between 0 and 1|0.5| |badgePositionPercentageOffset|the place of the badge component in the section, this field should be between 0 and 1|0.5| ### PieTouchData ([read about touch handling](handle_touches.md)) |PropName|Description|default value| |:-------|:----------|:------------| |enabled|determines to enable or disable touch behaviors|true| |mouseCursorResolver|you can change the mouse cursor based on the provided [FlTouchEvent](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/base_chart.md#fltouchevent) and [PieTouchResponse](#PieTouchResponse)|MouseCursor.defer| |touchCallback| listen to this callback to retrieve touch/pointer events and responses, it gives you a [FlTouchEvent](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/base_chart.md#fltouchevent) and [PieTouchResponse](#PieTouchResponse)| null| |longPressDuration| allows to customize the duration of the longPress gesture. If null, the duration of the longPressGesture is [kLongPressTimeout](https://api.flutter.dev/flutter/gestures/kLongPressTimeout-constant.html)| null| ### PieTouchResponse |PropName|Description|default value| |:-------|:----------|:------------| |touchLocation|the location of the touch event in the device pixels coordinates|required| |touchedSection|Instance of [PieTouchedSection](#PieTouchedSection) which holds data about the touched section|null| ### PieTouchedSection |PropName|Description|default value| |:-------|:----------|:------------| |touchedSection|the [PieChartSectionData](#PieChartSectionData) that user touched| null | |touchedSectionIndex| index of the touched section | null| |touchAngle|the angle of the touch|null| |touchRadius| the radius of the touch|null| ### some samples ---- ##### Sample 1 ([Source Code](/example/lib/presentation/samples/pie/pie_chart_sample1.dart)) ##### Sample 2 ([Source Code](/example/lib/presentation/samples/pie/pie_chart_sample2.dart)) ##### Sample 3 ([Source Code](/example/lib/presentation/samples/pie/pie_chart_sample3.dart)) ================================================ FILE: repo_files/documentations/radar_chart.md ================================================ # RadarChart ### How to use ```dart RadarChart( RadarChartData( // read about it in the RadarChartData section ), swapAnimationDuration: Duration(milliseconds: 150), // Optional swapAnimationCurve: Curves.linear, // Optional ); ``` ### Implicit Animations When you change the chart's state, it animates to the new state internally (using [implicit animations](https://flutter.dev/docs/development/ui/animations/implicit-animations)). You can control the animation [duration](https://api.flutter.dev/flutter/dart-core/Duration-class.html) and [curve](https://api.flutter.dev/flutter/animation/Curves-class.html) using optional `swapAnimationDuration` and `swapAnimationCurve` properties, respectively. ### RadarChartData |PropName |Description |default value| |:---------------|:---------------|:-------| |dataSets| list of [RadarDataSet ](#RadarDataSet) that is shown on the radar chart|[]| |radarBackgroundColor| This property fills the background of the radar with the specified color.| Colors.transparent| |radarShape| the shape of the border and background |RadarShape.circle| |radarBorderData| shows a border for radar chart|BorderSide(color: Colors.black, width: 2)| |getTitle| This function helps the radar chart to draw titles outside the chart. The default angle provided when called is making the title tangent to the radar chart. |null| |titleTextStyle|TextStyle of the titles|TextStyle(color: Colors.black, fontSize: 12)| |titlePositionPercentageOffset|this field is the place of showing title on the RadarChart. The higher the value of this field, the more titles move away from the chart. this field should be between 0 and 1.|0.2| |tickCount|Defines the number of ticks that should be paint in RadarChart|1| |ticksTextStyle|TextStyle of the tick titles|TextStyle(fontSize: 10, color: Colors.black)| |tickBorderData|Style of the tick borders|BorderSide(color: Colors.black, width: 2)| |gridBorderData|Style of the grid borders|BorderSide(color: Colors.black, width: 2)| |radarTouchData|[RadarTouchData](#radartouchdata-read-about-touch-handling) handles the touch behaviors and responses.|RadarTouchData()| |isMinValueAtCenter|If true, the minimum value of the [RadarChart] will be at the center of the chart.|false| ### RadarDataSet |PropName |Description |default value| |:---------------|:---------------|:-------| |dataEntries|Each RadarDataSet contains list of [RadarEntries ](#RadarEntry) that is shown in RadarChart.|[]| |fillColor|fills the DataSet with the specified color.|Colors.black12| |fillGradient|fills the DataSet with the specified gradient colors.| null | |borderColor|Paint the DataSet border with the specified color.|Colors.blueAccent| |borderWidth|defines the width of [RadarDataSet](#RadarDataSet) border.|2.0| |entryRadius|defines the radius of each [RadarEntries ](#RadarEntry).|5.0| ### RadarEntry |PropName |Description |default value| |:---------------|:---------------|:-------| |value| RadarChart uses this field to render every point in chart.| null | ### RadarTouchData ([read about touch handling](handle_touches.md)) |PropName|Description|default value| |:-------|:----------|:------------| |enabled|determines to enable or disable touch behaviors|true| |mouseCursorResolver|you can change the mouse cursor based on the provided [FlTouchEvent](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/base_chart.md#fltouchevent) and [RadarTouchResponse](#RadarTouchResponse)|MouseCursor.defer| |touchCallback| listen to this callback to retrieve touch/pointer events and responses, it gives you a [FlTouchEvent](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/base_chart.md#fltouchevent) and [RadarTouchResponse](#RadarTouchResponse)| null| |longPressDuration| allows to customize the duration of the longPress gesture. If null, the duration of the longPressGesture is [kLongPressTimeout](https://api.flutter.dev/flutter/gestures/kLongPressTimeout-constant.html)| null| |touchSpotThreshold|the threshold of the touch accuracy. we find the nearest spots on touched position based on this field.|10| ### RadarTouchResponse |PropName|Description|default value| |:-------|:----------|:------------| |touchLocation|the location of the touch event in the device pixels coordinates|required| |touchedSpot|the [RadarTouchedSpot](#RadarTouchedSpot) that user touched| null | ### RadarTouchedSpot |PropName|Description|default value| |:-------|:----------|:------------| |touchedDataSet|the [RadarDataSet](#RadarDataSet) that user touched| null | |touchedDataSetIndex| index of the [RadarDataSet](#RadarDataSet) that user touched| null | |touchedRadarEntry|the [RadarEntry](#RadarEntry) that user touched| null | |touchedRadarEntryIndex| index of the [RadarEntry](#RadarEntry) that user touched| null | ### RadarChartTitle |PropName|Description|default value| |:-------|:----------|:------------| |text|the text of the title|required| |children| A list of [InlineSpan](https://api.flutter.dev/flutter/painting/InlineSpan-class.html) that you can provide to have different texts with different styels. Just like how [TextSpan](https://api.flutter.dev/flutter/painting/TextSpan-class.html) works|null| |angle|the angle used to rotate the title (in degree)|0| |positionPercentageOffset|this field is the place of showing title. The higher the value of this field, the more titles move away from the chart. this field should be between 0 and 1|null| ### some samples ---- ##### Sample 1 ([Source Code](/example/lib/presentation/samples/radar/radar_chart_sample1.dart)) ================================================ FILE: repo_files/documentations/scatter_chart.md ================================================ # ScatterChart ### How to use ```dart ScatterChart( ScatterChartData( // read about it in the ScatterChartData section ), duration: Duration(milliseconds: 150), // Optional curve: Curves.linear, // Optional ); ``` ### Implicit Animations When you change the chart's state, it animates to the new state internally (using [implicit animations](https://flutter.dev/docs/development/ui/animations/implicit-animations)). You can control the animation [duration](https://api.flutter.dev/flutter/dart-core/Duration-class.html) and [curve](https://api.flutter.dev/flutter/animation/Curves-class.html) using optional `duration` and `curve` properties, respectively. ### ScatterChartData |PropName |Description |default value| |:---------------|:---------------|:-------| |scatterSpots| list of [ScatterSpot ](#ScatterSpot ) to show the scatter spots on the chart|[]| |titlesData| check the [FlTitlesData](base_chart.md#FlTitlesData)| FlTitlesData()| |axisTitleData| check the [FlAxisTitleData](base_chart.md#FlAxisTitleData)| FlAxisTitleData()| |scatterTouchData| [ScatterTouchData](#scattertouchdata-read-about-touch-handling) holds the touch interactivity details| ScatterTouchData()| |showingTooltipIndicators| indices of showing tooltip, The point is that you need to disable touches to show these tooltips manually|[]| |rotationQuarterTurns|Rotates the chart 90 degrees (clockwise) in every quarter turns. This feature works like the [RotatedBox](https://api.flutter.dev/flutter/widgets/RotatedBox-class.html) widget|0| |errorIndicatorData|Holds data for representing an error indicator (you see the error indicators if you provide the `xError` or `yError` in the [ScatterSpot](#ScatterSpot))|[ErrorIndicatorData()](base_chart.md#FlErrorIndicatorData)| ### ScatterSpot |PropName |Description |default value| |:---------------|:---------------|:-------| |show| determines to show or hide the spot|true| |radius| radius of the showing spot| [8] |color| colors of the spot|// a color based on the values| |renderPriority| sort by this to manage overlap|0| |xError| Determines the error range of the data point using (FlErrorRange)[base_chart.md#FlErrorRange] (which ontains `lowerBy` and `upperValue`) for the x-axis|null| |yError| Determines the error range of the data point using (FlErrorRange)[base_chart.md#FlErrorRange] (which ontains `upperBy` and `upperValue`) for the y-axis|null| ### ScatterTouchData ([read about touch handling](handle_touches.md)) |PropName|Description|default value| |:-------|:----------|:------------| |enabled|determines to enable or disable touch behaviors|true| |mouseCursorResolver|you can change the mouse cursor based on the provided [FlTouchEvent](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/base_chart.md#fltouchevent) and [ScatterTouchResponse](#ScatterTouchResponse)|MouseCursor.defer| |touchTooltipData|a [ScatterTouchTooltipData](#ScatterTouchTooltipData), that determines how show the tooltip on top of touched spot (appearance of the showing tooltip bubble)|ScatterTouchTooltipData()| |touchSpotThreshold|the threshold of the touch accuracy|0| |handleBuiltInTouches| set this true if you want the built in touch handling (show a tooltip bubble and an indicator on touched spots) | true| |touchCallback| listen to this callback to retrieve touch/pointer events and responses, it gives you a [FlTouchEvent](https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/base_chart.md#fltouchevent) and [ScatterTouchResponse](#ScatterTouchResponse)| null| |longPressDuration| allows to customize the duration of the longPress gesture. If null, the duration of the longPressGesture is [kLongPressTimeout](https://api.flutter.dev/flutter/gestures/kLongPressTimeout-constant.html)| null| ### ScatterTouchTooltipData |PropName|Description|default value| |:-------|:----------|:------------| |tooltipBorder|border of the tooltip bubble|BorderSide.none| |tooltipBorderRadius|background corner radius of the tooltip bubble|BorderRadius.circular(4)| |tooltipPadding|padding of the tooltip|EdgeInsets.symmetric(horizontal: 16, vertical: 8)| |tooltipHorizontalAlignment|horizontal alginment of tooltip relative to the spot|FLHorizontalAlignment.center| |tooltipHorizontalOffset|horizontal offset of tooltip|0| |maxContentWidth|maximum width of the tooltip (if a text row is wider than this, then the text breaks to a new line|120| |getTooltipItems|a callback that retrieve a [ScatterTooltipItem](#ScatterTooltipItem) by the given [ScatterSpot](#ScatterSpot) |defaultScatterTooltipItem| |fitInsideHorizontally| forces tooltip to horizontally shift inside the chart's bounding box| false| |fitInsideVertically| forces tooltip to vertically shift inside the chart's bounding box| false| |getTooltipColor|a callback that retrieves the Color for each touched spots separately from the given [ScatterSpot](#ScatterSpot) to set the background color of the tooltip bubble|Colors.blueGrey.darken(15)| ### ScatterTooltipItem |PropName|Description|default value| |:-------|:----------|:------------| |text|text string of each row in the tooltip bubble|null| |textStyle|[TextStyle](https://api.flutter.dev/flutter/dart-ui/TextStyle-class.html) of the showing text row|null| |textDirection|[TextDirection](https://api.flutter.dev/flutter/dart-ui/TextDirection-class.html) of the showing text row|TextDirection.ltr| |bottomMargin| bottom margin of the tooltip (to the top of most top spot) | 0| |children|[List](https://api.flutter.dev/flutter/painting/InlineSpan-class.html) pass additional InlineSpan children for a more advance tooltip|null| ### ScatterTouchResponse ###### you can listen to touch behaviors callback and retrieve this object when any touch action happened. |PropName|Description|default value| |:-------|:----------|:------------| |touchLocation|the location of the touch event in the device pixels coordinates|required| |touchChartCoordinate|the location of the touch event in the chart coordinates|required| |touchedSpot|Instance of [ScatterTouchedSpot](#ScatterTouchedSpot) which holds data about the touched section|null| ### ScatterTouchedSpot |PropName|Description|default value| |:-------|:----------|:------------| |spot|touched [ScatterSpot](#ScatterSpot)|null| |spotIndex|index of touched [ScatterSpot](#ScatterSpot)|null| ### ScatterLabelSettings |PropName|Description|default value| |:-------|:----------|:------------| |showLabel|Determines whether to show or hide the labels.|false| |getLabelTextStyleFunction|This function gives you the index value of the spot in the list and returns the text style.|null| |getLabelFunction|This function gives you the index value of the spot in the list and returns the label.|spot.radius.toString()| |textDirection|Determines the direction of the text for the labels.|TextDirection.ltr| ### some samples ---- ##### Sample 1 ([Source Code](/example/lib/presentation/samples/scatter/scatter_chart_sample1.dart)) ##### Sample 2 ([Source Code](/example/lib/presentation/samples/scatter/scatter_chart_sample2.dart)) ================================================ FILE: repo_files/drawio/flchart.drawio ================================================ 7V1Jk+I2FP41XZUc6PKCF44N3WRSNanqyhySzk3YAjxjLMaIBvLrI9mSV9lsNjZEfZixnp5kLU/f+7SZJ32y2v8WgvXyD+RC/0lT3P2T/vqkaZqhauQ/KjnEkpGixIJF6LmxSE0F37x/IRNyta3nwk1OESPkY2+dFzooCKCDczIQhmiXV5sjP//WNVjAkuCbA/yy9C/PxUsmVXk1aMQX6C2W7NW2wSJmwPmxCNE2YO970vR59BdHrwDPi+lvlsBFu4xIf3vSJyFCOH5a7SfQp23Lmy1ON62ITcodwgCfksD5oU787+//fHzR0O7ny3SxU/UB770NPvAGgS5pHxZEIV6iBQqA/5ZKx1GlIc1WIaFU5ytCayJUifA7xPjAOhtsMSKiJV75LBbuPfx35vmDZvVssNDrnuUcBQ4ssMEh+gEnyEdhVE5dUdTX8YSqBe4LNQQidnyw2XhOLJx6Pn/hnDzmkhoKnCeZ8n4nXTKOm4O2QWUzM9EGbUOHaWlDazSc2aYCZo7uaO6AWzIIFxBX6ViJFZDRBdEK4vBAEoXQB9j7zBcAMDNfJHppV5MH1tvinq8q3Sfwt5Abr+mTco7XOVswf26pdY5XpBpe8KS/kFhlvSf/Ri2lxPIBpt1O44aZOAz3eAB8b8HSOaT9YJiPdqGDQlJXxHSoWYW+F8D01eRpwf6PCjjjgqk/WYIQczlpg1lRl8jWRdkyLEouqrIP5ziOtGlksbRjsIGseIqTLeas9Pp8IQtj8ROG2CNY9RK342vU0mPWqq9xIcaIaM39yP7nkcmP5yjAbPCpGgtPwcrz6WD6Av1PSHPNj8n8oE7AigYctKJjKnr2wQz64wT6+KAKUADFg5T+lUaaYEwy7OSNIBhwtDHgPiMqDx0WOxgxzGU+SdU0gi6RZJfB+CHTWmbg3TCuH29CpO0UaJVni6PrRxZchUhL2j08xKk0g4c/eI40kKaLQjxhoUddW1EsXWQV06mi2GWraBJ/zX4DsPkACPyy9zY9xmAJubeEXFXLQ67Ow8cAV1daAlz+sisQ9/t2teb6rLkbY7vaMbpLBhantUlXExljtcppxLeaNZehl5qsCKSbguRr0ZYlfUdegFPDswuGpyoFDx67AJaqYFNJMa4ws+sde5tmVm9lqavP+nmWqsbTP6p15gmDuL8rcPAGpMF6ANIAZqRrgUMHcGaS1Ef+kBTvHXisDdbx0y+/Hilgq8V5BRjQdTMYPdWURRKbJoiNZuT9iyngNSMBr1HttoiN0e1MUrvBTPKcGaMLNsuoZmqD3sA+Zfpo3sgTiP2Q3jPeoWTNIl1vkMTjfFMTL9XfarVCbG7DXpubtLamre1acIuSkpYBh4zCms7CNtWTuYGVd7aGbmQt96i+qRgFS49LcOmMr7L97pqDfyVqPSbePO/NGgRcljJvmhd9FVhR+soSKEmV3lPFpB7ZjE5dFWyveilxP1IVSe9vsVU0LCCOIuD3toDfW23Re/se6X2f2b2wlU/aH+qW4F8/z5ME//aU61RrE28Sd2txZq8t7jKD06TF1Vqc0QXPTzzqiTy/pD+yG+X5dU1411R/DMIHY/q8RvdP9HlNJM8/jeeXSL0AGauX8dW+8Xz1LtfxzyL6l7jNxBkKqMTtpgfq8JT5Qbcrslav2ZqcH1xpbmK6NurU5Ea9NjlpcW1YnN3FBGGonzdBKOo3vRFQ24Z3PUN49x5tL4DX6P5nCLwmcoZwgxnC0OzbDIGfPOqNu73saOnZZ3/a8bc1mwxV/ra12cWpq3P6tXTvstU5s9aZHtW3VKVW3zbtOv2WnPUjLOcVt7n77bTlzZtb+K0Bo8bp2rjAb6kCv9XazRujb2fFzrp5k3Fcpm7lPNezouv3472igr3D0CO9CkP27tZc2s3uT1zk0qz8GLGseg9l6Wqdfkse6hHud+ROL0n3JN1T0T2Zw67dk3r9Kub/85snjd/dHKiF03daoc9jt1O6u1nOyKjPp+U7oPx1AuSmw53CbNIRKRrp9I9+LigVCbCOZjDYRFZFEU01RZD2J5xD0lkOzCBZ/OYKMKM4n7fPvK0xDMnaEBNxwGOOQx8XcXHluW40ZHZLD8NvaxDxhl0I1qVh1AS8DPPwopbRRbRmUzSQ5rivWmrrPnHfRi+dM40UdoSY1aN1l2YOO5U3HxTx+sfN4Md4AOJYONcjqaOkjqUzO92vbPTtTLZE9wYPT4vRXS9wS6u439M2uj/CwjVB9zpETwRy6UDifyX+d790YGgS//uL/42cVCrhvznqmN0/wpXjwpkcie4S3Uvnbbpn9327/ybRvcGTzxXornbM7kcPhO6SuEtoF0F798Rdv35Z/hIcb/3+06kA2/7WHb8bcu3WnXEa/p79ZaGRUSju85FPCx1J0M4ZEF2rdAdsJ/HCHcLfgyUMPQzubo+w/i7MdXuHBdMToJToy46t7R3qj8AFRB94lbM+SQ0qPtc9Ev4yh2jYtfeh+Ef4ORzRsJN0XI450RxX1dsbciSY/qRZzIrS343T3/4D ================================================ FILE: repo_files/images/architecture/fl_chart_architecture.txt ================================================ https://drive.google.com/file/d/1bj-2TqTRUh80dRKJk10drPNeA3fp3EA8/view ================================================ FILE: scripts/makefile_scripts.sh ================================================ #!/bin/bash # Opens a link with the default browser of OS (It works cross-platform) # ## You can call it like `open_link balad.ir` to open balad website on your default browser open_link () { case "$(uname -s)" in Darwin) # macOS open "$1" ;; Linux) # Linux: xdg-open "$1" ;; CYGWIN*|MINGW32*|MSYS*|MINGW*) # Windows start "$1" ;; *) echo 'Not supported OS' ;; esac } ================================================ FILE: test/chart/bar_chart/bar_chart_data_test.dart ================================================ import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import '../data_pool.dart'; void main() { group('BarChart data equality check', () { test('BarChartGroupData equality test', () { expect(barChartGroupData1 == barChartGroupData1Clone, true); expect(barChartGroupData1 == barChartGroupData2, false); expect(barChartGroupData1 == barChartGroupData3, false); expect(barChartGroupData1 == barChartGroupData4, false); expect(barChartGroupData1 == barChartGroupData5, false); expect(barChartGroupData1 == barChartGroupData6, false); expect(barChartGroupData1 == barChartGroupData7, false); expect(barChartGroupData1 == barChartGroupData8, false); expect(barChartGroupData1 == barChartGroupData9, false); }); test('BarChartRodData equality test', () { expect(barChartRodData1 == barChartRodData1Clone, true); expect(barChartRodData1 == barChartRodData2, false); expect(barChartRodData1 == barChartRodData3, false); expect(barChartRodData1 == barChartRodData4, false); expect(barChartRodData1 == barChartRodData5, false); expect(barChartRodData1 == barChartRodData6, false); expect(barChartRodData1 == barChartRodData7, false); expect(barChartRodData1 == barChartRodData8, false); }); test('BarChartRodStackItem equality test', () { expect(barChartRodStackItem1 == barChartRodStackItem1Clone, true); expect( barChartRodStackItem1 == barChartRodStackItem1Clone.copyWith(fromY: 2), false, ); expect( barChartRodStackItem1 == barChartRodStackItem1Clone.copyWith(toY: 2), true, ); expect( barChartRodStackItem1 == barChartRodStackItem1Clone.copyWith(toY: 3), false, ); expect( barChartRodStackItem1 == barChartRodStackItem1Clone.copyWith(color: Colors.red), false, ); expect( barChartRodStackItem1 == barChartRodStackItem1Clone.copyWith(color: Colors.green), true, ); expect(barChartRodStackItem1 == barChartRodStackItem2, false); expect(barChartRodStackItem1 == barChartRodStackItem3, false); expect(barChartRodStackItem2 == barChartRodStackItem4, false); }); test('BackgroundBarChartRodData equality test', () { expect( backgroundBarChartRodData1 == backgroundBarChartRodData1Clone, true, ); expect(backgroundBarChartRodData1 == backgroundBarChartRodData2, false); expect(backgroundBarChartRodData2 == backgroundBarChartRodData3, false); final changed = BackgroundBarChartRodData( toY: 21, color: Colors.blue, show: false, ); expect(backgroundBarChartRodData1 == changed, false); final changed2 = BackgroundBarChartRodData( toY: 22, color: Colors.blue, show: true, ); expect(backgroundBarChartRodData1 == changed2, false); }); test('BarTouchData equality test', () { expect(barTouchData1 == barTouchData1Clone, true); expect(barTouchData1 == barTouchData2, false); expect(barTouchData1 == barTouchData3, false); expect(barTouchData1 == barTouchData4, false); expect(barTouchData1 == barTouchData5, false); expect(barTouchData1 == barTouchData6, false); expect(barTouchData1 == barTouchData7, false); expect(barTouchData1 == barTouchData8, false); expect(barTouchData1 == barTouchData9, false); expect(barTouchData1 == barTouchData10, false); }); test('BarTouchTooltipData equality test', () { expect(barTouchTooltipData1 == barTouchTooltipData1Clone, true); expect(barTouchTooltipData1 == barTouchTooltipData2, false); expect(barTouchTooltipData1 == barTouchTooltipData3, false); expect(barTouchTooltipData1 == barTouchTooltipData4, false); expect(barTouchTooltipData1 == barTouchTooltipData5, false); expect(barTouchTooltipData1 == barTouchTooltipData6, false); expect(barTouchTooltipData1 == barTouchTooltipData7, false); expect(barTouchTooltipData1 == barTouchTooltipData8, false); expect(barTouchTooltipData1 == barTouchTooltipData9, false); expect(barTouchTooltipData1 == barTouchTooltipData10, false); expect(barTouchTooltipData1 == barTouchTooltipData11, false); }); test('BarTooltipItem equality test', () { expect(barTooltipItem1 == barTooltipItem1Clone, true); expect(barTooltipItem1 == barTooltipItem2, false); expect(barTooltipItem1 == barTooltipItem3, false); expect(barTooltipItem1 == barTooltipItem4, false); expect(barTooltipItem1 == barTooltipItem5, false); }); test('BarTouchedSpot equality test', () { expect(barTouchedSpot1 == barTouchedSpot1Clone, true); expect(barTouchedSpot1 == barTouchedSpot2, false); expect(barTouchedSpot1 == barTouchedSpot3, false); expect(barTouchedSpot1 == barTouchedSpot4, false); expect(barTouchedSpot1 == barTouchedSpot5, false); expect(barTouchedSpot1 == barTouchedSpot6, false); expect(barTouchedSpot1 == barTouchedSpot7, false); }); test('BarChartData equality test', () { expect(barChartData1 == barChartData1Clone, true); expect(barChartData1 == barChartData2, false); expect(barChartData1 == barChartData3, false); expect(barChartData1 == barChartData4, false); expect(barChartData1 == barChartData5, false); expect(barChartData1 == barChartData6, false); expect(barChartData1 == barChartData7, false); expect(barChartData1 == barChartData8, false); expect(barChartData1 == barChartData9, false); expect(barChartData1 == barChartData10, false); expect(barChartData1 == barChartData11, false); expect(barChartData1 == barChartData12, false); expect(barChartData1 == barChartData13, false); expect(barChartData1 == barChartData14, false); expect(barChartData1 == barChartData15, false); }); }); } ================================================ FILE: test/chart/bar_chart/bar_chart_helper_test.dart ================================================ import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/chart/bar_chart/bar_chart_helper.dart'; import 'package:flutter_test/flutter_test.dart'; import '../data_pool.dart'; void main() { group('Check caching of BarChartHelper.calculateMaxAxisValues', () { test('Test validity 1', () { final barChartHelper = BarChartHelper(); final barGroups = [barChartGroupData1, barChartGroupData2]; final (minY, maxY) = barChartHelper.calculateMaxAxisValues(barGroups); expect(minY, 0); expect(maxY, 1132); }); test('Test validity 2', () { final barChartHelper = BarChartHelper(); final barGroups = [ barChartGroupData1.copyWith( barRods: [ BarChartRodData(toY: -10), BarChartRodData(toY: -40), BarChartRodData(toY: 0), BarChartRodData(toY: 10), BarChartRodData(toY: 5), ], ), ]; final (minY, maxY) = barChartHelper.calculateMaxAxisValues(barGroups); expect(minY, -40); expect(maxY, 10); }); test('Test validity 3', () { final barChartHelper = BarChartHelper(); final barGroups = [ barChartGroupData1.copyWith(barRods: []), ]; final (minY, maxY) = barChartHelper.calculateMaxAxisValues(barGroups); expect(minY, 0); expect(maxY, 0); }); test('Test validity 4', () { final barChartHelper = BarChartHelper(); final barGroups = [ barChartGroupData1.copyWith( barRods: [ BarChartRodData(fromY: 0, toY: -10), BarChartRodData(fromY: -10, toY: -40), BarChartRodData(toY: 0), BarChartRodData(toY: 10), BarChartRodData(toY: 5), BarChartRodData(fromY: 10, toY: -50), BarChartRodData(fromY: 39, toY: -50), ], ), ]; final (minY, maxY) = barChartHelper.calculateMaxAxisValues(barGroups); expect(minY, -50); expect(maxY, 39); }); test('Test equality', () { final barChartHelper = BarChartHelper(); final barGroups = [barChartGroupData1, barChartGroupData2]; final result1 = barChartHelper.calculateMaxAxisValues(barGroups); final result2 = barChartHelper.calculateMaxAxisValues(barGroups); expect(result1, result2); }); test('Test calculateMaxAxisValues with all positive values', () { final barChartHelper = BarChartHelper(); final barGroups = [ barChartGroupData1.copyWith( barRods: barChartGroupData1.barRods .map( (rod) => rod.copyWith( fromY: 5, backDrawRodData: BackgroundBarChartRodData(show: false), ), ) .toList(), ), barChartGroupData2.copyWith( barRods: barChartGroupData2.barRods .map( (rod) => rod.copyWith( fromY: 8, backDrawRodData: BackgroundBarChartRodData(show: false), ), ) .toList(), ), ]; final (minY, _) = barChartHelper.calculateMaxAxisValues(barGroups); expect(minY, 5); }); test('Test calculateMaxAxisValues with all negative values', () { final barChartHelper = BarChartHelper(); final barGroups = [ barChartGroupData1.copyWith( barRods: barChartGroupData1.barRods .map((rod) => rod.copyWith(fromY: -5)) .toList(), ), barChartGroupData2.copyWith( barRods: barChartGroupData2.barRods .map((rod) => rod.copyWith(fromY: -8)) .toList(), ), ]; final (minY, _) = barChartHelper.calculateMaxAxisValues(barGroups); expect(minY, -8); }); }); } ================================================ FILE: test/chart/bar_chart/bar_chart_painter_test.dart ================================================ import 'dart:ui' as ui show Gradient; import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/chart/bar_chart/bar_chart_helper.dart'; import 'package:fl_chart/src/chart/bar_chart/bar_chart_painter.dart'; import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart'; import 'package:fl_chart/src/extensions/bar_chart_data_extension.dart'; import 'package:fl_chart/src/extensions/path_extension.dart'; import 'package:fl_chart/src/utils/canvas_wrapper.dart'; import 'package:fl_chart/src/utils/utils.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import '../../helper_methods.dart'; import '../data_pool.dart'; import 'bar_chart_painter_test.mocks.dart'; @GenerateMocks([Canvas, CanvasWrapper, BuildContext, Utils]) void main() { const tolerance = 0.01; group('paint()', () { test('test 1', () { final utilsMainInstance = Utils(); const viewSize = Size(400, 400); final barGroups = [ BarChartGroupData( x: 1, barRods: [ BarChartRodData(fromY: 1, toY: 10), BarChartRodData(fromY: 2, toY: 10), ], showingTooltipIndicators: [ 1, 2, ], ), BarChartGroupData( x: 2, barRods: [ BarChartRodData(fromY: 3, toY: 10), BarChartRodData(fromY: 4, toY: 10), ], ), ]; final (minY, maxY) = BarChartHelper().calculateMaxAxisValues(barGroups); final data = BarChartData( barGroups: barGroups, minY: minY, maxY: maxY, ); final barChartPainter = BarChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockUtils = MockUtils(); Utils.changeInstance(mockUtils); when(mockUtils.getThemeAwareTextStyle(any, any)) .thenAnswer((realInvocation) => textStyle1); when(mockUtils.calculateRotationOffset(any, any)) .thenAnswer((realInvocation) => Offset.zero); when(mockUtils.convertRadiusToSigma(any)) .thenAnswer((realInvocation) => 4.0); when(mockUtils.getEfficientInterval(any, any)) .thenAnswer((realInvocation) => 1.0); when(mockUtils.getBestInitialIntervalValue(any, any, any)) .thenAnswer((realInvocation) => 1.0); when(mockUtils.normalizeBorderRadius(any, any)) .thenAnswer((realInvocation) => BorderRadius.zero); when(mockUtils.normalizeBorderSide(any, any)).thenAnswer( (realInvocation) => const BorderSide(color: MockData.color0), ); final mockBuildContext = MockBuildContext(); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); barChartPainter.paint( mockBuildContext, mockCanvasWrapper, holder, ); Utils.changeInstance(utilsMainInstance); }); }); group('scaling related', () { final utilsMainInstance = Utils(); late MockUtils mockUtils; setUp(() { mockUtils = MockUtils(); Utils.changeInstance(mockUtils); when(mockUtils.getThemeAwareTextStyle(any, any)) .thenAnswer((realInvocation) => textStyle1); when(mockUtils.calculateRotationOffset(any, any)) .thenAnswer((realInvocation) => Offset.zero); when(mockUtils.convertRadiusToSigma(any)) .thenAnswer((realInvocation) => 4.0); when(mockUtils.getEfficientInterval(any, any)) .thenAnswer((realInvocation) => 1.0); when(mockUtils.getBestInitialIntervalValue(any, any, any)) .thenAnswer((realInvocation) => 1.0); when(mockUtils.normalizeBorderRadius(any, any)) .thenAnswer((realInvocation) => BorderRadius.zero); when(mockUtils.normalizeBorderSide(any, any)).thenAnswer( (realInvocation) => const BorderSide(color: MockData.color0), ); }); tearDown(() { Utils.changeInstance(utilsMainInstance); }); test('clips to canvas size if chart virtual rect is provided', () { const viewSize = Size(400, 400); final chartVirtualRect = (Offset.zero & viewSize).inflate(100); final barGroups = [ BarChartGroupData( x: 0, barRods: [ BarChartRodData( toY: 10, width: 10, color: const Color(0x00000000), borderRadius: const BorderRadius.all(Radius.circular(0.1)), ), BarChartRodData( toY: 8, width: 11, color: const Color(0x11111111), borderRadius: const BorderRadius.all(Radius.circular(0.2)), ), BarChartRodData( toY: 8, width: 12, color: const Color(0x22222222), borderRadius: const BorderRadius.all(Radius.circular(0.3)), ), ], barsSpace: 5, showingTooltipIndicators: [ 1, 2, ], ), BarChartGroupData( x: 1, barRods: [ BarChartRodData( toY: 10, width: 10, borderRadius: const BorderRadius.all(Radius.circular(0.4)), ), BarChartRodData(toY: 8, width: 10), ], barsSpace: 5, ), BarChartGroupData( x: 2, barRods: [ BarChartRodData(toY: 10, width: 10), BarChartRodData(toY: 8, width: 10), BarChartRodData(toY: 8, width: 10), BarChartRodData(toY: 8, width: 10), ], barsSpace: 5, ), ]; final tooltipData = BarTouchTooltipData( tooltipBorderRadius: BorderRadius.circular(8), getTooltipColor: (group) => const Color(0xf33f33f3), maxContentWidth: 80, rotateAngle: 12, tooltipBorder: const BorderSide(color: Color(0xf33f33f3), width: 2), getTooltipItem: ( group, groupIndex, rod, rodIndex, ) { return BarTooltipItem( 'helllo1', textStyle1, textAlign: TextAlign.right, textDirection: TextDirection.rtl, children: [ const TextSpan(text: 'helllo2'), const TextSpan(text: 'helllo3'), ], ); }, ); final (minY, maxY) = BarChartHelper().calculateMaxAxisValues(barGroups); final data = BarChartData( groupsSpace: 10, barGroups: barGroups, barTouchData: BarTouchData( touchTooltipData: tooltipData, ), alignment: BarChartAlignment.center, minY: minY, maxY: maxY, ); final barChartPainter = BarChartPainter(); final holder = PaintHolder( data, data, TextScaler.noScaling, chartVirtualRect, ); final mockBuildContext = MockBuildContext(); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenReturn(viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); barChartPainter.paint( mockBuildContext, mockCanvasWrapper, holder, ); final canvasRect = Offset.zero & viewSize; verifyInOrder([ mockCanvasWrapper.saveLayer(canvasRect, any), mockCanvasWrapper.clipRect(canvasRect), mockCanvasWrapper.drawRRect(any, any), mockCanvasWrapper.restore(), mockCanvasWrapper.drawRotated( size: anyNamed('size'), rotationOffset: anyNamed('rotationOffset'), drawOffset: anyNamed('drawOffset'), angle: anyNamed('angle'), drawCallback: anyNamed('drawCallback'), ), ]); }); test('only draws points within canvas when chart virtual rect is provided', () { const viewSize = Size(200, 200); const zoomedSize = Size(300, 300); final chartVirtualRect = const Offset(0, -150) & zoomedSize; const maxToY = 10.0; // Y coordinate -150 - outside of canvas const minToY = 5.0; // Y coordinate 150 - inside of canvas final barGroups = [ BarChartGroupData( x: 0, barRods: [ BarChartRodData( toY: maxToY, width: 10, color: const Color(0x00000000), borderRadius: const BorderRadius.all(Radius.circular(0.1)), ), BarChartRodData( toY: minToY, width: 11, color: const Color(0x11111111), borderRadius: const BorderRadius.all(Radius.circular(0.2)), ), ], barsSpace: 5, showingTooltipIndicators: [ 0, 1, ], ), ]; final tooltipData = BarTouchTooltipData( tooltipBorderRadius: BorderRadius.circular(8), getTooltipColor: (group) => const Color(0xf33f33f3), maxContentWidth: 80, rotateAngle: 12, tooltipBorder: const BorderSide(color: Color(0xf33f33f3), width: 2), getTooltipItem: ( group, groupIndex, rod, rodIndex, ) { return BarTooltipItem( 'helllo1', textStyle1, textAlign: TextAlign.right, textDirection: TextDirection.rtl, children: [ const TextSpan(text: 'helllo2'), const TextSpan(text: 'helllo3'), ], ); }, ); final (minY, maxY) = BarChartHelper().calculateMaxAxisValues(barGroups); final data = BarChartData( groupsSpace: 10, barGroups: barGroups, barTouchData: BarTouchData( touchTooltipData: tooltipData, ), alignment: BarChartAlignment.center, minY: minY, maxY: maxY, ); final barChartPainter = BarChartPainter(); final holder = PaintHolder( data, data, TextScaler.noScaling, chartVirtualRect, ); final mockBuildContext = MockBuildContext(); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenReturn(viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); barChartPainter.paint( mockBuildContext, mockCanvasWrapper, holder, ); verify( mockCanvasWrapper.drawRotated( size: anyNamed('size'), rotationOffset: anyNamed('rotationOffset'), drawOffset: anyNamed('drawOffset'), angle: anyNamed('angle'), drawCallback: anyNamed('drawCallback'), ), ).called(1); }); test('does not clip if chart virtual rect is null', () { { const viewSize = Size(400, 400); final barGroups = [ BarChartGroupData( x: 0, barRods: [ BarChartRodData( toY: 10, width: 10, color: const Color(0x00000000), borderRadius: const BorderRadius.all(Radius.circular(0.1)), ), BarChartRodData( toY: 8, width: 11, color: const Color(0x11111111), borderRadius: const BorderRadius.all(Radius.circular(0.2)), ), BarChartRodData( toY: 8, width: 12, color: const Color(0x22222222), borderRadius: const BorderRadius.all(Radius.circular(0.3)), ), ], barsSpace: 5, showingTooltipIndicators: [ 1, 2, ], ), BarChartGroupData( x: 1, barRods: [ BarChartRodData( toY: 10, width: 10, borderRadius: const BorderRadius.all(Radius.circular(0.4)), ), BarChartRodData(toY: 8, width: 10), ], barsSpace: 5, ), BarChartGroupData( x: 2, barRods: [ BarChartRodData(toY: 10, width: 10), BarChartRodData(toY: 8, width: 10), BarChartRodData(toY: 8, width: 10), BarChartRodData(toY: 8, width: 10), ], barsSpace: 5, ), ]; final tooltipData = BarTouchTooltipData( tooltipBorderRadius: BorderRadius.circular(8), getTooltipColor: (group) => const Color(0xf33f33f3), maxContentWidth: 80, rotateAngle: 12, tooltipBorder: const BorderSide(color: Color(0xf33f33f3), width: 2), getTooltipItem: ( group, groupIndex, rod, rodIndex, ) { return BarTooltipItem( 'helllo1', textStyle1, textAlign: TextAlign.right, textDirection: TextDirection.rtl, children: [ const TextSpan(text: 'helllo2'), const TextSpan(text: 'helllo3'), ], ); }, ); final (minY, maxY) = BarChartHelper().calculateMaxAxisValues(barGroups); final data = BarChartData( groupsSpace: 10, barGroups: barGroups, barTouchData: BarTouchData( touchTooltipData: tooltipData, ), alignment: BarChartAlignment.center, minY: minY, maxY: maxY, ); final barChartPainter = BarChartPainter(); final holder = PaintHolder( data, data, TextScaler.noScaling, ); final mockBuildContext = MockBuildContext(); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenReturn(viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); barChartPainter.paint( mockBuildContext, mockCanvasWrapper, holder, ); final canvasRect = Offset.zero & viewSize; verifyNever(mockCanvasWrapper.saveLayer(canvasRect, any)); verifyNever(mockCanvasWrapper.clipRect(canvasRect)); verifyNever(mockCanvasWrapper.restore()); } }); test('draws all points if chart virtual rect is null', () { const viewSize = Size(200, 200); const maxToY = 10.0; // Y coordinate -150 - outside of canvas const minToY = 5.0; // Y coordinate 150 - inside of canvas final barGroups = [ BarChartGroupData( x: 0, barRods: [ BarChartRodData( toY: maxToY, width: 10, color: const Color(0x00000000), borderRadius: const BorderRadius.all(Radius.circular(0.1)), ), BarChartRodData( toY: minToY, width: 11, color: const Color(0x11111111), borderRadius: const BorderRadius.all(Radius.circular(0.2)), ), ], barsSpace: 5, showingTooltipIndicators: [ 0, 1, ], ), ]; final tooltipData = BarTouchTooltipData( tooltipBorderRadius: BorderRadius.circular(8), getTooltipColor: (group) => const Color(0xf33f33f3), maxContentWidth: 80, rotateAngle: 12, tooltipBorder: const BorderSide(color: Color(0xf33f33f3), width: 2), getTooltipItem: ( group, groupIndex, rod, rodIndex, ) { return BarTooltipItem( 'helllo1', textStyle1, textAlign: TextAlign.right, textDirection: TextDirection.rtl, children: [ const TextSpan(text: 'helllo2'), const TextSpan(text: 'helllo3'), ], ); }, ); final (minY, maxY) = BarChartHelper().calculateMaxAxisValues(barGroups); final data = BarChartData( groupsSpace: 10, barGroups: barGroups, barTouchData: BarTouchData( touchTooltipData: tooltipData, ), alignment: BarChartAlignment.center, minY: minY, maxY: maxY, ); final barChartPainter = BarChartPainter(); final holder = PaintHolder( data, data, TextScaler.noScaling, ); final mockBuildContext = MockBuildContext(); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenReturn(viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); barChartPainter.paint( mockBuildContext, mockCanvasWrapper, holder, ); verify( mockCanvasWrapper.drawRotated( size: anyNamed('size'), rotationOffset: anyNamed('rotationOffset'), drawOffset: anyNamed('drawOffset'), angle: anyNamed('angle'), drawCallback: anyNamed('drawCallback'), ), ).called(2); }); }); group('calculateGroupsX()', () { test('test 1', () { const viewSize = Size(200, 100); final barGroups = [ BarChartGroupData( x: 0, barRods: [ BarChartRodData(toY: 10, width: 10), BarChartRodData(toY: 8, width: 10), BarChartRodData(toY: 8, width: 10), ], barsSpace: 5, ), BarChartGroupData( x: 1, barRods: [ BarChartRodData(toY: 10, width: 10), BarChartRodData(toY: 8, width: 10), ], barsSpace: 5, ), BarChartGroupData( x: 2, barRods: [ BarChartRodData(toY: 10, width: 10), BarChartRodData(toY: 8, width: 10), BarChartRodData(toY: 8, width: 10), BarChartRodData(toY: 8, width: 10), ], barsSpace: 5, ), ]; final data = BarChartData( titlesData: const FlTitlesData(show: false), groupsSpace: 10, barGroups: barGroups, ); List callWithAlignment(BarChartAlignment alignment) { return data .copyWith(alignment: alignment) .calculateGroupsX(viewSize.width); } expect(callWithAlignment(BarChartAlignment.center), [50, 92.5, 142.5]); expect(callWithAlignment(BarChartAlignment.start), [20, 62.5, 112.5]); expect(callWithAlignment(BarChartAlignment.end), [80.0, 122.5, 172.5]); expect( callWithAlignment(BarChartAlignment.spaceEvenly), [40, 92.5, 152.5], ); expect( callWithAlignment(BarChartAlignment.spaceAround), [ closeTo(33.33, tolerance), 92.5, closeTo(159.16, tolerance), ], ); expect( callWithAlignment(BarChartAlignment.spaceBetween), [20, 92.5, 172.5], ); }); }); group('calculateGroupAndBarsPosition()', () { test('test 1', () { const viewSize = Size(200, 100); final barGroups = [ BarChartGroupData( x: 0, barRods: [ BarChartRodData(toY: 10, width: 10), BarChartRodData(toY: 8, width: 10), BarChartRodData(toY: 8, width: 10), ], barsSpace: 5, ), BarChartGroupData( x: 1, barRods: [ BarChartRodData(toY: 10, width: 10), BarChartRodData(toY: 8, width: 10), ], barsSpace: 5, ), ]; final data = BarChartData( titlesData: const FlTitlesData(show: false), groupsSpace: 10, alignment: BarChartAlignment.center, barGroups: barGroups, ); final barChartPainter = BarChartPainter(); final groupsX = data.calculateGroupsX(viewSize.width); late Exception exception; try { barChartPainter.calculateGroupAndBarsPosition( viewSize, groupsX + [groupsX.last], barGroups, ); } catch (e) { exception = e as Exception; } expect(true, exception.toString().contains('inconsistent')); }); test('test 2', () { const viewSize = Size(200, 100); final barGroups = [ BarChartGroupData( x: 0, barRods: [ BarChartRodData(toY: 10, width: 10), BarChartRodData(toY: 8, width: 10), BarChartRodData(toY: 8, width: 10), ], barsSpace: 5, ), BarChartGroupData( x: 1, barRods: [ BarChartRodData(toY: 10, width: 10), BarChartRodData(toY: 8, width: 10), ], barsSpace: 5, ), BarChartGroupData( x: 2, barRods: [ BarChartRodData(toY: 10, width: 10), BarChartRodData(toY: 8, width: 10), BarChartRodData(toY: 8, width: 10), BarChartRodData(toY: 8, width: 10), ], barsSpace: 5, ), ]; final data = BarChartData( titlesData: const FlTitlesData(show: false), groupsSpace: 10, barGroups: barGroups, ); final barChartPainter = BarChartPainter(); List callWithAlignment(BarChartAlignment alignment) { final groupsX = data .copyWith(alignment: alignment) .calculateGroupsX(viewSize.width); return barChartPainter.calculateGroupAndBarsPosition( viewSize, groupsX, barGroups, ); } final centerResult = callWithAlignment(BarChartAlignment.center); expect(centerResult.map((e) => e.groupX).toList(), [50, 92.5, 142.5]); expect(centerResult[0].barsX, [35, 50, 65]); expect(centerResult[1].barsX, [85, 100]); expect(centerResult[2].barsX, [120, 135, 150, 165]); final startResult = callWithAlignment(BarChartAlignment.start); expect(startResult.map((e) => e.groupX).toList(), [20.0, 62.5, 112.5]); expect(startResult[0].barsX, [5, 20, 35]); expect(startResult[1].barsX, [55, 70]); expect(startResult[2].barsX, [90, 105, 120, 135]); }); }); group('drawBars()', () { test('test 1', () { const viewSize = Size(200, 100); final barGroups = [ BarChartGroupData( x: 0, barRods: [ BarChartRodData( toY: 10, width: 10, color: const Color(0x00000000), borderRadius: const BorderRadius.all(Radius.circular(0.1)), ), BarChartRodData( toY: 8, width: 11, color: const Color(0x11111111), borderRadius: const BorderRadius.all(Radius.circular(0.2)), ), BarChartRodData( toY: 8, width: 12, color: const Color(0x22222222), borderRadius: const BorderRadius.all(Radius.circular(0.3)), ), ], barsSpace: 5, ), BarChartGroupData( x: 1, barRods: [ BarChartRodData( toY: 10, width: 10, borderRadius: const BorderRadius.all(Radius.circular(0.4)), ), BarChartRodData(toY: 8, width: 10), ], barsSpace: 5, ), BarChartGroupData( x: 2, barRods: [ BarChartRodData( toY: 10, width: 10, backDrawRodData: BackgroundBarChartRodData( toY: 8, show: true, ), ), BarChartRodData( toY: 8, width: 10, backDrawRodData: BackgroundBarChartRodData( show: false, ), ), BarChartRodData( toY: 8, width: 10, backDrawRodData: BackgroundBarChartRodData( toY: -3, show: true, ), ), BarChartRodData( toY: 8, width: 10, backDrawRodData: BackgroundBarChartRodData( toY: 0, ), ), ], barsSpace: 5, ), ]; final (minY, maxY) = BarChartHelper().calculateMaxAxisValues(barGroups); final data = BarChartData( titlesData: const FlTitlesData(show: false), groupsSpace: 10, barGroups: barGroups, alignment: BarChartAlignment.center, minY: minY, maxY: maxY, ); final barChartPainter = BarChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final groupsX = data.calculateGroupsX(viewSize.width); final barGroupsPosition = barChartPainter.calculateGroupAndBarsPosition( viewSize, groupsX, barGroups, ); final results = >[]; when(mockCanvasWrapper.drawRRect(captureAny, captureAny)) .thenAnswer((inv) { final rRect = inv.positionalArguments[0] as RRect; final paint = inv.positionalArguments[1] as Paint; results.add({ 'rRect': RRect.fromLTRBAndCorners( rRect.left, rRect.top, rRect.right, rRect.bottom, topLeft: rRect.tlRadius, topRight: rRect.trRadius, bottomRight: rRect.brRadius, bottomLeft: rRect.blRadius, ), 'paint_color': paint.color, }); }); barChartPainter.drawBars(mockCanvasWrapper, barGroupsPosition, holder); expect(results.length, 11); expect( HelperMethods.equalsRRects( results[0]['rRect'] as RRect, RRect.fromLTRBR( 28.5, 0, 38.5, 76.923, const Radius.circular(0.1), ), ), true, ); expect( results[0]['paint_color'] as Color, isSameColorAs(const Color(0x00000000)), ); expect( HelperMethods.equalsRRects( results[1]['rRect'] as RRect, RRect.fromLTRBR( 43.5, 15.384, 54.5, 76.923, const Radius.circular(0.2), ), ), true, ); expect( results[1]['paint_color'] as Color, isSameColorAs(const Color(0x11111111)), ); expect( HelperMethods.equalsRRects( results[2]['rRect'] as RRect, RRect.fromLTRBR( 59.5, 15.384, 71.5, 76.923, const Radius.circular(0.3), ), ), true, ); expect( results[2]['paint_color'] as Color, isSameColorAs(const Color(0x22222222)), ); expect( HelperMethods.equalsRRects( results[3]['rRect'] as RRect, RRect.fromLTRBR( 81.5, 0, 91.5, 76.923, const Radius.circular(0.4), ), ), true, ); expect( HelperMethods.equalsRRects( results[4]['rRect'] as RRect, RRect.fromLTRBR( 96.5, 15.384, 106.5, 76.923, const Radius.circular(5), ), ), true, ); expect( HelperMethods.equalsRRects( results[5]['rRect'] as RRect, RRect.fromLTRBR( 116.5, 15.384, 126.5, 76.923, const Radius.circular(5), ), ), true, ); expect( HelperMethods.equalsRRects( results[6]['rRect'] as RRect, RRect.fromLTRBR( 116.5, 0, 126.5, 76.923, const Radius.circular(5), ), ), true, ); expect( HelperMethods.equalsRRects( results[7]['rRect'] as RRect, RRect.fromLTRBR( 131.5, 15.384, 141.5, 76.923, const Radius.circular(5), ), ), true, ); expect( HelperMethods.equalsRRects( results[8]['rRect'] as RRect, RRect.fromLTRBR( 146.5, 76.923, 156.5, 100, const Radius.circular(5), ), ), true, ); expect( HelperMethods.equalsRRects( results[9]['rRect'] as RRect, RRect.fromLTRBR( 146.5, 15.384, 156.5, 76.923, const Radius.circular(5), ), ), true, ); expect( HelperMethods.equalsRRects( results[10]['rRect'] as RRect, RRect.fromLTRBR( 161.5, 15.384, 171.5, 76.923, const Radius.circular(5), ), ), true, ); }); test('test 2', () { const viewSize = Size(200, 100); final barGroups = [ BarChartGroupData( x: 0, groupVertically: true, barRods: [ BarChartRodData( fromY: -9, toY: -10, width: 10, color: const Color(0x00000000), borderRadius: const BorderRadius.all(Radius.circular(0.1)), ), BarChartRodData( fromY: -11, toY: -20, width: 11, color: const Color(0x11111111), borderRadius: const BorderRadius.all(Radius.circular(0.2)), ), BarChartRodData( fromY: -21, toY: -30, width: 12, color: const Color(0x22222222), borderRadius: const BorderRadius.all(Radius.circular(0.3)), ), ], barsSpace: 5, ), BarChartGroupData( x: 1, groupVertically: true, barRods: [ BarChartRodData( fromY: 9, toY: 10, width: 10, color: const Color(0x00000000), borderRadius: const BorderRadius.all(Radius.circular(0.1)), ), BarChartRodData( fromY: 11, toY: 20, width: 11, color: const Color(0x11111111), borderRadius: const BorderRadius.all(Radius.circular(0.2)), ), BarChartRodData( fromY: 21, toY: 30, width: 12, color: const Color(0x22222222), borderRadius: const BorderRadius.all(Radius.circular(0.3)), ), ], barsSpace: 5, ), ]; final (minY, maxY) = BarChartHelper().calculateMaxAxisValues(barGroups); final data = BarChartData( titlesData: const FlTitlesData(show: false), groupsSpace: 10, barGroups: barGroups, alignment: BarChartAlignment.center, minY: minY, maxY: maxY, ); final barChartPainter = BarChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final groupsX = data.calculateGroupsX(viewSize.width); final barGroupsPosition = barChartPainter.calculateGroupAndBarsPosition( viewSize, groupsX, barGroups, ); final results = >[]; when(mockCanvasWrapper.drawRRect(captureAny, captureAny)) .thenAnswer((inv) { final rrect = inv.positionalArguments[0] as RRect; final paint = inv.positionalArguments[1] as Paint; results.add({ 'rrect': RRect.fromLTRBAndCorners( rrect.left, rrect.top, rrect.right, rrect.bottom, topLeft: rrect.tlRadius, topRight: rrect.trRadius, bottomRight: rrect.brRadius, bottomLeft: rrect.blRadius, ), 'paint_color': paint.color, }); }); barChartPainter.drawBars(mockCanvasWrapper, barGroupsPosition, holder); expect(results.length, 6); expect( HelperMethods.equalsRRects( results[0]['rrect'] as RRect, RRect.fromLTRBR( 84, 65, 94, 66.666, const Radius.circular(0.1), ), ), true, ); expect( results[0]['paint_color'] as Color, isSameColorAs(const Color(0x00000000)), ); expect( HelperMethods.equalsRRects( results[1]['rrect'] as RRect, RRect.fromLTRBR( 83.5, 68.333, 94.5, 83.333, const Radius.circular(0.2), ), ), true, ); expect( results[1]['paint_color'] as Color, isSameColorAs(const Color(0x11111111)), ); expect( HelperMethods.equalsRRects( results[2]['rrect'] as RRect, RRect.fromLTRBR( 83, 85, 95, 100, const Radius.circular(0.3), ), ), true, ); expect( results[2]['paint_color'] as Color, isSameColorAs(const Color(0x22222222)), ); expect( HelperMethods.equalsRRects( results[3]['rrect'] as RRect, RRect.fromLTRBR( 106, 33.333, 116, 35, const Radius.circular(0.1), ), ), true, ); expect( HelperMethods.equalsRRects( results[4]['rrect'] as RRect, RRect.fromLTRBR( 105.5, 16.666, 116.5, 31.666, const Radius.circular(0.2), ), ), true, ); expect( HelperMethods.equalsRRects( results[5]['rrect'] as RRect, RRect.fromLTRBR( 105, 0, 117, 15, const Radius.circular(0.3), ), ), true, ); }); test('test 3', () { const viewSize = Size(200, 100); final barGroups = [ BarChartGroupData( x: 0, barRods: [ BarChartRodData( fromY: -10, toY: 10, color: const Color(0x00000000), rodStackItems: [ BarChartRodStackItem( -5, -10, const Color(0x11111111), label: '5', ), BarChartRodStackItem( 0, -5, const Color(0x22222222), label: '5', ), BarChartRodStackItem( 0, 5, const Color(0x33333333), label: '5', ), BarChartRodStackItem( 5, 10, null, gradient: const LinearGradient( colors: [Color(0xFFFF0000), Color(0xFF00FF00)], ), label: '5', ), ], ), ], ), ]; final (minY, maxY) = BarChartHelper().calculateMaxAxisValues(barGroups); final data = BarChartData( barGroups: barGroups, minY: minY, maxY: maxY, ); final barChartPainter = BarChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final groupsX = data.calculateGroupsX(viewSize.width); final barGroupsPosition = barChartPainter.calculateGroupAndBarsPosition( viewSize, groupsX, barGroups, ); final results = >[]; when(mockCanvasWrapper.drawRRect(captureAny, captureAny)) .thenAnswer((inv) { final paint = inv.positionalArguments[1] as Paint; results.add({ 'paint_color': paint.color, 'gradient': paint.shader is ui.Gradient, }); }); barChartPainter.drawBars(mockCanvasWrapper, barGroupsPosition, holder); expect(results.length, 5); expect( results[1]['paint_color'], isSameColorAs(const Color(0x11111111)), ); expect( results[2]['paint_color'], isSameColorAs(const Color(0x22222222)), ); expect( results[3]['paint_color'], isSameColorAs(const Color(0x33333333)), ); expect( results[3]['gradient'], false, ); expect( results[4]['paint_color'], isSameColorAs(const Color(0xFF000000)), ); expect( results[4]['gradient'], true, ); }); test('test 4', () { const viewSize = Size(200, 100); final barGroups = [ BarChartGroupData( x: 0, barRods: [ BarChartRodData( fromY: 0, toY: 5, borderRadius: BorderRadius.zero, color: Colors.white, ), ], ), BarChartGroupData( x: 1, barRods: [ BarChartRodData( fromY: 0, toY: 10, borderRadius: BorderRadius.zero, color: Colors.white, ), ], ), BarChartGroupData( x: 3, barRods: [ BarChartRodData( fromY: 0, toY: 15, borderDashArray: [4, 4], borderSide: const BorderSide( color: Colors.white, width: 2, ), borderRadius: BorderRadius.zero, color: Colors.transparent, ), ], ), ]; final (minY, maxY) = BarChartHelper().calculateMaxAxisValues(barGroups); final data = BarChartData( barGroups: barGroups, minY: minY, maxY: maxY, ); final barChartPainter = BarChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final groupsX = data.calculateGroupsX(viewSize.width); final barGroupsPosition = barChartPainter.calculateGroupAndBarsPosition( viewSize, groupsX, barGroups, ); final rodDataResults = >[]; final borderResult = >[]; when(mockCanvasWrapper.drawRRect(captureAny, captureAny)) .thenAnswer((inv) { final rrect = inv.positionalArguments[0] as RRect; final paint = inv.positionalArguments[1] as Paint; rodDataResults.add({ 'rrect': rrect, 'paint_color': paint.color, }); }); when(mockCanvasWrapper.drawPath(captureAny, captureAny)) .thenAnswer((inv) { final path = inv.positionalArguments[0] as Path; final paint = inv.positionalArguments[1] as Paint; borderResult.add({ 'path': path, 'paint_color': paint.color, }); }); barChartPainter.drawBars(mockCanvasWrapper, barGroupsPosition, holder); expect(rodDataResults.length, 3); expect(rodDataResults[0]['paint_color'], Colors.white); expect(rodDataResults[1]['paint_color'], Colors.white); expect(rodDataResults[2]['paint_color'], Colors.transparent); expect(borderResult.length, 1); expect(borderResult[0]['paint_color'], Colors.white); final rrect = rodDataResults[2]['rrect'] as RRect; final path = Path()..addRRect(rrect); final expectedPath = path.toDashedPath([4, 4]); final currentPath = borderResult[0]['path'] as Path; expect(HelperMethods.equalsPaths(expectedPath, currentPath), true); }); test('test 5', () { const viewSize = Size(200, 100); final barGroups = [ BarChartGroupData( x: 0, barRods: [ BarChartRodData( fromY: 0, toY: 5, borderRadius: BorderRadius.zero, color: Colors.white, ), ], ), BarChartGroupData( x: 1, barRods: [ BarChartRodData( fromY: 0, toY: 10, borderRadius: BorderRadius.zero, color: Colors.white, ), ], ), BarChartGroupData( x: 3, barRods: [ BarChartRodData( fromY: 0, toY: 15, borderSide: const BorderSide( color: Colors.white, width: 2, ), borderRadius: BorderRadius.zero, color: Colors.transparent, ), ], ), ]; final (minY, maxY) = BarChartHelper().calculateMaxAxisValues(barGroups); final data = BarChartData( barGroups: barGroups, minY: minY, maxY: maxY, ); final barChartPainter = BarChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final groupsX = data.calculateGroupsX(viewSize.width); final barGroupsPosition = barChartPainter.calculateGroupAndBarsPosition( viewSize, groupsX, barGroups, ); final rodDataResults = >[]; final borderResult = >[]; when(mockCanvasWrapper.drawRRect(captureAny, captureAny)) .thenAnswer((inv) { final rrect = inv.positionalArguments[0] as RRect; final paint = inv.positionalArguments[1] as Paint; rodDataResults.add({ 'rrect': rrect, 'paint_color': paint.color, }); }); when(mockCanvasWrapper.drawPath(captureAny, captureAny)) .thenAnswer((inv) { final path = inv.positionalArguments[0] as Path; final paint = inv.positionalArguments[1] as Paint; borderResult.add({ 'path': path, 'paint_color': paint.color, }); }); barChartPainter.drawBars(mockCanvasWrapper, barGroupsPosition, holder); expect(rodDataResults.length, 3); expect(rodDataResults[0]['paint_color'], Colors.white); expect(rodDataResults[1]['paint_color'], Colors.white); expect(rodDataResults[2]['paint_color'], Colors.transparent); expect(borderResult.length, 1); expect(borderResult[0]['paint_color'], Colors.white); final rrect = rodDataResults[2]['rrect'] as RRect; final path = Path()..addRRect(rrect); final expectedPath = path.toDashedPath(null); final currentPath = borderResult[0]['path'] as Path; expect(HelperMethods.equalsPaths(expectedPath, currentPath), true); }); test('test small bar values with large border radius (issue #1757)', () { const viewSize = Size(200, 200); // Test case: bar height (2) is smaller than corner height (16) // This should trigger the scale factor logic final barGroups = [ BarChartGroupData( x: 0, barRods: [ BarChartRodData( toY: 2, width: 40, rodStackItems: [ BarChartRodStackItem( 0, 1, Colors.amber, ), BarChartRodStackItem( 1, 2, Colors.red, ), ], borderRadius: const BorderRadius.only( topLeft: Radius.circular(8), topRight: Radius.circular(8), ), ), ], ), ]; final data = BarChartData( barGroups: barGroups, minY: 0, maxY: 100, ); final barChartPainter = BarChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final groupsX = data.calculateGroupsX(viewSize.width); final barGroupsPosition = barChartPainter.calculateGroupAndBarsPosition( viewSize, groupsX, barGroups, ); final clipRectResults = []; when(mockCanvasWrapper.clipRect(captureAny)).thenAnswer((inv) { final rect = inv.positionalArguments[0] as Rect; clipRectResults.add(rect); }); when(mockCanvasWrapper.save()).thenReturn(null); when(mockCanvasWrapper.restore()).thenReturn(null); when(mockCanvasWrapper.drawRRect(any, any)).thenReturn(null); barChartPainter.drawBars(mockCanvasWrapper, barGroupsPosition, holder); // Verify that clipRect was called for each stack item expect( clipRectResults.length, 2, reason: 'Should have clipped for 2 stack items', ); // Verify that the clip rectangles have been scaled properly // The total bar height in pixels would be very small (2% of 200 = 4px) // But corner height is 8, so scale factor should be 8/4 = 2.0 // Each stack item should be scaled proportionally final rect1 = clipRectResults[0]; final rect2 = clipRectResults[1]; // Debug: print actual values // print('rect1: $rect1, height: ${rect1.height}'); // print('rect2: $rect2, height: ${rect2.height}'); // Stack items should not overlap and should be adjacent expect( rect2.bottom, closeTo(rect1.top, tolerance), reason: 'Stack items should be adjacent without gaps (rect2.bottom should equal rect1.top)', ); // The combined height should equal the scaled total height (cornerHeight) final totalScaledHeight = rect1.height + rect2.height; expect( totalScaledHeight, closeTo(8.0, tolerance), reason: 'Total scaled height should equal corner height (8px)', ); // Heights should be proportional to data values (1:1 ratio) expect( rect1.height, closeTo(rect2.height, tolerance), reason: 'Stack items with equal data heights (1) should have equal pixel heights', ); }); }); group('drawBars() - label tests', () { late MockUtils mockUtils; final utilsMainInstance = Utils(); setUp(() { mockUtils = MockUtils(); Utils.changeInstance(mockUtils); when(mockUtils.getThemeAwareTextStyle(any, any)) .thenAnswer((realInvocation) => textStyle1); when(mockUtils.calculateRotationOffset(any, any)) .thenAnswer((realInvocation) => Offset.zero); when(mockUtils.convertRadiusToSigma(any)) .thenAnswer((realInvocation) => 4.0); when(mockUtils.getEfficientInterval(any, any)) .thenAnswer((realInvocation) => 1.0); when(mockUtils.getBestInitialIntervalValue(any, any, any)) .thenAnswer((realInvocation) => 1.0); when(mockUtils.normalizeBorderRadius(any, any)) .thenAnswer((realInvocation) => BorderRadius.zero); when(mockUtils.normalizeBorderSide(any, any)).thenAnswer( (realInvocation) => const BorderSide(color: MockData.color0), ); }); tearDown(() { Utils.changeInstance(utilsMainInstance); }); test('should render simple stack item labels', () { const viewSize = Size(200, 100); final barGroups = [ BarChartGroupData( x: 0, barRods: [ BarChartRodData( toY: 10, width: 20, color: Colors.transparent, rodStackItems: [ BarChartRodStackItem( 0, 5, Colors.red, label: 'Label 1', labelStyle: const TextStyle(fontSize: 15), ), BarChartRodStackItem( 5, 10, Colors.blue, label: 'Label 2', ), ], ), ], ), ]; final data = BarChartData( titlesData: const FlTitlesData(show: false), barGroups: barGroups, minY: 0, maxY: 10, ); final barChartPainter = BarChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvas = MockCanvas(); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(mockCanvas); when(mockCanvasWrapper.save()).thenReturn(null); when(mockCanvasWrapper.restore()).thenReturn(null); when(mockCanvasWrapper.translate(any, any)).thenReturn(null); when(mockCanvasWrapper.rotate(any)).thenReturn(null); when(mockCanvasWrapper.drawRRect(any, any)).thenReturn(null); final paragraphs = >[]; when(mockCanvas.drawParagraph(captureAny, captureAny)).thenAnswer((inv) { final paragraph = inv.positionalArguments[0]; final offset = inv.positionalArguments[1] as Offset; paragraphs.add({ 'paragraph': paragraph, 'offset': offset, }); return; }); final groupsX = data.calculateGroupsX(viewSize.width); final barGroupsPosition = barChartPainter.calculateGroupAndBarsPosition( viewSize, groupsX, barGroups, ); barChartPainter.drawBars(mockCanvasWrapper, barGroupsPosition, holder); expect(paragraphs.length, 2, reason: 'Should have drawn 2 labels'); }); test('should render long stack item labels', () { const viewSize = Size(200, 100); final barGroups = [ BarChartGroupData( x: 0, barRods: [ BarChartRodData( toY: 10, width: 20, color: Colors.transparent, rodStackItems: [ BarChartRodStackItem( 0, 5, Colors.red, label: 'This is a very long label that might not fit', labelStyle: const TextStyle(fontSize: 12), ), ], ), ], ), ]; final data = BarChartData( titlesData: const FlTitlesData(show: false), barGroups: barGroups, minY: 0, maxY: 10, ); final barChartPainter = BarChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvas = MockCanvas(); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(mockCanvas); when(mockCanvasWrapper.save()).thenReturn(null); when(mockCanvasWrapper.restore()).thenReturn(null); when(mockCanvasWrapper.translate(any, any)).thenReturn(null); when(mockCanvasWrapper.rotate(any)).thenReturn(null); when(mockCanvasWrapper.drawRRect(any, any)).thenReturn(null); final paragraphs = >[]; when(mockCanvas.drawParagraph(captureAny, captureAny)).thenAnswer((inv) { final paragraph = inv.positionalArguments[0]; final offset = inv.positionalArguments[1] as Offset; paragraphs.add({ 'paragraph': paragraph, 'offset': offset, }); return; }); final groupsX = data.calculateGroupsX(viewSize.width); final barGroupsPosition = barChartPainter.calculateGroupAndBarsPosition( viewSize, groupsX, barGroups, ); barChartPainter.drawBars(mockCanvasWrapper, barGroupsPosition, holder); expect(paragraphs.length, 1, reason: 'Should have drawn 1 long label'); }); test('should render labels with special characters and emojis', () { const viewSize = Size(200, 100); final barGroups = [ BarChartGroupData( x: 0, barRods: [ BarChartRodData( toY: 10, width: 20, color: Colors.transparent, rodStackItems: [ BarChartRodStackItem( 0, 10, Colors.purple, label: 'Label with emojis 🎯 & symbols!', labelStyle: const TextStyle(fontWeight: FontWeight.bold), ), ], ), ], ), ]; final data = BarChartData( titlesData: const FlTitlesData(show: false), barGroups: barGroups, minY: 0, maxY: 10, ); final barChartPainter = BarChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvas = MockCanvas(); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(mockCanvas); when(mockCanvasWrapper.save()).thenReturn(null); when(mockCanvasWrapper.restore()).thenReturn(null); when(mockCanvasWrapper.translate(any, any)).thenReturn(null); when(mockCanvasWrapper.rotate(any)).thenReturn(null); when(mockCanvasWrapper.drawRRect(any, any)).thenReturn(null); final paragraphs = >[]; when(mockCanvas.drawParagraph(captureAny, captureAny)).thenAnswer((inv) { final paragraph = inv.positionalArguments[0]; final offset = inv.positionalArguments[1] as Offset; paragraphs.add({ 'paragraph': paragraph, 'offset': offset, }); return; }); final groupsX = data.calculateGroupsX(viewSize.width); final barGroupsPosition = barChartPainter.calculateGroupAndBarsPosition( viewSize, groupsX, barGroups, ); barChartPainter.drawBars(mockCanvasWrapper, barGroupsPosition, holder); expect( paragraphs.length, 1, reason: 'Should have drawn 1 label with special characters', ); }); }); group('drawTouchTooltip()', () { test('test 1', () { final mockUtils = MockUtils(); when(mockUtils.getThemeAwareTextStyle(any, any)).thenReturn(textStyle1); when(mockUtils.getEfficientInterval(any, any)).thenReturn(11); when(mockUtils.normalizeBorderRadius(any, any)) .thenReturn(BorderRadius.zero); when(mockUtils.normalizeBorderSide(any, any)).thenReturn(BorderSide.none); when(mockUtils.calculateRotationOffset(any, any)).thenReturn(Offset.zero); when(mockUtils.getBestInitialIntervalValue(any, any, any)).thenReturn(0); when(mockUtils.formatNumber(any, any, captureAny)).thenAnswer((inv) { final value = inv.positionalArguments[0] as double; return '${value.toInt()}'; }); Utils.changeInstance(mockUtils); const viewSize = Size(200, 100); final barGroups = [ BarChartGroupData( x: 0, barRods: [ BarChartRodData( toY: 10, width: 10, color: const Color(0x00000000), borderRadius: const BorderRadius.all(Radius.circular(0.1)), ), BarChartRodData( toY: 8, width: 11, color: const Color(0x11111111), borderRadius: const BorderRadius.all(Radius.circular(0.2)), ), BarChartRodData( toY: 8, width: 12, color: const Color(0x22222222), borderRadius: const BorderRadius.all(Radius.circular(0.3)), ), ], barsSpace: 5, ), BarChartGroupData( x: 1, barRods: [ BarChartRodData( toY: 10, width: 10, borderRadius: const BorderRadius.all(Radius.circular(0.4)), ), BarChartRodData(toY: 8, width: 10), ], barsSpace: 5, ), BarChartGroupData( x: 2, barRods: [ BarChartRodData(toY: 10, width: 10), BarChartRodData(toY: 8, width: 10), BarChartRodData(toY: 8, width: 10), BarChartRodData(toY: 8, width: 10), ], barsSpace: 5, ), ]; final tooltipData = BarTouchTooltipData( tooltipBorderRadius: BorderRadius.circular(8), getTooltipColor: (group) => const Color(0xf33f33f3), maxContentWidth: 80, rotateAngle: 12, tooltipBorder: const BorderSide(color: Color(0xf33f33f3), width: 2), getTooltipItem: ( group, groupIndex, rod, rodIndex, ) { return BarTooltipItem( 'helllo1', textStyle1, textAlign: TextAlign.right, textDirection: TextDirection.rtl, children: [ const TextSpan(text: 'helllo2'), const TextSpan(text: 'helllo3'), ], ); }, ); final (minY, maxY) = BarChartHelper().calculateMaxAxisValues(barGroups); final data = BarChartData( groupsSpace: 10, barGroups: barGroups, barTouchData: BarTouchData( touchTooltipData: tooltipData, ), alignment: BarChartAlignment.center, minY: minY, maxY: maxY, ); final barChartPainter = BarChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final mockBuildContext = MockBuildContext(); final groupsX = data.calculateGroupsX(viewSize.width); final barGroupsPosition = barChartPainter.calculateGroupAndBarsPosition( viewSize, groupsX, barGroups, ); final angles = []; when( mockCanvasWrapper.drawRotated( size: anyNamed('size'), rotationOffset: anyNamed('rotationOffset'), drawOffset: anyNamed('drawOffset'), angle: anyNamed('angle'), drawCallback: anyNamed('drawCallback'), ), ).thenAnswer((inv) { final callback = inv.namedArguments[const Symbol('drawCallback')] as DrawCallback; callback(); angles.add(inv.namedArguments[const Symbol('angle')] as double); }); barChartPainter.drawTouchTooltip( mockBuildContext, mockCanvasWrapper, barGroupsPosition, tooltipData, barGroups[0], 0, barGroups[0].barRods[0], 0, holder, ); final result1 = verify(mockCanvasWrapper.drawRRect(captureAny, captureAny)) ..called(2); final rrect = result1.captured[0] as RRect; expect(rrect.blRadius, const Radius.circular(8)); expect(rrect.width, 112); expect(rrect.height, 90); expect(rrect.left, -22.5); expect(rrect.top, -106); final bgTooltipPaint = result1.captured[1] as Paint; expect(bgTooltipPaint.color, isSameColorAs(const Color(0xf33f33f3))); expect(bgTooltipPaint.style, PaintingStyle.fill); final rRectBorder = result1.captured[2] as RRect; final paintBorder = result1.captured[3] as Paint; expect(rRectBorder.blRadius, const Radius.circular(8)); expect(rRectBorder.width, 112); expect(rRectBorder.height, 90); expect(rRectBorder.left, -22.5); expect(rRectBorder.top, -106); expect(paintBorder.color, isSameColorAs(const Color(0xf33f33f3))); expect(paintBorder.strokeWidth, 2); expect(paintBorder.style, PaintingStyle.stroke); expect(angles.length, 1); expect(angles[0], 12); final result2 = verify(mockCanvasWrapper.drawText(captureAny, captureAny)) ..called(1); final textPainter = result2.captured[0] as TextPainter; expect((textPainter.text as TextSpan?)!.text, 'helllo1'); expect((textPainter.text as TextSpan?)!.style, textStyle1); expect(textPainter.textAlign, TextAlign.right); expect(textPainter.textDirection, TextDirection.rtl); expect( (textPainter.text as TextSpan?)!.children![0], const TextSpan(text: 'helllo2'), ); expect( (textPainter.text as TextSpan?)!.children![1], const TextSpan(text: 'helllo3'), ); final drawOffset = result2.captured[1] as Offset; expect(drawOffset, const Offset(-6.5, -98)); }); test('test 2', () { final mockUtils = MockUtils(); when(mockUtils.getThemeAwareTextStyle(any, any)).thenReturn(textStyle1); when(mockUtils.getEfficientInterval(any, any)).thenReturn(11); when(mockUtils.normalizeBorderRadius(any, any)) .thenReturn(BorderRadius.zero); when(mockUtils.normalizeBorderSide(any, any)).thenReturn(BorderSide.none); when(mockUtils.calculateRotationOffset(any, any)).thenReturn(Offset.zero); when(mockUtils.getBestInitialIntervalValue(any, any, any)).thenReturn(0); when(mockUtils.formatNumber(any, any, captureAny)).thenAnswer((inv) { final value = inv.positionalArguments[0] as double; return '${value.toInt()}'; }); Utils.changeInstance(mockUtils); const viewSize = Size(200, 100); final barGroups = [ BarChartGroupData( x: 0, barRods: [ BarChartRodData( toY: 10, width: 10, color: const Color(0x00000000), borderRadius: const BorderRadius.all(Radius.circular(0.1)), ), BarChartRodData( toY: 8, width: 11, color: const Color(0x11111111), borderRadius: const BorderRadius.all(Radius.circular(0.2)), ), BarChartRodData( toY: 8, width: 12, color: const Color(0x22222222), borderRadius: const BorderRadius.all(Radius.circular(0.3)), ), ], barsSpace: 5, ), BarChartGroupData( x: 1, barRods: [ BarChartRodData( toY: 10, width: 10, borderRadius: const BorderRadius.all(Radius.circular(0.4)), ), BarChartRodData(toY: 8, width: 10), ], barsSpace: 5, ), BarChartGroupData( x: 2, barRods: [ BarChartRodData(toY: 10, width: 10), BarChartRodData(toY: 8, width: 10), BarChartRodData(toY: 8, width: 10), BarChartRodData(toY: 8, width: 10), ], barsSpace: 5, ), ]; final tooltipData = BarTouchTooltipData( tooltipBorderRadius: const BorderRadius.only( topLeft: Radius.circular(10), topRight: Radius.circular(8), ), getTooltipColor: (group) => const Color(0xf33f33f3), maxContentWidth: 80, rotateAngle: 12, direction: TooltipDirection.bottom, tooltipBorder: const BorderSide(color: Color(0xf33f33f3), width: 2), tooltipHorizontalAlignment: FLHorizontalAlignment.left, tooltipHorizontalOffset: -1.5, getTooltipItem: ( group, groupIndex, rod, rodIndex, ) { return BarTooltipItem( 'helllo1', textStyle1, textAlign: TextAlign.right, textDirection: TextDirection.rtl, children: [ const TextSpan(text: 'helllo2'), const TextSpan(text: 'helllo3'), ], ); }, ); final (minY, maxY) = BarChartHelper().calculateMaxAxisValues(barGroups); final data = BarChartData( groupsSpace: 10, barGroups: barGroups, barTouchData: BarTouchData( touchTooltipData: tooltipData, ), alignment: BarChartAlignment.center, minY: minY, maxY: maxY, ); final barChartPainter = BarChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final mockBuildContext = MockBuildContext(); final groupsX = data.calculateGroupsX(viewSize.width); final barGroupsPosition = barChartPainter.calculateGroupAndBarsPosition( viewSize, groupsX, barGroups, ); final angles = []; when( mockCanvasWrapper.drawRotated( size: anyNamed('size'), rotationOffset: anyNamed('rotationOffset'), drawOffset: anyNamed('drawOffset'), angle: anyNamed('angle'), drawCallback: anyNamed('drawCallback'), ), ).thenAnswer((inv) { final callback = inv.namedArguments[const Symbol('drawCallback')] as DrawCallback; callback(); angles.add(inv.namedArguments[const Symbol('angle')] as double); }); barChartPainter.drawTouchTooltip( mockBuildContext, mockCanvasWrapper, barGroupsPosition, tooltipData, barGroups[0], 0, barGroups[0].barRods[0], 0, holder, ); final result1 = verify(mockCanvasWrapper.drawRRect(captureAny, captureAny)) ..called(2); final rrect = result1.captured[0] as RRect; expect(rrect.tlRadius, const Radius.circular(10)); expect(rrect.trRadius, const Radius.circular(8)); expect(rrect.blRadius, Radius.zero); expect(rrect.brRadius, Radius.zero); expect(rrect.width, 112); expect(rrect.height, 90); expect(rrect.left, -80); expect(rrect.top, 116); final bgTooltipPaint = result1.captured[1] as Paint; expect(bgTooltipPaint.color, isSameColorAs(const Color(0xf33f33f3))); expect(bgTooltipPaint.style, PaintingStyle.fill); final rRectBorder = result1.captured[2] as RRect; final paintBorder = result1.captured[3] as Paint; expect(rRectBorder.tlRadius, const Radius.circular(10)); expect(rRectBorder.trRadius, const Radius.circular(8)); expect(rRectBorder.blRadius, Radius.zero); expect(rRectBorder.brRadius, Radius.zero); expect(rRectBorder.width, 112); expect(rRectBorder.height, 90); expect(rRectBorder.left, -80); expect(rRectBorder.top, 116); expect(paintBorder.color, isSameColorAs(const Color(0xf33f33f3))); expect(paintBorder.strokeWidth, 2); expect(paintBorder.style, PaintingStyle.stroke); expect(angles.length, 1); expect(angles[0], 12); final result2 = verify(mockCanvasWrapper.drawText(captureAny, captureAny)) ..called(1); final textPainter = result2.captured[0] as TextPainter; expect((textPainter.text as TextSpan?)!.text, 'helllo1'); expect((textPainter.text as TextSpan?)!.style, textStyle1); expect(textPainter.textAlign, TextAlign.right); expect(textPainter.textDirection, TextDirection.rtl); expect( (textPainter.text as TextSpan?)!.children![0], const TextSpan(text: 'helllo2'), ); expect( (textPainter.text as TextSpan?)!.children![1], const TextSpan(text: 'helllo3'), ); final drawOffset = result2.captured[1] as Offset; expect(drawOffset, const Offset(-64, 124)); }); test('test 3', () { final mockUtils = MockUtils(); when(mockUtils.getThemeAwareTextStyle(any, any)).thenReturn(textStyle1); when(mockUtils.getEfficientInterval(any, any)).thenReturn(11); when(mockUtils.normalizeBorderRadius(any, any)) .thenReturn(BorderRadius.zero); when(mockUtils.normalizeBorderSide(any, any)).thenReturn(BorderSide.none); when(mockUtils.calculateRotationOffset(any, any)).thenReturn(Offset.zero); when(mockUtils.getBestInitialIntervalValue(any, any, any)).thenReturn(0); when(mockUtils.formatNumber(any, any, captureAny)).thenAnswer((inv) { final value = inv.positionalArguments[0] as double; return '${value.toInt()}'; }); Utils.changeInstance(mockUtils); const viewSize = Size(200, 100); final barGroups = [ BarChartGroupData( x: 0, barRods: [ BarChartRodData( toY: 10, width: 10, color: const Color(0x00000000), borderRadius: const BorderRadius.all(Radius.circular(0.1)), ), BarChartRodData( toY: -10, width: 10, color: const Color(0x11111111), borderRadius: const BorderRadius.all(Radius.circular(0.2)), ), ], ), ]; final tooltipData = BarTouchTooltipData( tooltipBorderRadius: BorderRadius.circular(8), getTooltipColor: (group) => const Color(0xf33f33f3), maxContentWidth: 8000, rotateAngle: 12, fitInsideHorizontally: true, fitInsideVertically: true, direction: TooltipDirection.top, tooltipHorizontalAlignment: FLHorizontalAlignment.right, tooltipBorder: const BorderSide(color: Color(0xf33f33f3), width: 2), getTooltipItem: ( group, groupIndex, rod, rodIndex, ) { return BarTooltipItem( 'helllo1asdfasdfasdfasdfasdfasdfhelllo1asdfasdfasdfasd' 'fasdfasdfhelllo1asdfasdfasdfasdfasdfasdfhelllo1asdf' 'asdfasdfasdfasdfasdfhelllo1asdfasdfasdfasdfasdfasdfh' 'elllo1asdfasdfasdfasdfasdfasdf', textStyle1, textAlign: TextAlign.right, textDirection: TextDirection.rtl, children: List.generate( 500, (index) => const TextSpan(text: '\nhelllo3'), ), ); }, ); final (minY, maxY) = BarChartHelper().calculateMaxAxisValues(barGroups); final data = BarChartData( groupsSpace: 10, barGroups: barGroups, barTouchData: BarTouchData( touchTooltipData: tooltipData, ), alignment: BarChartAlignment.center, minY: minY, maxY: maxY, ); final barChartPainter = BarChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final mockBuildContext = MockBuildContext(); final groupsX = data.calculateGroupsX(viewSize.width); final barGroupsPosition = barChartPainter.calculateGroupAndBarsPosition( viewSize, groupsX, barGroups, ); final angles = []; when( mockCanvasWrapper.drawRotated( size: anyNamed('size'), rotationOffset: anyNamed('rotationOffset'), drawOffset: anyNamed('drawOffset'), angle: anyNamed('angle'), drawCallback: anyNamed('drawCallback'), ), ).thenAnswer((inv) { final callback = inv.namedArguments[const Symbol('drawCallback')] as DrawCallback; callback(); angles.add(inv.namedArguments[const Symbol('angle')] as double); }); barChartPainter.drawTouchTooltip( mockBuildContext, mockCanvasWrapper, barGroupsPosition, tooltipData, barGroups[0], 0, barGroups[0].barRods[1], 1, holder, ); final result1 = verify(mockCanvasWrapper.drawRRect(captureAny, captureAny)) ..called(2); final rrect = result1.captured[0] as RRect; expect(rrect.blRadius, const Radius.circular(8)); expect(rrect.width, 2636); expect(rrect.height, 7034.0); expect(rrect.left, -2436); expect(rrect.top, -6934.0); final bgTooltipPaint = result1.captured[1] as Paint; expect(bgTooltipPaint.color, isSameColorAs(const Color(0xf33f33f3))); expect(bgTooltipPaint.style, PaintingStyle.fill); final rRectBorder = result1.captured[2] as RRect; final paintBorder = result1.captured[3] as Paint; expect(rRectBorder.blRadius, const Radius.circular(8)); expect(rRectBorder.width, 2636); expect(rRectBorder.height, 7034.0); expect(rRectBorder.left, -2436); expect(rRectBorder.top, -6934.0); expect(paintBorder.color, isSameColorAs(const Color(0xf33f33f3))); expect(paintBorder.strokeWidth, 2); expect(paintBorder.style, PaintingStyle.stroke); expect(angles.length, 1); expect(angles[0], 12); final result2 = verify(mockCanvasWrapper.drawText(captureAny, captureAny)) ..called(1); final drawOffset = result2.captured[1] as Offset; expect(drawOffset, const Offset(-2420, -6926)); }); test('shifts tooltip up when upward rod has a visible label', () { final mockUtils = MockUtils(); when(mockUtils.getThemeAwareTextStyle(any, any)).thenReturn(textStyle1); when(mockUtils.getEfficientInterval(any, any)).thenReturn(11); when(mockUtils.normalizeBorderRadius(any, any)) .thenReturn(BorderRadius.zero); when(mockUtils.normalizeBorderSide(any, any)).thenReturn(BorderSide.none); when(mockUtils.calculateRotationOffset(any, any)).thenReturn(Offset.zero); when(mockUtils.getBestInitialIntervalValue(any, any, any)).thenReturn(0); Utils.changeInstance(mockUtils); const viewSize = Size(200, 100); final barGroups = [ BarChartGroupData( x: 0, barRods: [ BarChartRodData( toY: 10, width: 10, color: const Color(0x00000000), borderRadius: const BorderRadius.all(Radius.circular(0.1)), label: const BarChartRodLabel( text: 'Label', ), ), ], ), ]; final tooltipData = BarTouchTooltipData( tooltipBorderRadius: BorderRadius.circular(8), getTooltipColor: (group) => const Color(0xf33f33f3), maxContentWidth: 80, rotateAngle: 0, tooltipBorder: const BorderSide(color: Color(0xf33f33f3), width: 2), getTooltipItem: (group, groupIndex, rod, rodIndex) { return BarTooltipItem('tooltip', textStyle1); }, ); final (minY, maxY) = BarChartHelper().calculateMaxAxisValues(barGroups); final data = BarChartData( groupsSpace: 10, barGroups: barGroups, barTouchData: BarTouchData(touchTooltipData: tooltipData), alignment: BarChartAlignment.center, minY: minY, maxY: maxY, ); final barChartPainter = BarChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final mockBuildContext = MockBuildContext(); final groupsX = data.calculateGroupsX(viewSize.width); final barGroupsPosition = barChartPainter.calculateGroupAndBarsPosition( viewSize, groupsX, barGroups, ); when( mockCanvasWrapper.drawRotated( size: anyNamed('size'), rotationOffset: anyNamed('rotationOffset'), drawOffset: anyNamed('drawOffset'), angle: anyNamed('angle'), drawCallback: anyNamed('drawCallback'), ), ).thenAnswer((inv) { final callback = inv.namedArguments[const Symbol('drawCallback')] as DrawCallback; callback(); }); // Draw tooltip WITHOUT label first (to get baseline position) final barGroupsNoLabel = [ BarChartGroupData( x: 0, barRods: [ BarChartRodData( toY: 10, width: 10, color: const Color(0x00000000), borderRadius: const BorderRadius.all(Radius.circular(0.1)), ), ], ), ]; final dataNoLabel = BarChartData( groupsSpace: 10, barGroups: barGroupsNoLabel, barTouchData: BarTouchData(touchTooltipData: tooltipData), alignment: BarChartAlignment.center, minY: minY, maxY: maxY, ); final holderNoLabel = PaintHolder( dataNoLabel, dataNoLabel, TextScaler.noScaling, ); final groupsXNoLabel = dataNoLabel.calculateGroupsX(viewSize.width); final barGroupsPositionNoLabel = barChartPainter.calculateGroupAndBarsPosition( viewSize, groupsXNoLabel, barGroupsNoLabel, ); barChartPainter.drawTouchTooltip( mockBuildContext, mockCanvasWrapper, barGroupsPositionNoLabel, tooltipData, barGroupsNoLabel[0], 0, barGroupsNoLabel[0].barRods[0], 0, holderNoLabel, ); final resultNoLabel = verify(mockCanvasWrapper.drawRRect(captureAny, captureAny)) ..called(2); final rrectNoLabel = resultNoLabel.captured[0] as RRect; // Now draw tooltip WITH label barChartPainter.drawTouchTooltip( mockBuildContext, mockCanvasWrapper, barGroupsPosition, tooltipData, barGroups[0], 0, barGroups[0].barRods[0], 0, holder, ); final resultWithLabel = verify(mockCanvasWrapper.drawRRect(captureAny, captureAny)) ..called(2); final rrectWithLabel = resultWithLabel.captured[0] as RRect; // With a visible label, the tooltip should be shifted further up expect( rrectWithLabel.top, lessThan(rrectNoLabel.top), reason: 'Tooltip should be shifted up to avoid overlapping with label', ); }); test('shifts tooltip down when downward rod has a visible label', () { final mockUtils = MockUtils(); when(mockUtils.getThemeAwareTextStyle(any, any)).thenReturn(textStyle1); when(mockUtils.getEfficientInterval(any, any)).thenReturn(11); when(mockUtils.normalizeBorderRadius(any, any)) .thenReturn(BorderRadius.zero); when(mockUtils.normalizeBorderSide(any, any)).thenReturn(BorderSide.none); when(mockUtils.calculateRotationOffset(any, any)).thenReturn(Offset.zero); when(mockUtils.getBestInitialIntervalValue(any, any, any)).thenReturn(0); Utils.changeInstance(mockUtils); const viewSize = Size(200, 100); final barGroups = [ BarChartGroupData( x: 0, barRods: [ BarChartRodData( fromY: 10, toY: 0, width: 10, color: const Color(0x00000000), borderRadius: const BorderRadius.all(Radius.circular(0.1)), label: const BarChartRodLabel( text: 'Label', ), ), ], ), ]; final tooltipData = BarTouchTooltipData( tooltipBorderRadius: BorderRadius.circular(8), getTooltipColor: (group) => const Color(0xf33f33f3), maxContentWidth: 80, rotateAngle: 0, direction: TooltipDirection.bottom, tooltipBorder: const BorderSide(color: Color(0xf33f33f3), width: 2), getTooltipItem: (group, groupIndex, rod, rodIndex) { return BarTooltipItem('tooltip', textStyle1); }, ); final data = BarChartData( groupsSpace: 10, barGroups: barGroups, barTouchData: BarTouchData(touchTooltipData: tooltipData), alignment: BarChartAlignment.center, minY: 0, maxY: 10, ); final barChartPainter = BarChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final mockBuildContext = MockBuildContext(); final groupsX = data.calculateGroupsX(viewSize.width); final barGroupsPosition = barChartPainter.calculateGroupAndBarsPosition( viewSize, groupsX, barGroups, ); when( mockCanvasWrapper.drawRotated( size: anyNamed('size'), rotationOffset: anyNamed('rotationOffset'), drawOffset: anyNamed('drawOffset'), angle: anyNamed('angle'), drawCallback: anyNamed('drawCallback'), ), ).thenAnswer((inv) { final callback = inv.namedArguments[const Symbol('drawCallback')] as DrawCallback; callback(); }); // Draw tooltip WITHOUT label first (to get baseline position) final barGroupsNoLabel = [ BarChartGroupData( x: 0, barRods: [ BarChartRodData( fromY: 10, toY: 0, width: 10, color: const Color(0x00000000), borderRadius: const BorderRadius.all(Radius.circular(0.1)), ), ], ), ]; final dataNoLabel = BarChartData( groupsSpace: 10, barGroups: barGroupsNoLabel, barTouchData: BarTouchData(touchTooltipData: tooltipData), alignment: BarChartAlignment.center, minY: 0, maxY: 10, ); final holderNoLabel = PaintHolder( dataNoLabel, dataNoLabel, TextScaler.noScaling, ); final groupsXNoLabel = dataNoLabel.calculateGroupsX(viewSize.width); final barGroupsPositionNoLabel = barChartPainter.calculateGroupAndBarsPosition( viewSize, groupsXNoLabel, barGroupsNoLabel, ); barChartPainter.drawTouchTooltip( mockBuildContext, mockCanvasWrapper, barGroupsPositionNoLabel, tooltipData, barGroupsNoLabel[0], 0, barGroupsNoLabel[0].barRods[0], 0, holderNoLabel, ); final resultNoLabel = verify(mockCanvasWrapper.drawRRect(captureAny, captureAny)) ..called(2); final rrectNoLabel = resultNoLabel.captured[0] as RRect; // Now draw tooltip WITH label barChartPainter.drawTouchTooltip( mockBuildContext, mockCanvasWrapper, barGroupsPosition, tooltipData, barGroups[0], 0, barGroups[0].barRods[0], 0, holder, ); final resultWithLabel = verify(mockCanvasWrapper.drawRRect(captureAny, captureAny)) ..called(2); final rrectWithLabel = resultWithLabel.captured[0] as RRect; // With a visible label on a downward bar, tooltip should be shifted further down expect( rrectWithLabel.top, greaterThan(rrectNoLabel.top), reason: 'Tooltip should be shifted down to avoid overlapping with label', ); }); }); group('drawStackItemBorderStroke()', () { test('test 1', () { const viewSize = Size(200, 100); final rodStackItems = [ BarChartRodStackItem( 0, 3, const Color(0x11111110), label: '5', borderSide: const BorderSide(color: Color(0x11111111)), ), BarChartRodStackItem( 3, 8, const Color(0x22222220), label: '5', borderSide: const BorderSide(color: Color(0x22222221), width: 2), ), BarChartRodStackItem( 8, 10, const Color(0x33333330), label: '5', borderSide: const BorderSide(color: Color(0x33333331), width: 3), ), ]; final barRod = BarChartRodData( toY: 10, width: 10, color: const Color(0x00000000), borderRadius: const BorderRadius.all(Radius.circular(0.1)), rodStackItems: rodStackItems, ); final barGroups = [ BarChartGroupData(x: 0, barRods: [barRod], barsSpace: 5), ]; final (minY, maxY) = BarChartHelper().calculateMaxAxisValues(barGroups); final data = BarChartData( groupsSpace: 10, barGroups: barGroups, barTouchData: const BarTouchData(), minY: minY, maxY: maxY, ); final barChartPainter = BarChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final results = >[]; when( mockCanvasWrapper.drawRRect( captureAny, captureAny, ), ).thenAnswer((inv) { final rrect = inv.positionalArguments[0] as RRect; final paint = inv.positionalArguments[1] as Paint; results.add({ 'rRect': rrect, 'paint.color': paint.color, 'paint.strokeWidth': paint.strokeWidth, }); }); barChartPainter ..drawStackItemBorderStroke( mockCanvasWrapper, rodStackItems[0], 0, 3, barRod.width, RRect.fromLTRBAndCorners( 0, 0, 10, 100, bottomRight: const Radius.circular(12), ), viewSize, holder, ) ..drawStackItemBorderStroke( mockCanvasWrapper, rodStackItems[1], 1, 3, barRod.width, RRect.fromLTRBAndCorners( 0, 0, 10, 100, bottomRight: const Radius.circular(12), ), viewSize, holder, ) ..drawStackItemBorderStroke( mockCanvasWrapper, rodStackItems[2], 2, 3, barRod.width, RRect.fromLTRBAndCorners( 0, 0, 10, 100, bottomRight: const Radius.circular(12), ), viewSize, holder, ); expect(results.length, 3); expect( results[0]['rRect'], RRect.fromLTRBAndCorners( 0, 70, 10, 100, bottomRight: const Radius.circular(12), ), ); expect( results[0]['paint.color'], isSameColorAs(const Color(0x11111111)), ); expect(results[0]['paint.strokeWidth'], 1.0); expect( results[1]['rRect'], RRect.fromLTRBAndCorners( 0, 20, 10, 70, ), ); expect( results[1]['paint.color'], isSameColorAs(const Color(0x22222221)), ); expect(results[1]['paint.strokeWidth'], 2.0); expect( results[2]['rRect'], RRect.fromLTRBAndCorners( 0, 0, 10, 20, ), ); expect( results[2]['paint.color'], isSameColorAs(const Color(0x33333331)), ); expect(results[2]['paint.strokeWidth'], 3.0); }); }); group('handleTouch()', () { test('test 1', () { const viewSize = Size(200, 100); final barGroups = [ BarChartGroupData( x: 0, barRods: [ BarChartRodData( toY: 10, width: 10, color: const Color(0x00000000), borderRadius: const BorderRadius.all(Radius.circular(0.1)), ), BarChartRodData( toY: 8, width: 11, color: const Color(0x11111111), borderRadius: const BorderRadius.all(Radius.circular(0.2)), ), BarChartRodData( toY: 8, width: 12, color: const Color(0x22222222), borderRadius: const BorderRadius.all(Radius.circular(0.3)), ), ], barsSpace: 5, ), BarChartGroupData( x: 1, barRods: [ BarChartRodData( toY: 10, width: 10, borderRadius: const BorderRadius.all(Radius.circular(0.4)), ), BarChartRodData(toY: 8, width: 10), ], barsSpace: 5, ), BarChartGroupData( x: 2, barRods: [ BarChartRodData(toY: 10, width: 10), BarChartRodData(toY: 8, width: 10), BarChartRodData(toY: 8, width: 10), BarChartRodData(toY: 8, width: 10), ], barsSpace: 5, ), ]; final (minY, maxY) = BarChartHelper().calculateMaxAxisValues(barGroups); final data = BarChartData( barGroups: barGroups, titlesData: const FlTitlesData(show: false), alignment: BarChartAlignment.center, groupsSpace: 10, barTouchData: const BarTouchData( handleBuiltInTouches: true, touchExtraThreshold: EdgeInsets.all(1), ), minY: minY, maxY: maxY, ); final painter = BarChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); expect(painter.handleTouch(const Offset(10, 10), viewSize, holder), null); expect( painter.handleTouch(const Offset(27.49, 10), viewSize, holder), null, ); // Group 0 // 28.5, 0.0, 38.5, 100.0 // 43.5, 20.0, 54.5, 100.0 // 59.5, 20.0, 71.5, 100.0 final result1 = painter.handleTouch(const Offset(27.5, 10), viewSize, holder); expect(result1!.touchedBarGroupIndex, 0); expect(result1.touchedRodDataIndex, 0); final result11 = painter.handleTouch(const Offset(39.5, 10), viewSize, holder); expect(result11!.touchedBarGroupIndex, 0); expect(result11.touchedRodDataIndex, 0); expect( painter.handleTouch(const Offset(39.51, 10), viewSize, holder), null, ); // Group 1 // 81.5, 0.0, 91.5, 100.0 // 96.5, 20.0, 106.5, 100.0 expect( painter.handleTouch(const Offset(100, 18.99), viewSize, holder), null, ); final result2 = painter.handleTouch(const Offset(100, 19), viewSize, holder); expect(result2!.touchedBarGroupIndex, 1); expect(result2.touchedRodDataIndex, 1); // Group 2 // 116.5, 0.0, 126.5, 100.0 // 131.5, 20.0, 141.5, 100.0 // 146.5, 20.0, 156.5, 100.0 // 161.5, 20.0, 171.5, 100.0 expect( painter.handleTouch(const Offset(165, 101.1), viewSize, holder), null, ); final result3 = painter.handleTouch(const Offset(165, 101), viewSize, holder); expect(result3!.touchedBarGroupIndex, 2); expect(result3.touchedRodDataIndex, 3); }); test('test 2', () { const viewSize = Size(200, 100); final barGroups = [ BarChartGroupData( x: 0, barRods: [ BarChartRodData( toY: 10, width: 10, color: const Color(0x00000000), borderRadius: const BorderRadius.all(Radius.circular(0.1)), rodStackItems: [ BarChartRodStackItem( 0, 5, const Color(0xFF0F0F0F), label: '5', ), ], ), ], barsSpace: 5, ), BarChartGroupData( x: 1, barRods: [ BarChartRodData( toY: -10, width: 10, borderRadius: const BorderRadius.all(Radius.circular(0.4)), ), ], barsSpace: 5, ), ]; final (minY, maxY) = BarChartHelper().calculateMaxAxisValues(barGroups); final data = BarChartData( barGroups: barGroups, titlesData: const FlTitlesData(show: false), alignment: BarChartAlignment.center, groupsSpace: 10, barTouchData: const BarTouchData( handleBuiltInTouches: true, touchExtraThreshold: EdgeInsets.all(1), ), minY: minY, maxY: maxY, ); final painter = BarChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); expect( painter.handleTouch(const Offset(134, 48.6), viewSize, holder), null, ); expect( painter.handleTouch(const Offset(111.2, 31.1), viewSize, holder), null, ); expect( painter.handleTouch(const Offset(103.2, 74.8), viewSize, holder), null, ); expect( painter.handleTouch(const Offset(100.4, 21.2), viewSize, holder), null, ); expect( painter.handleTouch(const Offset(80.1, 22), viewSize, holder), null, ); final result1 = painter.handleTouch(const Offset(89, 38.5), viewSize, holder); expect(result1!.touchedBarGroupIndex, 0); expect(result1.touchedRodDataIndex, 0); expect(result1.touchedStackItemIndex, 0); final result2 = painter.handleTouch(const Offset(88.8, 16.5), viewSize, holder); expect(result2!.touchedBarGroupIndex, 0); expect(result2.touchedRodDataIndex, 0); expect(result2.touchedStackItemIndex, -1); }); test('test 3', () { const viewSize = Size(200, 100); final barGroups = [ BarChartGroupData( x: 1, barRods: [ BarChartRodData( toY: 5, backDrawRodData: BackgroundBarChartRodData( show: true, fromY: -5, toY: 5, ), ), ], ), BarChartGroupData( x: 1, barRods: [ BarChartRodData( toY: -6, backDrawRodData: BackgroundBarChartRodData( show: true, fromY: 5, toY: -6, ), ), ], ), ]; final data = BarChartData( barGroups: barGroups, titlesData: const FlTitlesData(show: false), alignment: BarChartAlignment.start, groupsSpace: 10, minY: -10, maxY: 15, barTouchData: const BarTouchData( enabled: true, handleBuiltInTouches: true, allowTouchBarBackDraw: true, touchExtraThreshold: EdgeInsets.all(1), ), ); final painter = BarChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final result1 = painter.handleTouch(const Offset(4, 60), viewSize, holder); expect(result1!.touchedBarGroupIndex, 0); expect(result1.touchedRodDataIndex, 0); // tap below the positive bar final result11 = painter.handleTouch(const Offset(4, 61.1), viewSize, holder); expect(result11!.touchedBarGroupIndex, 0); expect(result11.touchedRodDataIndex, 0); final result2 = painter.handleTouch(const Offset(22, 60), viewSize, holder); expect(result2!.touchedBarGroupIndex, 1); expect(result2.touchedRodDataIndex, 0); final result22 = painter.handleTouch(const Offset(22, 58.9), viewSize, holder); expect(result22!.touchedBarGroupIndex, 1); expect(result22.touchedRodDataIndex, 0); }); test( 'returns null when chart virtual rect is provided and touch is outside ' 'of canvas', () { const viewSize = Size(50, 50); final barGroups = [ BarChartGroupData( x: 1, barRods: [ BarChartRodData( toY: 5, backDrawRodData: BackgroundBarChartRodData( show: true, fromY: -5, toY: 5, ), ), ], ), BarChartGroupData( x: 1, barRods: [ BarChartRodData( toY: -6, backDrawRodData: BackgroundBarChartRodData( show: true, fromY: 5, toY: -6, ), ), ], ), ]; final data = BarChartData( barGroups: barGroups, titlesData: const FlTitlesData(show: false), alignment: BarChartAlignment.start, groupsSpace: 10, minY: -10, maxY: 15, barTouchData: const BarTouchData( enabled: true, handleBuiltInTouches: true, allowTouchBarBackDraw: true, touchExtraThreshold: EdgeInsets.all(1), ), ); final painter = BarChartPainter(); final holder = PaintHolder( data, data, TextScaler.noScaling, Offset.zero & const Size(50, 50), ); final result1 = painter.handleTouch(const Offset(4, 60), viewSize, holder); expect(result1, null); }, ); test( 'returns result when chart virtual rect is provided and touch is inside ' 'of canvas', () { const viewSize = Size(50, 50); final barGroups = [ BarChartGroupData( x: 1, barRods: [ BarChartRodData( toY: 5, backDrawRodData: BackgroundBarChartRodData( show: true, fromY: -5, toY: 5, ), ), ], ), BarChartGroupData( x: 1, barRods: [ BarChartRodData( toY: -6, backDrawRodData: BackgroundBarChartRodData( show: true, fromY: 5, toY: -6, ), ), ], ), ]; final data = BarChartData( barGroups: barGroups, titlesData: const FlTitlesData(show: false), alignment: BarChartAlignment.start, groupsSpace: 10, minY: -10, maxY: 15, barTouchData: const BarTouchData( enabled: true, handleBuiltInTouches: true, allowTouchBarBackDraw: true, touchExtraThreshold: EdgeInsets.all(1), ), ); final painter = BarChartPainter(); final holder = PaintHolder( data, data, TextScaler.noScaling, Offset.zero & const Size(50, 50), ); final result1 = painter.handleTouch(const Offset(4, 30), viewSize, holder); expect(result1!.touchedBarGroupIndex, 0); expect(result1.touchedRodDataIndex, 0); }, ); }); group('drawExtraLines()', () { test( 'should not draw lines when constructor is called with empty ExtraLinesData object', () { const viewSize = Size(400, 400); final data = BarChartData( barGroups: [ BarChartGroupData( x: 1, barRods: [ BarChartRodData(fromY: 1, toY: 10), BarChartRodData(fromY: 2, toY: 10), ], showingTooltipIndicators: [ 1, 2, ], ), BarChartGroupData( x: 2, barRods: [ BarChartRodData(fromY: 3, toY: 10), BarChartRodData(fromY: 4, toY: 10), ], ), ], extraLinesData: const ExtraLinesData(), ); final barChartPainter = BarChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockBuildContext = MockBuildContext(); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); barChartPainter.drawExtraLines( mockBuildContext, mockCanvasWrapper, holder, ); verifyNever( mockCanvasWrapper.drawDashedLine( any, any, any, any, ), ); }); test('should not draw lines when constructor is not passed extraLinesData', () { const viewSize = Size(400, 400); final data = BarChartData( barGroups: [ BarChartGroupData( x: 1, barRods: [ BarChartRodData(fromY: 1, toY: 10), BarChartRodData(fromY: 2, toY: 10), ], showingTooltipIndicators: [ 1, 2, ], ), BarChartGroupData( x: 2, barRods: [ BarChartRodData(fromY: 3, toY: 10), BarChartRodData(fromY: 4, toY: 10), ], ), ], ); final barChartPainter = BarChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockBuildContext = MockBuildContext(); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); barChartPainter.drawExtraLines( mockBuildContext, mockCanvasWrapper, holder, ); verifyNever( mockCanvasWrapper.drawDashedLine( any, any, any, any, ), ); }); test('bar chart should not paint vertical lines', () { final utilsMainInstance = Utils(); const viewSize = Size(400, 400); final barGroups = [ BarChartGroupData( x: 1, barRods: [ BarChartRodData(fromY: 1, toY: 10), BarChartRodData(fromY: 2, toY: 10), ], showingTooltipIndicators: [ 1, 2, ], ), BarChartGroupData( x: 2, barRods: [ BarChartRodData(fromY: 3, toY: 10), BarChartRodData(fromY: 4, toY: 10), ], ), ]; final (minY, maxY) = BarChartHelper().calculateMaxAxisValues(barGroups); final data = BarChartData( minY: minY, maxY: maxY, barGroups: barGroups, extraLinesData: ExtraLinesData( verticalLines: [verticalLine1], ), ); final barChartPainter = BarChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockUtils = MockUtils(); Utils.changeInstance(mockUtils); when(mockUtils.getThemeAwareTextStyle(any, any)) .thenAnswer((realInvocation) => textStyle1); when(mockUtils.calculateRotationOffset(any, any)) .thenAnswer((realInvocation) => Offset.zero); when(mockUtils.convertRadiusToSigma(any)) .thenAnswer((realInvocation) => 4.0); when(mockUtils.getEfficientInterval(any, any)) .thenAnswer((realInvocation) => 1.0); when(mockUtils.getBestInitialIntervalValue(any, any, any)) .thenAnswer((realInvocation) => 1.0); when(mockUtils.normalizeBorderRadius(any, any)) .thenAnswer((realInvocation) => BorderRadius.zero); when(mockUtils.normalizeBorderSide(any, any)).thenAnswer( (realInvocation) => const BorderSide(color: MockData.color0), ); final mockBuildContext = MockBuildContext(); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); barChartPainter.paint( mockBuildContext, mockCanvasWrapper, holder, ); verifyNever( mockCanvasWrapper.drawDashedLine( any, any, argThat( const TypeMatcher().having( (p0) => p0.color, 'colors match', isSameColorAs(Colors.red), ), ), holder.data.extraLinesData.verticalLines[0].dashArray, ), ); Utils.changeInstance(utilsMainInstance); }); test( 'should not paint horizontal line if Y value is greater or less than Y axis', () { final utilsMainInstance = Utils(); const viewSize = Size(400, 400); final horizontalLine1 = HorizontalLine( y: 10.1, color: Colors.red, dashArray: [0, 1], ); final horizontalLine2 = HorizontalLine( y: -10.1, color: Colors.red, dashArray: [0, 1], ); final data = BarChartData( minY: -10, maxY: 10, barGroups: [ BarChartGroupData( x: 1, barRods: [ BarChartRodData(fromY: 1, toY: 10), BarChartRodData(fromY: 2, toY: 10), ], showingTooltipIndicators: [ 1, 2, ], ), BarChartGroupData( x: 2, barRods: [ BarChartRodData(fromY: 3, toY: 10), BarChartRodData(fromY: 4, toY: 10), ], ), ], extraLinesData: ExtraLinesData( horizontalLines: [horizontalLine1, horizontalLine2], ), ); final barChartPainter = BarChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockUtils = MockUtils(); Utils.changeInstance(mockUtils); when(mockUtils.getThemeAwareTextStyle(any, any)) .thenAnswer((realInvocation) => textStyle1); when(mockUtils.calculateRotationOffset(any, any)) .thenAnswer((realInvocation) => Offset.zero); when(mockUtils.convertRadiusToSigma(any)) .thenAnswer((realInvocation) => 4.0); when(mockUtils.getEfficientInterval(any, any)) .thenAnswer((realInvocation) => 1.0); when(mockUtils.getBestInitialIntervalValue(any, any, any)) .thenAnswer((realInvocation) => 1.0); when(mockUtils.normalizeBorderRadius(any, any)) .thenAnswer((realInvocation) => BorderRadius.zero); when(mockUtils.normalizeBorderSide(any, any)).thenAnswer( (realInvocation) => const BorderSide(color: MockData.color0), ); final mockBuildContext = MockBuildContext(); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); barChartPainter.paint( mockBuildContext, mockCanvasWrapper, holder, ); verifyNever( mockCanvasWrapper.drawDashedLine( any, any, argThat( const TypeMatcher().having( (p0) => p0.color, 'colors match', isSameColorAs(Colors.red), ), ), holder.data.extraLinesData.horizontalLines[0].dashArray, ), ); Utils.changeInstance(utilsMainInstance); }); test('should paint horizontal lines', () { final horizontalLine = HorizontalLine( y: 2.5, strokeWidth: 90, color: Colors.cyanAccent, dashArray: [100, 20], ); final horizontalLine1 = HorizontalLine( y: 0.2, strokeWidth: 100, color: Colors.cyanAccent, dashArray: [100, 20], ); final utilsMainInstance = Utils(); const viewSize = Size(400, 400); final barGroups = [ BarChartGroupData( x: 1, barRods: [ BarChartRodData(fromY: 1, toY: 10), BarChartRodData(fromY: 2, toY: 10), ], showingTooltipIndicators: [ 1, 2, ], ), BarChartGroupData( x: 2, barRods: [ BarChartRodData(fromY: 3, toY: 10), BarChartRodData(fromY: 4, toY: 10), ], ), ]; final (minY, maxY) = BarChartHelper().calculateMaxAxisValues(barGroups); final data = BarChartData( minY: minY, maxY: maxY, barGroups: barGroups, extraLinesData: ExtraLinesData( horizontalLines: [horizontalLine, horizontalLine1], ), ); final barChartPainter = BarChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockUtils = MockUtils(); Utils.changeInstance(mockUtils); when(mockUtils.getThemeAwareTextStyle(any, any)) .thenAnswer((realInvocation) => textStyle1); when(mockUtils.calculateRotationOffset(any, any)) .thenAnswer((realInvocation) => Offset.zero); when(mockUtils.convertRadiusToSigma(any)) .thenAnswer((realInvocation) => 4.0); when(mockUtils.getEfficientInterval(any, any)) .thenAnswer((realInvocation) => 1.0); when(mockUtils.getBestInitialIntervalValue(any, any, any)) .thenAnswer((realInvocation) => 1.0); when(mockUtils.normalizeBorderRadius(any, any)) .thenAnswer((realInvocation) => BorderRadius.zero); when(mockUtils.normalizeBorderSide(any, any)).thenAnswer( (realInvocation) => const BorderSide(color: MockData.color0), ); final mockBuildContext = MockBuildContext(); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final results = >[]; when( mockCanvasWrapper.drawDashedLine( any, any, captureThat( const TypeMatcher().having( (p0) => p0.color, 'colors match', isSameColorAs(Colors.cyanAccent), ), ), [100, 20], ), ).thenAnswer((inv) { results.add({ 'from': inv.positionalArguments[0] as Offset, 'to': inv.positionalArguments[1] as Offset, 'paint_color': (inv.positionalArguments[2] as Paint).color, 'paint_stroke_width': (inv.positionalArguments[2] as Paint).strokeWidth, }); }); barChartPainter.paint( mockBuildContext, mockCanvasWrapper, holder, ); expect(results.length, 1); expect(results[0]['paint_color'], isSameColorAs(Colors.cyanAccent)); expect(results[0]['paint_stroke_width'], 90); Utils.changeInstance(utilsMainInstance); }); test('should draw extra horizontal lines under chart', () { const viewSize = Size(100, 100); final data = BarChartData( minY: -10, maxY: 10, barGroups: [ BarChartGroupData( x: 1, barRods: [ BarChartRodData(fromY: 1, toY: 10), BarChartRodData(fromY: 2, toY: 10), ], ), ], titlesData: const FlTitlesData(show: false), extraLinesData: ExtraLinesData( horizontalLines: [ HorizontalLine( y: -9.9, color: Colors.cyanAccent, dashArray: [12, 22], ), HorizontalLine( y: -.5, color: Colors.cyanAccent, dashArray: [12, 22], ), ], extraLinesOnTop: false, ), ); final barChartPainter = BarChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final mockBuildContext = MockBuildContext(); barChartPainter.paint( mockBuildContext, mockCanvasWrapper, holder, ); verify( mockCanvasWrapper.drawDashedLine( any, any, argThat( const TypeMatcher().having( (p0) => p0.color, 'colors match', isSameColorAs(Colors.cyanAccent), ), ), holder.data.extraLinesData.horizontalLines[0].dashArray, ), ).called(2); }); }); group('drawBarLabels()', () { late MockUtils mockUtils; final utilsMainInstance = Utils(); setUp(() { mockUtils = MockUtils(); Utils.changeInstance(mockUtils); when(mockUtils.getThemeAwareTextStyle(any, any)) .thenAnswer((realInvocation) => textStyle1); when(mockUtils.calculateRotationOffset(any, any)) .thenAnswer((realInvocation) => Offset.zero); when(mockUtils.convertRadiusToSigma(any)) .thenAnswer((realInvocation) => 4.0); when(mockUtils.getEfficientInterval(any, any)) .thenAnswer((realInvocation) => 1.0); when(mockUtils.getBestInitialIntervalValue(any, any, any)) .thenAnswer((realInvocation) => 1.0); when(mockUtils.normalizeBorderRadius(any, any)) .thenAnswer((realInvocation) => BorderRadius.zero); when(mockUtils.normalizeBorderSide(any, any)).thenAnswer( (realInvocation) => const BorderSide(color: MockData.color0), ); }); tearDown(() { Utils.changeInstance(utilsMainInstance); }); test('should render label above upward bar', () { const viewSize = Size(200, 100); final barGroups = [ BarChartGroupData( x: 0, barRods: [ BarChartRodData( toY: 10, width: 20, label: const BarChartRodLabel( text: 'Top', ), ), ], ), ]; final data = BarChartData( titlesData: const FlTitlesData(show: false), barGroups: barGroups, minY: 0, maxY: 10, ); final barChartPainter = BarChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvas = MockCanvas(); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(mockCanvas); when(mockCanvasWrapper.save()).thenReturn(null); when(mockCanvasWrapper.restore()).thenReturn(null); when(mockCanvasWrapper.translate(any, any)).thenReturn(null); when(mockCanvasWrapper.rotate(any)).thenReturn(null); final paragraphs = >[]; when(mockCanvas.drawParagraph(captureAny, captureAny)).thenAnswer((inv) { final paragraph = inv.positionalArguments[0]; final offset = inv.positionalArguments[1] as Offset; paragraphs.add({ 'paragraph': paragraph, 'offset': offset, }); }); final groupsX = data.calculateGroupsX(viewSize.width); final barGroupsPosition = barChartPainter.calculateGroupAndBarsPosition( viewSize, groupsX, barGroups, ); final mockBuildContext = MockBuildContext(); barChartPainter.drawBarLabels( mockBuildContext, mockCanvasWrapper, barGroupsPosition, holder, ); expect(paragraphs.length, 1, reason: 'Should have drawn 1 label'); }); test('should render label below downward bar', () { const viewSize = Size(200, 100); final barGroups = [ BarChartGroupData( x: 0, barRods: [ BarChartRodData( fromY: 10, toY: 0, width: 20, label: const BarChartRodLabel( text: 'Bottom', ), ), ], ), ]; final data = BarChartData( titlesData: const FlTitlesData(show: false), barGroups: barGroups, minY: 0, maxY: 10, ); final barChartPainter = BarChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvas = MockCanvas(); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(mockCanvas); when(mockCanvasWrapper.save()).thenReturn(null); when(mockCanvasWrapper.restore()).thenReturn(null); when(mockCanvasWrapper.translate(any, any)).thenReturn(null); when(mockCanvasWrapper.rotate(any)).thenReturn(null); final paragraphs = >[]; when(mockCanvas.drawParagraph(captureAny, captureAny)).thenAnswer((inv) { final paragraph = inv.positionalArguments[0]; final offset = inv.positionalArguments[1] as Offset; paragraphs.add({ 'paragraph': paragraph, 'offset': offset, }); }); final groupsX = data.calculateGroupsX(viewSize.width); final barGroupsPosition = barChartPainter.calculateGroupAndBarsPosition( viewSize, groupsX, barGroups, ); final mockBuildContext = MockBuildContext(); barChartPainter.drawBarLabels( mockBuildContext, mockCanvasWrapper, barGroupsPosition, holder, ); expect(paragraphs.length, 1, reason: 'Should have drawn 1 label'); }); test('should not render label when show is false', () { const viewSize = Size(200, 100); final barGroups = [ BarChartGroupData( x: 0, barRods: [ BarChartRodData( toY: 10, width: 20, label: const BarChartRodLabel( show: false, text: 'Hidden', ), ), ], ), ]; final data = BarChartData( titlesData: const FlTitlesData(show: false), barGroups: barGroups, minY: 0, maxY: 10, ); final barChartPainter = BarChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvas = MockCanvas(); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(mockCanvas); final paragraphs = >[]; when(mockCanvas.drawParagraph(captureAny, captureAny)).thenAnswer((inv) { paragraphs.add({}); }); final groupsX = data.calculateGroupsX(viewSize.width); final barGroupsPosition = barChartPainter.calculateGroupAndBarsPosition( viewSize, groupsX, barGroups, ); final mockBuildContext = MockBuildContext(); barChartPainter.drawBarLabels( mockBuildContext, mockCanvasWrapper, barGroupsPosition, holder, ); expect(paragraphs.length, 0, reason: 'Should not draw any label'); }); test('should not render label when text is empty', () { const viewSize = Size(200, 100); final barGroups = [ BarChartGroupData( x: 0, barRods: [ BarChartRodData( toY: 10, width: 20, ), ], ), ]; final data = BarChartData( titlesData: const FlTitlesData(show: false), barGroups: barGroups, minY: 0, maxY: 10, ); final barChartPainter = BarChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvas = MockCanvas(); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(mockCanvas); final paragraphs = >[]; when(mockCanvas.drawParagraph(captureAny, captureAny)).thenAnswer((inv) { paragraphs.add({}); }); final groupsX = data.calculateGroupsX(viewSize.width); final barGroupsPosition = barChartPainter.calculateGroupAndBarsPosition( viewSize, groupsX, barGroups, ); final mockBuildContext = MockBuildContext(); barChartPainter.drawBarLabels( mockBuildContext, mockCanvasWrapper, barGroupsPosition, holder, ); expect( paragraphs.length, 0, reason: 'Should not draw label for empty text', ); }); test('should use getLabel callback when provided', () { const viewSize = Size(200, 100); final barGroups = [ BarChartGroupData( x: 0, barRods: [ BarChartRodData( toY: 10, width: 20, label: const BarChartRodLabel( text: 'Custom 10', ), ), ], ), ]; final data = BarChartData( titlesData: const FlTitlesData(show: false), barGroups: barGroups, minY: 0, maxY: 10, ); final barChartPainter = BarChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvas = MockCanvas(); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(mockCanvas); when(mockCanvasWrapper.save()).thenReturn(null); when(mockCanvasWrapper.restore()).thenReturn(null); when(mockCanvasWrapper.translate(any, any)).thenReturn(null); when(mockCanvasWrapper.rotate(any)).thenReturn(null); final paragraphs = >[]; when(mockCanvas.drawParagraph(captureAny, captureAny)).thenAnswer((inv) { final paragraph = inv.positionalArguments[0]; final offset = inv.positionalArguments[1] as Offset; paragraphs.add({ 'paragraph': paragraph, 'offset': offset, }); }); final groupsX = data.calculateGroupsX(viewSize.width); final barGroupsPosition = barChartPainter.calculateGroupAndBarsPosition( viewSize, groupsX, barGroups, ); final mockBuildContext = MockBuildContext(); barChartPainter.drawBarLabels( mockBuildContext, mockCanvasWrapper, barGroupsPosition, holder, ); expect(paragraphs.length, 1, reason: 'Should draw callback label'); }); test('should not render when label has default show:false', () { const viewSize = Size(200, 100); final barGroups = [ BarChartGroupData( x: 0, barRods: [ BarChartRodData( toY: 10, width: 20, ), ], ), ]; final data = BarChartData( titlesData: const FlTitlesData(show: false), barGroups: barGroups, minY: 0, maxY: 10, ); final barChartPainter = BarChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvas = MockCanvas(); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(mockCanvas); final paragraphs = >[]; when(mockCanvas.drawParagraph(captureAny, captureAny)).thenAnswer((inv) { paragraphs.add({}); }); final groupsX = data.calculateGroupsX(viewSize.width); final barGroupsPosition = barChartPainter.calculateGroupAndBarsPosition( viewSize, groupsX, barGroups, ); final mockBuildContext = MockBuildContext(); barChartPainter.drawBarLabels( mockBuildContext, mockCanvasWrapper, barGroupsPosition, holder, ); expect( paragraphs.length, 0, reason: 'Should not draw label when show is false', ); }); }); group('BarChartRodStackItem()', () { test('throws an exception if color and gradient is null', () { expect(() => BarChartRodStackItem(0, 10, null), throwsAssertionError); }); }); } ================================================ FILE: test/chart/bar_chart/bar_chart_painter_test.mocks.dart ================================================ // Mocks generated by Mockito 5.4.6 from annotations // in fl_chart/test/chart/bar_chart/bar_chart_painter_test.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:typed_data' as _i5; import 'dart:ui' as _i2; import 'package:fl_chart/fl_chart.dart' as _i7; import 'package:fl_chart/src/utils/canvas_wrapper.dart' as _i6; import 'package:fl_chart/src/utils/utils.dart' as _i8; import 'package:flutter/cupertino.dart' as _i3; import 'package:flutter/foundation.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; import 'package:mockito/src/dummies.dart' as _i9; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values // ignore_for_file: avoid_setters_without_getters // ignore_for_file: comment_references // ignore_for_file: deprecated_member_use // ignore_for_file: deprecated_member_use_from_same_package // ignore_for_file: implementation_imports // ignore_for_file: invalid_use_of_visible_for_testing_member // ignore_for_file: must_be_immutable // ignore_for_file: prefer_const_constructors // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class // ignore_for_file: invalid_use_of_internal_member class _FakeRect_0 extends _i1.SmartFake implements _i2.Rect { _FakeRect_0( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeCanvas_1 extends _i1.SmartFake implements _i2.Canvas { _FakeCanvas_1( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeSize_2 extends _i1.SmartFake implements _i2.Size { _FakeSize_2( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeWidget_3 extends _i1.SmartFake implements _i3.Widget { _FakeWidget_3( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info}) => super.toString(); } class _FakeInheritedWidget_4 extends _i1.SmartFake implements _i3.InheritedWidget { _FakeInheritedWidget_4( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info}) => super.toString(); } class _FakeDiagnosticsNode_5 extends _i1.SmartFake implements _i3.DiagnosticsNode { _FakeDiagnosticsNode_5( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({ _i4.TextTreeConfiguration? parentConfiguration, _i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info, }) => super.toString(); } class _FakeOffset_6 extends _i1.SmartFake implements _i2.Offset { _FakeOffset_6( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeBorderSide_7 extends _i1.SmartFake implements _i3.BorderSide { _FakeBorderSide_7( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info}) => super.toString(); } class _FakeTextStyle_8 extends _i1.SmartFake implements _i3.TextStyle { _FakeTextStyle_8( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info}) => super.toString(); } /// A class which mocks [Canvas]. /// /// See the documentation for Mockito's code generation for more information. class MockCanvas extends _i1.Mock implements _i2.Canvas { MockCanvas() { _i1.throwOnMissingStub(this); } @override void save() => super.noSuchMethod( Invocation.method( #save, [], ), returnValueForMissingStub: null, ); @override void saveLayer( _i2.Rect? bounds, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #saveLayer, [ bounds, paint, ], ), returnValueForMissingStub: null, ); @override void restore() => super.noSuchMethod( Invocation.method( #restore, [], ), returnValueForMissingStub: null, ); @override void restoreToCount(int? count) => super.noSuchMethod( Invocation.method( #restoreToCount, [count], ), returnValueForMissingStub: null, ); @override int getSaveCount() => (super.noSuchMethod( Invocation.method( #getSaveCount, [], ), returnValue: 0, ) as int); @override void translate( double? dx, double? dy, ) => super.noSuchMethod( Invocation.method( #translate, [ dx, dy, ], ), returnValueForMissingStub: null, ); @override void scale( double? sx, [ double? sy, ]) => super.noSuchMethod( Invocation.method( #scale, [ sx, sy, ], ), returnValueForMissingStub: null, ); @override void rotate(double? radians) => super.noSuchMethod( Invocation.method( #rotate, [radians], ), returnValueForMissingStub: null, ); @override void skew( double? sx, double? sy, ) => super.noSuchMethod( Invocation.method( #skew, [ sx, sy, ], ), returnValueForMissingStub: null, ); @override void transform(_i5.Float64List? matrix4) => super.noSuchMethod( Invocation.method( #transform, [matrix4], ), returnValueForMissingStub: null, ); @override _i5.Float64List getTransform() => (super.noSuchMethod( Invocation.method( #getTransform, [], ), returnValue: _i5.Float64List(0), ) as _i5.Float64List); @override void clipRect( _i2.Rect? rect, { _i2.ClipOp? clipOp = _i2.ClipOp.intersect, bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipRect, [rect], { #clipOp: clipOp, #doAntiAlias: doAntiAlias, }, ), returnValueForMissingStub: null, ); @override void clipRRect( _i2.RRect? rrect, { bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipRRect, [rrect], {#doAntiAlias: doAntiAlias}, ), returnValueForMissingStub: null, ); @override void clipRSuperellipse( _i2.RSuperellipse? rsuperellipse, { bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipRSuperellipse, [rsuperellipse], {#doAntiAlias: doAntiAlias}, ), returnValueForMissingStub: null, ); @override void clipPath( _i2.Path? path, { bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipPath, [path], {#doAntiAlias: doAntiAlias}, ), returnValueForMissingStub: null, ); @override _i2.Rect getLocalClipBounds() => (super.noSuchMethod( Invocation.method( #getLocalClipBounds, [], ), returnValue: _FakeRect_0( this, Invocation.method( #getLocalClipBounds, [], ), ), ) as _i2.Rect); @override _i2.Rect getDestinationClipBounds() => (super.noSuchMethod( Invocation.method( #getDestinationClipBounds, [], ), returnValue: _FakeRect_0( this, Invocation.method( #getDestinationClipBounds, [], ), ), ) as _i2.Rect); @override void drawColor( _i2.Color? color, _i2.BlendMode? blendMode, ) => super.noSuchMethod( Invocation.method( #drawColor, [ color, blendMode, ], ), returnValueForMissingStub: null, ); @override void drawLine( _i2.Offset? p1, _i2.Offset? p2, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawLine, [ p1, p2, paint, ], ), returnValueForMissingStub: null, ); @override void drawPaint(_i2.Paint? paint) => super.noSuchMethod( Invocation.method( #drawPaint, [paint], ), returnValueForMissingStub: null, ); @override void drawRect( _i2.Rect? rect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRect, [ rect, paint, ], ), returnValueForMissingStub: null, ); @override void drawRRect( _i2.RRect? rrect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRRect, [ rrect, paint, ], ), returnValueForMissingStub: null, ); @override void drawDRRect( _i2.RRect? outer, _i2.RRect? inner, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawDRRect, [ outer, inner, paint, ], ), returnValueForMissingStub: null, ); @override void drawRSuperellipse( _i2.RSuperellipse? rsuperellipse, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRSuperellipse, [ rsuperellipse, paint, ], ), returnValueForMissingStub: null, ); @override void drawOval( _i2.Rect? rect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawOval, [ rect, paint, ], ), returnValueForMissingStub: null, ); @override void drawCircle( _i2.Offset? c, double? radius, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawCircle, [ c, radius, paint, ], ), returnValueForMissingStub: null, ); @override void drawArc( _i2.Rect? rect, double? startAngle, double? sweepAngle, bool? useCenter, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawArc, [ rect, startAngle, sweepAngle, useCenter, paint, ], ), returnValueForMissingStub: null, ); @override void drawPath( _i2.Path? path, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawPath, [ path, paint, ], ), returnValueForMissingStub: null, ); @override void drawImage( _i2.Image? image, _i2.Offset? offset, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawImage, [ image, offset, paint, ], ), returnValueForMissingStub: null, ); @override void drawImageRect( _i2.Image? image, _i2.Rect? src, _i2.Rect? dst, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawImageRect, [ image, src, dst, paint, ], ), returnValueForMissingStub: null, ); @override void drawImageNine( _i2.Image? image, _i2.Rect? center, _i2.Rect? dst, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawImageNine, [ image, center, dst, paint, ], ), returnValueForMissingStub: null, ); @override void drawPicture(_i2.Picture? picture) => super.noSuchMethod( Invocation.method( #drawPicture, [picture], ), returnValueForMissingStub: null, ); @override void drawParagraph( _i2.Paragraph? paragraph, _i2.Offset? offset, ) => super.noSuchMethod( Invocation.method( #drawParagraph, [ paragraph, offset, ], ), returnValueForMissingStub: null, ); @override void drawPoints( _i2.PointMode? pointMode, List<_i2.Offset>? points, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawPoints, [ pointMode, points, paint, ], ), returnValueForMissingStub: null, ); @override void drawRawPoints( _i2.PointMode? pointMode, _i5.Float32List? points, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRawPoints, [ pointMode, points, paint, ], ), returnValueForMissingStub: null, ); @override void drawVertices( _i2.Vertices? vertices, _i2.BlendMode? blendMode, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawVertices, [ vertices, blendMode, paint, ], ), returnValueForMissingStub: null, ); @override void drawAtlas( _i2.Image? atlas, List<_i2.RSTransform>? transforms, List<_i2.Rect>? rects, List<_i2.Color>? colors, _i2.BlendMode? blendMode, _i2.Rect? cullRect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawAtlas, [ atlas, transforms, rects, colors, blendMode, cullRect, paint, ], ), returnValueForMissingStub: null, ); @override void drawRawAtlas( _i2.Image? atlas, _i5.Float32List? rstTransforms, _i5.Float32List? rects, _i5.Int32List? colors, _i2.BlendMode? blendMode, _i2.Rect? cullRect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRawAtlas, [ atlas, rstTransforms, rects, colors, blendMode, cullRect, paint, ], ), returnValueForMissingStub: null, ); @override void drawShadow( _i2.Path? path, _i2.Color? color, double? elevation, bool? transparentOccluder, ) => super.noSuchMethod( Invocation.method( #drawShadow, [ path, color, elevation, transparentOccluder, ], ), returnValueForMissingStub: null, ); } /// A class which mocks [CanvasWrapper]. /// /// See the documentation for Mockito's code generation for more information. class MockCanvasWrapper extends _i1.Mock implements _i6.CanvasWrapper { MockCanvasWrapper() { _i1.throwOnMissingStub(this); } @override _i2.Canvas get canvas => (super.noSuchMethod( Invocation.getter(#canvas), returnValue: _FakeCanvas_1( this, Invocation.getter(#canvas), ), ) as _i2.Canvas); @override _i2.Size get size => (super.noSuchMethod( Invocation.getter(#size), returnValue: _FakeSize_2( this, Invocation.getter(#size), ), ) as _i2.Size); @override void drawRRect( _i2.RRect? rrect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRRect, [ rrect, paint, ], ), returnValueForMissingStub: null, ); @override void save() => super.noSuchMethod( Invocation.method( #save, [], ), returnValueForMissingStub: null, ); @override void restore() => super.noSuchMethod( Invocation.method( #restore, [], ), returnValueForMissingStub: null, ); @override void clipRect( _i2.Rect? rect, { _i2.ClipOp? clipOp = _i2.ClipOp.intersect, bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipRect, [rect], { #clipOp: clipOp, #doAntiAlias: doAntiAlias, }, ), returnValueForMissingStub: null, ); @override void translate( double? dx, double? dy, ) => super.noSuchMethod( Invocation.method( #translate, [ dx, dy, ], ), returnValueForMissingStub: null, ); @override void rotate(double? radius) => super.noSuchMethod( Invocation.method( #rotate, [radius], ), returnValueForMissingStub: null, ); @override void drawPath( _i2.Path? path, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawPath, [ path, paint, ], ), returnValueForMissingStub: null, ); @override void saveLayer( _i2.Rect? bounds, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #saveLayer, [ bounds, paint, ], ), returnValueForMissingStub: null, ); @override void drawPicture(_i2.Picture? picture) => super.noSuchMethod( Invocation.method( #drawPicture, [picture], ), returnValueForMissingStub: null, ); @override void drawImage( _i2.Image? image, _i2.Offset? offset, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawImage, [ image, offset, paint, ], ), returnValueForMissingStub: null, ); @override void clipPath( _i2.Path? path, { bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipPath, [path], {#doAntiAlias: doAntiAlias}, ), returnValueForMissingStub: null, ); @override void drawRect( _i2.Rect? rect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRect, [ rect, paint, ], ), returnValueForMissingStub: null, ); @override void drawLine( _i2.Offset? p1, _i2.Offset? p2, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawLine, [ p1, p2, paint, ], ), returnValueForMissingStub: null, ); @override void drawCircle( _i2.Offset? center, double? radius, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawCircle, [ center, radius, paint, ], ), returnValueForMissingStub: null, ); @override void drawArc( _i2.Rect? rect, double? startAngle, double? sweepAngle, bool? useCenter, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawArc, [ rect, startAngle, sweepAngle, useCenter, paint, ], ), returnValueForMissingStub: null, ); @override void drawText( _i3.TextPainter? tp, _i2.Offset? offset, [ double? rotateAngle, ]) => super.noSuchMethod( Invocation.method( #drawText, [ tp, offset, rotateAngle, ], ), returnValueForMissingStub: null, ); @override void drawVerticalText( _i3.TextPainter? tp, _i2.Offset? offset, [ double? rotateAngle = 90.0, ]) => super.noSuchMethod( Invocation.method( #drawVerticalText, [ tp, offset, rotateAngle, ], ), returnValueForMissingStub: null, ); @override void drawDot( _i7.FlDotPainter? painter, _i7.FlSpot? spot, _i2.Offset? offset, ) => super.noSuchMethod( Invocation.method( #drawDot, [ painter, spot, offset, ], ), returnValueForMissingStub: null, ); @override void drawErrorIndicator( _i7.FlSpotErrorRangePainter? painter, _i7.FlSpot? origin, _i2.Offset? offset, _i2.Rect? errorRelativeRect, _i7.AxisChartData? axisData, ) => super.noSuchMethod( Invocation.method( #drawErrorIndicator, [ painter, origin, offset, errorRelativeRect, axisData, ], ), returnValueForMissingStub: null, ); @override void drawRotated({ required _i2.Size? size, _i2.Offset? rotationOffset = _i2.Offset.zero, _i2.Offset? drawOffset = _i2.Offset.zero, required double? angle, required _i6.DrawCallback? drawCallback, }) => super.noSuchMethod( Invocation.method( #drawRotated, [], { #size: size, #rotationOffset: rotationOffset, #drawOffset: drawOffset, #angle: angle, #drawCallback: drawCallback, }, ), returnValueForMissingStub: null, ); @override void drawDashedLine( _i2.Offset? from, _i2.Offset? to, _i2.Paint? painter, List? dashArray, ) => super.noSuchMethod( Invocation.method( #drawDashedLine, [ from, to, painter, dashArray, ], ), returnValueForMissingStub: null, ); } /// A class which mocks [BuildContext]. /// /// See the documentation for Mockito's code generation for more information. class MockBuildContext extends _i1.Mock implements _i3.BuildContext { MockBuildContext() { _i1.throwOnMissingStub(this); } @override _i3.Widget get widget => (super.noSuchMethod( Invocation.getter(#widget), returnValue: _FakeWidget_3( this, Invocation.getter(#widget), ), ) as _i3.Widget); @override bool get mounted => (super.noSuchMethod( Invocation.getter(#mounted), returnValue: false, ) as bool); @override bool get debugDoingBuild => (super.noSuchMethod( Invocation.getter(#debugDoingBuild), returnValue: false, ) as bool); @override _i3.InheritedWidget dependOnInheritedElement( _i3.InheritedElement? ancestor, { Object? aspect, }) => (super.noSuchMethod( Invocation.method( #dependOnInheritedElement, [ancestor], {#aspect: aspect}, ), returnValue: _FakeInheritedWidget_4( this, Invocation.method( #dependOnInheritedElement, [ancestor], {#aspect: aspect}, ), ), ) as _i3.InheritedWidget); @override void visitAncestorElements(_i3.ConditionalElementVisitor? visitor) => super.noSuchMethod( Invocation.method( #visitAncestorElements, [visitor], ), returnValueForMissingStub: null, ); @override void visitChildElements(_i3.ElementVisitor? visitor) => super.noSuchMethod( Invocation.method( #visitChildElements, [visitor], ), returnValueForMissingStub: null, ); @override void dispatchNotification(_i3.Notification? notification) => super.noSuchMethod( Invocation.method( #dispatchNotification, [notification], ), returnValueForMissingStub: null, ); @override _i3.DiagnosticsNode describeElement( String? name, { _i4.DiagnosticsTreeStyle? style = _i4.DiagnosticsTreeStyle.errorProperty, }) => (super.noSuchMethod( Invocation.method( #describeElement, [name], {#style: style}, ), returnValue: _FakeDiagnosticsNode_5( this, Invocation.method( #describeElement, [name], {#style: style}, ), ), ) as _i3.DiagnosticsNode); @override _i3.DiagnosticsNode describeWidget( String? name, { _i4.DiagnosticsTreeStyle? style = _i4.DiagnosticsTreeStyle.errorProperty, }) => (super.noSuchMethod( Invocation.method( #describeWidget, [name], {#style: style}, ), returnValue: _FakeDiagnosticsNode_5( this, Invocation.method( #describeWidget, [name], {#style: style}, ), ), ) as _i3.DiagnosticsNode); @override List<_i3.DiagnosticsNode> describeMissingAncestor( {required Type? expectedAncestorType}) => (super.noSuchMethod( Invocation.method( #describeMissingAncestor, [], {#expectedAncestorType: expectedAncestorType}, ), returnValue: <_i3.DiagnosticsNode>[], ) as List<_i3.DiagnosticsNode>); @override _i3.DiagnosticsNode describeOwnershipChain(String? name) => (super.noSuchMethod( Invocation.method( #describeOwnershipChain, [name], ), returnValue: _FakeDiagnosticsNode_5( this, Invocation.method( #describeOwnershipChain, [name], ), ), ) as _i3.DiagnosticsNode); } /// A class which mocks [Utils]. /// /// See the documentation for Mockito's code generation for more information. class MockUtils extends _i1.Mock implements _i8.Utils { MockUtils() { _i1.throwOnMissingStub(this); } @override double radians(double? degrees) => (super.noSuchMethod( Invocation.method( #radians, [degrees], ), returnValue: 0.0, ) as double); @override double degrees(double? radians) => (super.noSuchMethod( Invocation.method( #degrees, [radians], ), returnValue: 0.0, ) as double); @override double translateRotatedPosition( double? size, double? degree, ) => (super.noSuchMethod( Invocation.method( #translateRotatedPosition, [ size, degree, ], ), returnValue: 0.0, ) as double); @override _i2.Offset calculateRotationOffset( _i2.Size? size, double? degree, ) => (super.noSuchMethod( Invocation.method( #calculateRotationOffset, [ size, degree, ], ), returnValue: _FakeOffset_6( this, Invocation.method( #calculateRotationOffset, [ size, degree, ], ), ), ) as _i2.Offset); @override _i3.BorderRadius? normalizeBorderRadius( _i3.BorderRadius? borderRadius, double? width, ) => (super.noSuchMethod(Invocation.method( #normalizeBorderRadius, [ borderRadius, width, ], )) as _i3.BorderRadius?); @override _i3.BorderSide normalizeBorderSide( _i3.BorderSide? borderSide, double? width, ) => (super.noSuchMethod( Invocation.method( #normalizeBorderSide, [ borderSide, width, ], ), returnValue: _FakeBorderSide_7( this, Invocation.method( #normalizeBorderSide, [ borderSide, width, ], ), ), ) as _i3.BorderSide); @override double getEfficientInterval( double? axisViewSize, double? diffInAxis, { double? pixelPerInterval = 40.0, }) => (super.noSuchMethod( Invocation.method( #getEfficientInterval, [ axisViewSize, diffInAxis, ], {#pixelPerInterval: pixelPerInterval}, ), returnValue: 0.0, ) as double); @override double roundInterval(double? input) => (super.noSuchMethod( Invocation.method( #roundInterval, [input], ), returnValue: 0.0, ) as double); @override int getFractionDigits(double? value) => (super.noSuchMethod( Invocation.method( #getFractionDigits, [value], ), returnValue: 0, ) as int); @override String formatNumber( double? axisMin, double? axisMax, double? axisValue, ) => (super.noSuchMethod( Invocation.method( #formatNumber, [ axisMin, axisMax, axisValue, ], ), returnValue: _i9.dummyValue( this, Invocation.method( #formatNumber, [ axisMin, axisMax, axisValue, ], ), ), ) as String); @override _i3.TextStyle getThemeAwareTextStyle( _i3.BuildContext? context, _i3.TextStyle? providedStyle, ) => (super.noSuchMethod( Invocation.method( #getThemeAwareTextStyle, [ context, providedStyle, ], ), returnValue: _FakeTextStyle_8( this, Invocation.method( #getThemeAwareTextStyle, [ context, providedStyle, ], ), ), ) as _i3.TextStyle); @override double getBestInitialIntervalValue( double? min, double? max, double? interval, { double? baseline = 0.0, }) => (super.noSuchMethod( Invocation.method( #getBestInitialIntervalValue, [ min, max, interval, ], {#baseline: baseline}, ), returnValue: 0.0, ) as double); @override double convertRadiusToSigma(double? radius) => (super.noSuchMethod( Invocation.method( #convertRadiusToSigma, [radius], ), returnValue: 0.0, ) as double); } ================================================ FILE: test/chart/bar_chart/bar_chart_renderer_test.dart ================================================ import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/chart/bar_chart/bar_chart_painter.dart'; import 'package:fl_chart/src/chart/bar_chart/bar_chart_renderer.dart'; import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart'; import 'package:fl_chart/src/utils/canvas_wrapper.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import '../data_pool.dart'; import 'bar_chart_renderer_test.mocks.dart'; @GenerateMocks([Canvas, PaintingContext, BuildContext, BarChartPainter]) void main() { group('BarChartRenderer', () { final data = BarChartData( titlesData: const FlTitlesData( leftTitles: AxisTitles( sideTitles: SideTitles(reservedSize: 20, showTitles: true), ), rightTitles: AxisTitles( sideTitles: SideTitles(reservedSize: 64, showTitles: true), ), topTitles: AxisTitles(), bottomTitles: AxisTitles(), ), ); final targetData = BarChartData( titlesData: const FlTitlesData( leftTitles: AxisTitles( sideTitles: SideTitles(reservedSize: 8, showTitles: true), ), rightTitles: AxisTitles( sideTitles: SideTitles(reservedSize: 20, showTitles: true), ), topTitles: AxisTitles(), bottomTitles: AxisTitles(), ), barTouchData: const BarTouchData(enabled: false), ); const textScaler = TextScaler.linear(4); final mockBuildContext = MockBuildContext(); final renderBarChart = RenderBarChart( mockBuildContext, data, targetData, textScaler, null, canBeScaled: false, ); final mockPainter = MockBarChartPainter(); final mockPaintingContext = MockPaintingContext(); final mockCanvas = MockCanvas(); const mockSize = Size(44, 44); when(mockPaintingContext.canvas).thenAnswer((realInvocation) => mockCanvas); renderBarChart ..mockTestSize = mockSize ..painter = mockPainter; test('test 1 correct data set', () { expect(renderBarChart.data == data, true); expect(renderBarChart.data == targetData, false); expect(renderBarChart.targetData == targetData, true); expect(renderBarChart.textScaler == textScaler, true); expect(renderBarChart.paintHolder.data == data, true); expect(renderBarChart.paintHolder.targetData == targetData, true); expect(renderBarChart.paintHolder.textScaler == textScaler, true); expect(renderBarChart.hitTestSelf(Offset.zero), false); }); test('test 2 check paint function', () { renderBarChart.paint(mockPaintingContext, const Offset(10, 10)); verify(mockCanvas.save()).called(1); verify(mockCanvas.translate(10, 10)).called(1); final result = verify(mockPainter.paint(any, captureAny, captureAny)); expect(result.callCount, 1); final canvasWrapper = result.captured[0] as CanvasWrapper; expect(canvasWrapper.size, const Size(44, 44)); expect(canvasWrapper.canvas, mockCanvas); final paintHolder = result.captured[1] as PaintHolder; expect(paintHolder.data, data); expect(paintHolder.targetData, targetData); expect(paintHolder.textScaler, textScaler); verify(mockCanvas.restore()).called(1); }); test('test 3 check getResponseAtLocation function', () { final results = >[]; when(mockPainter.handleTouch(captureAny, captureAny, captureAny)) .thenAnswer((inv) { results.add({ 'local_position': inv.positionalArguments[0] as Offset, 'size': inv.positionalArguments[1] as Size, 'paint_holder': inv.positionalArguments[2] as PaintHolder, }); return MockData.barTouchedSpot; }); when(mockPainter.getChartCoordinateFromPixel(any, any, any)) .thenAnswer((_) => const Offset(10, 10)); final touchResponse = renderBarChart.getResponseAtLocation(MockData.offset1); expect(touchResponse.spot, MockData.barTouchedSpot); expect(touchResponse.touchChartCoordinate, const Offset(10, 10)); expect(results[0]['local_position'] as Offset, MockData.offset1); expect(results[0]['size'] as Size, mockSize); final paintHolder = results[0]['paint_holder'] as PaintHolder; expect(paintHolder.data, data); expect(paintHolder.targetData, targetData); expect(paintHolder.textScaler, textScaler); }); test('test 4 check setters', () { renderBarChart ..data = targetData ..targetData = data ..textScaler = const TextScaler.linear(22); expect(renderBarChart.data, targetData); expect(renderBarChart.targetData, data); expect(renderBarChart.textScaler, const TextScaler.linear(22)); }); test('passes chart virtual rect to paint holder', () { final rect1 = Offset.zero & const Size(100, 100); final renderBarChart = RenderBarChart( mockBuildContext, data, targetData, textScaler, null, canBeScaled: false, ); expect(renderBarChart.chartVirtualRect, isNull); expect(renderBarChart.paintHolder.chartVirtualRect, isNull); renderBarChart.chartVirtualRect = rect1; expect(renderBarChart.chartVirtualRect, rect1); expect(renderBarChart.paintHolder.chartVirtualRect, rect1); }); test('uses canBeScaled', () { final renderBarChart = RenderBarChart( mockBuildContext, data, targetData, textScaler, null, canBeScaled: false, ); expect(renderBarChart.canBeScaled, false); renderBarChart.canBeScaled = true; expect(renderBarChart.canBeScaled, true); }); }); } ================================================ FILE: test/chart/bar_chart/bar_chart_renderer_test.mocks.dart ================================================ // Mocks generated by Mockito 5.4.6 from annotations // in fl_chart/test/chart/bar_chart/bar_chart_renderer_test.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:typed_data' as _i7; import 'dart:ui' as _i2; import 'package:fl_chart/fl_chart.dart' as _i13; import 'package:fl_chart/src/chart/bar_chart/bar_chart_painter.dart' as _i10; import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart' as _i12; import 'package:fl_chart/src/utils/canvas_wrapper.dart' as _i11; import 'package:flutter/foundation.dart' as _i5; import 'package:flutter/gestures.dart' as _i8; import 'package:flutter/material.dart' as _i6; import 'package:flutter/rendering.dart' as _i3; import 'package:flutter/src/rendering/layer.dart' as _i4; import 'package:flutter/src/widgets/notification_listener.dart' as _i9; import 'package:mockito/mockito.dart' as _i1; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values // ignore_for_file: avoid_setters_without_getters // ignore_for_file: comment_references // ignore_for_file: deprecated_member_use // ignore_for_file: deprecated_member_use_from_same_package // ignore_for_file: implementation_imports // ignore_for_file: invalid_use_of_visible_for_testing_member // ignore_for_file: must_be_immutable // ignore_for_file: prefer_const_constructors // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class // ignore_for_file: invalid_use_of_internal_member class _FakeRect_0 extends _i1.SmartFake implements _i2.Rect { _FakeRect_0( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeCanvas_1 extends _i1.SmartFake implements _i2.Canvas { _FakeCanvas_1( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakePaintingContext_2 extends _i1.SmartFake implements _i3.PaintingContext { _FakePaintingContext_2( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeColorFilterLayer_3 extends _i1.SmartFake implements _i4.ColorFilterLayer { _FakeColorFilterLayer_3( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => super.toString(); } class _FakeOpacityLayer_4 extends _i1.SmartFake implements _i4.OpacityLayer { _FakeOpacityLayer_4( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => super.toString(); } class _FakeWidget_5 extends _i1.SmartFake implements _i6.Widget { _FakeWidget_5( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => super.toString(); } class _FakeInheritedWidget_6 extends _i1.SmartFake implements _i6.InheritedWidget { _FakeInheritedWidget_6( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => super.toString(); } class _FakeDiagnosticsNode_7 extends _i1.SmartFake implements _i5.DiagnosticsNode { _FakeDiagnosticsNode_7( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({ _i5.TextTreeConfiguration? parentConfiguration, _i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info, }) => super.toString(); } class _FakeOffset_8 extends _i1.SmartFake implements _i2.Offset { _FakeOffset_8( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } /// A class which mocks [Canvas]. /// /// See the documentation for Mockito's code generation for more information. class MockCanvas extends _i1.Mock implements _i2.Canvas { MockCanvas() { _i1.throwOnMissingStub(this); } @override void save() => super.noSuchMethod( Invocation.method( #save, [], ), returnValueForMissingStub: null, ); @override void saveLayer( _i2.Rect? bounds, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #saveLayer, [ bounds, paint, ], ), returnValueForMissingStub: null, ); @override void restore() => super.noSuchMethod( Invocation.method( #restore, [], ), returnValueForMissingStub: null, ); @override void restoreToCount(int? count) => super.noSuchMethod( Invocation.method( #restoreToCount, [count], ), returnValueForMissingStub: null, ); @override int getSaveCount() => (super.noSuchMethod( Invocation.method( #getSaveCount, [], ), returnValue: 0, ) as int); @override void translate( double? dx, double? dy, ) => super.noSuchMethod( Invocation.method( #translate, [ dx, dy, ], ), returnValueForMissingStub: null, ); @override void scale( double? sx, [ double? sy, ]) => super.noSuchMethod( Invocation.method( #scale, [ sx, sy, ], ), returnValueForMissingStub: null, ); @override void rotate(double? radians) => super.noSuchMethod( Invocation.method( #rotate, [radians], ), returnValueForMissingStub: null, ); @override void skew( double? sx, double? sy, ) => super.noSuchMethod( Invocation.method( #skew, [ sx, sy, ], ), returnValueForMissingStub: null, ); @override void transform(_i7.Float64List? matrix4) => super.noSuchMethod( Invocation.method( #transform, [matrix4], ), returnValueForMissingStub: null, ); @override _i7.Float64List getTransform() => (super.noSuchMethod( Invocation.method( #getTransform, [], ), returnValue: _i7.Float64List(0), ) as _i7.Float64List); @override void clipRect( _i2.Rect? rect, { _i2.ClipOp? clipOp = _i2.ClipOp.intersect, bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipRect, [rect], { #clipOp: clipOp, #doAntiAlias: doAntiAlias, }, ), returnValueForMissingStub: null, ); @override void clipRRect( _i2.RRect? rrect, { bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipRRect, [rrect], {#doAntiAlias: doAntiAlias}, ), returnValueForMissingStub: null, ); @override void clipRSuperellipse( _i2.RSuperellipse? rsuperellipse, { bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipRSuperellipse, [rsuperellipse], {#doAntiAlias: doAntiAlias}, ), returnValueForMissingStub: null, ); @override void clipPath( _i2.Path? path, { bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipPath, [path], {#doAntiAlias: doAntiAlias}, ), returnValueForMissingStub: null, ); @override _i2.Rect getLocalClipBounds() => (super.noSuchMethod( Invocation.method( #getLocalClipBounds, [], ), returnValue: _FakeRect_0( this, Invocation.method( #getLocalClipBounds, [], ), ), ) as _i2.Rect); @override _i2.Rect getDestinationClipBounds() => (super.noSuchMethod( Invocation.method( #getDestinationClipBounds, [], ), returnValue: _FakeRect_0( this, Invocation.method( #getDestinationClipBounds, [], ), ), ) as _i2.Rect); @override void drawColor( _i2.Color? color, _i2.BlendMode? blendMode, ) => super.noSuchMethod( Invocation.method( #drawColor, [ color, blendMode, ], ), returnValueForMissingStub: null, ); @override void drawLine( _i2.Offset? p1, _i2.Offset? p2, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawLine, [ p1, p2, paint, ], ), returnValueForMissingStub: null, ); @override void drawPaint(_i2.Paint? paint) => super.noSuchMethod( Invocation.method( #drawPaint, [paint], ), returnValueForMissingStub: null, ); @override void drawRect( _i2.Rect? rect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRect, [ rect, paint, ], ), returnValueForMissingStub: null, ); @override void drawRRect( _i2.RRect? rrect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRRect, [ rrect, paint, ], ), returnValueForMissingStub: null, ); @override void drawDRRect( _i2.RRect? outer, _i2.RRect? inner, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawDRRect, [ outer, inner, paint, ], ), returnValueForMissingStub: null, ); @override void drawRSuperellipse( _i2.RSuperellipse? rsuperellipse, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRSuperellipse, [ rsuperellipse, paint, ], ), returnValueForMissingStub: null, ); @override void drawOval( _i2.Rect? rect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawOval, [ rect, paint, ], ), returnValueForMissingStub: null, ); @override void drawCircle( _i2.Offset? c, double? radius, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawCircle, [ c, radius, paint, ], ), returnValueForMissingStub: null, ); @override void drawArc( _i2.Rect? rect, double? startAngle, double? sweepAngle, bool? useCenter, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawArc, [ rect, startAngle, sweepAngle, useCenter, paint, ], ), returnValueForMissingStub: null, ); @override void drawPath( _i2.Path? path, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawPath, [ path, paint, ], ), returnValueForMissingStub: null, ); @override void drawImage( _i2.Image? image, _i2.Offset? offset, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawImage, [ image, offset, paint, ], ), returnValueForMissingStub: null, ); @override void drawImageRect( _i2.Image? image, _i2.Rect? src, _i2.Rect? dst, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawImageRect, [ image, src, dst, paint, ], ), returnValueForMissingStub: null, ); @override void drawImageNine( _i2.Image? image, _i2.Rect? center, _i2.Rect? dst, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawImageNine, [ image, center, dst, paint, ], ), returnValueForMissingStub: null, ); @override void drawPicture(_i2.Picture? picture) => super.noSuchMethod( Invocation.method( #drawPicture, [picture], ), returnValueForMissingStub: null, ); @override void drawParagraph( _i2.Paragraph? paragraph, _i2.Offset? offset, ) => super.noSuchMethod( Invocation.method( #drawParagraph, [ paragraph, offset, ], ), returnValueForMissingStub: null, ); @override void drawPoints( _i2.PointMode? pointMode, List<_i2.Offset>? points, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawPoints, [ pointMode, points, paint, ], ), returnValueForMissingStub: null, ); @override void drawRawPoints( _i2.PointMode? pointMode, _i7.Float32List? points, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRawPoints, [ pointMode, points, paint, ], ), returnValueForMissingStub: null, ); @override void drawVertices( _i2.Vertices? vertices, _i2.BlendMode? blendMode, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawVertices, [ vertices, blendMode, paint, ], ), returnValueForMissingStub: null, ); @override void drawAtlas( _i2.Image? atlas, List<_i2.RSTransform>? transforms, List<_i2.Rect>? rects, List<_i2.Color>? colors, _i2.BlendMode? blendMode, _i2.Rect? cullRect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawAtlas, [ atlas, transforms, rects, colors, blendMode, cullRect, paint, ], ), returnValueForMissingStub: null, ); @override void drawRawAtlas( _i2.Image? atlas, _i7.Float32List? rstTransforms, _i7.Float32List? rects, _i7.Int32List? colors, _i2.BlendMode? blendMode, _i2.Rect? cullRect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRawAtlas, [ atlas, rstTransforms, rects, colors, blendMode, cullRect, paint, ], ), returnValueForMissingStub: null, ); @override void drawShadow( _i2.Path? path, _i2.Color? color, double? elevation, bool? transparentOccluder, ) => super.noSuchMethod( Invocation.method( #drawShadow, [ path, color, elevation, transparentOccluder, ], ), returnValueForMissingStub: null, ); } /// A class which mocks [PaintingContext]. /// /// See the documentation for Mockito's code generation for more information. class MockPaintingContext extends _i1.Mock implements _i3.PaintingContext { MockPaintingContext() { _i1.throwOnMissingStub(this); } @override _i2.Rect get estimatedBounds => (super.noSuchMethod( Invocation.getter(#estimatedBounds), returnValue: _FakeRect_0( this, Invocation.getter(#estimatedBounds), ), ) as _i2.Rect); @override _i2.Canvas get canvas => (super.noSuchMethod( Invocation.getter(#canvas), returnValue: _FakeCanvas_1( this, Invocation.getter(#canvas), ), ) as _i2.Canvas); @override void paintChild( _i3.RenderObject? child, _i2.Offset? offset, ) => super.noSuchMethod( Invocation.method( #paintChild, [ child, offset, ], ), returnValueForMissingStub: null, ); @override void appendLayer(_i4.Layer? layer) => super.noSuchMethod( Invocation.method( #appendLayer, [layer], ), returnValueForMissingStub: null, ); @override _i2.VoidCallback addCompositionCallback(_i4.CompositionCallback? callback) => (super.noSuchMethod( Invocation.method( #addCompositionCallback, [callback], ), returnValue: () {}, ) as _i2.VoidCallback); @override void stopRecordingIfNeeded() => super.noSuchMethod( Invocation.method( #stopRecordingIfNeeded, [], ), returnValueForMissingStub: null, ); @override void setIsComplexHint() => super.noSuchMethod( Invocation.method( #setIsComplexHint, [], ), returnValueForMissingStub: null, ); @override void setWillChangeHint() => super.noSuchMethod( Invocation.method( #setWillChangeHint, [], ), returnValueForMissingStub: null, ); @override void addLayer(_i4.Layer? layer) => super.noSuchMethod( Invocation.method( #addLayer, [layer], ), returnValueForMissingStub: null, ); @override void pushLayer( _i4.ContainerLayer? childLayer, _i3.PaintingContextCallback? painter, _i2.Offset? offset, { _i2.Rect? childPaintBounds, }) => super.noSuchMethod( Invocation.method( #pushLayer, [ childLayer, painter, offset, ], {#childPaintBounds: childPaintBounds}, ), returnValueForMissingStub: null, ); @override _i3.PaintingContext createChildContext( _i4.ContainerLayer? childLayer, _i2.Rect? bounds, ) => (super.noSuchMethod( Invocation.method( #createChildContext, [ childLayer, bounds, ], ), returnValue: _FakePaintingContext_2( this, Invocation.method( #createChildContext, [ childLayer, bounds, ], ), ), ) as _i3.PaintingContext); @override _i4.ClipRectLayer? pushClipRect( bool? needsCompositing, _i2.Offset? offset, _i2.Rect? clipRect, _i3.PaintingContextCallback? painter, { _i2.Clip? clipBehavior = _i2.Clip.hardEdge, _i4.ClipRectLayer? oldLayer, }) => (super.noSuchMethod(Invocation.method( #pushClipRect, [ needsCompositing, offset, clipRect, painter, ], { #clipBehavior: clipBehavior, #oldLayer: oldLayer, }, )) as _i4.ClipRectLayer?); @override _i4.ClipRRectLayer? pushClipRRect( bool? needsCompositing, _i2.Offset? offset, _i2.Rect? bounds, _i2.RRect? clipRRect, _i3.PaintingContextCallback? painter, { _i2.Clip? clipBehavior = _i2.Clip.antiAlias, _i4.ClipRRectLayer? oldLayer, }) => (super.noSuchMethod(Invocation.method( #pushClipRRect, [ needsCompositing, offset, bounds, clipRRect, painter, ], { #clipBehavior: clipBehavior, #oldLayer: oldLayer, }, )) as _i4.ClipRRectLayer?); @override _i4.ClipRSuperellipseLayer? pushClipRSuperellipse( bool? needsCompositing, _i2.Offset? offset, _i2.Rect? bounds, _i2.RSuperellipse? clipRSuperellipse, _i3.PaintingContextCallback? painter, { _i2.Clip? clipBehavior = _i2.Clip.antiAlias, _i4.ClipRSuperellipseLayer? oldLayer, }) => (super.noSuchMethod(Invocation.method( #pushClipRSuperellipse, [ needsCompositing, offset, bounds, clipRSuperellipse, painter, ], { #clipBehavior: clipBehavior, #oldLayer: oldLayer, }, )) as _i4.ClipRSuperellipseLayer?); @override _i4.ClipPathLayer? pushClipPath( bool? needsCompositing, _i2.Offset? offset, _i2.Rect? bounds, _i2.Path? clipPath, _i3.PaintingContextCallback? painter, { _i2.Clip? clipBehavior = _i2.Clip.antiAlias, _i4.ClipPathLayer? oldLayer, }) => (super.noSuchMethod(Invocation.method( #pushClipPath, [ needsCompositing, offset, bounds, clipPath, painter, ], { #clipBehavior: clipBehavior, #oldLayer: oldLayer, }, )) as _i4.ClipPathLayer?); @override _i4.ColorFilterLayer pushColorFilter( _i2.Offset? offset, _i2.ColorFilter? colorFilter, _i3.PaintingContextCallback? painter, { _i4.ColorFilterLayer? oldLayer, }) => (super.noSuchMethod( Invocation.method( #pushColorFilter, [ offset, colorFilter, painter, ], {#oldLayer: oldLayer}, ), returnValue: _FakeColorFilterLayer_3( this, Invocation.method( #pushColorFilter, [ offset, colorFilter, painter, ], {#oldLayer: oldLayer}, ), ), ) as _i4.ColorFilterLayer); @override _i4.TransformLayer? pushTransform( bool? needsCompositing, _i2.Offset? offset, _i8.Matrix4? transform, _i3.PaintingContextCallback? painter, { _i4.TransformLayer? oldLayer, }) => (super.noSuchMethod(Invocation.method( #pushTransform, [ needsCompositing, offset, transform, painter, ], {#oldLayer: oldLayer}, )) as _i4.TransformLayer?); @override _i4.OpacityLayer pushOpacity( _i2.Offset? offset, int? alpha, _i3.PaintingContextCallback? painter, { _i4.OpacityLayer? oldLayer, }) => (super.noSuchMethod( Invocation.method( #pushOpacity, [ offset, alpha, painter, ], {#oldLayer: oldLayer}, ), returnValue: _FakeOpacityLayer_4( this, Invocation.method( #pushOpacity, [ offset, alpha, painter, ], {#oldLayer: oldLayer}, ), ), ) as _i4.OpacityLayer); @override void clipPathAndPaint( _i2.Path? path, _i2.Clip? clipBehavior, _i2.Rect? bounds, _i2.VoidCallback? painter, ) => super.noSuchMethod( Invocation.method( #clipPathAndPaint, [ path, clipBehavior, bounds, painter, ], ), returnValueForMissingStub: null, ); @override void clipRRectAndPaint( _i2.RRect? rrect, _i2.Clip? clipBehavior, _i2.Rect? bounds, _i2.VoidCallback? painter, ) => super.noSuchMethod( Invocation.method( #clipRRectAndPaint, [ rrect, clipBehavior, bounds, painter, ], ), returnValueForMissingStub: null, ); @override void clipRSuperellipseAndPaint( _i2.RSuperellipse? rse, _i2.Clip? clipBehavior, _i2.Rect? bounds, _i2.VoidCallback? painter, ) => super.noSuchMethod( Invocation.method( #clipRSuperellipseAndPaint, [ rse, clipBehavior, bounds, painter, ], ), returnValueForMissingStub: null, ); @override void clipRectAndPaint( _i2.Rect? rect, _i2.Clip? clipBehavior, _i2.Rect? bounds, _i2.VoidCallback? painter, ) => super.noSuchMethod( Invocation.method( #clipRectAndPaint, [ rect, clipBehavior, bounds, painter, ], ), returnValueForMissingStub: null, ); } /// A class which mocks [BuildContext]. /// /// See the documentation for Mockito's code generation for more information. class MockBuildContext extends _i1.Mock implements _i6.BuildContext { MockBuildContext() { _i1.throwOnMissingStub(this); } @override _i6.Widget get widget => (super.noSuchMethod( Invocation.getter(#widget), returnValue: _FakeWidget_5( this, Invocation.getter(#widget), ), ) as _i6.Widget); @override bool get mounted => (super.noSuchMethod( Invocation.getter(#mounted), returnValue: false, ) as bool); @override bool get debugDoingBuild => (super.noSuchMethod( Invocation.getter(#debugDoingBuild), returnValue: false, ) as bool); @override _i6.InheritedWidget dependOnInheritedElement( _i6.InheritedElement? ancestor, { Object? aspect, }) => (super.noSuchMethod( Invocation.method( #dependOnInheritedElement, [ancestor], {#aspect: aspect}, ), returnValue: _FakeInheritedWidget_6( this, Invocation.method( #dependOnInheritedElement, [ancestor], {#aspect: aspect}, ), ), ) as _i6.InheritedWidget); @override void visitAncestorElements(_i6.ConditionalElementVisitor? visitor) => super.noSuchMethod( Invocation.method( #visitAncestorElements, [visitor], ), returnValueForMissingStub: null, ); @override void visitChildElements(_i6.ElementVisitor? visitor) => super.noSuchMethod( Invocation.method( #visitChildElements, [visitor], ), returnValueForMissingStub: null, ); @override void dispatchNotification(_i9.Notification? notification) => super.noSuchMethod( Invocation.method( #dispatchNotification, [notification], ), returnValueForMissingStub: null, ); @override _i5.DiagnosticsNode describeElement( String? name, { _i5.DiagnosticsTreeStyle? style = _i5.DiagnosticsTreeStyle.errorProperty, }) => (super.noSuchMethod( Invocation.method( #describeElement, [name], {#style: style}, ), returnValue: _FakeDiagnosticsNode_7( this, Invocation.method( #describeElement, [name], {#style: style}, ), ), ) as _i5.DiagnosticsNode); @override _i5.DiagnosticsNode describeWidget( String? name, { _i5.DiagnosticsTreeStyle? style = _i5.DiagnosticsTreeStyle.errorProperty, }) => (super.noSuchMethod( Invocation.method( #describeWidget, [name], {#style: style}, ), returnValue: _FakeDiagnosticsNode_7( this, Invocation.method( #describeWidget, [name], {#style: style}, ), ), ) as _i5.DiagnosticsNode); @override List<_i5.DiagnosticsNode> describeMissingAncestor( {required Type? expectedAncestorType}) => (super.noSuchMethod( Invocation.method( #describeMissingAncestor, [], {#expectedAncestorType: expectedAncestorType}, ), returnValue: <_i5.DiagnosticsNode>[], ) as List<_i5.DiagnosticsNode>); @override _i5.DiagnosticsNode describeOwnershipChain(String? name) => (super.noSuchMethod( Invocation.method( #describeOwnershipChain, [name], ), returnValue: _FakeDiagnosticsNode_7( this, Invocation.method( #describeOwnershipChain, [name], ), ), ) as _i5.DiagnosticsNode); } /// A class which mocks [BarChartPainter]. /// /// See the documentation for Mockito's code generation for more information. class MockBarChartPainter extends _i1.Mock implements _i10.BarChartPainter { MockBarChartPainter() { _i1.throwOnMissingStub(this); } @override void paint( _i6.BuildContext? context, _i11.CanvasWrapper? canvasWrapper, _i12.PaintHolder<_i13.BarChartData>? holder, ) => super.noSuchMethod( Invocation.method( #paint, [ context, canvasWrapper, holder, ], ), returnValueForMissingStub: null, ); @override List<_i10.GroupBarsPosition> calculateGroupAndBarsPosition( _i2.Size? viewSize, List? groupsX, List<_i13.BarChartGroupData>? barGroups, ) => (super.noSuchMethod( Invocation.method( #calculateGroupAndBarsPosition, [ viewSize, groupsX, barGroups, ], ), returnValue: <_i10.GroupBarsPosition>[], ) as List<_i10.GroupBarsPosition>); @override void drawBars( _i11.CanvasWrapper? canvasWrapper, List<_i10.GroupBarsPosition>? groupBarsPosition, _i12.PaintHolder<_i13.BarChartData>? holder, ) => super.noSuchMethod( Invocation.method( #drawBars, [ canvasWrapper, groupBarsPosition, holder, ], ), returnValueForMissingStub: null, ); @override void drawErrorIndicatorData( _i11.CanvasWrapper? canvasWrapper, List<_i10.GroupBarsPosition>? groupBarsPosition, _i12.PaintHolder<_i13.BarChartData>? holder, ) => super.noSuchMethod( Invocation.method( #drawErrorIndicatorData, [ canvasWrapper, groupBarsPosition, holder, ], ), returnValueForMissingStub: null, ); @override void drawTouchTooltip( _i6.BuildContext? context, _i11.CanvasWrapper? canvasWrapper, List<_i10.GroupBarsPosition>? groupPositions, _i13.BarTouchTooltipData? tooltipData, _i13.BarChartGroupData? showOnBarGroup, int? barGroupIndex, _i13.BarChartRodData? showOnRodData, int? barRodIndex, _i12.PaintHolder<_i13.BarChartData>? holder, ) => super.noSuchMethod( Invocation.method( #drawTouchTooltip, [ context, canvasWrapper, groupPositions, tooltipData, showOnBarGroup, barGroupIndex, showOnRodData, barRodIndex, holder, ], ), returnValueForMissingStub: null, ); @override void drawStackItemBorderStroke( _i11.CanvasWrapper? canvasWrapper, _i13.BarChartRodStackItem? stackItem, int? index, int? rodStacksSize, double? barThickSize, _i2.RRect? barRRect, _i2.Size? drawSize, _i12.PaintHolder<_i13.BarChartData>? holder, ) => super.noSuchMethod( Invocation.method( #drawStackItemBorderStroke, [ canvasWrapper, stackItem, index, rodStacksSize, barThickSize, barRRect, drawSize, holder, ], ), returnValueForMissingStub: null, ); @override _i13.BarTouchedSpot? handleTouch( _i2.Offset? localPosition, _i2.Size? size, _i12.PaintHolder<_i13.BarChartData>? holder, ) => (super.noSuchMethod(Invocation.method( #handleTouch, [ localPosition, size, holder, ], )) as _i13.BarTouchedSpot?); @override void drawGrid( _i11.CanvasWrapper? canvasWrapper, _i12.PaintHolder<_i13.BarChartData>? holder, ) => super.noSuchMethod( Invocation.method( #drawGrid, [ canvasWrapper, holder, ], ), returnValueForMissingStub: null, ); @override void drawBackground( _i11.CanvasWrapper? canvasWrapper, _i12.PaintHolder<_i13.BarChartData>? holder, ) => super.noSuchMethod( Invocation.method( #drawBackground, [ canvasWrapper, holder, ], ), returnValueForMissingStub: null, ); @override void drawRangeAnnotation( _i11.CanvasWrapper? canvasWrapper, _i12.PaintHolder<_i13.BarChartData>? holder, ) => super.noSuchMethod( Invocation.method( #drawRangeAnnotation, [ canvasWrapper, holder, ], ), returnValueForMissingStub: null, ); @override void drawExtraLines( _i6.BuildContext? context, _i11.CanvasWrapper? canvasWrapper, _i12.PaintHolder<_i13.BarChartData>? holder, ) => super.noSuchMethod( Invocation.method( #drawExtraLines, [ context, canvasWrapper, holder, ], ), returnValueForMissingStub: null, ); @override void drawHorizontalLines( _i6.BuildContext? context, _i11.CanvasWrapper? canvasWrapper, _i12.PaintHolder<_i13.BarChartData>? holder, _i2.Size? viewSize, ) => super.noSuchMethod( Invocation.method( #drawHorizontalLines, [ context, canvasWrapper, holder, viewSize, ], ), returnValueForMissingStub: null, ); @override void drawVerticalLines( _i6.BuildContext? context, _i11.CanvasWrapper? canvasWrapper, _i12.PaintHolder<_i13.BarChartData>? holder, _i2.Size? viewSize, ) => super.noSuchMethod( Invocation.method( #drawVerticalLines, [ context, canvasWrapper, holder, viewSize, ], ), returnValueForMissingStub: null, ); @override double getPixelX( double? spotX, _i2.Size? viewSize, _i12.PaintHolder<_i13.BarChartData>? holder, ) => (super.noSuchMethod( Invocation.method( #getPixelX, [ spotX, viewSize, holder, ], ), returnValue: 0.0, ) as double); @override double getPixelY( double? spotY, _i2.Size? viewSize, _i12.PaintHolder<_i13.BarChartData>? holder, ) => (super.noSuchMethod( Invocation.method( #getPixelY, [ spotY, viewSize, holder, ], ), returnValue: 0.0, ) as double); @override double getXForPixel( double? pixelX, _i2.Size? viewSize, _i12.PaintHolder<_i13.BarChartData>? holder, ) => (super.noSuchMethod( Invocation.method( #getXForPixel, [ pixelX, viewSize, holder, ], ), returnValue: 0.0, ) as double); @override double getYForPixel( double? pixelY, _i2.Size? viewSize, _i12.PaintHolder<_i13.BarChartData>? holder, ) => (super.noSuchMethod( Invocation.method( #getYForPixel, [ pixelY, viewSize, holder, ], ), returnValue: 0.0, ) as double); @override _i2.Offset getChartCoordinateFromPixel( _i2.Offset? pixelOffset, _i2.Size? viewSize, _i12.PaintHolder<_i13.BarChartData>? holder, ) => (super.noSuchMethod( Invocation.method( #getChartCoordinateFromPixel, [ pixelOffset, viewSize, holder, ], ), returnValue: _FakeOffset_8( this, Invocation.method( #getChartCoordinateFromPixel, [ pixelOffset, viewSize, holder, ], ), ), ) as _i2.Offset); @override double getTooltipLeft( double? dx, double? tooltipWidth, _i13.FLHorizontalAlignment? tooltipHorizontalAlignment, double? tooltipHorizontalOffset, ) => (super.noSuchMethod( Invocation.method( #getTooltipLeft, [ dx, tooltipWidth, tooltipHorizontalAlignment, tooltipHorizontalOffset, ], ), returnValue: 0.0, ) as double); } ================================================ FILE: test/chart/bar_chart/bar_chart_test.dart ================================================ import 'package:fl_chart/src/chart/bar_chart/bar_chart.dart'; import 'package:fl_chart/src/chart/bar_chart/bar_chart_data.dart'; import 'package:fl_chart/src/chart/bar_chart/bar_chart_renderer.dart'; import 'package:fl_chart/src/chart/base/axis_chart/axis_chart_scaffold_widget.dart'; import 'package:fl_chart/src/chart/base/axis_chart/scale_axis.dart'; import 'package:fl_chart/src/chart/base/axis_chart/transformation_config.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { Widget createTestWidget({ required BarChart chart, }) { return MaterialApp( home: chart, ); } group('BarChart', () { group('throws AssertionError for', () { final verticallyScalableAlignments = [ BarChartAlignment.start, BarChartAlignment.center, BarChartAlignment.end, ]; for (final alignment in verticallyScalableAlignments) { testWidgets('FlScaleAxis.horizontal with $alignment', (tester) async { expect( () => tester.pumpWidget( createTestWidget( chart: BarChart( BarChartData( alignment: alignment, ), transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.horizontal, ), ), ), ), throwsAssertionError, ); }); } for (final alignment in verticallyScalableAlignments) { testWidgets('FlScaleAxis.free with $alignment', (tester) async { expect( () => tester.pumpWidget( createTestWidget( chart: BarChart( BarChartData( alignment: alignment, ), transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.free, ), ), ), ), throwsAssertionError, ); }); } }); group('allows passing', () { for (final alignment in BarChartAlignment.values) { testWidgets('FlScaleAxis.none with $alignment', (tester) async { await tester.pumpWidget( createTestWidget( chart: BarChart( BarChartData(alignment: alignment), // This is for test // ignore: avoid_redundant_argument_values transformationConfig: const FlTransformationConfig( // This is for test // ignore: avoid_redundant_argument_values scaleAxis: FlScaleAxis.none, ), ), ), ); }); } for (final alignment in BarChartAlignment.values) { testWidgets('FlScaleAxis.vertical with $alignment', (tester) async { await tester.pumpWidget( createTestWidget( chart: BarChart( BarChartData(alignment: alignment), transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.vertical, ), ), ), ); }); } final scalableAlignments = [ BarChartAlignment.spaceAround, BarChartAlignment.spaceBetween, BarChartAlignment.spaceEvenly, ]; for (final alignment in scalableAlignments) { testWidgets('FlScaleAxis.free with $alignment', (tester) async { await tester.pumpWidget( createTestWidget( chart: BarChart( BarChartData(alignment: alignment), transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.free, ), ), ), ); }); } for (final alignment in scalableAlignments) { testWidgets('FlScaleAxis.horizontal with $alignment', (tester) async { await tester.pumpWidget( createTestWidget( chart: BarChart( BarChartData(alignment: alignment), transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.horizontal, ), ), ), ); }); } }); testWidgets('has correct default values', (tester) async { await tester.pumpWidget( createTestWidget( chart: BarChart( BarChartData(), ), ), ); final barChart = tester.widget(find.byType(BarChart)); expect(barChart.transformationConfig, const FlTransformationConfig()); }); testWidgets('passes interaction parameters to AxisChartScaffoldWidget', (tester) async { await tester.pumpWidget( createTestWidget( chart: BarChart( BarChartData(), ), ), ); final axisChartScaffoldWidget = tester.widget( find.byType(AxisChartScaffoldWidget), ); expect( axisChartScaffoldWidget.transformationConfig, const FlTransformationConfig(), ); await tester.pumpAndSettle(); final transformationConfig = FlTransformationConfig( scaleAxis: FlScaleAxis.free, trackpadScrollCausesScale: true, maxScale: 10, minScale: 1.5, transformationController: TransformationController(), ); await tester.pumpWidget( createTestWidget( chart: BarChart( BarChartData(), transformationConfig: transformationConfig, ), ), ); final axisChartScaffoldWidget1 = tester.widget( find.byType(AxisChartScaffoldWidget), ); expect( axisChartScaffoldWidget1.transformationConfig, transformationConfig, ); }); for (final scaleAxis in FlScaleAxis.scalingEnabledAxis) { testWidgets('passes canBeScaled true for $scaleAxis', (tester) async { await tester.pumpWidget( createTestWidget( chart: BarChart( BarChartData(), transformationConfig: FlTransformationConfig( scaleAxis: scaleAxis, ), ), ), ); final barChartLeaf = tester.widget( find.byType(BarChartLeaf), ); expect(barChartLeaf.canBeScaled, true); }); } testWidgets('passes canBeScaled false for FlScaleAxis.none', (tester) async { await tester.pumpWidget( createTestWidget( chart: BarChart( BarChartData(), // This is for test // ignore: avoid_redundant_argument_values transformationConfig: const FlTransformationConfig( // This is for test // ignore: avoid_redundant_argument_values scaleAxis: FlScaleAxis.none, ), ), ), ); final barChartLeaf = tester.widget( find.byType(BarChartLeaf), ); expect(barChartLeaf.canBeScaled, false); }); group('touch gesture', () { testWidgets('does not scale with FlScaleAxis.none', (tester) async { await tester.pumpWidget( createTestWidget( chart: BarChart( BarChartData(), ), ), ); final barChartCenterOffset = tester.getCenter(find.byType(BarChartLeaf)); final scaleStart1 = barChartCenterOffset; final scaleStart2 = barChartCenterOffset; final scaleEnd1 = barChartCenterOffset + const Offset(100, 100); final scaleEnd2 = barChartCenterOffset - const Offset(100, 100); final gesture1 = await tester.startGesture(scaleStart1); final gesture2 = await tester.startGesture(scaleStart2); await tester.pump(); await gesture1.moveTo(scaleEnd1); await gesture2.moveTo(scaleEnd2); await tester.pump(); await gesture1.up(); await gesture2.up(); await tester.pumpAndSettle(); final barChartLeaf = tester.widget( find.byType(BarChartLeaf), ); expect(barChartLeaf.chartVirtualRect, isNull); }); testWidgets('scales freely with FlScaleAxis.free', (tester) async { await tester.pumpWidget( createTestWidget( chart: BarChart( BarChartData(alignment: BarChartAlignment.spaceEvenly), transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.free, ), ), ), ); final barChartCenterOffset = tester.getCenter(find.byType(BarChartLeaf)); final scaleStart1 = barChartCenterOffset; final scaleStart2 = barChartCenterOffset; final scaleEnd1 = barChartCenterOffset + const Offset(100, 100); final scaleEnd2 = barChartCenterOffset - const Offset(100, 100); final gesture1 = await tester.startGesture(scaleStart1); final gesture2 = await tester.startGesture(scaleStart2); await tester.pump(); await gesture1.moveTo(scaleEnd1); await gesture2.moveTo(scaleEnd2); await tester.pump(); await gesture1.up(); await gesture2.up(); await tester.pumpAndSettle(); final barChartLeaf = tester.widget( find.byType(BarChartLeaf), ); final renderBox = tester.renderObject( find.byType(BarChartLeaf), ); final chartVirtualRect = barChartLeaf.chartVirtualRect!; expect(chartVirtualRect.size, greaterThan(renderBox.size)); expect(chartVirtualRect.left, isNegative); expect(chartVirtualRect.top, isNegative); }); testWidgets('scales horizontally with FlScaleAxis.horizontal', (tester) async { await tester.pumpWidget( createTestWidget( chart: BarChart( BarChartData(), transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.horizontal, ), ), ), ); final chartCenterOffset = tester.getCenter(find.byType(BarChartLeaf)); final scaleStart1 = chartCenterOffset; final scaleStart2 = chartCenterOffset; final scaleEnd1 = chartCenterOffset + const Offset(100, 100); final scaleEnd2 = chartCenterOffset - const Offset(100, 100); final gesture1 = await tester.startGesture(scaleStart1); final gesture2 = await tester.startGesture(scaleStart2); await tester.pump(); await gesture1.moveTo(scaleEnd1); await gesture2.moveTo(scaleEnd2); await tester.pump(); await gesture1.up(); await gesture2.up(); await tester.pumpAndSettle(); final barChartLeaf = tester.widget( find.byType(BarChartLeaf), ); final renderBox = tester.renderObject( find.byType(BarChartLeaf), ); final chartVirtualRect = barChartLeaf.chartVirtualRect!; expect(chartVirtualRect.size.height, renderBox.size.height); expect(chartVirtualRect.size.width, greaterThan(renderBox.size.width)); expect(chartVirtualRect.left, isNegative); expect(chartVirtualRect.top, 0); }); testWidgets('scales vertically with FlScaleAxis.vertical', (tester) async { await tester.pumpWidget( createTestWidget( chart: BarChart( BarChartData(), transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.vertical, ), ), ), ); final chartCenterOffset = tester.getCenter(find.byType(BarChartLeaf)); final scaleStart1 = chartCenterOffset; final scaleStart2 = chartCenterOffset; final scaleEnd1 = chartCenterOffset + const Offset(100, 100); final scaleEnd2 = chartCenterOffset - const Offset(100, 100); final gesture1 = await tester.startGesture(scaleStart1); final gesture2 = await tester.startGesture(scaleStart2); await tester.pump(); await gesture1.moveTo(scaleEnd1); await gesture2.moveTo(scaleEnd2); await tester.pump(); await gesture1.up(); await gesture2.up(); await tester.pumpAndSettle(); final barChartLeaf = tester.widget( find.byType(BarChartLeaf), ); final renderBox = tester.renderObject( find.byType(BarChartLeaf), ); final chartVirtualRect = barChartLeaf.chartVirtualRect!; expect( chartVirtualRect.size.height, greaterThan(renderBox.size.height), ); expect(chartVirtualRect.size.width, renderBox.size.width); expect(chartVirtualRect.left, 0); expect(chartVirtualRect.top, isNegative); }); group('pans', () { testWidgets('only horizontally with FlScaleAxis.horizontal', (tester) async { await tester.pumpWidget( createTestWidget( chart: BarChart( BarChartData(), transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.horizontal, ), ), ), ); final chartCenterOffset = tester.getCenter(find.byType(BarChartLeaf)); final scaleStart1 = chartCenterOffset; final scaleStart2 = chartCenterOffset; final scaleEnd1 = chartCenterOffset + const Offset(100, 100); final scaleEnd2 = chartCenterOffset - const Offset(100, 100); final gesture1 = await tester.startGesture(scaleStart1); final gesture2 = await tester.startGesture(scaleStart2); await gesture1.moveTo(scaleEnd1); await gesture2.moveTo(scaleEnd2); await gesture1.up(); await gesture2.up(); await tester.pumpAndSettle(); final barChartLeafBeforePan = tester.widget( find.byType(BarChartLeaf), ); final chartVirtualRectBeforePan = barChartLeafBeforePan.chartVirtualRect!; expect(chartVirtualRectBeforePan.top, 0); const panOffset = Offset(100, 100); await tester.dragFrom(chartCenterOffset, panOffset); await tester.pumpAndSettle(); final barChartLeafAfterPan = tester.widget( find.byType(BarChartLeaf), ); final chartVirtualRectAfterPan = barChartLeafAfterPan.chartVirtualRect!; expect(chartVirtualRectBeforePan.size, chartVirtualRectAfterPan.size); expect( chartVirtualRectAfterPan.left, greaterThan(chartVirtualRectBeforePan.left), ); expect(chartVirtualRectAfterPan.top, 0); }); testWidgets('only vertically with FlScaleAxis.vertical', (tester) async { await tester.pumpWidget( createTestWidget( chart: BarChart( BarChartData(), transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.vertical, ), ), ), ); final chartCenterOffset = tester.getCenter(find.byType(BarChartLeaf)); final scaleStart1 = chartCenterOffset; final scaleStart2 = chartCenterOffset; final scaleEnd1 = chartCenterOffset + const Offset(100, 100); final scaleEnd2 = chartCenterOffset - const Offset(100, 100); final gesture1 = await tester.startGesture(scaleStart1); final gesture2 = await tester.startGesture(scaleStart2); await gesture1.moveTo(scaleEnd1); await gesture2.moveTo(scaleEnd2); await gesture1.up(); await gesture2.up(); await tester.pumpAndSettle(); final barChartLeafBeforePan = tester.widget( find.byType(BarChartLeaf), ); final chartVirtualRectBeforePan = barChartLeafBeforePan.chartVirtualRect!; expect(chartVirtualRectBeforePan.left, 0); const panOffset = Offset(100, 100); await tester.dragFrom(chartCenterOffset, panOffset); await tester.pumpAndSettle(); final barChartLeafAfterPan = tester.widget( find.byType(BarChartLeaf), ); final chartVirtualRectAfterPan = barChartLeafAfterPan.chartVirtualRect!; expect(chartVirtualRectAfterPan.left, 0); expect( chartVirtualRectAfterPan.top, greaterThan(chartVirtualRectBeforePan.top), ); }); testWidgets('freely with FlScaleAxis.free', (tester) async { await tester.pumpWidget( createTestWidget( chart: BarChart( BarChartData(), transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.free, ), ), ), ); final chartCenterOffset = tester.getCenter(find.byType(BarChartLeaf)); final scaleStart1 = chartCenterOffset; final scaleStart2 = chartCenterOffset; final scaleEnd1 = chartCenterOffset + const Offset(100, 100); final scaleEnd2 = chartCenterOffset - const Offset(100, 100); final gesture1 = await tester.startGesture(scaleStart1); final gesture2 = await tester.startGesture(scaleStart2); await gesture1.moveTo(scaleEnd1); await gesture2.moveTo(scaleEnd2); await gesture1.up(); await gesture2.up(); await tester.pumpAndSettle(); final barChartLeafBeforePan = tester.widget( find.byType(BarChartLeaf), ); final chartVirtualRectBeforePan = barChartLeafBeforePan.chartVirtualRect!; expect(chartVirtualRectBeforePan.top, isNegative); expect(chartVirtualRectBeforePan.left, isNegative); const panOffset = Offset(100, 100); await tester.dragFrom(chartCenterOffset, panOffset); await tester.pumpAndSettle(); final barChartLeafAfterPan = tester.widget( find.byType(BarChartLeaf), ); final chartVirtualRectAfterPan = barChartLeafAfterPan.chartVirtualRect!; expect( chartVirtualRectAfterPan.left, greaterThan(chartVirtualRectBeforePan.left), ); expect( chartVirtualRectAfterPan.top, greaterThan(chartVirtualRectBeforePan.top), ); }); }); }); group('trackpad scroll', () { group('pans', () { testWidgets('only horizontally with FlScaleAxis.horizontal', (tester) async { await tester.pumpWidget( createTestWidget( chart: BarChart( BarChartData(), transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.horizontal, ), ), ), ); final chartCenterOffset = tester.getCenter(find.byType(BarChartLeaf)); final scaleStart1 = chartCenterOffset; final scaleStart2 = chartCenterOffset; final scaleEnd1 = chartCenterOffset + const Offset(100, 100); final scaleEnd2 = chartCenterOffset - const Offset(100, 100); final gesture1 = await tester.startGesture(scaleStart1); final gesture2 = await tester.startGesture(scaleStart2); await tester.pump(); await gesture1.moveTo(scaleEnd1); await gesture2.moveTo(scaleEnd2); await tester.pump(); await gesture1.up(); await gesture2.up(); await tester.pumpAndSettle(); final barChartLeafBeforePan = tester.widget( find.byType(BarChartLeaf), ); final chartVirtualRectBeforePan = barChartLeafBeforePan.chartVirtualRect!; expect(chartVirtualRectBeforePan.top, 0); final pointer = TestPointer(1, PointerDeviceKind.trackpad); const leftAndUp = Offset(-100, -100); await tester.sendEventToBinding(pointer.hover(chartCenterOffset)); await tester.pump(); await tester.sendEventToBinding(pointer.scroll(leftAndUp)); await tester.pump(); final barChartLeafAfterPan = tester.widget( find.byType(BarChartLeaf), ); final chartVirtualRectAfterPan = barChartLeafAfterPan.chartVirtualRect!; expect( chartVirtualRectAfterPan.left, greaterThan(chartVirtualRectBeforePan.left), ); expect(chartVirtualRectAfterPan.top, 0); }); testWidgets('vertically with FlScaleAxis.vertical', (tester) async { await tester.pumpWidget( createTestWidget( chart: BarChart( BarChartData(), transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.vertical, ), ), ), ); final chartCenterOffset = tester.getCenter(find.byType(BarChartLeaf)); final scaleStart1 = chartCenterOffset; final scaleStart2 = chartCenterOffset; final scaleEnd1 = chartCenterOffset + const Offset(100, 100); final scaleEnd2 = chartCenterOffset - const Offset(100, 100); final gesture1 = await tester.startGesture(scaleStart1); final gesture2 = await tester.startGesture(scaleStart2); await tester.pump(); await gesture1.moveTo(scaleEnd1); await gesture2.moveTo(scaleEnd2); await tester.pump(); await gesture1.up(); await gesture2.up(); await tester.pumpAndSettle(); final barChartLeafBeforePan = tester.widget( find.byType(BarChartLeaf), ); final chartVirtualRectBeforePan = barChartLeafBeforePan.chartVirtualRect!; expect(chartVirtualRectBeforePan.left, 0); final pointer = TestPointer(1, PointerDeviceKind.trackpad); const leftAndUp = Offset(-100, -100); await tester.sendEventToBinding(pointer.hover(chartCenterOffset)); await tester.pump(); await tester.sendEventToBinding(pointer.scroll(leftAndUp)); await tester.pump(); final barChartLeafAfterPan = tester.widget( find.byType(BarChartLeaf), ); final chartVirtualRectAfterPan = barChartLeafAfterPan.chartVirtualRect!; expect(chartVirtualRectAfterPan.left, 0); expect( chartVirtualRectAfterPan.top, greaterThan(chartVirtualRectBeforePan.top), ); }); testWidgets('freely with FlScaleAxis.free', (tester) async { await tester.pumpWidget( createTestWidget( chart: BarChart( BarChartData(), transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.free, ), ), ), ); final chartCenterOffset = tester.getCenter(find.byType(BarChartLeaf)); final scaleStart1 = chartCenterOffset; final scaleStart2 = chartCenterOffset; final scaleEnd1 = chartCenterOffset + const Offset(100, 100); final scaleEnd2 = chartCenterOffset - const Offset(100, 100); final gesture1 = await tester.startGesture(scaleStart1); final gesture2 = await tester.startGesture(scaleStart2); await tester.pump(); await gesture1.moveTo(scaleEnd1); await gesture2.moveTo(scaleEnd2); await tester.pump(); await gesture1.up(); await gesture2.up(); await tester.pumpAndSettle(); final barChartLeafBeforePan = tester.widget( find.byType(BarChartLeaf), ); final chartVirtualRectBeforePan = barChartLeafBeforePan.chartVirtualRect!; final pointer = TestPointer(1, PointerDeviceKind.trackpad); const leftAndUp = Offset(-100, -100); await tester.sendEventToBinding(pointer.hover(chartCenterOffset)); await tester.pump(); await tester.sendEventToBinding(pointer.scroll(leftAndUp)); await tester.pump(); final barChartLeafAfterPan = tester.widget( find.byType(BarChartLeaf), ); final chartVirtualRectAfterPan = barChartLeafAfterPan.chartVirtualRect!; expect( chartVirtualRectAfterPan.left, greaterThan(chartVirtualRectBeforePan.left), ); expect( chartVirtualRectAfterPan.top, greaterThan(chartVirtualRectBeforePan.top), ); }); }); testWidgets( 'does not scale with FlScaleAxis.none when ' 'trackpadScrollCausesScale is true', (tester) async { await tester.pumpWidget( createTestWidget( chart: BarChart( BarChartData(), transformationConfig: const FlTransformationConfig( // This is for test // ignore: avoid_redundant_argument_values scaleAxis: FlScaleAxis.none, trackpadScrollCausesScale: true, ), ), ), ); final pointer = TestPointer(1, PointerDeviceKind.trackpad); final chartCenterOffset = tester.getCenter(find.byType(BarChartLeaf)); const scrollAmount = Offset(0, -100); await tester.sendEventToBinding(pointer.hover(chartCenterOffset)); await tester.pump(); await tester.sendEventToBinding(pointer.scroll(scrollAmount)); await tester.pump(); final barChartLeaf = tester.widget( find.byType(BarChartLeaf), ); expect(barChartLeaf.chartVirtualRect, null); }, ); for (final scaleAxis in FlScaleAxis.scalingEnabledAxis) { testWidgets( 'does not scale when trackpadScrollCausesScale is false ' 'for $scaleAxis', (tester) async { await tester.pumpWidget( createTestWidget( chart: BarChart( BarChartData(), transformationConfig: FlTransformationConfig( scaleAxis: scaleAxis, // This is for test // ignore: avoid_redundant_argument_values trackpadScrollCausesScale: false, ), ), ), ); final pointer = TestPointer(1, PointerDeviceKind.trackpad); final chartCenterOffset = tester.getCenter( find.byType(BarChartLeaf), ); const scrollAmount = Offset(0, -100); await tester.sendEventToBinding(pointer.hover(chartCenterOffset)); await tester.pump(); await tester.sendEventToBinding(pointer.scroll(scrollAmount)); await tester.pump(); final barChartLeaf = tester.widget( find.byType(BarChartLeaf), ); expect(barChartLeaf.chartVirtualRect, null); }, ); } testWidgets('scales horizontally with FlScaleAxis.horizontal', (tester) async { await tester.pumpWidget( createTestWidget( chart: BarChart( BarChartData(), transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.horizontal, trackpadScrollCausesScale: true, ), ), ), ); final pointer = TestPointer(1, PointerDeviceKind.trackpad); final chartCenterOffset = tester.getCenter(find.byType(BarChartLeaf)); const scrollAmount = Offset(0, -100); await tester.sendEventToBinding(pointer.hover(chartCenterOffset)); await tester.pump(); await tester.sendEventToBinding(pointer.scroll(scrollAmount)); await tester.pump(); final barChartLeaf = tester.widget( find.byType(BarChartLeaf), ); final renderBox = tester.renderObject( find.byType(BarChartLeaf), ); final chartVirtualRect = barChartLeaf.chartVirtualRect!; expect(chartVirtualRect.size.height, renderBox.size.height); expect(chartVirtualRect.size.width, greaterThan(renderBox.size.width)); expect(chartVirtualRect.left, isNegative); expect(chartVirtualRect.top, 0); }); testWidgets('scales vertically with FlScaleAxis.vertical', (tester) async { await tester.pumpWidget( createTestWidget( chart: BarChart( BarChartData(), transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.vertical, trackpadScrollCausesScale: true, ), ), ), ); final pointer = TestPointer(1, PointerDeviceKind.trackpad); final chartCenterOffset = tester.getCenter(find.byType(BarChartLeaf)); const scrollAmount = Offset(0, -100); await tester.sendEventToBinding(pointer.hover(chartCenterOffset)); await tester.pump(); await tester.sendEventToBinding(pointer.scroll(scrollAmount)); await tester.pump(); final barChartLeaf = tester.widget( find.byType(BarChartLeaf), ); final renderBox = tester.renderObject( find.byType(BarChartLeaf), ); final chartVirtualRect = barChartLeaf.chartVirtualRect!; expect( chartVirtualRect.size.height, greaterThan(renderBox.size.height), ); expect(chartVirtualRect.size.width, renderBox.size.width); expect(chartVirtualRect.left, 0); expect(chartVirtualRect.top, isNegative); }); testWidgets('scales freely with FlScaleAxis.free', (tester) async { await tester.pumpWidget( createTestWidget( chart: BarChart( BarChartData(), transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.free, trackpadScrollCausesScale: true, ), ), ), ); final pointer = TestPointer(1, PointerDeviceKind.trackpad); final chartCenterOffset = tester.getCenter(find.byType(BarChartLeaf)); const scrollAmount = Offset(0, -100); await tester.sendEventToBinding(pointer.hover(chartCenterOffset)); await tester.pump(); await tester.sendEventToBinding(pointer.scroll(scrollAmount)); await tester.pump(); final barChartLeaf = tester.widget(find.byType(BarChartLeaf)); final renderBox = tester.renderObject( find.byType(BarChartLeaf), ); final chartVirtualRect = barChartLeaf.chartVirtualRect!; expect(chartVirtualRect.size, greaterThan(renderBox.size)); expect(chartVirtualRect.left, isNegative); expect(chartVirtualRect.top, isNegative); }); }); }); } ================================================ FILE: test/chart/base/axis_chart/axis_chart_data_test.dart ================================================ import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import '../../data_pool.dart'; import 'axis_chart_data_test.mocks.dart'; @GenerateMocks([Canvas]) void main() { group('AxisChartData data equality check', () { test('AxisTitle equality test', () { expect(MockData.axisTitles1 == MockData.axisTitles1Clone, true); expect(MockData.axisTitles1 == MockData.axisTitles2, false); expect(MockData.axisTitles1 == MockData.axisTitles3, false); expect(MockData.axisTitles1 == MockData.axisTitles4, false); expect(MockData.axisTitles1 == MockData.axisTitles5, false); }); test('FlTitlesData equality test', () { expect(MockData.flTitlesData1 == MockData.flTitlesData1Clone, true); expect(MockData.flTitlesData1 == MockData.flTitlesData2, false); expect(MockData.flTitlesData1 == MockData.flTitlesData3, false); expect(MockData.flTitlesData1 == MockData.flTitlesData4, false); expect(MockData.flTitlesData1 == MockData.flTitlesData5, false); expect(MockData.flTitlesData1 == MockData.flTitlesData6, false); }); test('SideTitles equality test', () { expect(MockData.sideTitles1 == MockData.sideTitles1Clone, true); expect(MockData.sideTitles1 == MockData.sideTitles2, false); expect(MockData.sideTitles1 == MockData.sideTitles3, false); expect(MockData.sideTitles1 == MockData.sideTitles4, false); expect(MockData.sideTitles1 == MockData.sideTitles5, false); expect(MockData.sideTitles1 == MockData.sideTitles6, false); }); test('SideTitleFitInsideData equality test', () { expect( MockData.sideTitleFitInsideData1 == MockData.sideTitleFitInsideData1Clone, true, ); expect( MockData.sideTitleFitInsideData1 == MockData.sideTitleFitInsideData2, false, ); expect( MockData.sideTitleFitInsideData1 == MockData.sideTitleFitInsideData3, false, ); expect( MockData.sideTitleFitInsideData1 == MockData.sideTitleFitInsideData4, false, ); expect( MockData.sideTitleFitInsideData1 == MockData.sideTitleFitInsideData5, false, ); expect( MockData.sideTitleFitInsideData1 == MockData.sideTitleFitInsideData6, false, ); }); test('FlSpot equality test', () { expect(flSpot1 == flSpot1Clone, true); expect(flSpot1 == flSpot2, false); expect(flSpot2 == flSpot2Clone, true); expect(nullSpot1 == nullSpot2, true); expect(nullSpot2 == nullSpot3, true); expect(nullSpot1 == nullSpot3, true); }); test('FlGridData equality test', () { expect(flGridData1 == flGridData1Clone, true); expect(flGridData1 == flGridData2, false); expect(flGridData1 == flGridData3, false); expect(flGridData1 == flGridData4, false); expect(flGridData1 == flGridData5, false); }); test('FlLine equality test', () { expect(flLine1 == flLine1Clone, true); expect( flLine1 == const FlLine( color: Colors.green, strokeWidth: 1.001, dashArray: [1, 2, 3], ), false, ); expect( flLine1 == const FlLine( color: Colors.green, strokeWidth: 1, dashArray: [ 1, ], ), false, ); expect( flLine1 == const FlLine(color: Colors.green, strokeWidth: 1, dashArray: []), false, ); expect( flLine1 == const FlLine(color: Colors.green, strokeWidth: 1), false, ); expect( flLine1 == const FlLine( color: Colors.white, strokeWidth: 1, dashArray: [1, 2, 3], ), false, ); expect( flLine1 == const FlLine( color: Colors.green, strokeWidth: 100, dashArray: [1, 2, 3], ), false, ); }); test('RangeAnnotations equality test', () { expect(rangeAnnotations1 == rangeAnnotations1Clone, true); expect(rangeAnnotations1 == rangeAnnotations2, false); expect( rangeAnnotations1 == RangeAnnotations( horizontalRangeAnnotations: [ horizontalRangeAnnotation1Clone, horizontalRangeAnnotation1, ], verticalRangeAnnotations: [ verticalRangeAnnotation1Clone, verticalRangeAnnotation1, ], ), true, ); expect( rangeAnnotations1 == RangeAnnotations( horizontalRangeAnnotations: [ horizontalRangeAnnotation1Clone, ], verticalRangeAnnotations: [ verticalRangeAnnotation1Clone, ], ), false, ); expect( rangeAnnotations1 == RangeAnnotations( horizontalRangeAnnotations: [], verticalRangeAnnotations: [ verticalRangeAnnotation1, verticalRangeAnnotation1Clone, ], ), false, ); expect( rangeAnnotations1 == RangeAnnotations( horizontalRangeAnnotations: [ horizontalRangeAnnotation1, horizontalRangeAnnotation1Clone, ], verticalRangeAnnotations: [ verticalRangeAnnotation1, VerticalRangeAnnotation( color: Colors.green, x2: 12.01, x1: 12.1, ), ], ), false, ); }); test('HorizontalRangeAnnotation equality test', () { expect( horizontalRangeAnnotation1 == horizontalRangeAnnotation1Clone, true, ); expect( horizontalRangeAnnotation1 == HorizontalRangeAnnotation( color: Colors.green, y2: 12.1, y1: 12.1, ), false, ); expect( horizontalRangeAnnotation1 == HorizontalRangeAnnotation( color: Colors.green, y2: 12, y1: 12.1, ), true, ); expect( horizontalRangeAnnotation1 == HorizontalRangeAnnotation( color: Colors.green, y2: 12.1, y1: 12, ), false, ); expect( horizontalRangeAnnotation1 == HorizontalRangeAnnotation( color: Colors.green.withValues(alpha: 0.5), y2: 12, y1: 12.1, ), false, ); }); test('VerticalRangeAnnotation equality test', () { expect(verticalRangeAnnotation1 == verticalRangeAnnotation1Clone, true); expect( verticalRangeAnnotation1 == VerticalRangeAnnotation(color: Colors.green, x2: 12.1, x1: 12.1), false, ); expect( verticalRangeAnnotation1 == VerticalRangeAnnotation(color: Colors.green, x2: 12, x1: 12.1), true, ); expect( verticalRangeAnnotation1 == VerticalRangeAnnotation(color: Colors.green, x2: 12.1, x1: 12), false, ); expect( verticalRangeAnnotation1 == VerticalRangeAnnotation( color: Colors.green.withValues(alpha: 0.5), x2: 12, x1: 12.1, ), false, ); }); test('FlSpotErrorRangePainter equality', () { final FlSpotErrorRangePainter painter1 = FlSimpleErrorPainter(); final painter2 = FlSimpleErrorPainter(); final painter3 = FlSimpleErrorPainter( lineWidth: 1.1, ); expect(painter1 == painter2, true); expect(painter1 != painter3, true); }); test('FlSpotErrorRangePainter render functionality (without texts)', () { final painter = FlSimpleErrorPainter( lineWidth: 5.3, lineColor: Colors.green, capLength: 10, ); final mockCanvas = MockCanvas(); const offsetInCanvas = Offset(24, 34); const origin = FlSpot(4, 1); painter.draw( mockCanvas, offsetInCanvas, origin, const Rect.fromLTWH(0, 4, 0, 6), LineChartData(), ); verify( mockCanvas.drawLine( captureAny, captureAny, captureAny, ), ).called(3); painter.draw( mockCanvas, offsetInCanvas, origin, const Rect.fromLTWH(4, 4, 6, 6), LineChartData(), ); final result = verify( mockCanvas.drawLine( captureAny, captureAny, any, ), )..called(6); expect(result.captured[0], const Offset(24, 38)); expect(result.captured[1], const Offset(24, 44)); expect(result.captured[2], const Offset(19, 38)); expect(result.captured[3], const Offset(29, 38)); expect(result.captured[4], const Offset(19, 44)); expect(result.captured[5], const Offset(29, 44)); expect(result.captured[6], const Offset(28, 34)); expect(result.captured[7], const Offset(34, 34)); expect(result.captured[8], const Offset(28, 29)); expect(result.captured[9], const Offset(28, 39)); verifyNever(mockCanvas.drawParagraph(any, any)); }); test('FlSpotErrorRangePainter render functionality (with texts)', () { final painter = FlSimpleErrorPainter( showErrorTexts: true, errorTextDirection: TextDirection.rtl, errorTextStyle: const TextStyle( color: Colors.red, fontSize: 12, ), ); final mockCanvas = MockCanvas(); painter.draw( mockCanvas, const Offset(24, 34), const FlSpot( 4, 1, xError: FlErrorRange.symmetric(1), yError: FlErrorRange.symmetric(1), ), const Rect.fromLTWH(4, 4, 6, 6), LineChartData(), ); verify( mockCanvas.drawLine( captureAny, captureAny, any, ), ).called(6); verify( mockCanvas.drawParagraph(captureAny, captureAny), ).called(4); }); }); } ================================================ FILE: test/chart/base/axis_chart/axis_chart_data_test.mocks.dart ================================================ // Mocks generated by Mockito 5.4.6 from annotations // in fl_chart/test/chart/base/axis_chart/axis_chart_data_test.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:typed_data' as _i3; import 'dart:ui' as _i2; import 'package:mockito/mockito.dart' as _i1; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values // ignore_for_file: avoid_setters_without_getters // ignore_for_file: comment_references // ignore_for_file: deprecated_member_use // ignore_for_file: deprecated_member_use_from_same_package // ignore_for_file: implementation_imports // ignore_for_file: invalid_use_of_visible_for_testing_member // ignore_for_file: must_be_immutable // ignore_for_file: prefer_const_constructors // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class // ignore_for_file: invalid_use_of_internal_member class _FakeRect_0 extends _i1.SmartFake implements _i2.Rect { _FakeRect_0( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } /// A class which mocks [Canvas]. /// /// See the documentation for Mockito's code generation for more information. class MockCanvas extends _i1.Mock implements _i2.Canvas { MockCanvas() { _i1.throwOnMissingStub(this); } @override void save() => super.noSuchMethod( Invocation.method( #save, [], ), returnValueForMissingStub: null, ); @override void saveLayer( _i2.Rect? bounds, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #saveLayer, [ bounds, paint, ], ), returnValueForMissingStub: null, ); @override void restore() => super.noSuchMethod( Invocation.method( #restore, [], ), returnValueForMissingStub: null, ); @override void restoreToCount(int? count) => super.noSuchMethod( Invocation.method( #restoreToCount, [count], ), returnValueForMissingStub: null, ); @override int getSaveCount() => (super.noSuchMethod( Invocation.method( #getSaveCount, [], ), returnValue: 0, ) as int); @override void translate( double? dx, double? dy, ) => super.noSuchMethod( Invocation.method( #translate, [ dx, dy, ], ), returnValueForMissingStub: null, ); @override void scale( double? sx, [ double? sy, ]) => super.noSuchMethod( Invocation.method( #scale, [ sx, sy, ], ), returnValueForMissingStub: null, ); @override void rotate(double? radians) => super.noSuchMethod( Invocation.method( #rotate, [radians], ), returnValueForMissingStub: null, ); @override void skew( double? sx, double? sy, ) => super.noSuchMethod( Invocation.method( #skew, [ sx, sy, ], ), returnValueForMissingStub: null, ); @override void transform(_i3.Float64List? matrix4) => super.noSuchMethod( Invocation.method( #transform, [matrix4], ), returnValueForMissingStub: null, ); @override _i3.Float64List getTransform() => (super.noSuchMethod( Invocation.method( #getTransform, [], ), returnValue: _i3.Float64List(0), ) as _i3.Float64List); @override void clipRect( _i2.Rect? rect, { _i2.ClipOp? clipOp = _i2.ClipOp.intersect, bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipRect, [rect], { #clipOp: clipOp, #doAntiAlias: doAntiAlias, }, ), returnValueForMissingStub: null, ); @override void clipRRect( _i2.RRect? rrect, { bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipRRect, [rrect], {#doAntiAlias: doAntiAlias}, ), returnValueForMissingStub: null, ); @override void clipRSuperellipse( _i2.RSuperellipse? rsuperellipse, { bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipRSuperellipse, [rsuperellipse], {#doAntiAlias: doAntiAlias}, ), returnValueForMissingStub: null, ); @override void clipPath( _i2.Path? path, { bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipPath, [path], {#doAntiAlias: doAntiAlias}, ), returnValueForMissingStub: null, ); @override _i2.Rect getLocalClipBounds() => (super.noSuchMethod( Invocation.method( #getLocalClipBounds, [], ), returnValue: _FakeRect_0( this, Invocation.method( #getLocalClipBounds, [], ), ), ) as _i2.Rect); @override _i2.Rect getDestinationClipBounds() => (super.noSuchMethod( Invocation.method( #getDestinationClipBounds, [], ), returnValue: _FakeRect_0( this, Invocation.method( #getDestinationClipBounds, [], ), ), ) as _i2.Rect); @override void drawColor( _i2.Color? color, _i2.BlendMode? blendMode, ) => super.noSuchMethod( Invocation.method( #drawColor, [ color, blendMode, ], ), returnValueForMissingStub: null, ); @override void drawLine( _i2.Offset? p1, _i2.Offset? p2, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawLine, [ p1, p2, paint, ], ), returnValueForMissingStub: null, ); @override void drawPaint(_i2.Paint? paint) => super.noSuchMethod( Invocation.method( #drawPaint, [paint], ), returnValueForMissingStub: null, ); @override void drawRect( _i2.Rect? rect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRect, [ rect, paint, ], ), returnValueForMissingStub: null, ); @override void drawRRect( _i2.RRect? rrect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRRect, [ rrect, paint, ], ), returnValueForMissingStub: null, ); @override void drawDRRect( _i2.RRect? outer, _i2.RRect? inner, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawDRRect, [ outer, inner, paint, ], ), returnValueForMissingStub: null, ); @override void drawRSuperellipse( _i2.RSuperellipse? rsuperellipse, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRSuperellipse, [ rsuperellipse, paint, ], ), returnValueForMissingStub: null, ); @override void drawOval( _i2.Rect? rect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawOval, [ rect, paint, ], ), returnValueForMissingStub: null, ); @override void drawCircle( _i2.Offset? c, double? radius, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawCircle, [ c, radius, paint, ], ), returnValueForMissingStub: null, ); @override void drawArc( _i2.Rect? rect, double? startAngle, double? sweepAngle, bool? useCenter, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawArc, [ rect, startAngle, sweepAngle, useCenter, paint, ], ), returnValueForMissingStub: null, ); @override void drawPath( _i2.Path? path, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawPath, [ path, paint, ], ), returnValueForMissingStub: null, ); @override void drawImage( _i2.Image? image, _i2.Offset? offset, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawImage, [ image, offset, paint, ], ), returnValueForMissingStub: null, ); @override void drawImageRect( _i2.Image? image, _i2.Rect? src, _i2.Rect? dst, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawImageRect, [ image, src, dst, paint, ], ), returnValueForMissingStub: null, ); @override void drawImageNine( _i2.Image? image, _i2.Rect? center, _i2.Rect? dst, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawImageNine, [ image, center, dst, paint, ], ), returnValueForMissingStub: null, ); @override void drawPicture(_i2.Picture? picture) => super.noSuchMethod( Invocation.method( #drawPicture, [picture], ), returnValueForMissingStub: null, ); @override void drawParagraph( _i2.Paragraph? paragraph, _i2.Offset? offset, ) => super.noSuchMethod( Invocation.method( #drawParagraph, [ paragraph, offset, ], ), returnValueForMissingStub: null, ); @override void drawPoints( _i2.PointMode? pointMode, List<_i2.Offset>? points, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawPoints, [ pointMode, points, paint, ], ), returnValueForMissingStub: null, ); @override void drawRawPoints( _i2.PointMode? pointMode, _i3.Float32List? points, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRawPoints, [ pointMode, points, paint, ], ), returnValueForMissingStub: null, ); @override void drawVertices( _i2.Vertices? vertices, _i2.BlendMode? blendMode, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawVertices, [ vertices, blendMode, paint, ], ), returnValueForMissingStub: null, ); @override void drawAtlas( _i2.Image? atlas, List<_i2.RSTransform>? transforms, List<_i2.Rect>? rects, List<_i2.Color>? colors, _i2.BlendMode? blendMode, _i2.Rect? cullRect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawAtlas, [ atlas, transforms, rects, colors, blendMode, cullRect, paint, ], ), returnValueForMissingStub: null, ); @override void drawRawAtlas( _i2.Image? atlas, _i3.Float32List? rstTransforms, _i3.Float32List? rects, _i3.Int32List? colors, _i2.BlendMode? blendMode, _i2.Rect? cullRect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRawAtlas, [ atlas, rstTransforms, rects, colors, blendMode, cullRect, paint, ], ), returnValueForMissingStub: null, ); @override void drawShadow( _i2.Path? path, _i2.Color? color, double? elevation, bool? transparentOccluder, ) => super.noSuchMethod( Invocation.method( #drawShadow, [ path, color, elevation, transparentOccluder, ], ), returnValueForMissingStub: null, ); } ================================================ FILE: test/chart/base/axis_chart/axis_chart_extensions_test.dart ================================================ import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/chart/base/axis_chart/axis_chart_extensions.dart'; import 'package:flutter_test/flutter_test.dart'; import '../../data_pool.dart'; void main() { group('splitByNullSpots()', () { test('test 1 - null spots start', () { final spots = [ FlSpot.nullSpot, FlSpot.nullSpot, FlSpot.nullSpot, FlSpot.nullSpot, FlSpot.nullSpot, MockData.flSpot1, MockData.flSpot2, MockData.flSpot3, ]; final result = spots.splitByNullSpots(); expect( result, [ [ MockData.flSpot1, MockData.flSpot2, MockData.flSpot3, ] ], ); }); test('test 2 - null spots end', () { final spots = [ MockData.flSpot1, MockData.flSpot2, MockData.flSpot3, FlSpot.nullSpot, FlSpot.nullSpot, FlSpot.nullSpot, ]; final result = spots.splitByNullSpots(); expect( result, [ [ MockData.flSpot1, MockData.flSpot2, MockData.flSpot3, ] ], ); }); test('test 3 - null spots around', () { final spots = [ FlSpot.nullSpot, FlSpot.nullSpot, FlSpot.nullSpot, FlSpot.nullSpot, FlSpot.nullSpot, MockData.flSpot1, MockData.flSpot2, MockData.flSpot3, FlSpot.nullSpot, FlSpot.nullSpot, FlSpot.nullSpot, ]; final result = spots.splitByNullSpots(); expect( result, [ [ MockData.flSpot1, MockData.flSpot2, MockData.flSpot3, ] ], ); }); test('test 4 - null spots between', () { final spots = [ MockData.flSpot1, MockData.flSpot2, FlSpot.nullSpot, MockData.flSpot3, FlSpot.nullSpot, MockData.flSpot4, MockData.flSpot5, FlSpot.nullSpot, MockData.flSpot1, ]; final result = spots.splitByNullSpots(); expect( result, [ [ MockData.flSpot1, MockData.flSpot2, ], [ MockData.flSpot3, ], [ MockData.flSpot4, MockData.flSpot5, ], [ MockData.flSpot1, ] ], ); }); }); } ================================================ FILE: test/chart/base/axis_chart/axis_chart_helper_test.dart ================================================ import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/chart/base/axis_chart/axis_chart_helper.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { const tolerance = 0.0001; group('iterateThroughAxis()', () { test('test 1', () { final results = []; AxisChartHelper() .iterateThroughAxis(min: 0, max: 0.1, interval: 0.001, baseLine: 0) .forEach(results.add); expect(results.length, 101); }); test('test 2', () { final results = []; AxisChartHelper() .iterateThroughAxis( min: 0, minIncluded: false, max: 0.1, maxIncluded: false, interval: 0.001, baseLine: 0, ) .forEach(results.add); expect(results.length, 99); expect(results[0], closeTo(0.001, tolerance)); expect(results[98], closeTo(0.099, tolerance)); }); test('test 3', () { final results = []; AxisChartHelper() .iterateThroughAxis(min: 0, max: 1000, interval: 200, baseLine: 0) .forEach(results.add); expect(results.length, 6); expect(results[0], 0); expect(results[1], 200); expect(results[2], 400); expect(results[3], 600); expect(results[4], 800); expect(results[5], 1000); }); test('test 4', () { final results = []; AxisChartHelper() .iterateThroughAxis(min: 0, max: 10, interval: 3, baseLine: 0) .forEach(results.add); expect(results.length, 5); expect(results[0], 0); expect(results[1], 3); expect(results[2], 6); expect(results[3], 9); expect(results[4], 10); }); test('test 5', () { final results = []; AxisChartHelper() .iterateThroughAxis( min: 0, minIncluded: false, max: 10, maxIncluded: false, interval: 3, baseLine: 0, ) .forEach(results.add); expect(results.length, 3); expect(results[0], 3); expect(results[1], 6); expect(results[2], 9); }); test('test 6', () { final results = []; AxisChartHelper() .iterateThroughAxis(min: 35, max: 130, interval: 50, baseLine: 0) .forEach(results.add); expect(results.length, 4); expect(results[0], 35); expect(results[1], 50); expect(results[2], 100); expect(results[3], 130); }); test('test 7', () { final results = []; AxisChartHelper() .iterateThroughAxis(min: 5, max: 35, interval: 10, baseLine: 5) .forEach(results.add); expect(results.length, 4); expect(results[0], 5); expect(results[1], 15); expect(results[2], 25); expect(results[3], 35); }); }); group('calcFitInsideOffset', () { group('not overflowed', () { test('vertical axis', () { const result = Offset.zero; final offset = AxisChartHelper().calcFitInsideOffset( axisSide: AxisSide.left, childSize: 10, parentAxisSize: 100, axisPosition: 20, distanceFromEdge: 0, ); expect(offset, result); }); test('horizontal axis', () { const result = Offset.zero; final offset = AxisChartHelper().calcFitInsideOffset( axisSide: AxisSide.bottom, childSize: 10, parentAxisSize: 100, axisPosition: 20, distanceFromEdge: 0, ); expect(offset, result); }); }); group('overflowed', () { test('vertical axis at start', () { const result = Offset(0, 5); final offset = AxisChartHelper().calcFitInsideOffset( axisSide: AxisSide.left, childSize: 10, parentAxisSize: 100, axisPosition: 0, distanceFromEdge: 0, ); expect(offset, result); }); test('vertical axis at end', () { const result = Offset(0, -5); final offset = AxisChartHelper().calcFitInsideOffset( axisSide: AxisSide.left, childSize: 10, parentAxisSize: 100, axisPosition: 100, distanceFromEdge: 0, ); expect(offset, result); }); test('horizontal axis at start', () { const result = Offset(5, 0); final offset = AxisChartHelper().calcFitInsideOffset( axisSide: AxisSide.bottom, childSize: 10, parentAxisSize: 100, axisPosition: 0, distanceFromEdge: 0, ); expect(offset, result); }); test('horizontal axis at end', () { const result = Offset(-5, 0); final offset = AxisChartHelper().calcFitInsideOffset( axisSide: AxisSide.bottom, childSize: 10, parentAxisSize: 100, axisPosition: 100, distanceFromEdge: 0, ); expect(offset, result); }); }); }); } ================================================ FILE: test/chart/base/axis_chart/axis_chart_scaffold_widget_test.dart ================================================ import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/chart/base/axis_chart/axis_chart_scaffold_widget.dart'; import 'package:fl_chart/src/chart/base/axis_chart/side_titles/side_titles_widget.dart'; import 'package:fl_chart/src/chart/base/custom_interactive_viewer.dart'; import 'package:fl_chart/src/extensions/size_extension.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { const Rect? isNotScaled = null; final isScaled = isA(); const viewSize = Size(400, 400); const dummyChartKey = Key('chart'); const dummyChart = SizedBox(key: dummyChartKey); final lineChartDataBase = LineChartData( minX: 0, maxX: 10, minY: 0, maxY: 10, ); final lineChartDataWithNoTitles = lineChartDataBase.copyWith( titlesData: const FlTitlesData( show: false, leftTitles: AxisTitles(), topTitles: AxisTitles(), rightTitles: AxisTitles(), bottomTitles: AxisTitles(), ), borderData: FlBorderData(show: false), ); final lineChartDataWithAllTitles = lineChartDataBase.copyWith( borderData: FlBorderData( show: true, border: Border.all( color: Colors.red, width: 10, ), ), titlesData: FlTitlesData( leftTitles: AxisTitles( axisNameWidget: const Icon(Icons.arrow_left), axisNameSize: 10, sideTitles: SideTitles( showTitles: true, reservedSize: 10, getTitlesWidget: (value, meta) { return Text('L-${value.toInt()}'); }, interval: 1, ), ), topTitles: AxisTitles( axisNameWidget: const Icon(Icons.arrow_drop_up), axisNameSize: 20, sideTitles: SideTitles( showTitles: true, reservedSize: 20, getTitlesWidget: (value, meta) { return Text('T-${value.toInt()}'); }, interval: 1, ), ), rightTitles: AxisTitles( axisNameWidget: const Icon(Icons.arrow_right), axisNameSize: 30, sideTitles: SideTitles( showTitles: true, reservedSize: 30, getTitlesWidget: (value, meta) { return Text('R-${value.toInt()}'); }, interval: 1, ), ), bottomTitles: AxisTitles( axisNameWidget: const Icon(Icons.arrow_drop_down), axisNameSize: 40, sideTitles: SideTitles( showTitles: true, reservedSize: 40, getTitlesWidget: (value, meta) { return Text('B-${value.toInt()}'); }, interval: 1, ), ), ), ); final lineChartDataWithOnlyLeftTitles = lineChartDataBase.copyWith( borderData: FlBorderData( show: true, border: const Border( left: BorderSide( color: Colors.red, width: 6, ), ), ), titlesData: FlTitlesData( leftTitles: AxisTitles( axisNameWidget: const Icon(Icons.arrow_left), axisNameSize: 10, sideTitles: SideTitles( showTitles: true, reservedSize: 10, getTitlesWidget: (value, meta) { return Text('L-${value.toInt()}'); }, interval: 1, ), ), topTitles: const AxisTitles(), rightTitles: const AxisTitles(), bottomTitles: const AxisTitles(), ), ); final lineChartDataWithOnlyLeftTitlesWithoutAxisName = lineChartDataBase.copyWith( borderData: FlBorderData(show: false), titlesData: FlTitlesData( leftTitles: AxisTitles( axisNameSize: 10, sideTitles: SideTitles( showTitles: true, reservedSize: 10, getTitlesWidget: (value, meta) { return Text('L-${value.toInt()}'); }, interval: 1, ), ), topTitles: const AxisTitles(), rightTitles: const AxisTitles(), bottomTitles: const AxisTitles(), ), ); final lineChartDataWithOnlyLeftAxisNameWithoutSideTitles = lineChartDataBase.copyWith( borderData: FlBorderData(show: false), titlesData: FlTitlesData( leftTitles: AxisTitles( axisNameSize: 10, axisNameWidget: const Icon(Icons.arrow_left), sideTitles: SideTitles( reservedSize: 10, getTitlesWidget: (value, meta) { return Text('L-${value.toInt()}'); }, interval: 1, ), ), topTitles: const AxisTitles(), rightTitles: const AxisTitles(), bottomTitles: const AxisTitles(), ), ); testWidgets( 'LineChart with no titles', (tester) async { Size? chartDrawingSize; await tester.pumpWidget( MaterialApp( home: Scaffold( body: Center( child: SizedBox( width: viewSize.width, height: viewSize.height, child: AxisChartScaffoldWidget( chartBuilder: (context, chartVirtualRect) => LayoutBuilder( builder: (context, constraints) { chartDrawingSize = constraints.biggest; return const ColoredBox( color: Colors.red, ); }, ), data: lineChartDataWithNoTitles, ), ), ), ), ), ); expect(chartDrawingSize, viewSize); expect(find.byType(Text), findsNothing); expect(find.byType(Icon), findsNothing); }, ); testWidgets( 'LineChart with all titles', (tester) async { Size? chartDrawingSize; await tester.pumpWidget( MaterialApp( home: Scaffold( body: Center( child: SizedBox( width: viewSize.width, height: viewSize.height, child: AxisChartScaffoldWidget( chartBuilder: (context, chartVirtualRect) => LayoutBuilder( builder: (context, constraints) { chartDrawingSize = constraints.biggest; return const ColoredBox( color: Colors.red, ); }, ), data: lineChartDataWithAllTitles, ), ), ), ), ), ); Future checkSide(AxisSide side) async { final axisChar = switch (side) { AxisSide.left => 'L', AxisSide.top => 'T', AxisSide.right => 'R', AxisSide.bottom => 'B', }; for (var i = 0; i <= 10; i++) { expect(find.text('$axisChar-$i'), findsOneWidget); } } expect(chartDrawingSize, const Size(300, 260)); expect(find.byIcon(Icons.arrow_left), findsOneWidget); await checkSide(AxisSide.left); expect(find.byIcon(Icons.arrow_drop_up), findsOneWidget); await checkSide(AxisSide.top); expect(find.byIcon(Icons.arrow_right), findsOneWidget); await checkSide(AxisSide.right); expect(find.byIcon(Icons.arrow_drop_down), findsOneWidget); await checkSide(AxisSide.bottom); expect(find.byType(Text), findsNWidgets(44)); expect(find.byType(Icon), findsNWidgets(4)); }, ); testWidgets( 'LineChart with only left titles', (tester) async { Size? chartDrawingSize; await tester.pumpWidget( MaterialApp( home: Scaffold( body: Center( child: SizedBox( width: viewSize.width, height: viewSize.height, child: AxisChartScaffoldWidget( chartBuilder: (context, chartVirtualRect) => LayoutBuilder( builder: (context, constraints) { chartDrawingSize = constraints.biggest; return const ColoredBox( color: Colors.red, ); }, ), data: lineChartDataWithOnlyLeftTitles, ), ), ), ), ), ); expect(chartDrawingSize, const Size(374, 400)); expect(find.byIcon(Icons.arrow_left), findsOneWidget); for (var i = 0; i <= 10; i++) { expect(find.text('L-$i'), findsOneWidget); } expect(find.byType(Text), findsNWidgets(11)); expect(find.byType(Icon), findsNWidgets(1)); }, ); testWidgets( 'LineChart with only left titles without axis name', (tester) async { Size? chartDrawingSize; await tester.pumpWidget( MaterialApp( home: Scaffold( body: Center( child: SizedBox( width: viewSize.width, height: viewSize.height, child: AxisChartScaffoldWidget( chartBuilder: (context, chartVirtualRect) => LayoutBuilder( builder: (context, constraints) { chartDrawingSize = constraints.biggest; return const ColoredBox( color: Colors.red, ); }, ), data: lineChartDataWithOnlyLeftTitlesWithoutAxisName, ), ), ), ), ), ); expect(chartDrawingSize, const Size(390, 400)); for (var i = 0; i <= 10; i++) { expect(find.text('L-$i'), findsOneWidget); } expect(find.byType(Text), findsNWidgets(11)); expect(find.byType(Icon), findsNothing); }, ); testWidgets( 'LineChart with only left axis name without side titles', (tester) async { Size? chartDrawingSize; await tester.pumpWidget( MaterialApp( home: Scaffold( body: Center( child: SizedBox( width: viewSize.width, height: viewSize.height, child: AxisChartScaffoldWidget( chartBuilder: (context, chartVirtualRect) => LayoutBuilder( builder: (context, constraints) { chartDrawingSize = constraints.biggest; return const ColoredBox( color: Colors.red, ); }, ), data: lineChartDataWithOnlyLeftAxisNameWithoutSideTitles, ), ), ), ), ), ); expect(chartDrawingSize, const Size(390, 400)); expect(find.byType(Text), findsNothing); expect(find.byType(Icon), findsOneWidget); }, ); testWidgets( 'LineChart with rotationQuarterTurns', (tester) async { for (var rotationTurns = 0; rotationTurns <= 8; rotationTurns++) { Size? chartDrawingSize; await tester.pumpWidget( MaterialApp( home: Scaffold( body: Center( child: SizedBox( width: 400, height: 200, child: AxisChartScaffoldWidget( chartBuilder: (context, chartVirtualRect) => LayoutBuilder( builder: (context, constraints) { chartDrawingSize = constraints.biggest; return const ColoredBox( color: Colors.red, ); }, ), data: lineChartDataWithNoTitles.copyWith( rotationQuarterTurns: rotationTurns, ), ), ), ), ), ), ); expect( chartDrawingSize, const Size(400, 200).rotateByQuarterTurns(rotationTurns), ); final types = find.byType(RotatedBox); final rotatedBox = tester.widget(types); expect(rotatedBox.quarterTurns, rotationTurns); expect(types, findsOne); } }, ); group('AxisChartScaffoldWidget', () { for (final scaleAxis in FlScaleAxis.scalingEnabledAxis) { testWidgets( 'wraps chart in interactive viewer when scaling is $scaleAxis', (tester) async { await tester.pumpWidget( MaterialApp( home: Scaffold( body: Center( child: SizedBox( width: viewSize.width, height: viewSize.height, child: AxisChartScaffoldWidget( data: lineChartDataWithAllTitles, transformationConfig: FlTransformationConfig( scaleAxis: scaleAxis, ), chartBuilder: (context, chartVirtualRect) => dummyChart, ), ), ), ), ), ); final interactiveViewer = find.ancestor( of: find.byKey(dummyChartKey), matching: find.byType(CustomInteractiveViewer), ); expect(interactiveViewer, findsOneWidget); }, ); } testWidgets( 'does not wrap chart in interactive viewer when scaling is disabled', (tester) async { await tester.pumpWidget( MaterialApp( home: Scaffold( body: Center( child: SizedBox( width: viewSize.width, height: viewSize.height, child: AxisChartScaffoldWidget( data: lineChartDataWithAllTitles, // This is for test // ignore: avoid_redundant_argument_values transformationConfig: const FlTransformationConfig( // This is for test // ignore: avoid_redundant_argument_values scaleAxis: FlScaleAxis.none, ), chartBuilder: (context, chartVirtualRect) => dummyChart, ), ), ), ), ), ); final interactiveViewer = find.ancestor( of: find.byKey(dummyChartKey), matching: find.byType(CustomInteractiveViewer), ); expect(interactiveViewer, findsNothing); }, ); testWidgets('passes interaction parameters to interactive viewer', (tester) async { Future pumpTestWidget(AxisChartScaffoldWidget widget) async { await tester.pumpWidget( MaterialApp( home: Scaffold( body: Center( child: SizedBox( width: viewSize.width, height: viewSize.height, child: widget, ), ), ), ), ); } await pumpTestWidget( AxisChartScaffoldWidget( data: lineChartDataWithAllTitles, transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.free, ), chartBuilder: (context, chartVirtualRect) => dummyChart, ), ); final interactiveViewer1 = tester.widget( find.byType(CustomInteractiveViewer), ); expect(interactiveViewer1.trackpadScrollCausesScale, false); expect(interactiveViewer1.maxScale, 2.5); expect(interactiveViewer1.minScale, 1); expect(interactiveViewer1.clipBehavior, Clip.none); expect(interactiveViewer1.panEnabled, true); expect(interactiveViewer1.scaleEnabled, true); expect( interactiveViewer1.transformationController, isA().having( (controller) => controller.value, 'value', Matrix4.identity(), ), ); final transformationController = TransformationController(); await pumpTestWidget( AxisChartScaffoldWidget( data: lineChartDataWithAllTitles, transformationConfig: FlTransformationConfig( scaleAxis: FlScaleAxis.free, trackpadScrollCausesScale: true, maxScale: 10, minScale: 1.5, panEnabled: false, scaleEnabled: false, transformationController: transformationController, ), chartBuilder: (context, chartVirtualRect) => dummyChart, ), ); final interactiveViewer2 = tester.widget( find.byType(CustomInteractiveViewer), ); expect(interactiveViewer2.trackpadScrollCausesScale, true); expect(interactiveViewer2.maxScale, 10); expect(interactiveViewer2.minScale, 1.5); expect(interactiveViewer2.clipBehavior, Clip.none); expect(interactiveViewer2.panEnabled, false); expect(interactiveViewer2.scaleEnabled, false); expect( interactiveViewer2.transformationController, transformationController, ); }); testWidgets('asserts minScale is greater than 1', (tester) async { expect( () => AxisChartScaffoldWidget( data: lineChartDataWithAllTitles, transformationConfig: FlTransformationConfig( scaleAxis: FlScaleAxis.free, minScale: 0.5, ), chartBuilder: (context, chartVirtualRect) => dummyChart, ), throwsAssertionError, ); }); testWidgets('asserts maxScale is greater than or equal to minScale', (tester) async { expect( () => AxisChartScaffoldWidget( data: lineChartDataWithAllTitles, transformationConfig: FlTransformationConfig( scaleAxis: FlScaleAxis.free, maxScale: 0.5, ), chartBuilder: (context, chartVirtualRect) => dummyChart, ), throwsAssertionError, ); }); group('scaling and panning', () { group('touch gesture', () { testWidgets('does not scale with FlScaleAxis.none', (tester) async { Rect? chartVirtualRect; await tester.pumpWidget( MaterialApp( home: Scaffold( body: Center( child: SizedBox( width: viewSize.width, height: viewSize.height, child: AxisChartScaffoldWidget( chartBuilder: (context, rect) { chartVirtualRect = rect; return dummyChart; }, data: lineChartDataWithNoTitles, ), ), ), ), ), ); final chartCenterOffset = tester.getCenter(find.byKey(dummyChartKey)); final scaleStart1 = chartCenterOffset; final scaleStart2 = chartCenterOffset; final scaleEnd1 = chartCenterOffset + const Offset(100, 100); final scaleEnd2 = chartCenterOffset - const Offset(100, 100); final gesture1 = await tester.startGesture(scaleStart1); final gesture2 = await tester.startGesture(scaleStart2); await tester.pump(); await gesture1.moveTo(scaleEnd1); await gesture2.moveTo(scaleEnd2); await tester.pump(); await gesture1.up(); await gesture2.up(); await tester.pumpAndSettle(); expect(chartVirtualRect, isNull); }); testWidgets('scales freely with FlScaleAxis.free', (tester) async { Rect? chartVirtualRect; await tester.pumpWidget( MaterialApp( home: Scaffold( body: Center( child: SizedBox( width: viewSize.width, height: viewSize.height, child: AxisChartScaffoldWidget( transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.free, ), chartBuilder: (context, rect) { chartVirtualRect = rect; return dummyChart; }, data: lineChartDataWithNoTitles, ), ), ), ), ), ); final renderBox = tester.renderObject( find.byKey(dummyChartKey), ); final chartCenterOffset = tester.getCenter(find.byKey(dummyChartKey)); final scaleStart1 = chartCenterOffset + const Offset(10, 10); final scaleStart2 = chartCenterOffset - const Offset(10, 10); final scaleEnd1 = chartCenterOffset + const Offset(100, 100); final scaleEnd2 = chartCenterOffset - const Offset(100, 100); final gesture1 = await tester.startGesture(scaleStart1); final gesture2 = await tester.startGesture(scaleStart2); await tester.pump(); await gesture1.moveTo(scaleEnd1); await gesture2.moveTo(scaleEnd2); await tester.pump(); await gesture1.up(); await gesture2.up(); await tester.pumpAndSettle(); expect(chartVirtualRect!.size, greaterThan(renderBox.size)); expect(chartVirtualRect!.left, isNegative); expect(chartVirtualRect!.top, isNegative); }); testWidgets('scales horizontally with FlScaleAxis.horizontal', (tester) async { Rect? chartVirtualRect; await tester.pumpWidget( MaterialApp( home: Scaffold( body: Center( child: SizedBox( width: viewSize.width, height: viewSize.height, child: AxisChartScaffoldWidget( transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.horizontal, ), chartBuilder: (context, rect) { chartVirtualRect = rect; return dummyChart; }, data: lineChartDataWithNoTitles, ), ), ), ), ), ); final renderBox = tester.renderObject( find.byKey(dummyChartKey), ); final chartCenterOffset = tester.getCenter(find.byKey(dummyChartKey)); final scaleStart1 = chartCenterOffset + const Offset(10, 10); final scaleStart2 = chartCenterOffset - const Offset(10, 10); final scaleEnd1 = chartCenterOffset + const Offset(100, 100); final scaleEnd2 = chartCenterOffset - const Offset(100, 100); final gesture1 = await tester.startGesture(scaleStart1); final gesture2 = await tester.startGesture(scaleStart2); await tester.pump(); await gesture1.moveTo(scaleEnd1); await gesture2.moveTo(scaleEnd2); await tester.pump(); await gesture1.up(); await gesture2.up(); await tester.pumpAndSettle(); expect(chartVirtualRect!.size.height, renderBox.size.height); expect( chartVirtualRect!.size.width, greaterThan(renderBox.size.width), ); expect(chartVirtualRect!.left, isNegative); expect(chartVirtualRect!.top, 0); }); testWidgets('scales vertically with FlScaleAxis.vertical', (tester) async { Rect? chartVirtualRect; await tester.pumpWidget( MaterialApp( home: Scaffold( body: Center( child: SizedBox( width: viewSize.width, height: viewSize.height, child: AxisChartScaffoldWidget( transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.vertical, ), chartBuilder: (context, rect) { chartVirtualRect = rect; return dummyChart; }, data: lineChartDataWithNoTitles, ), ), ), ), ), ); final renderBox = tester.renderObject( find.byKey(dummyChartKey), ); final chartCenterOffset = tester.getCenter(find.byKey(dummyChartKey)); final scaleStart1 = chartCenterOffset + const Offset(10, 10); final scaleStart2 = chartCenterOffset - const Offset(10, 10); final scaleEnd1 = chartCenterOffset + const Offset(100, 100); final scaleEnd2 = chartCenterOffset - const Offset(100, 100); final gesture1 = await tester.startGesture(scaleStart1); final gesture2 = await tester.startGesture(scaleStart2); await tester.pump(); await gesture1.moveTo(scaleEnd1); await gesture2.moveTo(scaleEnd2); await tester.pump(); await gesture1.up(); await gesture2.up(); await tester.pumpAndSettle(); expect( chartVirtualRect!.size.height, greaterThan(renderBox.size.height), ); expect(chartVirtualRect!.size.width, renderBox.size.width); expect(chartVirtualRect!.left, 0); expect(chartVirtualRect!.top, isNegative); }); }); group('trackpad scroll', () { testWidgets( 'does not scale with FlScaleAxis.none when trackpadScrollCausesScale is true', (tester) async { Rect? chartVirtualRect; await tester.pumpWidget( MaterialApp( home: Scaffold( body: Center( child: SizedBox( width: viewSize.width, height: viewSize.height, child: AxisChartScaffoldWidget( transformationConfig: const FlTransformationConfig( trackpadScrollCausesScale: true, ), chartBuilder: (context, rect) { chartVirtualRect = rect; return dummyChart; }, data: lineChartDataWithNoTitles, ), ), ), ), ), ); final pointer = TestPointer(1, PointerDeviceKind.trackpad); final chartCenterOffset = tester.getCenter(find.byKey(dummyChartKey)); const scrollAmount = Offset(0, -100); await tester.sendEventToBinding(pointer.hover(chartCenterOffset)); await tester.pump(); await tester.sendEventToBinding(pointer.scroll(scrollAmount)); await tester.pump(); expect(chartVirtualRect, isNull); }, ); for (final scaleAxis in FlScaleAxis.scalingEnabledAxis) { testWidgets( 'does not scale when trackpadScrollCausesScale is false ' 'for $scaleAxis', (tester) async { Rect? chartVirtualRect; await tester.pumpWidget( MaterialApp( home: Scaffold( body: Center( child: SizedBox( width: viewSize.width, height: viewSize.height, child: AxisChartScaffoldWidget( transformationConfig: FlTransformationConfig( scaleAxis: scaleAxis, ), chartBuilder: (context, rect) { chartVirtualRect = rect; return dummyChart; }, data: lineChartDataWithNoTitles, ), ), ), ), ), ); final pointer = TestPointer(1, PointerDeviceKind.trackpad); final chartCenterOffset = tester.getCenter( find.byKey(dummyChartKey), ); const scrollAmount = Offset(0, -100); await tester.sendEventToBinding(pointer.hover(chartCenterOffset)); await tester.pump(); await tester.sendEventToBinding(pointer.scroll(scrollAmount)); await tester.pump(); expect(chartVirtualRect, isNull); }, ); } testWidgets('scales horizontally with FlScaleAxis.horizontal', (tester) async { Rect? chartVirtualRect; await tester.pumpWidget( MaterialApp( home: Scaffold( body: Center( child: SizedBox( width: viewSize.width, height: viewSize.height, child: AxisChartScaffoldWidget( transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.horizontal, trackpadScrollCausesScale: true, ), chartBuilder: (context, rect) { chartVirtualRect = rect; return dummyChart; }, data: lineChartDataWithNoTitles, ), ), ), ), ), ); final renderBox = tester.renderObject( find.byKey(dummyChartKey), ); final pointer = TestPointer(1, PointerDeviceKind.trackpad); final chartCenterOffset = tester.getCenter(find.byKey(dummyChartKey)); const scrollAmount = Offset(0, -100); await tester.sendEventToBinding(pointer.hover(chartCenterOffset)); await tester.pump(); await tester.sendEventToBinding(pointer.scroll(scrollAmount)); await tester.pump(); expect(chartVirtualRect!.size.height, renderBox.size.height); expect( chartVirtualRect!.size.width, greaterThan(renderBox.size.width), ); expect(chartVirtualRect!.left, isNegative); expect(chartVirtualRect!.top, 0); }); testWidgets('scales vertically with FlScaleAxis.vertical', (tester) async { Rect? chartVirtualRect; await tester.pumpWidget( MaterialApp( home: Scaffold( body: Center( child: SizedBox( width: viewSize.width, height: viewSize.height, child: AxisChartScaffoldWidget( transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.vertical, trackpadScrollCausesScale: true, ), chartBuilder: (context, rect) { chartVirtualRect = rect; return dummyChart; }, data: lineChartDataWithNoTitles, ), ), ), ), ), ); final renderBox = tester.renderObject( find.byKey(dummyChartKey), ); final pointer = TestPointer(1, PointerDeviceKind.trackpad); final chartCenterOffset = tester.getCenter(find.byKey(dummyChartKey)); const scrollAmount = Offset(0, -100); await tester.sendEventToBinding(pointer.hover(chartCenterOffset)); await tester.pump(); await tester.sendEventToBinding(pointer.scroll(scrollAmount)); await tester.pump(); expect( chartVirtualRect!.size.height, greaterThan(renderBox.size.height), ); expect(chartVirtualRect!.size.width, renderBox.size.width); expect(chartVirtualRect!.left, 0); expect(chartVirtualRect!.top, isNegative); }); testWidgets('scales freely with FlScaleAxis.free', (tester) async { Rect? chartVirtualRect; await tester.pumpWidget( MaterialApp( home: Scaffold( body: Center( child: SizedBox( width: viewSize.width, height: viewSize.height, child: AxisChartScaffoldWidget( transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.free, trackpadScrollCausesScale: true, ), chartBuilder: (context, rect) { chartVirtualRect = rect; return dummyChart; }, data: lineChartDataWithNoTitles, ), ), ), ), ), ); final renderBox = tester.renderObject( find.byKey(dummyChartKey), ); final pointer = TestPointer(1, PointerDeviceKind.trackpad); final chartCenterOffset = tester.getCenter(find.byKey(dummyChartKey)); const scrollAmount = Offset(0, -100); await tester.sendEventToBinding(pointer.hover(chartCenterOffset)); await tester.pump(); await tester.sendEventToBinding(pointer.scroll(scrollAmount)); await tester.pump(); expect(chartVirtualRect!.size, greaterThan(renderBox.size)); expect(chartVirtualRect!.left, isNegative); expect(chartVirtualRect!.top, isNegative); }); }); group('pans', () { testWidgets('only horizontally with FlScaleAxis.horizontal', (tester) async { Rect? chartVirtualRect; await tester.pumpWidget( MaterialApp( home: Scaffold( body: Center( child: SizedBox( width: viewSize.width, height: viewSize.height, child: AxisChartScaffoldWidget( transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.horizontal, ), chartBuilder: (context, rect) { chartVirtualRect = rect; return const ColoredBox( color: Colors.red, ); }, data: lineChartDataWithNoTitles, ), ), ), ), ), ); final chartCenterOffset = tester.getCenter( find.byWidgetPredicate( (widget) => widget is ColoredBox && widget.color == Colors.red, ), ); final scaleStart1 = chartCenterOffset + const Offset(10, 10); final scaleStart2 = chartCenterOffset - const Offset(10, 10); final scaleEnd1 = chartCenterOffset + const Offset(100, 100); final scaleEnd2 = chartCenterOffset - const Offset(100, 100); final gesture1 = await tester.startGesture(scaleStart1); final gesture2 = await tester.startGesture(scaleStart2); await gesture1.moveTo(scaleEnd1); await gesture2.moveTo(scaleEnd2); await gesture1.up(); await gesture2.up(); await tester.pumpAndSettle(); final chartVirtualRectBeforePan = chartVirtualRect; expect(chartVirtualRectBeforePan!.top, 0); const panOffset = Offset(100, 100); await tester.dragFrom(chartCenterOffset, panOffset); await tester.pumpAndSettle(); expect(chartVirtualRect!.size, chartVirtualRectBeforePan.size); expect( chartVirtualRect!.left, greaterThan(chartVirtualRectBeforePan.left), ); expect(chartVirtualRect!.top, 0); }); testWidgets('only vertically with FlScaleAxis.vertical', (tester) async { Rect? chartVirtualRect; await tester.pumpWidget( MaterialApp( home: Scaffold( body: Center( child: SizedBox( width: viewSize.width, height: viewSize.height, child: AxisChartScaffoldWidget( transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.vertical, ), chartBuilder: (context, rect) { chartVirtualRect = rect; return const ColoredBox( color: Colors.red, ); }, data: lineChartDataWithNoTitles, ), ), ), ), ), ); final chartCenterOffset = tester.getCenter( find.byWidgetPredicate( (widget) => widget is ColoredBox && widget.color == Colors.red, ), ); final scaleStart1 = chartCenterOffset + const Offset(10, 10); final scaleStart2 = chartCenterOffset - const Offset(10, 10); final scaleEnd1 = chartCenterOffset + const Offset(100, 100); final scaleEnd2 = chartCenterOffset - const Offset(100, 100); final gesture1 = await tester.startGesture(scaleStart1); final gesture2 = await tester.startGesture(scaleStart2); await gesture1.moveTo(scaleEnd1); await gesture2.moveTo(scaleEnd2); await gesture1.up(); await gesture2.up(); await tester.pumpAndSettle(); final chartVirtualRectBeforePan = chartVirtualRect; expect(chartVirtualRectBeforePan!.left, 0); const panOffset = Offset(100, 100); await tester.dragFrom(chartCenterOffset, panOffset); await tester.pumpAndSettle(); expect(chartVirtualRect!.left, 0); expect( chartVirtualRect!.top, greaterThan(chartVirtualRectBeforePan.top), ); }); testWidgets('freely with FlScaleAxis.free', (tester) async { Rect? chartVirtualRect; await tester.pumpWidget( MaterialApp( home: Scaffold( body: Center( child: SizedBox( width: viewSize.width, height: viewSize.height, child: AxisChartScaffoldWidget( transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.free, ), chartBuilder: (context, rect) { chartVirtualRect = rect; return const ColoredBox( color: Colors.red, ); }, data: lineChartDataWithNoTitles, ), ), ), ), ), ); final chartCenterOffset = tester.getCenter( find.byWidgetPredicate( (widget) => widget is ColoredBox && widget.color == Colors.red, ), ); final scaleStart1 = chartCenterOffset + const Offset(10, 10); final scaleStart2 = chartCenterOffset - const Offset(10, 10); final scaleEnd1 = chartCenterOffset + const Offset(100, 100); final scaleEnd2 = chartCenterOffset - const Offset(100, 100); final gesture1 = await tester.startGesture(scaleStart1); final gesture2 = await tester.startGesture(scaleStart2); await gesture1.moveTo(scaleEnd1); await gesture2.moveTo(scaleEnd2); await gesture1.up(); await gesture2.up(); await tester.pumpAndSettle(); final chartVirtualRectBeforePan = chartVirtualRect; expect(chartVirtualRectBeforePan!.left, isNegative); expect(chartVirtualRectBeforePan.top, isNegative); const panOffset = Offset(100, 100); await tester.dragFrom(chartCenterOffset, panOffset); await tester.pumpAndSettle(); expect( chartVirtualRect!.left, greaterThan(chartVirtualRectBeforePan.left), ); expect( chartVirtualRect!.top, greaterThan(chartVirtualRectBeforePan.top), ); }); }); }); testWidgets('passes chart rect to SideTitlesWidgets', (tester) async { Rect? chartVirtualRect; await tester.pumpWidget( MaterialApp( home: Scaffold( body: Center( child: SizedBox( width: viewSize.width, height: viewSize.height, child: AxisChartScaffoldWidget( transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.free, ), chartBuilder: (context, rect) { chartVirtualRect = rect; return const ColoredBox( color: Colors.red, ); }, data: lineChartDataWithAllTitles, ), ), ), ), ), ); final chartCenterOffset = tester.getCenter( find.byWidgetPredicate( (widget) => widget is ColoredBox && widget.color == Colors.red, ), ); final scaleStart1 = chartCenterOffset + const Offset(10, 10); final scaleStart2 = chartCenterOffset - const Offset(10, 10); final scaleEnd1 = chartCenterOffset + const Offset(100, 100); final scaleEnd2 = chartCenterOffset - const Offset(100, 100); final gesture1 = await tester.startGesture(scaleStart1); final gesture2 = await tester.startGesture(scaleStart2); await gesture1.moveTo(scaleEnd1); await gesture2.moveTo(scaleEnd2); await gesture1.up(); await gesture2.up(); await tester.pumpAndSettle(); final sideTitlesWidgets = tester.allWidgets.whereType(); expect(sideTitlesWidgets.length, 4); for (final sideTitlesWidget in sideTitlesWidgets) { expect(sideTitlesWidget.chartVirtualRect, chartVirtualRect); } }); testWidgets( 'Initializes zoomed chart rect when controller scale != 1.0', (tester) async { final controller = TransformationController( Matrix4.identity()..scaleByDouble(3, 3, 3, 1), ); Rect? chartVirtualRect; await tester.pumpWidget( MaterialApp( home: Scaffold( body: Center( child: AxisChartScaffoldWidget( data: lineChartDataWithNoTitles, transformationConfig: FlTransformationConfig( transformationController: controller, scaleAxis: FlScaleAxis.free, ), chartBuilder: (context, rect) { chartVirtualRect = rect; return dummyChart; }, ), ), ), ), ); expect(chartVirtualRect, isNotNull); }, ); group('didUpdateWidget', () { const chartScaffoldKey = Key('chartScaffold'); final chartVirtualRects = []; tearDown(chartVirtualRects.clear); Widget createTestWidget({ TransformationController? controller, }) { return MaterialApp( home: Scaffold( body: Center( child: AxisChartScaffoldWidget( key: chartScaffoldKey, data: lineChartDataWithNoTitles, transformationConfig: FlTransformationConfig( scaleAxis: FlScaleAxis.free, transformationController: controller, ), chartBuilder: (context, rect) { chartVirtualRects.add(rect); return dummyChart; }, ), ), ), ); } TransformationController? getTransformationController( WidgetTester tester, ) { return tester .widget( find.byType(CustomInteractiveViewer), ) .transformationController; } testWidgets( 'oldWidget.controller is null and widget.controller is null: ' 'keeps old controller', (tester) async { final actualChartVirtualRects = [isNotScaled]; await tester.pumpWidget(createTestWidget()); expect(chartVirtualRects, actualChartVirtualRects); final transformationController = getTransformationController(tester); transformationController!.value = Matrix4.identity() ..scaleByDouble(2, 2, 2, 1); await tester.pump(); expect(chartVirtualRects, actualChartVirtualRects..add(isScaled)); await tester.pumpWidget(createTestWidget()); expect(chartVirtualRects, actualChartVirtualRects..add(isScaled)); final transformationController2 = getTransformationController(tester); expect(transformationController2, transformationController); transformationController2!.value = Matrix4.identity() ..scaleByDouble(3, 3, 3, 1); await tester.pump(); expect(chartVirtualRects, actualChartVirtualRects..add(isScaled)); }, ); testWidgets( 'oldWidget.controller is null and widget.controller is not null: ' 'disposes old controller and sets up widget.controller with listeners', (tester) async { final actualChartVirtualRects = [isNotScaled]; await tester.pumpWidget(createTestWidget()); expect(chartVirtualRects, actualChartVirtualRects); final transformationController = getTransformationController(tester); transformationController!.value = Matrix4.identity() ..scaleByDouble(2, 2, 2, 1); await tester.pump(); expect(chartVirtualRects, actualChartVirtualRects..add(isScaled)); final transformationController2 = TransformationController(); await tester.pumpWidget( createTestWidget(controller: transformationController2), ); expect(chartVirtualRects, actualChartVirtualRects..add(isNotScaled)); expect(transformationController2, isNot(transformationController)); expect( () => transformationController.addListener(() {}), throwsA(isA()), ); transformationController2.value = Matrix4.identity() ..scaleByDouble(2, 2, 2, 1); await tester.pump(); expect(chartVirtualRects, actualChartVirtualRects..add(isScaled)); }, ); testWidgets( 'oldWidget.controller is not null and widget.controller is null: ' 'removes listeners from old controller and sets up new controller ' 'with listeners', (tester) async { final actualChartVirtualRects = [isNotScaled]; final transformationController = TransformationController(); await tester.pumpWidget( createTestWidget(controller: transformationController), ); expect(chartVirtualRects, actualChartVirtualRects); transformationController.value = Matrix4.identity() ..scaleByDouble(2, 2, 2, 1); await tester.pump(); expect(chartVirtualRects, actualChartVirtualRects..add(isScaled)); await tester.pumpWidget(createTestWidget()); expect(chartVirtualRects, actualChartVirtualRects..add(isNotScaled)); final transformationController2 = getTransformationController(tester); expect(transformationController2, isNot(transformationController)); // This is for test // ignore: invalid_use_of_protected_member expect(transformationController.hasListeners, false); transformationController.addListener(() {}); // throws if disposed transformationController2!.value = Matrix4.identity() ..scaleByDouble(2, 2, 2, 1); await tester.pump(); expect(chartVirtualRects, actualChartVirtualRects..add(isScaled)); }, ); testWidgets( 'oldWidget.controller is not null and widget.controller is not null, ' 'controllers are different: ' 'removes listeners from old controller and sets up ' 'widget.controller with listeners', (tester) async { final actualChartVirtualRects = [isNotScaled]; final transformationController = TransformationController(); await tester.pumpWidget( createTestWidget(controller: transformationController), ); expect(chartVirtualRects, actualChartVirtualRects); transformationController.value = Matrix4.identity() ..scaleByDouble(2, 2, 2, 1); await tester.pump(); expect(chartVirtualRects, actualChartVirtualRects..add(isScaled)); final transformationController2 = TransformationController(); await tester.pumpWidget( createTestWidget(controller: transformationController2), ); expect(chartVirtualRects, actualChartVirtualRects..add(isNotScaled)); expect(transformationController2, isNot(transformationController)); // This is for test // ignore: invalid_use_of_protected_member expect(transformationController.hasListeners, false); transformationController.addListener(() {}); // throws if disposed transformationController2.value = Matrix4.identity() ..scaleByDouble(2, 2, 2, 1); await tester.pump(); expect(chartVirtualRects, actualChartVirtualRects..add(isScaled)); }, ); testWidgets( 'oldWidget.controller is not null and widget.controller is not null, ' 'controllers are the same: keeps old controller', (tester) async { final actualChartVirtualRects = [isNotScaled]; final transformationController = TransformationController(); await tester.pumpWidget( createTestWidget( controller: transformationController, ), ); expect(chartVirtualRects, actualChartVirtualRects); transformationController.value = Matrix4.identity() ..scaleByDouble(2, 2, 2, 1); await tester.pump(); expect(chartVirtualRects, actualChartVirtualRects..add(isScaled)); await tester.pumpWidget( createTestWidget( controller: transformationController, ), ); expect(chartVirtualRects, actualChartVirtualRects..add(isScaled)); final transformationController2 = getTransformationController(tester); expect(transformationController2, transformationController); transformationController.value = Matrix4.identity() ..scaleByDouble(3, 3, 3, 1); await tester.pump(); expect(chartVirtualRects, actualChartVirtualRects..add(isScaled)); }, ); }); testWidgets( 'sets chartVirtualRect to null, when scaling is updated to 1.0', (tester) async { final transformationController = TransformationController(); final chartVirtualRects = []; final actualChartVirtualRects = [isNotScaled]; await tester.pumpWidget( MaterialApp( home: AxisChartScaffoldWidget( data: lineChartDataWithNoTitles, transformationConfig: FlTransformationConfig( transformationController: transformationController, scaleAxis: FlScaleAxis.free, ), chartBuilder: (context, rect) { chartVirtualRects.add(rect); return dummyChart; }, ), ), ); expect(chartVirtualRects, actualChartVirtualRects); transformationController.value = Matrix4.identity() ..scaleByDouble(2, 2, 2, 1); await tester.pump(); expect(chartVirtualRects, actualChartVirtualRects..add(isScaled)); transformationController.value = Matrix4.identity() ..scaleByDouble(1, 1, 1, 1); await tester.pump(); expect(chartVirtualRects, actualChartVirtualRects..add(isNotScaled)); }, ); testWidgets('does not dispose external controller', (tester) async { final controller = TransformationController(); await tester.pumpWidget( MaterialApp( home: AxisChartScaffoldWidget( data: lineChartDataWithNoTitles, transformationConfig: FlTransformationConfig( transformationController: controller, ), chartBuilder: (context, rect) { return dummyChart; }, ), ), ); await tester.pumpWidget(Container()); // This is for test // ignore: invalid_use_of_protected_member expect(controller.hasListeners, false); controller.addListener(() {}); // throws if disposed }); }); } ================================================ FILE: test/chart/base/axis_chart/axis_chart_widgets_test.dart ================================================ import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { TitleMeta getTitleMeta(AxisSide axisSide) => TitleMeta( min: 0, max: 10, parentAxisSize: 100, axisPosition: 10, appliedInterval: 10, sideTitles: const SideTitles(), formattedValue: '12', axisSide: axisSide, rotationQuarterTurns: 0, ); group( 'SideTitle without FitInside enabled', () { testWidgets( 'SideTitleWidget left', (tester) async { await tester.pumpWidget( MaterialApp( home: Scaffold( body: SideTitleWidget( meta: getTitleMeta(AxisSide.left), child: const Text('1s'), ), ), ), ); expect(find.byType(Transform), findsAtLeastNWidgets(2)); expect(find.byType(Container), findsOneWidget); expect(find.text('1s'), findsOneWidget); }, ); testWidgets( 'SideTitleWidget top', (tester) async { await tester.pumpWidget( MaterialApp( home: Scaffold( body: SideTitleWidget( meta: getTitleMeta(AxisSide.top), child: const Text('1s'), ), ), ), ); expect(find.byType(Transform), findsAtLeastNWidgets(2)); expect(find.byType(Container), findsOneWidget); expect(find.text('1s'), findsOneWidget); }, ); testWidgets( 'SideTitleWidget right', (tester) async { await tester.pumpWidget( MaterialApp( home: Scaffold( body: SideTitleWidget( meta: getTitleMeta(AxisSide.right), child: const Text('1s'), ), ), ), ); expect(find.byType(Transform), findsAtLeastNWidgets(2)); expect(find.byType(Container), findsOneWidget); expect(find.text('1s'), findsOneWidget); }, ); testWidgets( 'SideTitleWidget bottom', (tester) async { const widgetKey = Key('SideTitleWidget'); final sideTitleWidget = SideTitleWidget( key: widgetKey, meta: getTitleMeta(AxisSide.bottom), child: const Text('1s'), ); await tester.pumpWidget( MaterialApp( home: Scaffold( body: sideTitleWidget, ), ), ); final element = tester.element(find.byKey(widgetKey)) as StatefulElement; final state = element.state as State; expect(state.widget, equals(sideTitleWidget)); expect(element.renderObject!.attached, isTrue); expect(find.byType(Transform), findsAtLeastNWidgets(2)); expect(find.byType(Container), findsOneWidget); expect(find.text('1s'), findsOneWidget); await tester.pump(); }, ); }, ); group( 'SideTitle with FitInside enabled', () { testWidgets( 'SideTitleWidget left with FitInsideEnabled on Top Side', (tester) async { const widgetKey = Key('SideTitleWidget'); final sideTitleWidget = SideTitleWidget( key: widgetKey, meta: getTitleMeta(AxisSide.left), fitInside: const SideTitleFitInsideData( enabled: true, axisPosition: 0, parentAxisSize: 100, distanceFromEdge: 0, ), child: const Text('A Long Text'), ); await tester.pumpWidget( MaterialApp( home: Scaffold( body: sideTitleWidget, ), ), ); final element = tester.element(find.byKey(widgetKey)) as StatefulElement; final state = element.state as State; expect(state.widget, equals(sideTitleWidget)); expect(element.renderObject!.attached, isTrue); expect(find.byType(Transform), findsAtLeastNWidgets(2)); expect(find.byType(Container), findsOneWidget); expect(find.text('A Long Text'), findsOneWidget); }, ); testWidgets( 'SideTitleWidget left with FitInsideEnabled on Bottom Side', (tester) async { const widgetKey = Key('SideTitleWidget'); final sideTitleWidget = SideTitleWidget( key: widgetKey, meta: getTitleMeta(AxisSide.left), fitInside: const SideTitleFitInsideData( enabled: true, axisPosition: 100, parentAxisSize: 100, distanceFromEdge: 0, ), child: const Text('A Long Text'), ); await tester.pumpWidget( MaterialApp( home: Scaffold( body: sideTitleWidget, ), ), ); final element = tester.element(find.byKey(widgetKey)) as StatefulElement; final state = element.state as State; expect(state.widget, equals(sideTitleWidget)); expect(element.renderObject!.attached, isTrue); expect(find.byType(Transform), findsAtLeastNWidgets(2)); expect(find.byType(Container), findsOneWidget); expect(find.text('A Long Text'), findsOneWidget); }, ); testWidgets( 'SideTitleWidget bottom with FitInsideEnabled on Left Side', (tester) async { const widgetKey = Key('SideTitleWidget'); final sideTitleWidget = SideTitleWidget( key: widgetKey, meta: getTitleMeta(AxisSide.bottom), fitInside: const SideTitleFitInsideData( enabled: true, axisPosition: 0, parentAxisSize: 100, distanceFromEdge: 0, ), child: const Text('A Long Text'), ); await tester.pumpWidget( MaterialApp( home: Scaffold( body: sideTitleWidget, ), ), ); final element = tester.element(find.byKey(widgetKey)) as StatefulElement; final state = element.state as State; expect(state.widget, equals(sideTitleWidget)); expect(element.renderObject!.attached, isTrue); expect(find.byType(Transform), findsAtLeastNWidgets(2)); expect(find.byType(Container), findsOneWidget); expect(find.text('A Long Text'), findsOneWidget); }, ); testWidgets( 'SideTitleWidget bottom with FitInsideEnabled on Right Side', (tester) async { const widgetKey = Key('SideTitleWidget'); final sideTitleWidget = SideTitleWidget( key: widgetKey, meta: getTitleMeta(AxisSide.bottom), fitInside: const SideTitleFitInsideData( enabled: true, axisPosition: 100, parentAxisSize: 100, distanceFromEdge: 0, ), child: const Text('A Long Text'), ); await tester.pumpWidget( MaterialApp( home: Scaffold( body: sideTitleWidget, ), ), ); final element = tester.element(find.byKey(widgetKey)) as StatefulElement; final state = element.state as State; expect(state.widget, equals(sideTitleWidget)); expect(element.renderObject!.attached, isTrue); expect(find.byType(Transform), findsAtLeastNWidgets(2)); expect(find.byType(Container), findsOneWidget); expect(find.text('A Long Text'), findsOneWidget); }, ); }, ); } ================================================ FILE: test/chart/base/axis_chart/base_chart_data_test.dart ================================================ import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import '../../data_pool.dart'; void main() { group('BaseChartData data equality check', () { test('FlBorderData equality test', () { expect(borderData1 == borderData1Clone, true); expect( borderData1 == FlBorderData( show: false, border: Border.all(color: Colors.green), ), false, ); expect( borderData1 == FlBorderData( show: true, ), false, ); }); }); } ================================================ FILE: test/chart/base/axis_chart/side_titles/side_titles_flex_test.dart ================================================ import 'package:fl_chart/src/chart/base/axis_chart/side_titles/side_titles_flex.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { group('SideTitlesFlex test', () { testWidgets( 'Test vertical mode', (tester) async { const viewHeight = 400.0; await tester.pumpWidget( MaterialApp( home: Scaffold( body: Center( child: SizedBox( width: 40, height: viewHeight, child: SideTitlesFlex( direction: Axis.vertical, axisSideMetaData: AxisSideMetaData( 0, 10, viewHeight, ), widgetHolders: oneToNineWidgetHolders(viewHeight), ), ), ), ), ), ); for (var i = 1; i <= 9; i++) { expect(find.text(i.toDouble().toString()), findsOneWidget); } expect(find.text('10.0'), findsNothing); expect(find.text('11.0'), findsNothing); expect(find.text('0.0'), findsNothing); }, ); testWidgets( 'Test horizontal mode', (tester) async { const viewWidth = 400.0; await tester.pumpWidget( MaterialApp( home: Scaffold( body: Center( child: SizedBox( width: viewWidth, height: 40, child: SideTitlesFlex( direction: Axis.horizontal, axisSideMetaData: AxisSideMetaData( 0, 10, viewWidth, ), widgetHolders: List.generate(9, (index) => index.toDouble()) .map( (value) => AxisSideTitleWidgetHolder( AxisSideTitleMetaData( value + 1, (value / 10) * viewWidth, ), Text((value + 1).toString()), ), ) .toList(), ), ), ), ), ), ); for (var i = 1; i <= 9; i++) { expect(find.text(i.toDouble().toString()), findsOneWidget); } expect(find.text('10.0'), findsNothing); expect(find.text('11.0'), findsNothing); expect(find.text('0.0'), findsNothing); }, ); testWidgets( 'Test update from horizontal to vertical', (tester) async { const valueKey = ValueKey('asdf'); const viewSize = 400.0; await tester.pumpWidget( MaterialApp( home: Scaffold( body: Center( child: SizedBox( width: viewSize, height: 40, child: SideTitlesFlex( key: valueKey, direction: Axis.horizontal, axisSideMetaData: AxisSideMetaData( 0, 10, viewSize, ), widgetHolders: oneToNineWidgetHolders(viewSize), ), ), ), ), ), ); for (var i = 1; i <= 9; i++) { expect(find.text(i.toDouble().toString()), findsOneWidget); } expect(find.text('10.0'), findsNothing); expect(find.text('11.0'), findsNothing); expect(find.text('0.0'), findsNothing); await tester.pumpWidget( MaterialApp( home: Scaffold( body: Center( child: SizedBox( width: 40, height: viewSize, child: SideTitlesFlex( key: valueKey, direction: Axis.vertical, axisSideMetaData: AxisSideMetaData( 0, 10, viewSize, ), widgetHolders: oneToNineWidgetHolders(viewSize), ), ), ), ), ), ); for (var i = 1; i <= 9; i++) { expect(find.text(i.toDouble().toString()), findsOneWidget); } expect(find.text('10.0'), findsNothing); expect(find.text('11.0'), findsNothing); expect(find.text('0.0'), findsNothing); }, ); }); test('AxisSideTitlesRenderFlex horizontal', () { const viewSize = 400.0; final axisSideMetaData = AxisSideMetaData( 0, 10, viewSize, ); final sideTitlesMetaData = oneToNineSideTitleMetaData(viewSize); final renderFlex = AxisSideTitlesRenderFlex( axisSideMetaData: axisSideMetaData, axisSideTitlesMetaData: sideTitlesMetaData, ); final builder = DiagnosticPropertiesBuilder(); renderFlex.debugFillProperties(builder); expect(builder.properties.length > 1, true); expect( (builder.properties.last as EnumProperty).value, Axis.horizontal, ); expect(renderFlex.direction, Axis.horizontal); expect(renderFlex.axisSideMetaData, axisSideMetaData); expect(renderFlex.axisSideTitlesMetaData, sideTitlesMetaData); expect(renderFlex.debugNeedsLayout, false); expect( renderFlex.hitTestChildren(BoxHitTestResult(), position: Offset.zero), false, ); expect( renderFlex .computeDryLayout(BoxConstraints.tight(const Size(viewSize, 40))), const Size(viewSize, 40), ); expect( renderFlex.computeDistanceToActualBaseline(TextBaseline.alphabetic), null, ); }); test('AxisSideTitlesRenderFlex vertical', () { const viewSize = 400.0; final axisSideMetaData = AxisSideMetaData( 0, 10, viewSize, ); final sideTitlesMetaData = oneToNineSideTitleMetaData(viewSize); final renderFlex = AxisSideTitlesRenderFlex( direction: Axis.vertical, axisSideMetaData: axisSideMetaData, axisSideTitlesMetaData: sideTitlesMetaData, ); expect( renderFlex .computeDryLayout(BoxConstraints.tight(const Size(viewSize, 40))), const Size(viewSize, 40), ); expect( renderFlex.computeDistanceToActualBaseline(TextBaseline.alphabetic), null, ); }); test('AxisSideTitleMetaData equality test', () { final data1 = AxisSideTitleMetaData(0, 0); final data1Clone = AxisSideTitleMetaData(0, 0); final data2 = AxisSideTitleMetaData(2, 0); expect(data1 == data1Clone, true); expect(data1 == data2, false); }); test('AxisSideMetaData diff', () { expect(AxisSideMetaData(5, 10, 100).diff, 5); expect(AxisSideMetaData(5, 10, 0).diff, 5); expect(AxisSideMetaData(9, 10, 0).diff, 1); }); } List oneToNineWidgetHolders(double viewSize) { return oneToNineSideTitleMetaData(viewSize).asMap().entries.map((e) { final index = e.key; final sideTitlesMetaData = e.value; return AxisSideTitleWidgetHolder( sideTitlesMetaData, Text((index + 1).toDouble().toString()), ); }).toList(); } List oneToNineSideTitleMetaData(double viewSize) { return List.generate(9, (index) => index.toDouble()) .map( (value) => AxisSideTitleMetaData(value + 1, (value / 10) * viewSize), ) .toList(); } ================================================ FILE: test/chart/base/axis_chart/side_titles/side_titles_test.dart ================================================ import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { final data = [ const FlSpot(0, 0.5), const FlSpot(1, 1.3), const FlSpot(2, 1.9), ]; const viewSize = Size(400, 400); testWidgets( 'Test the effect of minIncluded and maxIncluded in sideTitles', (tester) async { // Minimum/maximum included final mima = [ [true, true], [true, false], [false, true], [false, false], ]; for (final e in mima) { final titlesData = FlTitlesData( leftTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, minIncluded: e[0], maxIncluded: e[1], reservedSize: 50, interval: 1, ), ), rightTitles: const AxisTitles(), topTitles: const AxisTitles(), bottomTitles: const AxisTitles(), ); await tester.pumpWidget( MaterialApp( home: Scaffold( body: Center( child: SizedBox( width: viewSize.width, height: viewSize.height, child: LineChart( LineChartData( titlesData: titlesData, lineBarsData: [ LineChartBarData( spots: data, ), ], ), ), ), ), ), ), ); // Number of expected text widgets (titles) on the y-axis expect( find.byType(Text), findsNWidgets((e[0] ? 1 : 0) + (e[1] ? 1 : 0) + 1), ); // Always there expect(find.text('1'), findsOneWidget); if (e[0]) { // Minimum included expect(find.text('0.5'), findsOneWidget); } if (e[1]) { // Maximum included expect(find.text('1.9'), findsOneWidget); } } }, ); testWidgets('LineChart with only left titles overlayed on chart area', (tester) async { await tester.pumpWidget( MaterialApp( home: Scaffold( body: Center( child: SizedBox( width: viewSize.width, height: viewSize.height, child: LineChart( LineChartData( titlesData: FlTitlesData( leftTitles: AxisTitles( sideTitleAlignment: SideTitleAlignment.inside, axisNameWidget: const Text('Left Titles'), sideTitles: SideTitles( showTitles: true, getTitlesWidget: (value, meta) { return Text('L-${value.toInt()}'); }, interval: 1, ), ), ), lineBarsData: [ LineChartBarData( spots: data, ), ], ), ), ), ), ), ), ); final leftTitleFinder = find.text('L-0'); expect(leftTitleFinder, findsOneWidget); final leftTitleRect = tester.getRect(leftTitleFinder); final chartFinder = find.byType(LineChart); final chartRect = tester.getRect(chartFinder); expect(leftTitleRect.left >= chartRect.left, true); }); testWidgets('LineChart with only left titles overlayed on chart border', (tester) async { await tester.pumpWidget( MaterialApp( home: Scaffold( body: Center( child: SizedBox( width: viewSize.width, height: viewSize.height, child: LineChart( LineChartData( titlesData: FlTitlesData( leftTitles: AxisTitles( sideTitleAlignment: SideTitleAlignment.border, axisNameWidget: const Text('Left Titles'), sideTitles: SideTitles( showTitles: true, getTitlesWidget: (value, meta) { return Text('L-${value.toInt()}'); }, interval: 1, ), ), ), lineBarsData: [ LineChartBarData( spots: data, ), ], ), ), ), ), ), ), ); final leftTitleFinder = find.text('L-0'); expect(leftTitleFinder, findsOneWidget); final leftTitleRect = tester.getRect(leftTitleFinder); final chartFinder = find.byType(LineChart); final chartRect = tester.getRect(chartFinder); expect(leftTitleRect.left >= chartRect.left, true); }); } ================================================ FILE: test/chart/base/axis_chart/side_titles/side_titles_widget_test.dart ================================================ import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/chart/bar_chart/bar_chart_helper.dart'; import 'package:fl_chart/src/chart/base/axis_chart/side_titles/side_titles_widget.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { const viewSize = Size(400, 400); final lineChartDataBase = LineChartData( minX: 0, maxX: 10, minY: 0, maxY: 10, ); final lineChartDataWithNoTitles = lineChartDataBase.copyWith( titlesData: const FlTitlesData( show: false, leftTitles: AxisTitles(), topTitles: AxisTitles(), rightTitles: AxisTitles(), bottomTitles: AxisTitles(), ), ); final lineChartDataWithAllTitles = lineChartDataBase.copyWith( titlesData: FlTitlesData( leftTitles: AxisTitles( axisNameWidget: const Text('Left Titles'), sideTitles: SideTitles( showTitles: true, getTitlesWidget: (value, meta) { return Text('L-${value.toInt()}'); }, interval: 1, ), ), topTitles: AxisTitles( axisNameWidget: const Text('Top Titles'), sideTitles: SideTitles( showTitles: true, getTitlesWidget: (value, meta) { return Text('T-${value.toInt()}'); }, interval: 1, ), ), rightTitles: AxisTitles( axisNameWidget: const Text('Right Titles'), sideTitles: SideTitles( showTitles: true, getTitlesWidget: (value, meta) { return Text('R-${value.toInt()}'); }, interval: 1, ), ), bottomTitles: AxisTitles( axisNameWidget: const Text('Bottom Titles'), sideTitles: SideTitles( showTitles: true, getTitlesWidget: (value, meta) { return Text('B-${value.toInt()}'); }, interval: 1, ), ), ), ); final lineChartDataWithOnlyLeftTitles = lineChartDataBase.copyWith( titlesData: FlTitlesData( leftTitles: AxisTitles( axisNameWidget: const Text('Left Titles'), sideTitles: SideTitles( showTitles: true, getTitlesWidget: (value, meta) { return Text('L-${value.toInt()}'); }, interval: 1, ), ), topTitles: const AxisTitles(), rightTitles: const AxisTitles(), bottomTitles: const AxisTitles(), ), ); final lineChartDataWithOnlyLeftTitlesWithoutAxisName = lineChartDataBase.copyWith( titlesData: FlTitlesData( leftTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, getTitlesWidget: (value, meta) { return Text('L-${value.toInt()}'); }, interval: 1, ), ), topTitles: const AxisTitles(), rightTitles: const AxisTitles(), bottomTitles: const AxisTitles(), ), ); final barChartDataWithOnlyBottomTitles = BarChartData( barGroups: [ BarChartGroupData( x: 0, barRods: [ BarChartRodData(toY: 10), ], ), BarChartGroupData( x: 1, barRods: [ BarChartRodData(toY: 10), ], ), BarChartGroupData( x: 2, barRods: [ BarChartRodData(toY: 10), ], ), ], titlesData: FlTitlesData( leftTitles: const AxisTitles(), topTitles: const AxisTitles(), rightTitles: const AxisTitles(), bottomTitles: AxisTitles( axisNameWidget: const Icon(Icons.check), sideTitles: SideTitles( showTitles: true, getTitlesWidget: (value, meta) { return TextButton( onPressed: () {}, child: Text( value.toInt().toString(), ), ); }, ), ), ), ); BarChartData createBarChartDataWithOnlyRightTitles() { final barGroups = [ BarChartGroupData( x: 0, barRods: [ BarChartRodData(toY: 10), ], ), BarChartGroupData( x: 1, barRods: [ BarChartRodData(toY: 10), ], ), BarChartGroupData( x: 2, barRods: [ BarChartRodData(toY: 10), ], ), ]; final (minY, maxY) = BarChartHelper().calculateMaxAxisValues(barGroups); return BarChartData( barGroups: barGroups, titlesData: FlTitlesData( leftTitles: const AxisTitles(), topTitles: const AxisTitles(), rightTitles: AxisTitles( axisNameWidget: const Icon(Icons.arrow_right), sideTitles: SideTitles( showTitles: true, interval: 1, getTitlesWidget: (value, meta) { return TextButton( onPressed: () {}, child: Text( value.toInt().toString(), ), ); }, ), ), bottomTitles: const AxisTitles(), ), minY: minY, maxY: maxY, ); } BarChartData createBarChartDataWithEmptyGroups() { final barGroups = []; final (minY, maxY) = BarChartHelper().calculateMaxAxisValues(barGroups); return BarChartData( barGroups: [], titlesData: FlTitlesData( leftTitles: const AxisTitles(), topTitles: const AxisTitles(), rightTitles: AxisTitles( axisNameWidget: const Icon(Icons.arrow_right), sideTitles: SideTitles( showTitles: true, interval: 1, getTitlesWidget: (value, meta) { return TextButton( onPressed: () {}, child: Text( value.toInt().toString(), ), ); }, ), ), bottomTitles: const AxisTitles(), ), minY: minY, maxY: maxY, ); } testWidgets( 'LineChart with no titles', (tester) async { await tester.pumpWidget( MaterialApp( home: Scaffold( body: Center( child: SizedBox( width: viewSize.width, height: viewSize.height, child: SideTitlesWidget( side: AxisSide.left, axisChartData: lineChartDataWithNoTitles, parentSize: viewSize, ), ), ), ), ), ); expect(find.byType(Text), findsNothing); }, ); testWidgets( 'LineChart with all titles', (tester) async { Future checkSide(AxisSide side) async { await tester.pumpWidget( MaterialApp( home: Scaffold( body: Center( child: SizedBox( width: viewSize.width, height: viewSize.height, child: SideTitlesWidget( side: side, axisChartData: lineChartDataWithAllTitles, parentSize: viewSize, ), ), ), ), ), ); final axisName = switch (side) { AxisSide.left => 'Left', AxisSide.top => 'Top', AxisSide.right => 'Right', AxisSide.bottom => 'Bottom', }; expect(find.text('$axisName Titles'), findsOneWidget); for (var i = 0; i <= 10; i++) { expect(find.text('${axisName.characters.first}-$i'), findsOneWidget); } } await checkSide(AxisSide.left); await checkSide(AxisSide.top); await checkSide(AxisSide.right); await checkSide(AxisSide.bottom); }, ); testWidgets( 'LineChart with Only left titles', (tester) async { await tester.pumpWidget( MaterialApp( home: Scaffold( body: Center( child: SizedBox( width: viewSize.width, height: viewSize.height, child: SideTitlesWidget( side: AxisSide.left, axisChartData: lineChartDataWithOnlyLeftTitles, parentSize: viewSize, ), ), ), ), ), ); expect(find.text('Left Titles'), findsOneWidget); for (var i = 0; i <= 10; i++) { expect(find.text('L-$i'), findsOneWidget); } expect(find.byType(Text), findsNWidgets(12)); }, ); testWidgets( 'LineChart with Only left titles without axis name', (tester) async { await tester.pumpWidget( MaterialApp( home: Scaffold( body: Center( child: SizedBox( width: viewSize.width, height: viewSize.height, child: SideTitlesWidget( side: AxisSide.left, axisChartData: lineChartDataWithOnlyLeftTitlesWithoutAxisName, parentSize: viewSize, ), ), ), ), ), ); for (var i = 0; i <= 10; i++) { expect(find.text('L-$i'), findsOneWidget); } expect(find.byType(Text), findsNWidgets(11)); }, ); testWidgets( 'BarChart with Only bottom titles', (tester) async { await tester.pumpWidget( MaterialApp( home: Scaffold( body: Center( child: SizedBox( width: viewSize.width, height: viewSize.height, child: SideTitlesWidget( side: AxisSide.bottom, axisChartData: barChartDataWithOnlyBottomTitles, parentSize: viewSize, ), ), ), ), ), ); expect(find.byIcon(Icons.check), findsOneWidget); expect(find.text('0'), findsOneWidget); expect(find.text('1'), findsOneWidget); expect(find.text('2'), findsOneWidget); expect(find.byType(TextButton), findsNWidgets(3)); }, ); testWidgets( 'BarChart with Only right titles', (tester) async { await tester.pumpWidget( MaterialApp( home: Scaffold( body: Center( child: SizedBox( width: viewSize.width, height: viewSize.height, child: SideTitlesWidget( side: AxisSide.right, axisChartData: createBarChartDataWithOnlyRightTitles(), parentSize: viewSize, ), ), ), ), ), ); expect(find.byIcon(Icons.arrow_right), findsOneWidget); for (var i = 0; i <= 10; i++) { expect(find.text('$i'), findsOneWidget); } expect(find.byType(TextButton), findsNWidgets(11)); }, ); testWidgets( 'BarChart with empty bars', (tester) async { await tester.pumpWidget( MaterialApp( home: Scaffold( body: Center( child: SizedBox( width: viewSize.width, height: viewSize.height, child: SideTitlesWidget( side: AxisSide.right, axisChartData: createBarChartDataWithEmptyGroups(), parentSize: viewSize, ), ), ), ), ), ); expect(find.byIcon(Icons.arrow_right), findsOneWidget); expect(find.byType(Text), findsOneWidget); expect(find.byType(TextButton), findsOneWidget); }, ); } ================================================ FILE: test/chart/base/axis_chart/transformation_config_test.dart ================================================ import 'package:fl_chart/src/chart/base/axis_chart/scale_axis.dart'; import 'package:fl_chart/src/chart/base/axis_chart/transformation_config.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { group('FlTransformationConfig', () { test('throws assertion error when minScale is less than 1', () { expect( () => FlTransformationConfig(minScale: 0.99), throwsAssertionError, ); }); test('throws assertion error when maxScale is less than minScale', () { expect( () => FlTransformationConfig(minScale: 1.1, maxScale: 1), throwsAssertionError, ); }); test('has correct default values', () { const config = FlTransformationConfig(); expect(config.minScale, 1); expect(config.maxScale, 2.5); expect(config.trackpadScrollCausesScale, false); expect(config.scaleAxis, FlScaleAxis.none); expect(config.transformationController, isNull); }); }); } ================================================ FILE: test/chart/base/line_test.dart ================================================ import 'package:fl_chart/src/utils/utils.dart'; import 'package:flutter_test/flutter_test.dart'; import '../data_pool.dart'; void main() { const tolerance = 0.001; test('test magnitude()', () { expect(line1.magnitude(), closeTo(14.142, tolerance)); expect(line2.magnitude(), closeTo(22.360, tolerance)); expect(line3.magnitude(), closeTo(18.027, tolerance)); expect(line4.magnitude(), closeTo(32.310, tolerance)); expect(line5.magnitude(), closeTo(5.830, tolerance)); }); test('test angle()', () { expect(line1.direction(), closeTo(Utils().radians(45), tolerance)); expect(line2.direction(), closeTo(Utils().radians(63.434), tolerance)); expect(line3.direction(), closeTo(Utils().radians(-3.179), tolerance)); expect(line4.direction(), closeTo(Utils().radians(68.198), tolerance)); expect(line5.direction(), closeTo(Utils().radians(59), tolerance)); }); test('test normalize()', () { expect(line1.normalize().dx, closeTo(0.707, tolerance)); expect(line1.normalize().dy, closeTo(0.707, tolerance)); expect(line2.normalize().dx, closeTo(0.447, tolerance)); expect(line2.normalize().dy, closeTo(0.894, tolerance)); expect(line3.normalize().dx, closeTo(-0.998, tolerance)); expect(line3.normalize().dy, closeTo(0.0554, tolerance)); expect(line4.normalize().dx, closeTo(-0.371, tolerance)); expect(line4.normalize().dy, closeTo(-0.928, tolerance)); expect(line5.normalize().dx, closeTo(0.514, tolerance)); expect(line5.normalize().dy, closeTo(0.857, tolerance)); }); } ================================================ FILE: test/chart/base/render_base_chart_test.dart ================================================ import 'package:fl_chart/src/chart/base/base_chart/base_chart_data.dart'; import 'package:fl_chart/src/chart/base/base_chart/fl_touch_event.dart'; import 'package:fl_chart/src/chart/base/base_chart/render_base_chart.dart'; import 'package:fl_chart/src/chart/line_chart/line_chart_data.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'render_base_chart_test.mocks.dart'; @GenerateMocks([ BuildContext, PanGestureRecognizer, TapGestureRecognizer, LongPressGestureRecognizer, ]) void main() { group('RenderBaseChart', () { late BuildContext mockContext; late PanGestureRecognizer panGestureRecognizer; late TapGestureRecognizer tapGestureRecognizer; late LongPressGestureRecognizer longPressGestureRecognizer; late TestTouchData data; void touchCallback(_, __) {} setUp(() { mockContext = MockBuildContext(); panGestureRecognizer = MockPanGestureRecognizer(); tapGestureRecognizer = MockTapGestureRecognizer(); longPressGestureRecognizer = MockLongPressGestureRecognizer(); data = TestTouchData( false, touchCallback, null, null, ); }); group('handleEvent', () { test('respects canBeScaled for pan gestures for PointerDownEvent', () { const pointerDownEvent = PointerDownEvent(); final scalableChart = TestRenderBaseChart( mockContext, data, canBeScaled: true, panGestureRecognizerOverride: panGestureRecognizer, tapGestureRecognizerOverride: tapGestureRecognizer, longPressGestureRecognizerOverride: longPressGestureRecognizer, ); final nonScalableChart = TestRenderBaseChart( mockContext, data, canBeScaled: false, panGestureRecognizerOverride: panGestureRecognizer, tapGestureRecognizerOverride: tapGestureRecognizer, longPressGestureRecognizerOverride: longPressGestureRecognizer, ); final hitTestEntry = BoxHitTestEntry( scalableChart, Offset.zero, ); scalableChart.handleEvent(pointerDownEvent, hitTestEntry); verifyNever(panGestureRecognizer.addPointer(pointerDownEvent)); verify(longPressGestureRecognizer.addPointer(pointerDownEvent)) .called(1); verify(tapGestureRecognizer.addPointer(pointerDownEvent)).called(1); nonScalableChart.handleEvent(pointerDownEvent, hitTestEntry); verify(panGestureRecognizer.addPointer(pointerDownEvent)).called(1); verify(longPressGestureRecognizer.addPointer(pointerDownEvent)) .called(1); verify(tapGestureRecognizer.addPointer(pointerDownEvent)).called(1); }); test( 'does not add pointers for PointerDownEvent when no ' 'touchCallback provided', () { const pointerDownEvent = PointerDownEvent(); final chart = TestRenderBaseChart( mockContext, TestTouchData( false, null, null, null, ), canBeScaled: true, panGestureRecognizerOverride: panGestureRecognizer, tapGestureRecognizerOverride: tapGestureRecognizer, longPressGestureRecognizerOverride: longPressGestureRecognizer, ); final hitTestEntry = BoxHitTestEntry( chart, Offset.zero, ); chart.handleEvent(pointerDownEvent, hitTestEntry); verifyNever(panGestureRecognizer.addPointer(pointerDownEvent)); verifyNever(tapGestureRecognizer.addPointer(pointerDownEvent)); verifyNever(longPressGestureRecognizer.addPointer(pointerDownEvent)); }, ); test('calls touchCallback for PointerHoverEvent', () { late FlTouchEvent testEvent; late LineTouchResponse? testResponse; void callback(FlTouchEvent event, LineTouchResponse? response) { testEvent = event; testResponse = response; } const pointerHoverEvent = PointerHoverEvent(); final chart = TestRenderBaseChart( mockContext, TestTouchData( false, callback, null, null, ), canBeScaled: false, panGestureRecognizerOverride: panGestureRecognizer, tapGestureRecognizerOverride: tapGestureRecognizer, longPressGestureRecognizerOverride: longPressGestureRecognizer, ); final hitTestEntry = BoxHitTestEntry( chart, Offset.zero, ); chart.handleEvent(pointerHoverEvent, hitTestEntry); expect(testEvent, isA()); expect(testResponse, isA()); }); }); }); } // Modify TestRenderBaseChart to track gesture recognizer calls class TestRenderBaseChart extends RenderBaseChart { TestRenderBaseChart( BuildContext context, FlTouchData? touchData, { required bool canBeScaled, required this.panGestureRecognizerOverride, required this.tapGestureRecognizerOverride, required this.longPressGestureRecognizerOverride, }) : super(touchData, context, canBeScaled: canBeScaled); final PanGestureRecognizer panGestureRecognizerOverride; final TapGestureRecognizer tapGestureRecognizerOverride; final LongPressGestureRecognizer longPressGestureRecognizerOverride; @override void initGestureRecognizers() { super.initGestureRecognizers(); panGestureRecognizer = panGestureRecognizerOverride; tapGestureRecognizer = tapGestureRecognizerOverride; longPressGestureRecognizer = longPressGestureRecognizerOverride; } @override LineTouchResponse getResponseAtLocation(Offset localPosition) { return LineTouchResponse( touchLocation: Offset.zero, touchChartCoordinate: Offset.zero, lineBarSpots: [], ); } } class TestTouchData extends FlTouchData { TestTouchData( super.enabled, super.touchCallback, super.mouseCursorResolver, super.longPressDuration, ); } ================================================ FILE: test/chart/base/render_base_chart_test.mocks.dart ================================================ // Mocks generated by Mockito 5.4.6 from annotations // in fl_chart/test/chart/base/render_base_chart_test.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:ui' as _i12; import 'package:flutter/foundation.dart' as _i3; import 'package:flutter/rendering.dart' as _i9; import 'package:flutter/src/gestures/drag_details.dart' as _i10; import 'package:flutter/src/gestures/events.dart' as _i11; import 'package:flutter/src/gestures/long_press.dart' as _i14; import 'package:flutter/src/gestures/monodrag.dart' as _i7; import 'package:flutter/src/gestures/recognizer.dart' as _i5; import 'package:flutter/src/gestures/tap.dart' as _i13; import 'package:flutter/src/gestures/velocity_tracker.dart' as _i4; import 'package:flutter/src/widgets/framework.dart' as _i2; import 'package:flutter/src/widgets/notification_listener.dart' as _i6; import 'package:mockito/mockito.dart' as _i1; import 'package:mockito/src/dummies.dart' as _i8; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values // ignore_for_file: avoid_setters_without_getters // ignore_for_file: comment_references // ignore_for_file: deprecated_member_use // ignore_for_file: deprecated_member_use_from_same_package // ignore_for_file: implementation_imports // ignore_for_file: invalid_use_of_visible_for_testing_member // ignore_for_file: must_be_immutable // ignore_for_file: prefer_const_constructors // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class // ignore_for_file: invalid_use_of_internal_member class _FakeWidget_0 extends _i1.SmartFake implements _i2.Widget { _FakeWidget_0( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info}) => super.toString(); } class _FakeInheritedWidget_1 extends _i1.SmartFake implements _i2.InheritedWidget { _FakeInheritedWidget_1( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info}) => super.toString(); } class _FakeDiagnosticsNode_2 extends _i1.SmartFake implements _i3.DiagnosticsNode { _FakeDiagnosticsNode_2( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({ _i3.TextTreeConfiguration? parentConfiguration, _i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info, }) => super.toString(); } class _FakeVelocityTracker_3 extends _i1.SmartFake implements _i4.VelocityTracker { _FakeVelocityTracker_3( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeOffsetPair_4 extends _i1.SmartFake implements _i5.OffsetPair { _FakeOffsetPair_4( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } /// A class which mocks [BuildContext]. /// /// See the documentation for Mockito's code generation for more information. class MockBuildContext extends _i1.Mock implements _i2.BuildContext { MockBuildContext() { _i1.throwOnMissingStub(this); } @override _i2.Widget get widget => (super.noSuchMethod( Invocation.getter(#widget), returnValue: _FakeWidget_0( this, Invocation.getter(#widget), ), ) as _i2.Widget); @override bool get mounted => (super.noSuchMethod( Invocation.getter(#mounted), returnValue: false, ) as bool); @override bool get debugDoingBuild => (super.noSuchMethod( Invocation.getter(#debugDoingBuild), returnValue: false, ) as bool); @override _i2.InheritedWidget dependOnInheritedElement( _i2.InheritedElement? ancestor, { Object? aspect, }) => (super.noSuchMethod( Invocation.method( #dependOnInheritedElement, [ancestor], {#aspect: aspect}, ), returnValue: _FakeInheritedWidget_1( this, Invocation.method( #dependOnInheritedElement, [ancestor], {#aspect: aspect}, ), ), ) as _i2.InheritedWidget); @override void visitAncestorElements(_i2.ConditionalElementVisitor? visitor) => super.noSuchMethod( Invocation.method( #visitAncestorElements, [visitor], ), returnValueForMissingStub: null, ); @override void visitChildElements(_i2.ElementVisitor? visitor) => super.noSuchMethod( Invocation.method( #visitChildElements, [visitor], ), returnValueForMissingStub: null, ); @override void dispatchNotification(_i6.Notification? notification) => super.noSuchMethod( Invocation.method( #dispatchNotification, [notification], ), returnValueForMissingStub: null, ); @override _i3.DiagnosticsNode describeElement( String? name, { _i3.DiagnosticsTreeStyle? style = _i3.DiagnosticsTreeStyle.errorProperty, }) => (super.noSuchMethod( Invocation.method( #describeElement, [name], {#style: style}, ), returnValue: _FakeDiagnosticsNode_2( this, Invocation.method( #describeElement, [name], {#style: style}, ), ), ) as _i3.DiagnosticsNode); @override _i3.DiagnosticsNode describeWidget( String? name, { _i3.DiagnosticsTreeStyle? style = _i3.DiagnosticsTreeStyle.errorProperty, }) => (super.noSuchMethod( Invocation.method( #describeWidget, [name], {#style: style}, ), returnValue: _FakeDiagnosticsNode_2( this, Invocation.method( #describeWidget, [name], {#style: style}, ), ), ) as _i3.DiagnosticsNode); @override List<_i3.DiagnosticsNode> describeMissingAncestor( {required Type? expectedAncestorType}) => (super.noSuchMethod( Invocation.method( #describeMissingAncestor, [], {#expectedAncestorType: expectedAncestorType}, ), returnValue: <_i3.DiagnosticsNode>[], ) as List<_i3.DiagnosticsNode>); @override _i3.DiagnosticsNode describeOwnershipChain(String? name) => (super.noSuchMethod( Invocation.method( #describeOwnershipChain, [name], ), returnValue: _FakeDiagnosticsNode_2( this, Invocation.method( #describeOwnershipChain, [name], ), ), ) as _i3.DiagnosticsNode); } /// A class which mocks [PanGestureRecognizer]. /// /// See the documentation for Mockito's code generation for more information. class MockPanGestureRecognizer extends _i1.Mock implements _i7.PanGestureRecognizer { MockPanGestureRecognizer() { _i1.throwOnMissingStub(this); } @override String get debugDescription => (super.noSuchMethod( Invocation.getter(#debugDescription), returnValue: _i8.dummyValue( this, Invocation.getter(#debugDescription), ), ) as String); @override _i5.DragStartBehavior get dragStartBehavior => (super.noSuchMethod( Invocation.getter(#dragStartBehavior), returnValue: _i5.DragStartBehavior.down, ) as _i5.DragStartBehavior); @override _i5.MultitouchDragStrategy get multitouchDragStrategy => (super.noSuchMethod( Invocation.getter(#multitouchDragStrategy), returnValue: _i5.MultitouchDragStrategy.latestPointer, ) as _i5.MultitouchDragStrategy); @override bool get onlyAcceptDragOnThreshold => (super.noSuchMethod( Invocation.getter(#onlyAcceptDragOnThreshold), returnValue: false, ) as bool); @override _i7.GestureVelocityTrackerBuilder get velocityTrackerBuilder => (super.noSuchMethod( Invocation.getter(#velocityTrackerBuilder), returnValue: (_i9.PointerEvent event) => _FakeVelocityTracker_3( this, Invocation.getter(#velocityTrackerBuilder), ), ) as _i7.GestureVelocityTrackerBuilder); @override _i5.OffsetPair get lastPosition => (super.noSuchMethod( Invocation.getter(#lastPosition), returnValue: _FakeOffsetPair_4( this, Invocation.getter(#lastPosition), ), ) as _i5.OffsetPair); @override double get globalDistanceMoved => (super.noSuchMethod( Invocation.getter(#globalDistanceMoved), returnValue: 0.0, ) as double); @override set dragStartBehavior(_i5.DragStartBehavior? value) => super.noSuchMethod( Invocation.setter( #dragStartBehavior, value, ), returnValueForMissingStub: null, ); @override set multitouchDragStrategy(_i5.MultitouchDragStrategy? value) => super.noSuchMethod( Invocation.setter( #multitouchDragStrategy, value, ), returnValueForMissingStub: null, ); @override set onDown(_i10.GestureDragDownCallback? value) => super.noSuchMethod( Invocation.setter( #onDown, value, ), returnValueForMissingStub: null, ); @override set onStart(_i10.GestureDragStartCallback? value) => super.noSuchMethod( Invocation.setter( #onStart, value, ), returnValueForMissingStub: null, ); @override set onUpdate(_i10.GestureDragUpdateCallback? value) => super.noSuchMethod( Invocation.setter( #onUpdate, value, ), returnValueForMissingStub: null, ); @override set onEnd(_i7.GestureDragEndCallback? value) => super.noSuchMethod( Invocation.setter( #onEnd, value, ), returnValueForMissingStub: null, ); @override set onCancel(_i7.GestureDragCancelCallback? value) => super.noSuchMethod( Invocation.setter( #onCancel, value, ), returnValueForMissingStub: null, ); @override set minFlingDistance(double? value) => super.noSuchMethod( Invocation.setter( #minFlingDistance, value, ), returnValueForMissingStub: null, ); @override set minFlingVelocity(double? value) => super.noSuchMethod( Invocation.setter( #minFlingVelocity, value, ), returnValueForMissingStub: null, ); @override set maxFlingVelocity(double? value) => super.noSuchMethod( Invocation.setter( #maxFlingVelocity, value, ), returnValueForMissingStub: null, ); @override set onlyAcceptDragOnThreshold(bool? value) => super.noSuchMethod( Invocation.setter( #onlyAcceptDragOnThreshold, value, ), returnValueForMissingStub: null, ); @override set velocityTrackerBuilder(_i7.GestureVelocityTrackerBuilder? value) => super.noSuchMethod( Invocation.setter( #velocityTrackerBuilder, value, ), returnValueForMissingStub: null, ); @override set team(_i5.GestureArenaTeam? value) => super.noSuchMethod( Invocation.setter( #team, value, ), returnValueForMissingStub: null, ); @override _i5.AllowedButtonsFilter get allowedButtonsFilter => (super.noSuchMethod( Invocation.getter(#allowedButtonsFilter), returnValue: (int buttons) => false, ) as _i5.AllowedButtonsFilter); @override set gestureSettings(_i11.DeviceGestureSettings? value) => super.noSuchMethod( Invocation.setter( #gestureSettings, value, ), returnValueForMissingStub: null, ); @override set supportedDevices(Set<_i12.PointerDeviceKind>? value) => super.noSuchMethod( Invocation.setter( #supportedDevices, value, ), returnValueForMissingStub: null, ); @override bool isFlingGesture( _i4.VelocityEstimate? estimate, _i12.PointerDeviceKind? kind, ) => (super.noSuchMethod( Invocation.method( #isFlingGesture, [ estimate, kind, ], ), returnValue: false, ) as bool); @override _i10.DragEndDetails? considerFling( _i4.VelocityEstimate? estimate, _i12.PointerDeviceKind? kind, ) => (super.noSuchMethod(Invocation.method( #considerFling, [ estimate, kind, ], )) as _i10.DragEndDetails?); @override bool hasSufficientGlobalDistanceToAccept( _i12.PointerDeviceKind? pointerDeviceKind, double? deviceTouchSlop, ) => (super.noSuchMethod( Invocation.method( #hasSufficientGlobalDistanceToAccept, [ pointerDeviceKind, deviceTouchSlop, ], ), returnValue: false, ) as bool); @override bool isPointerAllowed(_i9.PointerEvent? event) => (super.noSuchMethod( Invocation.method( #isPointerAllowed, [event], ), returnValue: false, ) as bool); @override void addAllowedPointer(_i9.PointerDownEvent? event) => super.noSuchMethod( Invocation.method( #addAllowedPointer, [event], ), returnValueForMissingStub: null, ); @override void addAllowedPointerPanZoom(_i11.PointerPanZoomStartEvent? event) => super.noSuchMethod( Invocation.method( #addAllowedPointerPanZoom, [event], ), returnValueForMissingStub: null, ); @override void handleEvent(_i9.PointerEvent? event) => super.noSuchMethod( Invocation.method( #handleEvent, [event], ), returnValueForMissingStub: null, ); @override void acceptGesture(int? pointer) => super.noSuchMethod( Invocation.method( #acceptGesture, [pointer], ), returnValueForMissingStub: null, ); @override void rejectGesture(int? pointer) => super.noSuchMethod( Invocation.method( #rejectGesture, [pointer], ), returnValueForMissingStub: null, ); @override void didStopTrackingLastPointer(int? pointer) => super.noSuchMethod( Invocation.method( #didStopTrackingLastPointer, [pointer], ), returnValueForMissingStub: null, ); @override void dispose() => super.noSuchMethod( Invocation.method( #dispose, [], ), returnValueForMissingStub: null, ); @override void debugFillProperties(_i3.DiagnosticPropertiesBuilder? properties) => super.noSuchMethod( Invocation.method( #debugFillProperties, [properties], ), returnValueForMissingStub: null, ); @override void handleNonAllowedPointer(_i9.PointerDownEvent? event) => super.noSuchMethod( Invocation.method( #handleNonAllowedPointer, [event], ), returnValueForMissingStub: null, ); @override void resolve(_i5.GestureDisposition? disposition) => super.noSuchMethod( Invocation.method( #resolve, [disposition], ), returnValueForMissingStub: null, ); @override void resolvePointer( int? pointer, _i5.GestureDisposition? disposition, ) => super.noSuchMethod( Invocation.method( #resolvePointer, [ pointer, disposition, ], ), returnValueForMissingStub: null, ); @override void startTrackingPointer( int? pointer, [ _i9.Matrix4? transform, ]) => super.noSuchMethod( Invocation.method( #startTrackingPointer, [ pointer, transform, ], ), returnValueForMissingStub: null, ); @override void stopTrackingPointer(int? pointer) => super.noSuchMethod( Invocation.method( #stopTrackingPointer, [pointer], ), returnValueForMissingStub: null, ); @override void stopTrackingIfPointerNoLongerDown(_i9.PointerEvent? event) => super.noSuchMethod( Invocation.method( #stopTrackingIfPointerNoLongerDown, [event], ), returnValueForMissingStub: null, ); @override void addPointerPanZoom(_i11.PointerPanZoomStartEvent? event) => super.noSuchMethod( Invocation.method( #addPointerPanZoom, [event], ), returnValueForMissingStub: null, ); @override void addPointer(_i9.PointerDownEvent? event) => super.noSuchMethod( Invocation.method( #addPointer, [event], ), returnValueForMissingStub: null, ); @override void handleNonAllowedPointerPanZoom(_i11.PointerPanZoomStartEvent? event) => super.noSuchMethod( Invocation.method( #handleNonAllowedPointerPanZoom, [event], ), returnValueForMissingStub: null, ); @override bool isPointerPanZoomAllowed(_i11.PointerPanZoomStartEvent? event) => (super.noSuchMethod( Invocation.method( #isPointerPanZoomAllowed, [event], ), returnValue: false, ) as bool); @override _i12.PointerDeviceKind getKindForPointer(int? pointer) => (super.noSuchMethod( Invocation.method( #getKindForPointer, [pointer], ), returnValue: _i12.PointerDeviceKind.touch, ) as _i12.PointerDeviceKind); @override T? invokeCallback( String? name, _i5.RecognizerCallback? callback, { String Function()? debugReport, }) => (super.noSuchMethod(Invocation.method( #invokeCallback, [ name, callback, ], {#debugReport: debugReport}, )) as T?); @override String toString({_i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info}) => super.toString(); @override String toStringShallow({ String? joiner = ', ', _i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.debug, }) => (super.noSuchMethod( Invocation.method( #toStringShallow, [], { #joiner: joiner, #minLevel: minLevel, }, ), returnValue: _i8.dummyValue( this, Invocation.method( #toStringShallow, [], { #joiner: joiner, #minLevel: minLevel, }, ), ), ) as String); @override String toStringDeep({ String? prefixLineOne = '', String? prefixOtherLines, _i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.debug, int? wrapWidth = 65, }) => (super.noSuchMethod( Invocation.method( #toStringDeep, [], { #prefixLineOne: prefixLineOne, #prefixOtherLines: prefixOtherLines, #minLevel: minLevel, #wrapWidth: wrapWidth, }, ), returnValue: _i8.dummyValue( this, Invocation.method( #toStringDeep, [], { #prefixLineOne: prefixLineOne, #prefixOtherLines: prefixOtherLines, #minLevel: minLevel, #wrapWidth: wrapWidth, }, ), ), ) as String); @override String toStringShort() => (super.noSuchMethod( Invocation.method( #toStringShort, [], ), returnValue: _i8.dummyValue( this, Invocation.method( #toStringShort, [], ), ), ) as String); @override _i3.DiagnosticsNode toDiagnosticsNode({ String? name, _i3.DiagnosticsTreeStyle? style, }) => (super.noSuchMethod( Invocation.method( #toDiagnosticsNode, [], { #name: name, #style: style, }, ), returnValue: _FakeDiagnosticsNode_2( this, Invocation.method( #toDiagnosticsNode, [], { #name: name, #style: style, }, ), ), ) as _i3.DiagnosticsNode); @override List<_i3.DiagnosticsNode> debugDescribeChildren() => (super.noSuchMethod( Invocation.method( #debugDescribeChildren, [], ), returnValue: <_i3.DiagnosticsNode>[], ) as List<_i3.DiagnosticsNode>); } /// A class which mocks [TapGestureRecognizer]. /// /// See the documentation for Mockito's code generation for more information. class MockTapGestureRecognizer extends _i1.Mock implements _i13.TapGestureRecognizer { MockTapGestureRecognizer() { _i1.throwOnMissingStub(this); } @override String get debugDescription => (super.noSuchMethod( Invocation.getter(#debugDescription), returnValue: _i8.dummyValue( this, Invocation.getter(#debugDescription), ), ) as String); @override set onTapDown(_i13.GestureTapDownCallback? value) => super.noSuchMethod( Invocation.setter( #onTapDown, value, ), returnValueForMissingStub: null, ); @override set onTapUp(_i13.GestureTapUpCallback? value) => super.noSuchMethod( Invocation.setter( #onTapUp, value, ), returnValueForMissingStub: null, ); @override set onTap(_i13.GestureTapCallback? value) => super.noSuchMethod( Invocation.setter( #onTap, value, ), returnValueForMissingStub: null, ); @override set onTapMove(_i13.GestureTapMoveCallback? value) => super.noSuchMethod( Invocation.setter( #onTapMove, value, ), returnValueForMissingStub: null, ); @override set onTapCancel(_i13.GestureTapCancelCallback? value) => super.noSuchMethod( Invocation.setter( #onTapCancel, value, ), returnValueForMissingStub: null, ); @override set onSecondaryTap(_i13.GestureTapCallback? value) => super.noSuchMethod( Invocation.setter( #onSecondaryTap, value, ), returnValueForMissingStub: null, ); @override set onSecondaryTapDown(_i13.GestureTapDownCallback? value) => super.noSuchMethod( Invocation.setter( #onSecondaryTapDown, value, ), returnValueForMissingStub: null, ); @override set onSecondaryTapUp(_i13.GestureTapUpCallback? value) => super.noSuchMethod( Invocation.setter( #onSecondaryTapUp, value, ), returnValueForMissingStub: null, ); @override set onSecondaryTapCancel(_i13.GestureTapCancelCallback? value) => super.noSuchMethod( Invocation.setter( #onSecondaryTapCancel, value, ), returnValueForMissingStub: null, ); @override set onTertiaryTapDown(_i13.GestureTapDownCallback? value) => super.noSuchMethod( Invocation.setter( #onTertiaryTapDown, value, ), returnValueForMissingStub: null, ); @override set onTertiaryTapUp(_i13.GestureTapUpCallback? value) => super.noSuchMethod( Invocation.setter( #onTertiaryTapUp, value, ), returnValueForMissingStub: null, ); @override set onTertiaryTapCancel(_i13.GestureTapCancelCallback? value) => super.noSuchMethod( Invocation.setter( #onTertiaryTapCancel, value, ), returnValueForMissingStub: null, ); @override _i5.GestureRecognizerState get state => (super.noSuchMethod( Invocation.getter(#state), returnValue: _i5.GestureRecognizerState.ready, ) as _i5.GestureRecognizerState); @override set team(_i5.GestureArenaTeam? value) => super.noSuchMethod( Invocation.setter( #team, value, ), returnValueForMissingStub: null, ); @override _i5.AllowedButtonsFilter get allowedButtonsFilter => (super.noSuchMethod( Invocation.getter(#allowedButtonsFilter), returnValue: (int buttons) => false, ) as _i5.AllowedButtonsFilter); @override set gestureSettings(_i11.DeviceGestureSettings? value) => super.noSuchMethod( Invocation.setter( #gestureSettings, value, ), returnValueForMissingStub: null, ); @override set supportedDevices(Set<_i12.PointerDeviceKind>? value) => super.noSuchMethod( Invocation.setter( #supportedDevices, value, ), returnValueForMissingStub: null, ); @override bool isPointerAllowed(_i9.PointerDownEvent? event) => (super.noSuchMethod( Invocation.method( #isPointerAllowed, [event], ), returnValue: false, ) as bool); @override void handleTapDown({required _i9.PointerDownEvent? down}) => super.noSuchMethod( Invocation.method( #handleTapDown, [], {#down: down}, ), returnValueForMissingStub: null, ); @override void handleTapUp({ required _i9.PointerDownEvent? down, required _i9.PointerUpEvent? up, }) => super.noSuchMethod( Invocation.method( #handleTapUp, [], { #down: down, #up: up, }, ), returnValueForMissingStub: null, ); @override void handleTapMove({required _i9.PointerMoveEvent? move}) => super.noSuchMethod( Invocation.method( #handleTapMove, [], {#move: move}, ), returnValueForMissingStub: null, ); @override void handleTapCancel({ required _i9.PointerDownEvent? down, _i9.PointerCancelEvent? cancel, required String? reason, }) => super.noSuchMethod( Invocation.method( #handleTapCancel, [], { #down: down, #cancel: cancel, #reason: reason, }, ), returnValueForMissingStub: null, ); @override void addAllowedPointer(_i9.PointerDownEvent? event) => super.noSuchMethod( Invocation.method( #addAllowedPointer, [event], ), returnValueForMissingStub: null, ); @override void startTrackingPointer( int? pointer, [ _i9.Matrix4? transform, ]) => super.noSuchMethod( Invocation.method( #startTrackingPointer, [ pointer, transform, ], ), returnValueForMissingStub: null, ); @override void handlePrimaryPointer(_i9.PointerEvent? event) => super.noSuchMethod( Invocation.method( #handlePrimaryPointer, [event], ), returnValueForMissingStub: null, ); @override void resolve(_i5.GestureDisposition? disposition) => super.noSuchMethod( Invocation.method( #resolve, [disposition], ), returnValueForMissingStub: null, ); @override void didExceedDeadline() => super.noSuchMethod( Invocation.method( #didExceedDeadline, [], ), returnValueForMissingStub: null, ); @override void acceptGesture(int? pointer) => super.noSuchMethod( Invocation.method( #acceptGesture, [pointer], ), returnValueForMissingStub: null, ); @override void rejectGesture(int? pointer) => super.noSuchMethod( Invocation.method( #rejectGesture, [pointer], ), returnValueForMissingStub: null, ); @override void debugFillProperties(_i3.DiagnosticPropertiesBuilder? properties) => super.noSuchMethod( Invocation.method( #debugFillProperties, [properties], ), returnValueForMissingStub: null, ); @override void handleNonAllowedPointer(_i9.PointerDownEvent? event) => super.noSuchMethod( Invocation.method( #handleNonAllowedPointer, [event], ), returnValueForMissingStub: null, ); @override void handleEvent(_i9.PointerEvent? event) => super.noSuchMethod( Invocation.method( #handleEvent, [event], ), returnValueForMissingStub: null, ); @override void didExceedDeadlineWithEvent(_i9.PointerDownEvent? event) => super.noSuchMethod( Invocation.method( #didExceedDeadlineWithEvent, [event], ), returnValueForMissingStub: null, ); @override void didStopTrackingLastPointer(int? pointer) => super.noSuchMethod( Invocation.method( #didStopTrackingLastPointer, [pointer], ), returnValueForMissingStub: null, ); @override void dispose() => super.noSuchMethod( Invocation.method( #dispose, [], ), returnValueForMissingStub: null, ); @override void resolvePointer( int? pointer, _i5.GestureDisposition? disposition, ) => super.noSuchMethod( Invocation.method( #resolvePointer, [ pointer, disposition, ], ), returnValueForMissingStub: null, ); @override void stopTrackingPointer(int? pointer) => super.noSuchMethod( Invocation.method( #stopTrackingPointer, [pointer], ), returnValueForMissingStub: null, ); @override void stopTrackingIfPointerNoLongerDown(_i9.PointerEvent? event) => super.noSuchMethod( Invocation.method( #stopTrackingIfPointerNoLongerDown, [event], ), returnValueForMissingStub: null, ); @override void addPointerPanZoom(_i11.PointerPanZoomStartEvent? event) => super.noSuchMethod( Invocation.method( #addPointerPanZoom, [event], ), returnValueForMissingStub: null, ); @override void addAllowedPointerPanZoom(_i11.PointerPanZoomStartEvent? event) => super.noSuchMethod( Invocation.method( #addAllowedPointerPanZoom, [event], ), returnValueForMissingStub: null, ); @override void addPointer(_i9.PointerDownEvent? event) => super.noSuchMethod( Invocation.method( #addPointer, [event], ), returnValueForMissingStub: null, ); @override void handleNonAllowedPointerPanZoom(_i11.PointerPanZoomStartEvent? event) => super.noSuchMethod( Invocation.method( #handleNonAllowedPointerPanZoom, [event], ), returnValueForMissingStub: null, ); @override bool isPointerPanZoomAllowed(_i11.PointerPanZoomStartEvent? event) => (super.noSuchMethod( Invocation.method( #isPointerPanZoomAllowed, [event], ), returnValue: false, ) as bool); @override _i12.PointerDeviceKind getKindForPointer(int? pointer) => (super.noSuchMethod( Invocation.method( #getKindForPointer, [pointer], ), returnValue: _i12.PointerDeviceKind.touch, ) as _i12.PointerDeviceKind); @override T? invokeCallback( String? name, _i5.RecognizerCallback? callback, { String Function()? debugReport, }) => (super.noSuchMethod(Invocation.method( #invokeCallback, [ name, callback, ], {#debugReport: debugReport}, )) as T?); @override String toString({_i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info}) => super.toString(); @override String toStringShallow({ String? joiner = ', ', _i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.debug, }) => (super.noSuchMethod( Invocation.method( #toStringShallow, [], { #joiner: joiner, #minLevel: minLevel, }, ), returnValue: _i8.dummyValue( this, Invocation.method( #toStringShallow, [], { #joiner: joiner, #minLevel: minLevel, }, ), ), ) as String); @override String toStringDeep({ String? prefixLineOne = '', String? prefixOtherLines, _i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.debug, int? wrapWidth = 65, }) => (super.noSuchMethod( Invocation.method( #toStringDeep, [], { #prefixLineOne: prefixLineOne, #prefixOtherLines: prefixOtherLines, #minLevel: minLevel, #wrapWidth: wrapWidth, }, ), returnValue: _i8.dummyValue( this, Invocation.method( #toStringDeep, [], { #prefixLineOne: prefixLineOne, #prefixOtherLines: prefixOtherLines, #minLevel: minLevel, #wrapWidth: wrapWidth, }, ), ), ) as String); @override String toStringShort() => (super.noSuchMethod( Invocation.method( #toStringShort, [], ), returnValue: _i8.dummyValue( this, Invocation.method( #toStringShort, [], ), ), ) as String); @override _i3.DiagnosticsNode toDiagnosticsNode({ String? name, _i3.DiagnosticsTreeStyle? style, }) => (super.noSuchMethod( Invocation.method( #toDiagnosticsNode, [], { #name: name, #style: style, }, ), returnValue: _FakeDiagnosticsNode_2( this, Invocation.method( #toDiagnosticsNode, [], { #name: name, #style: style, }, ), ), ) as _i3.DiagnosticsNode); @override List<_i3.DiagnosticsNode> debugDescribeChildren() => (super.noSuchMethod( Invocation.method( #debugDescribeChildren, [], ), returnValue: <_i3.DiagnosticsNode>[], ) as List<_i3.DiagnosticsNode>); } /// A class which mocks [LongPressGestureRecognizer]. /// /// See the documentation for Mockito's code generation for more information. class MockLongPressGestureRecognizer extends _i1.Mock implements _i14.LongPressGestureRecognizer { MockLongPressGestureRecognizer() { _i1.throwOnMissingStub(this); } @override String get debugDescription => (super.noSuchMethod( Invocation.getter(#debugDescription), returnValue: _i8.dummyValue( this, Invocation.getter(#debugDescription), ), ) as String); @override set onLongPressDown(_i14.GestureLongPressDownCallback? value) => super.noSuchMethod( Invocation.setter( #onLongPressDown, value, ), returnValueForMissingStub: null, ); @override set onLongPressCancel(_i14.GestureLongPressCancelCallback? value) => super.noSuchMethod( Invocation.setter( #onLongPressCancel, value, ), returnValueForMissingStub: null, ); @override set onLongPress(_i14.GestureLongPressCallback? value) => super.noSuchMethod( Invocation.setter( #onLongPress, value, ), returnValueForMissingStub: null, ); @override set onLongPressStart(_i14.GestureLongPressStartCallback? value) => super.noSuchMethod( Invocation.setter( #onLongPressStart, value, ), returnValueForMissingStub: null, ); @override set onLongPressMoveUpdate(_i14.GestureLongPressMoveUpdateCallback? value) => super.noSuchMethod( Invocation.setter( #onLongPressMoveUpdate, value, ), returnValueForMissingStub: null, ); @override set onLongPressUp(_i14.GestureLongPressUpCallback? value) => super.noSuchMethod( Invocation.setter( #onLongPressUp, value, ), returnValueForMissingStub: null, ); @override set onLongPressEnd(_i14.GestureLongPressEndCallback? value) => super.noSuchMethod( Invocation.setter( #onLongPressEnd, value, ), returnValueForMissingStub: null, ); @override set onSecondaryLongPressDown(_i14.GestureLongPressDownCallback? value) => super.noSuchMethod( Invocation.setter( #onSecondaryLongPressDown, value, ), returnValueForMissingStub: null, ); @override set onSecondaryLongPressCancel(_i14.GestureLongPressCancelCallback? value) => super.noSuchMethod( Invocation.setter( #onSecondaryLongPressCancel, value, ), returnValueForMissingStub: null, ); @override set onSecondaryLongPress(_i14.GestureLongPressCallback? value) => super.noSuchMethod( Invocation.setter( #onSecondaryLongPress, value, ), returnValueForMissingStub: null, ); @override set onSecondaryLongPressStart(_i14.GestureLongPressStartCallback? value) => super.noSuchMethod( Invocation.setter( #onSecondaryLongPressStart, value, ), returnValueForMissingStub: null, ); @override set onSecondaryLongPressMoveUpdate( _i14.GestureLongPressMoveUpdateCallback? value) => super.noSuchMethod( Invocation.setter( #onSecondaryLongPressMoveUpdate, value, ), returnValueForMissingStub: null, ); @override set onSecondaryLongPressUp(_i14.GestureLongPressUpCallback? value) => super.noSuchMethod( Invocation.setter( #onSecondaryLongPressUp, value, ), returnValueForMissingStub: null, ); @override set onSecondaryLongPressEnd(_i14.GestureLongPressEndCallback? value) => super.noSuchMethod( Invocation.setter( #onSecondaryLongPressEnd, value, ), returnValueForMissingStub: null, ); @override set onTertiaryLongPressDown(_i14.GestureLongPressDownCallback? value) => super.noSuchMethod( Invocation.setter( #onTertiaryLongPressDown, value, ), returnValueForMissingStub: null, ); @override set onTertiaryLongPressCancel(_i14.GestureLongPressCancelCallback? value) => super.noSuchMethod( Invocation.setter( #onTertiaryLongPressCancel, value, ), returnValueForMissingStub: null, ); @override set onTertiaryLongPress(_i14.GestureLongPressCallback? value) => super.noSuchMethod( Invocation.setter( #onTertiaryLongPress, value, ), returnValueForMissingStub: null, ); @override set onTertiaryLongPressStart(_i14.GestureLongPressStartCallback? value) => super.noSuchMethod( Invocation.setter( #onTertiaryLongPressStart, value, ), returnValueForMissingStub: null, ); @override set onTertiaryLongPressMoveUpdate( _i14.GestureLongPressMoveUpdateCallback? value) => super.noSuchMethod( Invocation.setter( #onTertiaryLongPressMoveUpdate, value, ), returnValueForMissingStub: null, ); @override set onTertiaryLongPressUp(_i14.GestureLongPressUpCallback? value) => super.noSuchMethod( Invocation.setter( #onTertiaryLongPressUp, value, ), returnValueForMissingStub: null, ); @override set onTertiaryLongPressEnd(_i14.GestureLongPressEndCallback? value) => super.noSuchMethod( Invocation.setter( #onTertiaryLongPressEnd, value, ), returnValueForMissingStub: null, ); @override _i5.GestureRecognizerState get state => (super.noSuchMethod( Invocation.getter(#state), returnValue: _i5.GestureRecognizerState.ready, ) as _i5.GestureRecognizerState); @override set team(_i5.GestureArenaTeam? value) => super.noSuchMethod( Invocation.setter( #team, value, ), returnValueForMissingStub: null, ); @override _i5.AllowedButtonsFilter get allowedButtonsFilter => (super.noSuchMethod( Invocation.getter(#allowedButtonsFilter), returnValue: (int buttons) => false, ) as _i5.AllowedButtonsFilter); @override set gestureSettings(_i11.DeviceGestureSettings? value) => super.noSuchMethod( Invocation.setter( #gestureSettings, value, ), returnValueForMissingStub: null, ); @override set supportedDevices(Set<_i12.PointerDeviceKind>? value) => super.noSuchMethod( Invocation.setter( #supportedDevices, value, ), returnValueForMissingStub: null, ); @override bool isPointerAllowed(_i9.PointerDownEvent? event) => (super.noSuchMethod( Invocation.method( #isPointerAllowed, [event], ), returnValue: false, ) as bool); @override void didExceedDeadline() => super.noSuchMethod( Invocation.method( #didExceedDeadline, [], ), returnValueForMissingStub: null, ); @override void handlePrimaryPointer(_i9.PointerEvent? event) => super.noSuchMethod( Invocation.method( #handlePrimaryPointer, [event], ), returnValueForMissingStub: null, ); @override void resolve(_i5.GestureDisposition? disposition) => super.noSuchMethod( Invocation.method( #resolve, [disposition], ), returnValueForMissingStub: null, ); @override void acceptGesture(int? pointer) => super.noSuchMethod( Invocation.method( #acceptGesture, [pointer], ), returnValueForMissingStub: null, ); @override void addAllowedPointer(_i9.PointerDownEvent? event) => super.noSuchMethod( Invocation.method( #addAllowedPointer, [event], ), returnValueForMissingStub: null, ); @override void handleNonAllowedPointer(_i9.PointerDownEvent? event) => super.noSuchMethod( Invocation.method( #handleNonAllowedPointer, [event], ), returnValueForMissingStub: null, ); @override void handleEvent(_i9.PointerEvent? event) => super.noSuchMethod( Invocation.method( #handleEvent, [event], ), returnValueForMissingStub: null, ); @override void didExceedDeadlineWithEvent(_i9.PointerDownEvent? event) => super.noSuchMethod( Invocation.method( #didExceedDeadlineWithEvent, [event], ), returnValueForMissingStub: null, ); @override void rejectGesture(int? pointer) => super.noSuchMethod( Invocation.method( #rejectGesture, [pointer], ), returnValueForMissingStub: null, ); @override void didStopTrackingLastPointer(int? pointer) => super.noSuchMethod( Invocation.method( #didStopTrackingLastPointer, [pointer], ), returnValueForMissingStub: null, ); @override void dispose() => super.noSuchMethod( Invocation.method( #dispose, [], ), returnValueForMissingStub: null, ); @override void debugFillProperties(_i3.DiagnosticPropertiesBuilder? properties) => super.noSuchMethod( Invocation.method( #debugFillProperties, [properties], ), returnValueForMissingStub: null, ); @override void resolvePointer( int? pointer, _i5.GestureDisposition? disposition, ) => super.noSuchMethod( Invocation.method( #resolvePointer, [ pointer, disposition, ], ), returnValueForMissingStub: null, ); @override void startTrackingPointer( int? pointer, [ _i9.Matrix4? transform, ]) => super.noSuchMethod( Invocation.method( #startTrackingPointer, [ pointer, transform, ], ), returnValueForMissingStub: null, ); @override void stopTrackingPointer(int? pointer) => super.noSuchMethod( Invocation.method( #stopTrackingPointer, [pointer], ), returnValueForMissingStub: null, ); @override void stopTrackingIfPointerNoLongerDown(_i9.PointerEvent? event) => super.noSuchMethod( Invocation.method( #stopTrackingIfPointerNoLongerDown, [event], ), returnValueForMissingStub: null, ); @override void addPointerPanZoom(_i11.PointerPanZoomStartEvent? event) => super.noSuchMethod( Invocation.method( #addPointerPanZoom, [event], ), returnValueForMissingStub: null, ); @override void addAllowedPointerPanZoom(_i11.PointerPanZoomStartEvent? event) => super.noSuchMethod( Invocation.method( #addAllowedPointerPanZoom, [event], ), returnValueForMissingStub: null, ); @override void addPointer(_i9.PointerDownEvent? event) => super.noSuchMethod( Invocation.method( #addPointer, [event], ), returnValueForMissingStub: null, ); @override void handleNonAllowedPointerPanZoom(_i11.PointerPanZoomStartEvent? event) => super.noSuchMethod( Invocation.method( #handleNonAllowedPointerPanZoom, [event], ), returnValueForMissingStub: null, ); @override bool isPointerPanZoomAllowed(_i11.PointerPanZoomStartEvent? event) => (super.noSuchMethod( Invocation.method( #isPointerPanZoomAllowed, [event], ), returnValue: false, ) as bool); @override _i12.PointerDeviceKind getKindForPointer(int? pointer) => (super.noSuchMethod( Invocation.method( #getKindForPointer, [pointer], ), returnValue: _i12.PointerDeviceKind.touch, ) as _i12.PointerDeviceKind); @override T? invokeCallback( String? name, _i5.RecognizerCallback? callback, { String Function()? debugReport, }) => (super.noSuchMethod(Invocation.method( #invokeCallback, [ name, callback, ], {#debugReport: debugReport}, )) as T?); @override String toString({_i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info}) => super.toString(); @override String toStringShallow({ String? joiner = ', ', _i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.debug, }) => (super.noSuchMethod( Invocation.method( #toStringShallow, [], { #joiner: joiner, #minLevel: minLevel, }, ), returnValue: _i8.dummyValue( this, Invocation.method( #toStringShallow, [], { #joiner: joiner, #minLevel: minLevel, }, ), ), ) as String); @override String toStringDeep({ String? prefixLineOne = '', String? prefixOtherLines, _i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.debug, int? wrapWidth = 65, }) => (super.noSuchMethod( Invocation.method( #toStringDeep, [], { #prefixLineOne: prefixLineOne, #prefixOtherLines: prefixOtherLines, #minLevel: minLevel, #wrapWidth: wrapWidth, }, ), returnValue: _i8.dummyValue( this, Invocation.method( #toStringDeep, [], { #prefixLineOne: prefixLineOne, #prefixOtherLines: prefixOtherLines, #minLevel: minLevel, #wrapWidth: wrapWidth, }, ), ), ) as String); @override String toStringShort() => (super.noSuchMethod( Invocation.method( #toStringShort, [], ), returnValue: _i8.dummyValue( this, Invocation.method( #toStringShort, [], ), ), ) as String); @override _i3.DiagnosticsNode toDiagnosticsNode({ String? name, _i3.DiagnosticsTreeStyle? style, }) => (super.noSuchMethod( Invocation.method( #toDiagnosticsNode, [], { #name: name, #style: style, }, ), returnValue: _FakeDiagnosticsNode_2( this, Invocation.method( #toDiagnosticsNode, [], { #name: name, #style: style, }, ), ), ) as _i3.DiagnosticsNode); @override List<_i3.DiagnosticsNode> debugDescribeChildren() => (super.noSuchMethod( Invocation.method( #debugDescribeChildren, [], ), returnValue: <_i3.DiagnosticsNode>[], ) as List<_i3.DiagnosticsNode>); } ================================================ FILE: test/chart/candlestick_chart/candlestick_chart_data_test.dart ================================================ import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import '../data_pool.dart'; void main() { group('Candlestick data equality check', () { test('CandlestickChartData equality test', () { expect(candleStickChartData1 == candleStickChartData1Clone, true); expect( candleStickChartData1 == candleStickChartData1Clone.copyWith(showingTooltipIndicators: []), false, ); expect( candleStickChartData1 == candleStickChartData1Clone.copyWith( borderData: FlBorderData( show: true, border: Border.all(color: Colors.red), ), ), false, ); expect( candleStickChartData1 == candleStickChartData1Clone.copyWith( borderData: FlBorderData( show: true, border: Border.all(color: Colors.green), ), ), true, ); expect( candleStickChartData1 == candleStickChartData1Clone.copyWith(maxX: 444), false, ); expect( candleStickChartData1 == candleStickChartData1Clone.copyWith( candlestickSpots: [ CandlestickSpot( x: 0, open: 10, high: 100, low: 0, close: 20, ), CandlestickSpot( x: 10, open: 30, high: 110, low: 10, close: 20, ), CandlestickSpot( x: 20, open: 30, high: 120, low: 20, close: 40, ), CandlestickSpot( x: 30, open: 40, high: 130, low: 30, close: 50, ), ], ), true, ); expect( candleStickChartData1 == candleStickChartData1Clone.copyWith( candlestickSpots: [ CandlestickSpot(x: 5, open: 10, high: 100, low: 0, close: 20), CandlestickSpot(x: 10, open: 20, high: 110, low: 10, close: 30), CandlestickSpot(x: 20, open: 30, high: 120, low: 20, close: 40), CandlestickSpot(x: 30, open: 40, high: 130, low: 30, close: 50), ], ), false, ); expect( candleStickChartData1 == candleStickChartData1Clone.copyWith( clipData: const FlClipData.all(), ), false, ); expect( candleStickChartData1 == candleStickChartData1Clone.copyWith( gridData: const FlGridData( verticalInterval: 12, horizontalInterval: 22, drawVerticalLine: false, checkToShowVerticalLine: checkToShowLine, getDrawingHorizontalLine: getDrawingLine, ), ), true, ); expect( candleStickChartData1 == candleStickChartData1Clone.copyWith( gridData: const FlGridData( getDrawingHorizontalLine: gridGetDrawingLine, getDrawingVerticalLine: gridGetDrawingLine, checkToShowHorizontalLine: gridCheckToShowLine, checkToShowVerticalLine: gridCheckToShowLine, drawVerticalLine: false, horizontalInterval: 33, verticalInterval: 1, ), ), false, ); expect( candleStickChartData1 == candleStickChartData1Clone.copyWith( gridData: FlGridData( show: false, getDrawingHorizontalLine: (value) => const FlLine( color: Colors.green, strokeWidth: 12, dashArray: [1, 2], ), getDrawingVerticalLine: (value) => const FlLine( color: Colors.yellow, strokeWidth: 33, dashArray: [0, 1], ), checkToShowHorizontalLine: (value) => false, checkToShowVerticalLine: (value) => true, drawVerticalLine: false, horizontalInterval: 32, verticalInterval: 1, ), ), false, ); expect( candleStickChartData1 == candleStickChartData1Clone.copyWith( titlesData: MockData.flTitlesData1, ), true, ); expect( candleStickChartData1 == candleStickChartData1Clone.copyWith( titlesData: const FlTitlesData( leftTitles: AxisTitles( axisNameSize: 332, axisNameWidget: Text('title 1'), ), rightTitles: AxisTitles( axisNameSize: 1326, axisNameWidget: Text('title 3'), sideTitles: SideTitles(reservedSize: 500, showTitles: true), ), topTitles: AxisTitles( axisNameSize: 34, axisNameWidget: Text('title 4'), ), bottomTitles: AxisTitles( axisNameSize: 22, axisNameWidget: Text('title 2'), ), ), ), false, ); expect( candleStickChartData1 == candleStickChartData1Clone.copyWith( titlesData: const FlTitlesData( leftTitles: AxisTitles( axisNameSize: 33, axisNameWidget: Text('title 1'), ), rightTitles: AxisTitles( axisNameSize: 1326, axisNameWidget: Text('title 3'), sideTitles: SideTitles(reservedSize: 500, showTitles: true), ), topTitles: AxisTitles( axisNameSize: 34, axisNameWidget: Text('title 4'), ), bottomTitles: AxisTitles( axisNameSize: 22, axisNameWidget: Text('title 2'), sideTitles: SideTitles(showTitles: true), ), ), ), false, ); expect( candleStickChartData1 == candleStickChartData1Clone.copyWith( titlesData: const FlTitlesData( leftTitles: AxisTitles( axisNameSize: 33, axisNameWidget: Text('title 1'), ), rightTitles: AxisTitles( axisNameSize: 1326, axisNameWidget: Text('title 1'), sideTitles: SideTitles(reservedSize: 500, showTitles: true), ), topTitles: AxisTitles( axisNameSize: 34, axisNameWidget: Text('title 4'), ), bottomTitles: AxisTitles( axisNameSize: 22, axisNameWidget: Text('title 2'), ), ), ), false, ); expect( candleStickChartData1 == candleStickChartData1Clone.copyWith( titlesData: const FlTitlesData( leftTitles: AxisTitles( axisNameSize: 33, axisNameWidget: Text('title 1'), ), rightTitles: AxisTitles( axisNameSize: 13262, axisNameWidget: Text('title 3'), sideTitles: SideTitles(reservedSize: 500, showTitles: true), ), topTitles: AxisTitles( axisNameSize: 34, axisNameWidget: Text('title 4'), ), bottomTitles: AxisTitles( axisNameSize: 22, axisNameWidget: Text('title 2'), ), ), ), false, ); expect( candleStickChartData1 == candleStickChartData1Clone.copyWith(showingTooltipIndicators: []), false, ); expect( candleStickChartData1 == candleStickChartData1Clone .copyWith(showingTooltipIndicators: [2, 1, 0]), false, ); expect( candleStickChartData1 == candleStickChartData1Clone.copyWith( candlestickPainter: DefaultCandlestickPainter(), ), false, ); }); test('CandlestickSpot equality test', () { expect(candlestickSpot1 == candlestickSpot1Clone, true); expect(candlestickSpot1 == candlestickSpot2, false); expect(candlestickSpot2 == candlestickSpot2.copyWith(), true); expect(candlestickSpot2 == candlestickSpot3, false); expect(candlestickSpot3 == candlestickSpot4, false); expect( candlestickSpot3 == candlestickSpot3.copyWith( show: false, ), false, ); expect( candlestickSpot3 == candlestickSpot3.copyWith( show: true, ), true, ); }); test('CandlestickTouchData equality test', () { final sample = CandlestickTouchData( touchTooltipData: CandlestickTouchTooltipData( maxContentWidth: 2, getTooltipColor: candlestickChartGetTooltipRedColor, tooltipPadding: const EdgeInsets.all(11), ), handleBuiltInTouches: false, touchSpotThreshold: 23, enabled: false, ); final sampleClone = CandlestickTouchData( touchTooltipData: CandlestickTouchTooltipData( maxContentWidth: 2, getTooltipColor: candlestickChartGetTooltipRedColor, tooltipPadding: const EdgeInsets.all(11), ), handleBuiltInTouches: false, touchSpotThreshold: 23, enabled: false, ); expect(sample == sampleClone, true); expect( sample == sampleClone.copyWith( touchCallback: (event, response) {}, ), false, ); expect( sample == sampleClone.copyWith( enabled: true, ), false, ); expect( sample == sampleClone.copyWith( touchSpotThreshold: 22, ), false, ); expect( sample == sampleClone.copyWith( handleBuiltInTouches: true, ), false, ); expect( sample == sampleClone.copyWith( longPressDuration: Duration.zero, ), false, ); }); test('CandlestickTouchTooltipData equality test', () { expect( candlestickTouchTooltipData1 == candlestickTouchTooltipData1Clone, true, ); expect( candlestickTouchTooltipData1 == candlestickTouchTooltipData2, false, ); expect( candlestickTouchTooltipData1 == candlestickTouchTooltipData3, false, ); }); test('CandlestickTooltipItem equality test', () { final sample1 = CandlestickTooltipItem( 'aa', textStyle: const TextStyle(color: Colors.red), bottomMargin: 23, ); final sample2 = CandlestickTooltipItem( 'aa', textStyle: const TextStyle(color: Colors.red), bottomMargin: 23, ); expect(sample1 == sample2, true); var changed = CandlestickTooltipItem( 'a3a', textStyle: const TextStyle(color: Colors.red), bottomMargin: 23, ); expect(sample1 == changed, false); changed = CandlestickTooltipItem( 'aa', textStyle: const TextStyle(color: Colors.green), bottomMargin: 23, ); expect(sample1 == changed, false); changed = CandlestickTooltipItem( 'aa', textStyle: const TextStyle(color: Colors.red), bottomMargin: 0, ); expect(sample1 == changed, false); }); }); } ================================================ FILE: test/chart/candlestick_chart/candlestick_chart_helper_test.dart ================================================ import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/chart/candlestick_chart/candlestick_chart_helper.dart'; import 'package:flutter_test/flutter_test.dart'; import '../data_pool.dart'; void main() { group('Check CandlestickChartHelper.calculateMaxAxisValues', () { test('Test validity 1', () { final candlestickSpots = [ candlestickSpot1, candlestickSpot2, candlestickSpot3, candlestickSpot4, ]; final (minX, maxX, minY, maxY) = CandlestickChartHelper.calculateMaxAxisValues(candlestickSpots); expect(minX, 0); expect(maxX, 30); expect(minY, 0); expect(maxY, 130); }); test('Test validity 2', () { final scatterSpots = [ CandlestickSpot( x: 0, open: 100, close: 200, high: 400, low: 4, ), CandlestickSpot( x: 1, open: 500, close: 200, high: 800, low: 40, ), ]; final (minX, maxX, minY, maxY) = CandlestickChartHelper.calculateMaxAxisValues(scatterSpots); expect(minX, 0); expect(maxX, 1); expect(minY, 4); expect(maxY, 800); }); test('Test validity 3', () { final candlestickSpots = []; final (minX, maxX, minY, maxY) = CandlestickChartHelper.calculateMaxAxisValues(candlestickSpots); expect(minX, 0); expect(maxX, 0); expect(minY, 0); expect(maxY, 0); }); test('Test validity 4', () { final candlestickSpots = [ candlestickSpot1, candlestickSpot2, candlestickSpot3, candlestickSpot4, candlestickSpot5, ]; final (minX, maxX, minY, maxY) = CandlestickChartHelper.calculateMaxAxisValues(candlestickSpots); expect(minX, -50); expect(maxX, 30); expect(minY, -30); expect(maxY, 130); }); test('Test equality', () { final candlestickSpots = [ candlestickSpot1, candlestickSpot2, candlestickSpot3, ]; final candlestickSpotsClone = [ candlestickSpot1Clone, candlestickSpot2Clone, candlestickSpot3, ]; final result1 = CandlestickChartHelper.calculateMaxAxisValues(candlestickSpots); final result2 = CandlestickChartHelper.calculateMaxAxisValues(candlestickSpotsClone); expect(result1, result2); }); }); } ================================================ FILE: test/chart/candlestick_chart/candlestick_chart_painter_test.dart ================================================ import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart'; import 'package:fl_chart/src/chart/candlestick_chart/candlestick_chart_painter.dart'; import 'package:fl_chart/src/utils/canvas_wrapper.dart'; import 'package:fl_chart/src/utils/utils.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import '../../helper_methods.dart'; import '../data_pool.dart'; import 'candlestick_chart_painter_test.mocks.dart'; @GenerateMocks([Canvas, CanvasWrapper, BuildContext, Utils]) void main() { group('paint()', () { test('test 1 - simple paint call', () { final utilsMainInstance = Utils(); const viewSize = Size(400, 400); final data = CandlestickChartData( candlestickSpots: [ candlestickSpot1, candlestickSpot2, candlestickSpot3, ], clipData: const FlClipData.all(), ); final candlestickPainter = CandlestickChartPainter(); final holder = PaintHolder( data, data, TextScaler.noScaling, ); final mockUtils = MockUtils(); Utils.changeInstance(mockUtils); when(mockUtils.getEfficientInterval(any, any)) .thenAnswer((realInvocation) => 1.0); when(mockUtils.getBestInitialIntervalValue(any, any, any)) .thenAnswer((realInvocation) => 1.0); final mockBuildContext = MockBuildContext(); final mockCanvas = MockCanvas(); final canvasWrapper = CanvasWrapper(mockCanvas, viewSize); candlestickPainter.paint( mockBuildContext, canvasWrapper, holder, ); verify(mockCanvas.clipRect(any)).called(1); verify(mockCanvas.drawLine(any, any, any)).called(6); Utils.changeInstance(utilsMainInstance); }); }); group('drawAxisSpotIndicator()', () { test('test 1 - draw both lines', () { final utilsMainInstance = Utils(); const viewSize = Size(400, 400); final data = CandlestickChartData( minX: 0, maxX: 100, minY: 0, maxY: 100, gridData: const FlGridData(show: false), touchedPointIndicator: AxisSpotIndicator( x: 50, y: 50, painter: AxisLinesIndicatorPainter( verticalLineProvider: (x) => VerticalLine( x: x, color: MockData.color2, strokeWidth: 8, ), horizontalLineProvider: (y) => HorizontalLine( y: y, color: MockData.color1, strokeWidth: 4, dashArray: [0, 1, 0], ), ), ), ); final candlestickPainter = CandlestickChartPainter(); final holder = PaintHolder( data, data, TextScaler.noScaling, ); final mockUtils = MockUtils(); Utils.changeInstance(mockUtils); when(mockUtils.getEfficientInterval(any, any)) .thenAnswer((realInvocation) => 1.0); when(mockUtils.getBestInitialIntervalValue(any, any, any)) .thenAnswer((realInvocation) => 1.0); final mockBuildContext = MockBuildContext(); final mockCanvas = MockCanvas(); final canvasWrapper = CanvasWrapper(mockCanvas, viewSize); final drawCalls = <(Path, Color, double)>[]; when(mockCanvas.drawPath(any, any)).thenAnswer((invocation) { drawCalls.add( ( invocation.positionalArguments[0] as Path, (invocation.positionalArguments[1] as Paint).color, (invocation.positionalArguments[1] as Paint).strokeWidth, ), ); }); candlestickPainter.paint( mockBuildContext, canvasWrapper, holder, ); expect(drawCalls.length, 2); // Horizontal line expect(drawCalls[0].$1.getBounds().left, 0); expect(drawCalls[0].$1.getBounds().right, 400); expect(drawCalls[0].$1.getBounds().top, 200); expect(drawCalls[0].$1.getBounds().bottom, 200); expect(drawCalls[0].$2.toARGB32(), MockData.color1.toARGB32()); expect(drawCalls[0].$3, 4); /// Vertical line expect(drawCalls[1].$1.getBounds().left, 200); expect(drawCalls[1].$1.getBounds().right, 200); expect(drawCalls[1].$1.getBounds().top, 0); expect(drawCalls[1].$1.getBounds().bottom, 400); expect(drawCalls[1].$2.toARGB32(), MockData.color2.toARGB32()); expect(drawCalls[1].$3, 8); Utils.changeInstance(utilsMainInstance); }); test('test 1 - draw only horizontal line', () { final utilsMainInstance = Utils(); const viewSize = Size(400, 400); final data = CandlestickChartData( minX: 0, maxX: 100, minY: 0, maxY: 100, gridData: const FlGridData(show: false), touchedPointIndicator: AxisSpotIndicator( y: 50, painter: AxisLinesIndicatorPainter( verticalLineProvider: null, horizontalLineProvider: (y) => HorizontalLine( y: y, color: MockData.color1, strokeWidth: 4, ), ), ), ); final candlestickPainter = CandlestickChartPainter(); final holder = PaintHolder( data, data, TextScaler.noScaling, ); final mockUtils = MockUtils(); Utils.changeInstance(mockUtils); when(mockUtils.getEfficientInterval(any, any)) .thenAnswer((realInvocation) => 1.0); when(mockUtils.getBestInitialIntervalValue(any, any, any)) .thenAnswer((realInvocation) => 1.0); final mockBuildContext = MockBuildContext(); final mockCanvas = MockCanvas(); final canvasWrapper = CanvasWrapper(mockCanvas, viewSize); final drawCalls = <(Path, Color, double)>[]; when(mockCanvas.drawPath(any, any)).thenAnswer((invocation) { drawCalls.add( ( invocation.positionalArguments[0] as Path, (invocation.positionalArguments[1] as Paint).color, (invocation.positionalArguments[1] as Paint).strokeWidth, ), ); }); candlestickPainter.paint( mockBuildContext, canvasWrapper, holder, ); expect(drawCalls.length, 1); // Horizontal line expect(drawCalls[0].$1.getBounds().left, 0); expect(drawCalls[0].$1.getBounds().right, 400); expect(drawCalls[0].$1.getBounds().top, 200); expect(drawCalls[0].$1.getBounds().bottom, 200); expect(drawCalls[0].$2.toARGB32(), MockData.color1.toARGB32()); expect(drawCalls[0].$3, 4); Utils.changeInstance(utilsMainInstance); }); test('test 1 - draw no line', () { final utilsMainInstance = Utils(); const viewSize = Size(400, 400); final data = CandlestickChartData( minX: 0, maxX: 100, minY: 0, maxY: 100, ); final candlestickPainter = CandlestickChartPainter(); final holder = PaintHolder( data, data, TextScaler.noScaling, ); final mockUtils = MockUtils(); Utils.changeInstance(mockUtils); when(mockUtils.getEfficientInterval(any, any)) .thenAnswer((realInvocation) => 1.0); when(mockUtils.getBestInitialIntervalValue(any, any, any)) .thenAnswer((realInvocation) => 1.0); final mockBuildContext = MockBuildContext(); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final drawCalls = <(Offset, Offset, Color, double)>[]; when(mockCanvasWrapper.drawLine(any, any, any)).thenAnswer((invocation) { drawCalls.add( ( invocation.positionalArguments[0] as Offset, invocation.positionalArguments[1] as Offset, (invocation.positionalArguments[2] as Paint).color, (invocation.positionalArguments[2] as Paint).strokeWidth, ), ); }); candlestickPainter.paint( mockBuildContext, mockCanvasWrapper, holder, ); expect(drawCalls.length, 0); Utils.changeInstance(utilsMainInstance); }); }); group('drawCandlesticks()', () { test('test 1 - check drawing candlesticks', () { final utilsMainInstance = Utils(); const viewSize = Size(400, 400); final data = CandlestickChartData( candlestickSpots: [ candlestickSpot1, candlestickSpot2, candlestickSpot3, ], candlestickPainter: DefaultCandlestickPainter( candlestickStyleProvider: (spot, index) { final generalColor = spot.isUp ? const Color(0xFF4CAF50) : const Color(0xFFEF5350); return CandlestickStyle( lineColor: generalColor, lineWidth: (1 + index).toDouble(), bodyStrokeColor: generalColor, bodyStrokeWidth: (1 + index).toDouble(), bodyFillColor: generalColor, bodyWidth: (4 + index).toDouble(), bodyRadius: index.toDouble(), ); }, ), ); final candlestickPainter = CandlestickChartPainter(); final holder = PaintHolder( data, data, TextScaler.noScaling, ); final mockUtils = MockUtils(); Utils.changeInstance(mockUtils); when(mockUtils.getEfficientInterval(any, any)) .thenAnswer((realInvocation) => 1.0); when(mockUtils.getBestInitialIntervalValue(any, any, any)) .thenAnswer((realInvocation) => 1.0); final mockBuildContext = MockBuildContext(); final mockCanvas = MockCanvas(); final canvasWrapper = CanvasWrapper(mockCanvas, viewSize); final lineDrawCalls = <(Offset, Offset, Color, double)>[]; when(mockCanvas.drawLine(any, any, any)).thenAnswer((invocation) { lineDrawCalls.add( ( invocation.positionalArguments[0] as Offset, invocation.positionalArguments[1] as Offset, (invocation.positionalArguments[2] as Paint).color, (invocation.positionalArguments[2] as Paint).strokeWidth, ), ); }); final rrectDrawCalls = <(RRect, Color, double)>[]; when(mockCanvas.drawRRect(any, any)).thenAnswer((invocation) { rrectDrawCalls.add( ( invocation.positionalArguments[0] as RRect, (invocation.positionalArguments[1] as Paint).color, (invocation.positionalArguments[1] as Paint).strokeWidth, ), ); }); candlestickPainter.paint( mockBuildContext, canvasWrapper, holder, ); expect(lineDrawCalls.length, 6); expect(rrectDrawCalls.length, 6); final expectedLines = [ (const Offset(0, 400), const Offset(0, 366.7)), (const Offset(0, 66.7), const Offset(0, 333.3)), (const Offset(200, 366.7), const Offset(200, 333.3)), (const Offset(200, 33.3), const Offset(200, 300)), (const Offset(400, 333.3), const Offset(400, 300)), (const Offset(400, 0), const Offset(400, 266.7)), ]; final expectedLineColors = [ const Color(0xFF4CAF50), const Color(0xFF4CAF50), const Color(0xFFEF5350), const Color(0xFFEF5350), const Color(0xFF4CAF50), const Color(0xFF4CAF50), ]; final expectedLineWidths = [ 1.0, 1.0, 2.0, 2.0, 3.0, 3.0, ]; final expectedRRectCalls = <({ double width, double height, double radius, Color color, double strokeWidth, })>[ ( width: 4, height: 33.3, radius: 0, color: const Color(0xFF4CAF50), strokeWidth: 1, ), ( width: 4, height: 33.3, radius: 0, color: const Color(0xFF4CAF50), strokeWidth: 1, ), ( width: 5, height: 33.3, radius: 1, color: const Color(0xFFEF5350), strokeWidth: 2, ), ( width: 5, height: 33.3, radius: 1, color: const Color(0xFFEF5350), strokeWidth: 2, ), ( width: 6, height: 33.3, radius: 2, color: const Color(0xFF4CAF50), strokeWidth: 3, ), ( width: 7, height: 33.3, radius: 2, color: const Color(0xFF4CAF50), strokeWidth: 3, ), ]; for (var i = 0; i < lineDrawCalls.length; i += 2) { // bottom line expect( HelperMethods.equalsOffsets(lineDrawCalls[i].$1, expectedLines[i].$1), true, ); expect( HelperMethods.equalsOffsets(lineDrawCalls[i].$2, expectedLines[i].$2), true, ); expect( lineDrawCalls[i].$3.toARGB32(), expectedLineColors[i].toARGB32(), ); expect(lineDrawCalls[i].$4, expectedLineWidths[i]); // top line expect( HelperMethods.equalsOffsets( lineDrawCalls[i + 1].$1, expectedLines[i + 1].$1, ), true, ); expect( HelperMethods.equalsOffsets( lineDrawCalls[i + 1].$2, expectedLines[i + 1].$2, ), true, ); expect( lineDrawCalls[i + 1].$3.toARGB32(), expectedLineColors[i + 1].toARGB32(), ); expect(lineDrawCalls[i + 1].$4, expectedLineWidths[i + 1]); // body expect( rrectDrawCalls[i].$1.blRadiusX, closeTo(expectedRRectCalls[i].radius, 0.1), ); expect( rrectDrawCalls[i].$1.width, closeTo(expectedRRectCalls[i].width, 0.1), ); expect( rrectDrawCalls[i].$1.height, closeTo(expectedRRectCalls[i].height, 0.1), ); expect( rrectDrawCalls[i].$2.toARGB32(), expectedRRectCalls[i].color.toARGB32(), ); // body stroke expect(rrectDrawCalls[i + 1].$3, expectedRRectCalls[i + 1].strokeWidth); expect( rrectDrawCalls[i + 1].$1.blRadiusY, expectedRRectCalls[i].radius, ); } Utils.changeInstance(utilsMainInstance); }); }); group('drawTouchTooltips()', () { test('test 1', () { const viewSize = Size(100, 100); final data = CandlestickChartData( minY: 0, maxY: 10, minX: 0, maxX: 10, candlestickSpots: [ candlestickSpot1, candlestickSpot2, candlestickSpot3, candlestickSpot4, ], showingTooltipIndicators: [0, 3], titlesData: const FlTitlesData(show: false), ); final candlestickChartPainter = CandlestickChartPainter(); final holder = PaintHolder( data, data, TextScaler.noScaling, ); final mockCanvasWrapper = MockCanvasWrapper(); final mockBuildContext = MockBuildContext(); final mockUtils = MockUtils(); Utils.changeInstance(mockUtils); when(mockUtils.getThemeAwareTextStyle(any, any)) .thenReturn(const TextStyle(color: Color(0x00ffffff))); when(mockUtils.calculateRotationOffset(any, any)).thenReturn(Offset.zero); when(mockCanvasWrapper.size).thenReturn(viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); candlestickChartPainter.drawTouchTooltips( mockBuildContext, mockCanvasWrapper, holder, ); verify( mockCanvasWrapper.drawRotated( size: anyNamed('size'), rotationOffset: anyNamed('rotationOffset'), drawOffset: anyNamed('drawOffset'), angle: anyNamed('angle'), drawCallback: anyNamed('drawCallback'), ), ).called(2); }); }); group('drawTouchTooltip()', () { test('test 1', () { const viewSize = Size(100, 100); final data = CandlestickChartData( minY: 0, maxY: 1000, minX: 0, maxX: 1000, candlestickSpots: [ candlestickSpot1, candlestickSpot2, candlestickSpot3, candlestickSpot4, ], showingTooltipIndicators: [0, 2, 3], titlesData: const FlTitlesData(show: false), candlestickTouchData: CandlestickTouchData( touchTooltipData: CandlestickTouchTooltipData( rotateAngle: 18, getTooltipColor: (touchedSpot) => const Color(0xFF00FF00), tooltipBorderRadius: const BorderRadius.only( topLeft: Radius.circular(85), topRight: Radius.circular(8), ), tooltipPadding: const EdgeInsets.all(12), getTooltipItems: (_, __, ___) { return CandlestickTooltipItem( 'faketext', textStyle: textStyle1, textAlign: TextAlign.left, textDirection: TextDirection.rtl, children: [ textSpan2, textSpan1, ], ); }, ), ), ); final candlestickChartPainter = CandlestickChartPainter(); final holder = PaintHolder( data, data, TextScaler.noScaling, ); final mockCanvasWrapper = MockCanvasWrapper(); final mockBuildContext = MockBuildContext(); final mockUtils = MockUtils(); Utils.changeInstance(mockUtils); when(mockUtils.getThemeAwareTextStyle(any, any)).thenReturn(textStyle2); when(mockUtils.calculateRotationOffset(any, any)).thenReturn(Offset.zero); when(mockCanvasWrapper.size).thenReturn(viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); candlestickChartPainter.drawTouchTooltip( mockBuildContext, mockCanvasWrapper, data.candlestickTouchData.touchTooltipData, candlestickSpot1, 0, holder, ); final verificationResult = verify( mockCanvasWrapper.drawRotated( size: anyNamed('size'), drawOffset: anyNamed('drawOffset'), angle: 18, drawCallback: captureAnyNamed('drawCallback'), ), ); final passedDrawCallback = verificationResult.captured.first as DrawCallback; passedDrawCallback(); verificationResult.called(1); final captured2 = verifyInOrder([ mockCanvasWrapper.drawRRect(captureAny, captureAny), mockCanvasWrapper.drawText(captureAny, any), ]).captured; final rRect = captured2[0][0] as RRect; final bgPaint = captured2[0][1] as Paint; final textPainter = captured2[1][0] as TextPainter; expect(rRect.blRadiusX, 0); expect(rRect.blRadiusY, 0); expect(rRect.tlRadiusY, 85); expect(rRect.trRadiusX, 8); expect(bgPaint.color, const Color(0xFF00FF00)); expect( textPainter.text, const TextSpan( style: textStyle2, text: 'faketext', children: [ textSpan2, textSpan1, ], ), ); }); test('test 2', () { const viewSize = Size(100, 100); final data = CandlestickChartData( minY: 0, maxY: 1000, minX: 0, maxX: 1000, candlestickSpots: [ candlestickSpot1, candlestickSpot2, candlestickSpot3, candlestickSpot4, ], showingTooltipIndicators: [0, 2, 3], titlesData: const FlTitlesData(show: false), candlestickTouchData: CandlestickTouchData( touchTooltipData: CandlestickTouchTooltipData( rotateAngle: 18, getTooltipColor: (touchedSpot) => const Color(0xFFFFFF00), tooltipBorderRadius: BorderRadius.circular(22), fitInsideHorizontally: false, fitInsideVertically: true, tooltipPadding: const EdgeInsets.all(12), tooltipHorizontalAlignment: FLHorizontalAlignment.left, getTooltipItems: (_, __, ___) => CandlestickTooltipItem( 'faketext', textStyle: textStyle2, textAlign: TextAlign.right, textDirection: TextDirection.ltr, children: [ textSpan1, textSpan2, ], ), ), ), ); final candlestickChartPainter = CandlestickChartPainter(); final holder = PaintHolder( data, data, TextScaler.noScaling, ); final mockCanvasWrapper = MockCanvasWrapper(); final mockBuildContext = MockBuildContext(); final mockUtils = MockUtils(); Utils.changeInstance(mockUtils); when(mockUtils.getThemeAwareTextStyle(any, any)).thenReturn(textStyle1); when(mockUtils.calculateRotationOffset(any, any)).thenReturn(Offset.zero); when(mockCanvasWrapper.size).thenReturn(viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); candlestickChartPainter.drawTouchTooltip( mockBuildContext, mockCanvasWrapper, data.candlestickTouchData.touchTooltipData, candlestickSpot1, 0, holder, ); final verificationResult = verify( mockCanvasWrapper.drawRotated( size: anyNamed('size'), drawOffset: anyNamed('drawOffset'), angle: 18, drawCallback: captureAnyNamed('drawCallback'), ), ); final passedDrawCallback = verificationResult.captured.first as DrawCallback; passedDrawCallback(); verificationResult.called(1); final captured2 = verifyInOrder([ mockCanvasWrapper.drawRRect(captureAny, captureAny), mockCanvasWrapper.drawText(captureAny, any), ]).captured; final rRect = captured2[0][0] as RRect; final bgPaint = captured2[0][1] as Paint; final textPainter = captured2[1][0] as TextPainter; expect(rRect.blRadiusX, 22); expect(rRect.tlRadiusY, 22); expect(rRect.left, -144); expect(bgPaint.color, const Color(0xFFFFFF00)); expect( textPainter.text, const TextSpan( style: textStyle1, text: 'faketext', children: [ textSpan1, textSpan2, ], ), ); }); test('test 3', () { const viewSize = Size(100, 100); final data = CandlestickChartData( minY: 0, maxY: 1000, minX: 0, maxX: 1000, candlestickSpots: [ candlestickSpot1, candlestickSpot2, candlestickSpot3, candlestickSpot4, ], showingTooltipIndicators: [0, 2, 3], titlesData: const FlTitlesData(show: false), candlestickTouchData: CandlestickTouchData( touchTooltipData: CandlestickTouchTooltipData( rotateAngle: 18, getTooltipColor: (touchedSpot) => const Color(0xFFFFFF00), tooltipBorderRadius: BorderRadius.circular(22), fitInsideHorizontally: false, fitInsideVertically: true, tooltipPadding: const EdgeInsets.all(12), tooltipHorizontalAlignment: FLHorizontalAlignment.right, getTooltipItems: (_, __, ___) => CandlestickTooltipItem( 'faketext', textStyle: textStyle2, textAlign: TextAlign.right, textDirection: TextDirection.ltr, children: [ textSpan1, textSpan2, ], ), ), ), ); final candlestickChartPainter = CandlestickChartPainter(); final holder = PaintHolder( data, data, TextScaler.noScaling, ); final mockCanvasWrapper = MockCanvasWrapper(); final mockBuildContext = MockBuildContext(); final mockUtils = MockUtils(); Utils.changeInstance(mockUtils); when(mockUtils.getThemeAwareTextStyle(any, any)).thenReturn(textStyle1); when(mockUtils.calculateRotationOffset(any, any)).thenReturn(Offset.zero); when(mockCanvasWrapper.size).thenReturn(viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); candlestickChartPainter.drawTouchTooltip( mockBuildContext, mockCanvasWrapper, data.candlestickTouchData.touchTooltipData, candlestickSpot1, 0, holder, ); final verificationResult = verify( mockCanvasWrapper.drawRotated( size: anyNamed('size'), drawOffset: anyNamed('drawOffset'), angle: 18, drawCallback: captureAnyNamed('drawCallback'), ), ); final passedDrawCallback = verificationResult.captured.first as DrawCallback; passedDrawCallback(); verificationResult.called(1); final captured2 = verifyInOrder([ mockCanvasWrapper.drawRRect(captureAny, captureAny), mockCanvasWrapper.drawText(captureAny, any), ]).captured; final rRect = captured2[0][0] as RRect; final bgPaint = captured2[0][1] as Paint; final textPainter = captured2[1][0] as TextPainter; expect(rRect.blRadiusX, 22); expect(rRect.tlRadiusY, 22); expect(rRect.left, 0); expect(bgPaint.color, const Color(0xFFFFFF00)); expect( textPainter.text, const TextSpan( style: textStyle1, text: 'faketext', children: [ textSpan1, textSpan2, ], ), ); }); test('test 4', () { const viewSize = Size(100, 100); final data = CandlestickChartData( candlestickSpots: [ candlestickSpot1, candlestickSpot2, candlestickSpot3, candlestickSpot4, ], showingTooltipIndicators: [0, 1, 2, 3], titlesData: const FlTitlesData(show: false), candlestickTouchData: CandlestickTouchData( touchTooltipData: CandlestickTouchTooltipData( rotateAngle: 18, getTooltipColor: (touchedSpot) => const Color(0xFFFFFF00), tooltipBorderRadius: BorderRadius.circular(22), fitInsideHorizontally: true, fitInsideVertically: true, tooltipPadding: const EdgeInsets.all(12), tooltipHorizontalAlignment: FLHorizontalAlignment.right, tooltipBorder: const BorderSide( color: Color(0xFF00FF00), width: 2, ), getTooltipItems: (_, __, ___) => CandlestickTooltipItem( 'faketext', textStyle: textStyle2, textAlign: TextAlign.right, textDirection: TextDirection.ltr, children: [ textSpan1, textSpan2, ], ), ), ), ); final candlestickChartPainter = CandlestickChartPainter(); final holder = PaintHolder( data, data, TextScaler.noScaling, ); final mockCanvasWrapper = MockCanvasWrapper(); final mockBuildContext = MockBuildContext(); final mockUtils = MockUtils(); Utils.changeInstance(mockUtils); when(mockUtils.getThemeAwareTextStyle(any, any)).thenReturn(textStyle1); when(mockUtils.calculateRotationOffset(any, any)).thenReturn(Offset.zero); when(mockCanvasWrapper.size).thenReturn(viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); candlestickChartPainter.drawTouchTooltip( mockBuildContext, mockCanvasWrapper, data.candlestickTouchData.touchTooltipData, candlestickSpot1, 0, holder, ); final verificationResult = verify( mockCanvasWrapper.drawRotated( size: anyNamed('size'), drawOffset: anyNamed('drawOffset'), angle: 18, drawCallback: captureAnyNamed('drawCallback'), ), ); final passedDrawCallback = verificationResult.captured.first as DrawCallback; passedDrawCallback(); verificationResult.called(1); final captured2 = verifyInOrder([ mockCanvasWrapper.drawRRect(captureAny, captureAny), mockCanvasWrapper.drawText(captureAny, any), ]).captured; final rRect = captured2[0][0] as RRect; final bgPaint = captured2[0][1] as Paint; final textPainter = captured2[1][0] as TextPainter; expect(rRect.blRadiusX, 22); expect(rRect.tlRadiusY, 22); expect(rRect.left, -44); expect(bgPaint.color, const Color(0xFFFFFF00)); expect( textPainter.text, const TextSpan( style: textStyle1, text: 'faketext', children: [ textSpan1, textSpan2, ], ), ); }); }); group('handleTouch()', () { test('test 1', () { const viewSize = Size(100, 100); final spots = [ candlestickSpot1, candlestickSpot2, candlestickSpot3, candlestickSpot4, ]; final data = CandlestickChartData( minY: 0, maxY: 150, minX: 0, maxX: 30, titlesData: const FlTitlesData(show: false), candlestickSpots: spots, candlestickTouchData: CandlestickTouchData( touchSpotThreshold: 5, ), ); final candlestickChartPainter = CandlestickChartPainter(); final holder = PaintHolder( data, data, TextScaler.noScaling, ); expect( candlestickChartPainter .handleTouch( const Offset(0, 1), viewSize, holder, )! .spot, spots[0], ); expect( candlestickChartPainter .handleTouch( const Offset(0, 100), viewSize, holder, )! .spot, spots[0], ); expect( candlestickChartPainter .handleTouch( const Offset(5, 100), viewSize, holder, )! .spot, spots[0], ); expect( candlestickChartPainter.handleTouch( const Offset(6, 100), viewSize, holder, ), null, ); expect( candlestickChartPainter .handleTouch( const Offset((1 / 3) * 100, 100), viewSize, holder, )! .spot, spots[1], ); expect( candlestickChartPainter .handleTouch( const Offset((1 / 3) * 100, 0), viewSize, holder, )! .spot, spots[1], ); expect( candlestickChartPainter .handleTouch( const Offset((2 / 3) * 100, 0), viewSize, holder, )! .spot, spots[2], ); expect( candlestickChartPainter.handleTouch( const Offset(((2 / 3) * 100) + 6, 0), viewSize, holder, ), null, ); }); }); } ================================================ FILE: test/chart/candlestick_chart/candlestick_chart_painter_test.mocks.dart ================================================ // Mocks generated by Mockito 5.4.6 from annotations // in fl_chart/test/chart/candlestick_chart/candlestick_chart_painter_test.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:typed_data' as _i5; import 'dart:ui' as _i2; import 'package:fl_chart/fl_chart.dart' as _i7; import 'package:fl_chart/src/utils/canvas_wrapper.dart' as _i6; import 'package:fl_chart/src/utils/utils.dart' as _i8; import 'package:flutter/cupertino.dart' as _i3; import 'package:flutter/foundation.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; import 'package:mockito/src/dummies.dart' as _i9; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values // ignore_for_file: avoid_setters_without_getters // ignore_for_file: comment_references // ignore_for_file: deprecated_member_use // ignore_for_file: deprecated_member_use_from_same_package // ignore_for_file: implementation_imports // ignore_for_file: invalid_use_of_visible_for_testing_member // ignore_for_file: must_be_immutable // ignore_for_file: prefer_const_constructors // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class // ignore_for_file: invalid_use_of_internal_member class _FakeRect_0 extends _i1.SmartFake implements _i2.Rect { _FakeRect_0( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeCanvas_1 extends _i1.SmartFake implements _i2.Canvas { _FakeCanvas_1( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeSize_2 extends _i1.SmartFake implements _i2.Size { _FakeSize_2( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeWidget_3 extends _i1.SmartFake implements _i3.Widget { _FakeWidget_3( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info}) => super.toString(); } class _FakeInheritedWidget_4 extends _i1.SmartFake implements _i3.InheritedWidget { _FakeInheritedWidget_4( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info}) => super.toString(); } class _FakeDiagnosticsNode_5 extends _i1.SmartFake implements _i3.DiagnosticsNode { _FakeDiagnosticsNode_5( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({ _i4.TextTreeConfiguration? parentConfiguration, _i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info, }) => super.toString(); } class _FakeOffset_6 extends _i1.SmartFake implements _i2.Offset { _FakeOffset_6( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeBorderSide_7 extends _i1.SmartFake implements _i3.BorderSide { _FakeBorderSide_7( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info}) => super.toString(); } class _FakeTextStyle_8 extends _i1.SmartFake implements _i3.TextStyle { _FakeTextStyle_8( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info}) => super.toString(); } /// A class which mocks [Canvas]. /// /// See the documentation for Mockito's code generation for more information. class MockCanvas extends _i1.Mock implements _i2.Canvas { MockCanvas() { _i1.throwOnMissingStub(this); } @override void save() => super.noSuchMethod( Invocation.method( #save, [], ), returnValueForMissingStub: null, ); @override void saveLayer( _i2.Rect? bounds, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #saveLayer, [ bounds, paint, ], ), returnValueForMissingStub: null, ); @override void restore() => super.noSuchMethod( Invocation.method( #restore, [], ), returnValueForMissingStub: null, ); @override void restoreToCount(int? count) => super.noSuchMethod( Invocation.method( #restoreToCount, [count], ), returnValueForMissingStub: null, ); @override int getSaveCount() => (super.noSuchMethod( Invocation.method( #getSaveCount, [], ), returnValue: 0, ) as int); @override void translate( double? dx, double? dy, ) => super.noSuchMethod( Invocation.method( #translate, [ dx, dy, ], ), returnValueForMissingStub: null, ); @override void scale( double? sx, [ double? sy, ]) => super.noSuchMethod( Invocation.method( #scale, [ sx, sy, ], ), returnValueForMissingStub: null, ); @override void rotate(double? radians) => super.noSuchMethod( Invocation.method( #rotate, [radians], ), returnValueForMissingStub: null, ); @override void skew( double? sx, double? sy, ) => super.noSuchMethod( Invocation.method( #skew, [ sx, sy, ], ), returnValueForMissingStub: null, ); @override void transform(_i5.Float64List? matrix4) => super.noSuchMethod( Invocation.method( #transform, [matrix4], ), returnValueForMissingStub: null, ); @override _i5.Float64List getTransform() => (super.noSuchMethod( Invocation.method( #getTransform, [], ), returnValue: _i5.Float64List(0), ) as _i5.Float64List); @override void clipRect( _i2.Rect? rect, { _i2.ClipOp? clipOp = _i2.ClipOp.intersect, bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipRect, [rect], { #clipOp: clipOp, #doAntiAlias: doAntiAlias, }, ), returnValueForMissingStub: null, ); @override void clipRRect( _i2.RRect? rrect, { bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipRRect, [rrect], {#doAntiAlias: doAntiAlias}, ), returnValueForMissingStub: null, ); @override void clipRSuperellipse( _i2.RSuperellipse? rsuperellipse, { bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipRSuperellipse, [rsuperellipse], {#doAntiAlias: doAntiAlias}, ), returnValueForMissingStub: null, ); @override void clipPath( _i2.Path? path, { bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipPath, [path], {#doAntiAlias: doAntiAlias}, ), returnValueForMissingStub: null, ); @override _i2.Rect getLocalClipBounds() => (super.noSuchMethod( Invocation.method( #getLocalClipBounds, [], ), returnValue: _FakeRect_0( this, Invocation.method( #getLocalClipBounds, [], ), ), ) as _i2.Rect); @override _i2.Rect getDestinationClipBounds() => (super.noSuchMethod( Invocation.method( #getDestinationClipBounds, [], ), returnValue: _FakeRect_0( this, Invocation.method( #getDestinationClipBounds, [], ), ), ) as _i2.Rect); @override void drawColor( _i2.Color? color, _i2.BlendMode? blendMode, ) => super.noSuchMethod( Invocation.method( #drawColor, [ color, blendMode, ], ), returnValueForMissingStub: null, ); @override void drawLine( _i2.Offset? p1, _i2.Offset? p2, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawLine, [ p1, p2, paint, ], ), returnValueForMissingStub: null, ); @override void drawPaint(_i2.Paint? paint) => super.noSuchMethod( Invocation.method( #drawPaint, [paint], ), returnValueForMissingStub: null, ); @override void drawRect( _i2.Rect? rect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRect, [ rect, paint, ], ), returnValueForMissingStub: null, ); @override void drawRRect( _i2.RRect? rrect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRRect, [ rrect, paint, ], ), returnValueForMissingStub: null, ); @override void drawDRRect( _i2.RRect? outer, _i2.RRect? inner, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawDRRect, [ outer, inner, paint, ], ), returnValueForMissingStub: null, ); @override void drawRSuperellipse( _i2.RSuperellipse? rsuperellipse, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRSuperellipse, [ rsuperellipse, paint, ], ), returnValueForMissingStub: null, ); @override void drawOval( _i2.Rect? rect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawOval, [ rect, paint, ], ), returnValueForMissingStub: null, ); @override void drawCircle( _i2.Offset? c, double? radius, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawCircle, [ c, radius, paint, ], ), returnValueForMissingStub: null, ); @override void drawArc( _i2.Rect? rect, double? startAngle, double? sweepAngle, bool? useCenter, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawArc, [ rect, startAngle, sweepAngle, useCenter, paint, ], ), returnValueForMissingStub: null, ); @override void drawPath( _i2.Path? path, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawPath, [ path, paint, ], ), returnValueForMissingStub: null, ); @override void drawImage( _i2.Image? image, _i2.Offset? offset, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawImage, [ image, offset, paint, ], ), returnValueForMissingStub: null, ); @override void drawImageRect( _i2.Image? image, _i2.Rect? src, _i2.Rect? dst, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawImageRect, [ image, src, dst, paint, ], ), returnValueForMissingStub: null, ); @override void drawImageNine( _i2.Image? image, _i2.Rect? center, _i2.Rect? dst, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawImageNine, [ image, center, dst, paint, ], ), returnValueForMissingStub: null, ); @override void drawPicture(_i2.Picture? picture) => super.noSuchMethod( Invocation.method( #drawPicture, [picture], ), returnValueForMissingStub: null, ); @override void drawParagraph( _i2.Paragraph? paragraph, _i2.Offset? offset, ) => super.noSuchMethod( Invocation.method( #drawParagraph, [ paragraph, offset, ], ), returnValueForMissingStub: null, ); @override void drawPoints( _i2.PointMode? pointMode, List<_i2.Offset>? points, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawPoints, [ pointMode, points, paint, ], ), returnValueForMissingStub: null, ); @override void drawRawPoints( _i2.PointMode? pointMode, _i5.Float32List? points, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRawPoints, [ pointMode, points, paint, ], ), returnValueForMissingStub: null, ); @override void drawVertices( _i2.Vertices? vertices, _i2.BlendMode? blendMode, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawVertices, [ vertices, blendMode, paint, ], ), returnValueForMissingStub: null, ); @override void drawAtlas( _i2.Image? atlas, List<_i2.RSTransform>? transforms, List<_i2.Rect>? rects, List<_i2.Color>? colors, _i2.BlendMode? blendMode, _i2.Rect? cullRect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawAtlas, [ atlas, transforms, rects, colors, blendMode, cullRect, paint, ], ), returnValueForMissingStub: null, ); @override void drawRawAtlas( _i2.Image? atlas, _i5.Float32List? rstTransforms, _i5.Float32List? rects, _i5.Int32List? colors, _i2.BlendMode? blendMode, _i2.Rect? cullRect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRawAtlas, [ atlas, rstTransforms, rects, colors, blendMode, cullRect, paint, ], ), returnValueForMissingStub: null, ); @override void drawShadow( _i2.Path? path, _i2.Color? color, double? elevation, bool? transparentOccluder, ) => super.noSuchMethod( Invocation.method( #drawShadow, [ path, color, elevation, transparentOccluder, ], ), returnValueForMissingStub: null, ); } /// A class which mocks [CanvasWrapper]. /// /// See the documentation for Mockito's code generation for more information. class MockCanvasWrapper extends _i1.Mock implements _i6.CanvasWrapper { MockCanvasWrapper() { _i1.throwOnMissingStub(this); } @override _i2.Canvas get canvas => (super.noSuchMethod( Invocation.getter(#canvas), returnValue: _FakeCanvas_1( this, Invocation.getter(#canvas), ), ) as _i2.Canvas); @override _i2.Size get size => (super.noSuchMethod( Invocation.getter(#size), returnValue: _FakeSize_2( this, Invocation.getter(#size), ), ) as _i2.Size); @override void drawRRect( _i2.RRect? rrect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRRect, [ rrect, paint, ], ), returnValueForMissingStub: null, ); @override void save() => super.noSuchMethod( Invocation.method( #save, [], ), returnValueForMissingStub: null, ); @override void restore() => super.noSuchMethod( Invocation.method( #restore, [], ), returnValueForMissingStub: null, ); @override void clipRect( _i2.Rect? rect, { _i2.ClipOp? clipOp = _i2.ClipOp.intersect, bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipRect, [rect], { #clipOp: clipOp, #doAntiAlias: doAntiAlias, }, ), returnValueForMissingStub: null, ); @override void translate( double? dx, double? dy, ) => super.noSuchMethod( Invocation.method( #translate, [ dx, dy, ], ), returnValueForMissingStub: null, ); @override void rotate(double? radius) => super.noSuchMethod( Invocation.method( #rotate, [radius], ), returnValueForMissingStub: null, ); @override void drawPath( _i2.Path? path, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawPath, [ path, paint, ], ), returnValueForMissingStub: null, ); @override void saveLayer( _i2.Rect? bounds, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #saveLayer, [ bounds, paint, ], ), returnValueForMissingStub: null, ); @override void drawPicture(_i2.Picture? picture) => super.noSuchMethod( Invocation.method( #drawPicture, [picture], ), returnValueForMissingStub: null, ); @override void drawImage( _i2.Image? image, _i2.Offset? offset, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawImage, [ image, offset, paint, ], ), returnValueForMissingStub: null, ); @override void clipPath( _i2.Path? path, { bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipPath, [path], {#doAntiAlias: doAntiAlias}, ), returnValueForMissingStub: null, ); @override void drawRect( _i2.Rect? rect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRect, [ rect, paint, ], ), returnValueForMissingStub: null, ); @override void drawLine( _i2.Offset? p1, _i2.Offset? p2, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawLine, [ p1, p2, paint, ], ), returnValueForMissingStub: null, ); @override void drawCircle( _i2.Offset? center, double? radius, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawCircle, [ center, radius, paint, ], ), returnValueForMissingStub: null, ); @override void drawArc( _i2.Rect? rect, double? startAngle, double? sweepAngle, bool? useCenter, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawArc, [ rect, startAngle, sweepAngle, useCenter, paint, ], ), returnValueForMissingStub: null, ); @override void drawText( _i3.TextPainter? tp, _i2.Offset? offset, [ double? rotateAngle, ]) => super.noSuchMethod( Invocation.method( #drawText, [ tp, offset, rotateAngle, ], ), returnValueForMissingStub: null, ); @override void drawVerticalText( _i3.TextPainter? tp, _i2.Offset? offset, [ double? rotateAngle = 90.0, ]) => super.noSuchMethod( Invocation.method( #drawVerticalText, [ tp, offset, rotateAngle, ], ), returnValueForMissingStub: null, ); @override void drawDot( _i7.FlDotPainter? painter, _i7.FlSpot? spot, _i2.Offset? offset, ) => super.noSuchMethod( Invocation.method( #drawDot, [ painter, spot, offset, ], ), returnValueForMissingStub: null, ); @override void drawErrorIndicator( _i7.FlSpotErrorRangePainter? painter, _i7.FlSpot? origin, _i2.Offset? offset, _i2.Rect? errorRelativeRect, _i7.AxisChartData? axisData, ) => super.noSuchMethod( Invocation.method( #drawErrorIndicator, [ painter, origin, offset, errorRelativeRect, axisData, ], ), returnValueForMissingStub: null, ); @override void drawRotated({ required _i2.Size? size, _i2.Offset? rotationOffset = _i2.Offset.zero, _i2.Offset? drawOffset = _i2.Offset.zero, required double? angle, required _i6.DrawCallback? drawCallback, }) => super.noSuchMethod( Invocation.method( #drawRotated, [], { #size: size, #rotationOffset: rotationOffset, #drawOffset: drawOffset, #angle: angle, #drawCallback: drawCallback, }, ), returnValueForMissingStub: null, ); @override void drawDashedLine( _i2.Offset? from, _i2.Offset? to, _i2.Paint? painter, List? dashArray, ) => super.noSuchMethod( Invocation.method( #drawDashedLine, [ from, to, painter, dashArray, ], ), returnValueForMissingStub: null, ); } /// A class which mocks [BuildContext]. /// /// See the documentation for Mockito's code generation for more information. class MockBuildContext extends _i1.Mock implements _i3.BuildContext { MockBuildContext() { _i1.throwOnMissingStub(this); } @override _i3.Widget get widget => (super.noSuchMethod( Invocation.getter(#widget), returnValue: _FakeWidget_3( this, Invocation.getter(#widget), ), ) as _i3.Widget); @override bool get mounted => (super.noSuchMethod( Invocation.getter(#mounted), returnValue: false, ) as bool); @override bool get debugDoingBuild => (super.noSuchMethod( Invocation.getter(#debugDoingBuild), returnValue: false, ) as bool); @override _i3.InheritedWidget dependOnInheritedElement( _i3.InheritedElement? ancestor, { Object? aspect, }) => (super.noSuchMethod( Invocation.method( #dependOnInheritedElement, [ancestor], {#aspect: aspect}, ), returnValue: _FakeInheritedWidget_4( this, Invocation.method( #dependOnInheritedElement, [ancestor], {#aspect: aspect}, ), ), ) as _i3.InheritedWidget); @override void visitAncestorElements(_i3.ConditionalElementVisitor? visitor) => super.noSuchMethod( Invocation.method( #visitAncestorElements, [visitor], ), returnValueForMissingStub: null, ); @override void visitChildElements(_i3.ElementVisitor? visitor) => super.noSuchMethod( Invocation.method( #visitChildElements, [visitor], ), returnValueForMissingStub: null, ); @override void dispatchNotification(_i3.Notification? notification) => super.noSuchMethod( Invocation.method( #dispatchNotification, [notification], ), returnValueForMissingStub: null, ); @override _i3.DiagnosticsNode describeElement( String? name, { _i4.DiagnosticsTreeStyle? style = _i4.DiagnosticsTreeStyle.errorProperty, }) => (super.noSuchMethod( Invocation.method( #describeElement, [name], {#style: style}, ), returnValue: _FakeDiagnosticsNode_5( this, Invocation.method( #describeElement, [name], {#style: style}, ), ), ) as _i3.DiagnosticsNode); @override _i3.DiagnosticsNode describeWidget( String? name, { _i4.DiagnosticsTreeStyle? style = _i4.DiagnosticsTreeStyle.errorProperty, }) => (super.noSuchMethod( Invocation.method( #describeWidget, [name], {#style: style}, ), returnValue: _FakeDiagnosticsNode_5( this, Invocation.method( #describeWidget, [name], {#style: style}, ), ), ) as _i3.DiagnosticsNode); @override List<_i3.DiagnosticsNode> describeMissingAncestor( {required Type? expectedAncestorType}) => (super.noSuchMethod( Invocation.method( #describeMissingAncestor, [], {#expectedAncestorType: expectedAncestorType}, ), returnValue: <_i3.DiagnosticsNode>[], ) as List<_i3.DiagnosticsNode>); @override _i3.DiagnosticsNode describeOwnershipChain(String? name) => (super.noSuchMethod( Invocation.method( #describeOwnershipChain, [name], ), returnValue: _FakeDiagnosticsNode_5( this, Invocation.method( #describeOwnershipChain, [name], ), ), ) as _i3.DiagnosticsNode); } /// A class which mocks [Utils]. /// /// See the documentation for Mockito's code generation for more information. class MockUtils extends _i1.Mock implements _i8.Utils { MockUtils() { _i1.throwOnMissingStub(this); } @override double radians(double? degrees) => (super.noSuchMethod( Invocation.method( #radians, [degrees], ), returnValue: 0.0, ) as double); @override double degrees(double? radians) => (super.noSuchMethod( Invocation.method( #degrees, [radians], ), returnValue: 0.0, ) as double); @override double translateRotatedPosition( double? size, double? degree, ) => (super.noSuchMethod( Invocation.method( #translateRotatedPosition, [ size, degree, ], ), returnValue: 0.0, ) as double); @override _i2.Offset calculateRotationOffset( _i2.Size? size, double? degree, ) => (super.noSuchMethod( Invocation.method( #calculateRotationOffset, [ size, degree, ], ), returnValue: _FakeOffset_6( this, Invocation.method( #calculateRotationOffset, [ size, degree, ], ), ), ) as _i2.Offset); @override _i3.BorderRadius? normalizeBorderRadius( _i3.BorderRadius? borderRadius, double? width, ) => (super.noSuchMethod(Invocation.method( #normalizeBorderRadius, [ borderRadius, width, ], )) as _i3.BorderRadius?); @override _i3.BorderSide normalizeBorderSide( _i3.BorderSide? borderSide, double? width, ) => (super.noSuchMethod( Invocation.method( #normalizeBorderSide, [ borderSide, width, ], ), returnValue: _FakeBorderSide_7( this, Invocation.method( #normalizeBorderSide, [ borderSide, width, ], ), ), ) as _i3.BorderSide); @override double getEfficientInterval( double? axisViewSize, double? diffInAxis, { double? pixelPerInterval = 40.0, }) => (super.noSuchMethod( Invocation.method( #getEfficientInterval, [ axisViewSize, diffInAxis, ], {#pixelPerInterval: pixelPerInterval}, ), returnValue: 0.0, ) as double); @override double roundInterval(double? input) => (super.noSuchMethod( Invocation.method( #roundInterval, [input], ), returnValue: 0.0, ) as double); @override int getFractionDigits(double? value) => (super.noSuchMethod( Invocation.method( #getFractionDigits, [value], ), returnValue: 0, ) as int); @override String formatNumber( double? axisMin, double? axisMax, double? axisValue, ) => (super.noSuchMethod( Invocation.method( #formatNumber, [ axisMin, axisMax, axisValue, ], ), returnValue: _i9.dummyValue( this, Invocation.method( #formatNumber, [ axisMin, axisMax, axisValue, ], ), ), ) as String); @override _i3.TextStyle getThemeAwareTextStyle( _i3.BuildContext? context, _i3.TextStyle? providedStyle, ) => (super.noSuchMethod( Invocation.method( #getThemeAwareTextStyle, [ context, providedStyle, ], ), returnValue: _FakeTextStyle_8( this, Invocation.method( #getThemeAwareTextStyle, [ context, providedStyle, ], ), ), ) as _i3.TextStyle); @override double getBestInitialIntervalValue( double? min, double? max, double? interval, { double? baseline = 0.0, }) => (super.noSuchMethod( Invocation.method( #getBestInitialIntervalValue, [ min, max, interval, ], {#baseline: baseline}, ), returnValue: 0.0, ) as double); @override double convertRadiusToSigma(double? radius) => (super.noSuchMethod( Invocation.method( #convertRadiusToSigma, [radius], ), returnValue: 0.0, ) as double); } ================================================ FILE: test/chart/candlestick_chart/candlestick_chart_renderer_test.dart ================================================ import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart'; import 'package:fl_chart/src/chart/candlestick_chart/candlestick_chart_painter.dart'; import 'package:fl_chart/src/chart/candlestick_chart/candlestick_chart_renderer.dart'; import 'package:fl_chart/src/utils/canvas_wrapper.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import '../data_pool.dart'; import 'candlestick_chart_renderer_test.mocks.dart'; @GenerateMocks([Canvas, PaintingContext, BuildContext, CandlestickChartPainter]) void main() { group('CandlestickChartRenderer', () { final data = CandlestickChartData( candlestickSpots: [candlestickSpot1, candlestickSpot2], ); final targetData = CandlestickChartData( candlestickSpots: [candlestickSpot3], candlestickTouchData: CandlestickTouchData(enabled: false), ); const textScaler = TextScaler.linear(4); final mockBuildContext = MockBuildContext(); final renderCandlestickChart = RenderCandlestickChart( mockBuildContext, data, targetData, textScaler, null, canBeScaled: false, ); final mockPainter = MockCandlestickChartPainter(); final mockPaintingContext = MockPaintingContext(); final mockCanvas = MockCanvas(); const mockSize = Size(44, 44); when(mockPaintingContext.canvas).thenAnswer((realInvocation) => mockCanvas); renderCandlestickChart ..mockTestSize = mockSize ..painter = mockPainter; test('test 1 correct data set', () { expect(renderCandlestickChart.data == data, true); expect(renderCandlestickChart.data == targetData, false); expect(renderCandlestickChart.targetData == targetData, true); expect(renderCandlestickChart.textScaler == textScaler, true); expect(renderCandlestickChart.paintHolder.data == data, true); expect(renderCandlestickChart.paintHolder.targetData == targetData, true); expect(renderCandlestickChart.paintHolder.textScaler == textScaler, true); expect(renderCandlestickChart.hitTestSelf(Offset.zero), false); }); test('test 2 check paint function', () { renderCandlestickChart.paint(mockPaintingContext, const Offset(10, 10)); verify(mockCanvas.save()).called(1); verify(mockCanvas.translate(10, 10)).called(1); final result = verify(mockPainter.paint(any, captureAny, captureAny)); expect(result.callCount, 1); final canvasWrapper = result.captured[0] as CanvasWrapper; expect(canvasWrapper.size, const Size(44, 44)); expect(canvasWrapper.canvas, mockCanvas); final paintHolder = result.captured[1] as PaintHolder; expect(paintHolder.data, data); expect(paintHolder.targetData, targetData); expect(paintHolder.textScaler, textScaler); verify(mockCanvas.restore()).called(1); }); test('test 3 check getResponseAtLocation function', () { final results = >[]; when(mockPainter.handleTouch(captureAny, captureAny, captureAny)) .thenAnswer((inv) { results.add({ 'local_position': inv.positionalArguments[0] as Offset, 'size': inv.positionalArguments[1] as Size, 'paint_holder': inv.positionalArguments[2] as PaintHolder, }); return candlestickTouchedSpot1; }); when(mockPainter.getChartCoordinateFromPixel(any, any, any)) .thenAnswer((_) => const Offset(10, 10)); final touchResponse = renderCandlestickChart.getResponseAtLocation(MockData.offset1); expect(touchResponse.touchedSpot, candlestickTouchedSpot1); expect(touchResponse.touchChartCoordinate, const Offset(10, 10)); expect(results[0]['local_position'] as Offset, MockData.offset1); expect(results[0]['size'] as Size, mockSize); final paintHolder = results[0]['paint_holder'] as PaintHolder; expect(paintHolder.data, data); expect(paintHolder.targetData, targetData); expect(paintHolder.textScaler, textScaler); }); test('test 4 check setters', () { renderCandlestickChart ..data = targetData ..targetData = data ..textScaler = const TextScaler.linear(22); expect(renderCandlestickChart.data, targetData); expect(renderCandlestickChart.targetData, data); expect(renderCandlestickChart.textScaler, const TextScaler.linear(22)); }); test('passes chart virtual rect to paint holder', () { final rect1 = Offset.zero & const Size(100, 100); final renderCandlestickChart = RenderCandlestickChart( mockBuildContext, data, targetData, textScaler, null, canBeScaled: false, ); expect(renderCandlestickChart.chartVirtualRect, isNull); expect(renderCandlestickChart.paintHolder.chartVirtualRect, isNull); renderCandlestickChart.chartVirtualRect = rect1; expect(renderCandlestickChart.chartVirtualRect, rect1); expect(renderCandlestickChart.paintHolder.chartVirtualRect, rect1); }); test('uses canBeScaled', () { final renderCandlestickChart = RenderCandlestickChart( mockBuildContext, data, targetData, textScaler, null, canBeScaled: false, ); expect(renderCandlestickChart.canBeScaled, false); renderCandlestickChart.canBeScaled = true; expect(renderCandlestickChart.canBeScaled, true); }); }); } ================================================ FILE: test/chart/candlestick_chart/candlestick_chart_renderer_test.mocks.dart ================================================ // Mocks generated by Mockito 5.4.6 from annotations // in fl_chart/test/chart/candlestick_chart/candlestick_chart_renderer_test.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:typed_data' as _i7; import 'dart:ui' as _i2; import 'package:fl_chart/fl_chart.dart' as _i13; import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart' as _i12; import 'package:fl_chart/src/chart/candlestick_chart/candlestick_chart_painter.dart' as _i10; import 'package:fl_chart/src/utils/canvas_wrapper.dart' as _i11; import 'package:flutter/foundation.dart' as _i5; import 'package:flutter/gestures.dart' as _i8; import 'package:flutter/material.dart' as _i6; import 'package:flutter/rendering.dart' as _i3; import 'package:flutter/src/rendering/layer.dart' as _i4; import 'package:flutter/src/widgets/notification_listener.dart' as _i9; import 'package:mockito/mockito.dart' as _i1; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values // ignore_for_file: avoid_setters_without_getters // ignore_for_file: comment_references // ignore_for_file: deprecated_member_use // ignore_for_file: deprecated_member_use_from_same_package // ignore_for_file: implementation_imports // ignore_for_file: invalid_use_of_visible_for_testing_member // ignore_for_file: must_be_immutable // ignore_for_file: prefer_const_constructors // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class // ignore_for_file: invalid_use_of_internal_member class _FakeRect_0 extends _i1.SmartFake implements _i2.Rect { _FakeRect_0( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeCanvas_1 extends _i1.SmartFake implements _i2.Canvas { _FakeCanvas_1( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakePaintingContext_2 extends _i1.SmartFake implements _i3.PaintingContext { _FakePaintingContext_2( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeColorFilterLayer_3 extends _i1.SmartFake implements _i4.ColorFilterLayer { _FakeColorFilterLayer_3( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => super.toString(); } class _FakeOpacityLayer_4 extends _i1.SmartFake implements _i4.OpacityLayer { _FakeOpacityLayer_4( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => super.toString(); } class _FakeWidget_5 extends _i1.SmartFake implements _i6.Widget { _FakeWidget_5( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => super.toString(); } class _FakeInheritedWidget_6 extends _i1.SmartFake implements _i6.InheritedWidget { _FakeInheritedWidget_6( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => super.toString(); } class _FakeDiagnosticsNode_7 extends _i1.SmartFake implements _i5.DiagnosticsNode { _FakeDiagnosticsNode_7( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({ _i5.TextTreeConfiguration? parentConfiguration, _i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info, }) => super.toString(); } class _FakeOffset_8 extends _i1.SmartFake implements _i2.Offset { _FakeOffset_8( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } /// A class which mocks [Canvas]. /// /// See the documentation for Mockito's code generation for more information. class MockCanvas extends _i1.Mock implements _i2.Canvas { MockCanvas() { _i1.throwOnMissingStub(this); } @override void save() => super.noSuchMethod( Invocation.method( #save, [], ), returnValueForMissingStub: null, ); @override void saveLayer( _i2.Rect? bounds, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #saveLayer, [ bounds, paint, ], ), returnValueForMissingStub: null, ); @override void restore() => super.noSuchMethod( Invocation.method( #restore, [], ), returnValueForMissingStub: null, ); @override void restoreToCount(int? count) => super.noSuchMethod( Invocation.method( #restoreToCount, [count], ), returnValueForMissingStub: null, ); @override int getSaveCount() => (super.noSuchMethod( Invocation.method( #getSaveCount, [], ), returnValue: 0, ) as int); @override void translate( double? dx, double? dy, ) => super.noSuchMethod( Invocation.method( #translate, [ dx, dy, ], ), returnValueForMissingStub: null, ); @override void scale( double? sx, [ double? sy, ]) => super.noSuchMethod( Invocation.method( #scale, [ sx, sy, ], ), returnValueForMissingStub: null, ); @override void rotate(double? radians) => super.noSuchMethod( Invocation.method( #rotate, [radians], ), returnValueForMissingStub: null, ); @override void skew( double? sx, double? sy, ) => super.noSuchMethod( Invocation.method( #skew, [ sx, sy, ], ), returnValueForMissingStub: null, ); @override void transform(_i7.Float64List? matrix4) => super.noSuchMethod( Invocation.method( #transform, [matrix4], ), returnValueForMissingStub: null, ); @override _i7.Float64List getTransform() => (super.noSuchMethod( Invocation.method( #getTransform, [], ), returnValue: _i7.Float64List(0), ) as _i7.Float64List); @override void clipRect( _i2.Rect? rect, { _i2.ClipOp? clipOp = _i2.ClipOp.intersect, bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipRect, [rect], { #clipOp: clipOp, #doAntiAlias: doAntiAlias, }, ), returnValueForMissingStub: null, ); @override void clipRRect( _i2.RRect? rrect, { bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipRRect, [rrect], {#doAntiAlias: doAntiAlias}, ), returnValueForMissingStub: null, ); @override void clipRSuperellipse( _i2.RSuperellipse? rsuperellipse, { bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipRSuperellipse, [rsuperellipse], {#doAntiAlias: doAntiAlias}, ), returnValueForMissingStub: null, ); @override void clipPath( _i2.Path? path, { bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipPath, [path], {#doAntiAlias: doAntiAlias}, ), returnValueForMissingStub: null, ); @override _i2.Rect getLocalClipBounds() => (super.noSuchMethod( Invocation.method( #getLocalClipBounds, [], ), returnValue: _FakeRect_0( this, Invocation.method( #getLocalClipBounds, [], ), ), ) as _i2.Rect); @override _i2.Rect getDestinationClipBounds() => (super.noSuchMethod( Invocation.method( #getDestinationClipBounds, [], ), returnValue: _FakeRect_0( this, Invocation.method( #getDestinationClipBounds, [], ), ), ) as _i2.Rect); @override void drawColor( _i2.Color? color, _i2.BlendMode? blendMode, ) => super.noSuchMethod( Invocation.method( #drawColor, [ color, blendMode, ], ), returnValueForMissingStub: null, ); @override void drawLine( _i2.Offset? p1, _i2.Offset? p2, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawLine, [ p1, p2, paint, ], ), returnValueForMissingStub: null, ); @override void drawPaint(_i2.Paint? paint) => super.noSuchMethod( Invocation.method( #drawPaint, [paint], ), returnValueForMissingStub: null, ); @override void drawRect( _i2.Rect? rect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRect, [ rect, paint, ], ), returnValueForMissingStub: null, ); @override void drawRRect( _i2.RRect? rrect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRRect, [ rrect, paint, ], ), returnValueForMissingStub: null, ); @override void drawDRRect( _i2.RRect? outer, _i2.RRect? inner, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawDRRect, [ outer, inner, paint, ], ), returnValueForMissingStub: null, ); @override void drawRSuperellipse( _i2.RSuperellipse? rsuperellipse, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRSuperellipse, [ rsuperellipse, paint, ], ), returnValueForMissingStub: null, ); @override void drawOval( _i2.Rect? rect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawOval, [ rect, paint, ], ), returnValueForMissingStub: null, ); @override void drawCircle( _i2.Offset? c, double? radius, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawCircle, [ c, radius, paint, ], ), returnValueForMissingStub: null, ); @override void drawArc( _i2.Rect? rect, double? startAngle, double? sweepAngle, bool? useCenter, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawArc, [ rect, startAngle, sweepAngle, useCenter, paint, ], ), returnValueForMissingStub: null, ); @override void drawPath( _i2.Path? path, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawPath, [ path, paint, ], ), returnValueForMissingStub: null, ); @override void drawImage( _i2.Image? image, _i2.Offset? offset, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawImage, [ image, offset, paint, ], ), returnValueForMissingStub: null, ); @override void drawImageRect( _i2.Image? image, _i2.Rect? src, _i2.Rect? dst, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawImageRect, [ image, src, dst, paint, ], ), returnValueForMissingStub: null, ); @override void drawImageNine( _i2.Image? image, _i2.Rect? center, _i2.Rect? dst, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawImageNine, [ image, center, dst, paint, ], ), returnValueForMissingStub: null, ); @override void drawPicture(_i2.Picture? picture) => super.noSuchMethod( Invocation.method( #drawPicture, [picture], ), returnValueForMissingStub: null, ); @override void drawParagraph( _i2.Paragraph? paragraph, _i2.Offset? offset, ) => super.noSuchMethod( Invocation.method( #drawParagraph, [ paragraph, offset, ], ), returnValueForMissingStub: null, ); @override void drawPoints( _i2.PointMode? pointMode, List<_i2.Offset>? points, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawPoints, [ pointMode, points, paint, ], ), returnValueForMissingStub: null, ); @override void drawRawPoints( _i2.PointMode? pointMode, _i7.Float32List? points, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRawPoints, [ pointMode, points, paint, ], ), returnValueForMissingStub: null, ); @override void drawVertices( _i2.Vertices? vertices, _i2.BlendMode? blendMode, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawVertices, [ vertices, blendMode, paint, ], ), returnValueForMissingStub: null, ); @override void drawAtlas( _i2.Image? atlas, List<_i2.RSTransform>? transforms, List<_i2.Rect>? rects, List<_i2.Color>? colors, _i2.BlendMode? blendMode, _i2.Rect? cullRect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawAtlas, [ atlas, transforms, rects, colors, blendMode, cullRect, paint, ], ), returnValueForMissingStub: null, ); @override void drawRawAtlas( _i2.Image? atlas, _i7.Float32List? rstTransforms, _i7.Float32List? rects, _i7.Int32List? colors, _i2.BlendMode? blendMode, _i2.Rect? cullRect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRawAtlas, [ atlas, rstTransforms, rects, colors, blendMode, cullRect, paint, ], ), returnValueForMissingStub: null, ); @override void drawShadow( _i2.Path? path, _i2.Color? color, double? elevation, bool? transparentOccluder, ) => super.noSuchMethod( Invocation.method( #drawShadow, [ path, color, elevation, transparentOccluder, ], ), returnValueForMissingStub: null, ); } /// A class which mocks [PaintingContext]. /// /// See the documentation for Mockito's code generation for more information. class MockPaintingContext extends _i1.Mock implements _i3.PaintingContext { MockPaintingContext() { _i1.throwOnMissingStub(this); } @override _i2.Rect get estimatedBounds => (super.noSuchMethod( Invocation.getter(#estimatedBounds), returnValue: _FakeRect_0( this, Invocation.getter(#estimatedBounds), ), ) as _i2.Rect); @override _i2.Canvas get canvas => (super.noSuchMethod( Invocation.getter(#canvas), returnValue: _FakeCanvas_1( this, Invocation.getter(#canvas), ), ) as _i2.Canvas); @override void paintChild( _i3.RenderObject? child, _i2.Offset? offset, ) => super.noSuchMethod( Invocation.method( #paintChild, [ child, offset, ], ), returnValueForMissingStub: null, ); @override void appendLayer(_i4.Layer? layer) => super.noSuchMethod( Invocation.method( #appendLayer, [layer], ), returnValueForMissingStub: null, ); @override _i2.VoidCallback addCompositionCallback(_i4.CompositionCallback? callback) => (super.noSuchMethod( Invocation.method( #addCompositionCallback, [callback], ), returnValue: () {}, ) as _i2.VoidCallback); @override void stopRecordingIfNeeded() => super.noSuchMethod( Invocation.method( #stopRecordingIfNeeded, [], ), returnValueForMissingStub: null, ); @override void setIsComplexHint() => super.noSuchMethod( Invocation.method( #setIsComplexHint, [], ), returnValueForMissingStub: null, ); @override void setWillChangeHint() => super.noSuchMethod( Invocation.method( #setWillChangeHint, [], ), returnValueForMissingStub: null, ); @override void addLayer(_i4.Layer? layer) => super.noSuchMethod( Invocation.method( #addLayer, [layer], ), returnValueForMissingStub: null, ); @override void pushLayer( _i4.ContainerLayer? childLayer, _i3.PaintingContextCallback? painter, _i2.Offset? offset, { _i2.Rect? childPaintBounds, }) => super.noSuchMethod( Invocation.method( #pushLayer, [ childLayer, painter, offset, ], {#childPaintBounds: childPaintBounds}, ), returnValueForMissingStub: null, ); @override _i3.PaintingContext createChildContext( _i4.ContainerLayer? childLayer, _i2.Rect? bounds, ) => (super.noSuchMethod( Invocation.method( #createChildContext, [ childLayer, bounds, ], ), returnValue: _FakePaintingContext_2( this, Invocation.method( #createChildContext, [ childLayer, bounds, ], ), ), ) as _i3.PaintingContext); @override _i4.ClipRectLayer? pushClipRect( bool? needsCompositing, _i2.Offset? offset, _i2.Rect? clipRect, _i3.PaintingContextCallback? painter, { _i2.Clip? clipBehavior = _i2.Clip.hardEdge, _i4.ClipRectLayer? oldLayer, }) => (super.noSuchMethod(Invocation.method( #pushClipRect, [ needsCompositing, offset, clipRect, painter, ], { #clipBehavior: clipBehavior, #oldLayer: oldLayer, }, )) as _i4.ClipRectLayer?); @override _i4.ClipRRectLayer? pushClipRRect( bool? needsCompositing, _i2.Offset? offset, _i2.Rect? bounds, _i2.RRect? clipRRect, _i3.PaintingContextCallback? painter, { _i2.Clip? clipBehavior = _i2.Clip.antiAlias, _i4.ClipRRectLayer? oldLayer, }) => (super.noSuchMethod(Invocation.method( #pushClipRRect, [ needsCompositing, offset, bounds, clipRRect, painter, ], { #clipBehavior: clipBehavior, #oldLayer: oldLayer, }, )) as _i4.ClipRRectLayer?); @override _i4.ClipRSuperellipseLayer? pushClipRSuperellipse( bool? needsCompositing, _i2.Offset? offset, _i2.Rect? bounds, _i2.RSuperellipse? clipRSuperellipse, _i3.PaintingContextCallback? painter, { _i2.Clip? clipBehavior = _i2.Clip.antiAlias, _i4.ClipRSuperellipseLayer? oldLayer, }) => (super.noSuchMethod(Invocation.method( #pushClipRSuperellipse, [ needsCompositing, offset, bounds, clipRSuperellipse, painter, ], { #clipBehavior: clipBehavior, #oldLayer: oldLayer, }, )) as _i4.ClipRSuperellipseLayer?); @override _i4.ClipPathLayer? pushClipPath( bool? needsCompositing, _i2.Offset? offset, _i2.Rect? bounds, _i2.Path? clipPath, _i3.PaintingContextCallback? painter, { _i2.Clip? clipBehavior = _i2.Clip.antiAlias, _i4.ClipPathLayer? oldLayer, }) => (super.noSuchMethod(Invocation.method( #pushClipPath, [ needsCompositing, offset, bounds, clipPath, painter, ], { #clipBehavior: clipBehavior, #oldLayer: oldLayer, }, )) as _i4.ClipPathLayer?); @override _i4.ColorFilterLayer pushColorFilter( _i2.Offset? offset, _i2.ColorFilter? colorFilter, _i3.PaintingContextCallback? painter, { _i4.ColorFilterLayer? oldLayer, }) => (super.noSuchMethod( Invocation.method( #pushColorFilter, [ offset, colorFilter, painter, ], {#oldLayer: oldLayer}, ), returnValue: _FakeColorFilterLayer_3( this, Invocation.method( #pushColorFilter, [ offset, colorFilter, painter, ], {#oldLayer: oldLayer}, ), ), ) as _i4.ColorFilterLayer); @override _i4.TransformLayer? pushTransform( bool? needsCompositing, _i2.Offset? offset, _i8.Matrix4? transform, _i3.PaintingContextCallback? painter, { _i4.TransformLayer? oldLayer, }) => (super.noSuchMethod(Invocation.method( #pushTransform, [ needsCompositing, offset, transform, painter, ], {#oldLayer: oldLayer}, )) as _i4.TransformLayer?); @override _i4.OpacityLayer pushOpacity( _i2.Offset? offset, int? alpha, _i3.PaintingContextCallback? painter, { _i4.OpacityLayer? oldLayer, }) => (super.noSuchMethod( Invocation.method( #pushOpacity, [ offset, alpha, painter, ], {#oldLayer: oldLayer}, ), returnValue: _FakeOpacityLayer_4( this, Invocation.method( #pushOpacity, [ offset, alpha, painter, ], {#oldLayer: oldLayer}, ), ), ) as _i4.OpacityLayer); @override void clipPathAndPaint( _i2.Path? path, _i2.Clip? clipBehavior, _i2.Rect? bounds, _i2.VoidCallback? painter, ) => super.noSuchMethod( Invocation.method( #clipPathAndPaint, [ path, clipBehavior, bounds, painter, ], ), returnValueForMissingStub: null, ); @override void clipRRectAndPaint( _i2.RRect? rrect, _i2.Clip? clipBehavior, _i2.Rect? bounds, _i2.VoidCallback? painter, ) => super.noSuchMethod( Invocation.method( #clipRRectAndPaint, [ rrect, clipBehavior, bounds, painter, ], ), returnValueForMissingStub: null, ); @override void clipRSuperellipseAndPaint( _i2.RSuperellipse? rse, _i2.Clip? clipBehavior, _i2.Rect? bounds, _i2.VoidCallback? painter, ) => super.noSuchMethod( Invocation.method( #clipRSuperellipseAndPaint, [ rse, clipBehavior, bounds, painter, ], ), returnValueForMissingStub: null, ); @override void clipRectAndPaint( _i2.Rect? rect, _i2.Clip? clipBehavior, _i2.Rect? bounds, _i2.VoidCallback? painter, ) => super.noSuchMethod( Invocation.method( #clipRectAndPaint, [ rect, clipBehavior, bounds, painter, ], ), returnValueForMissingStub: null, ); } /// A class which mocks [BuildContext]. /// /// See the documentation for Mockito's code generation for more information. class MockBuildContext extends _i1.Mock implements _i6.BuildContext { MockBuildContext() { _i1.throwOnMissingStub(this); } @override _i6.Widget get widget => (super.noSuchMethod( Invocation.getter(#widget), returnValue: _FakeWidget_5( this, Invocation.getter(#widget), ), ) as _i6.Widget); @override bool get mounted => (super.noSuchMethod( Invocation.getter(#mounted), returnValue: false, ) as bool); @override bool get debugDoingBuild => (super.noSuchMethod( Invocation.getter(#debugDoingBuild), returnValue: false, ) as bool); @override _i6.InheritedWidget dependOnInheritedElement( _i6.InheritedElement? ancestor, { Object? aspect, }) => (super.noSuchMethod( Invocation.method( #dependOnInheritedElement, [ancestor], {#aspect: aspect}, ), returnValue: _FakeInheritedWidget_6( this, Invocation.method( #dependOnInheritedElement, [ancestor], {#aspect: aspect}, ), ), ) as _i6.InheritedWidget); @override void visitAncestorElements(_i6.ConditionalElementVisitor? visitor) => super.noSuchMethod( Invocation.method( #visitAncestorElements, [visitor], ), returnValueForMissingStub: null, ); @override void visitChildElements(_i6.ElementVisitor? visitor) => super.noSuchMethod( Invocation.method( #visitChildElements, [visitor], ), returnValueForMissingStub: null, ); @override void dispatchNotification(_i9.Notification? notification) => super.noSuchMethod( Invocation.method( #dispatchNotification, [notification], ), returnValueForMissingStub: null, ); @override _i5.DiagnosticsNode describeElement( String? name, { _i5.DiagnosticsTreeStyle? style = _i5.DiagnosticsTreeStyle.errorProperty, }) => (super.noSuchMethod( Invocation.method( #describeElement, [name], {#style: style}, ), returnValue: _FakeDiagnosticsNode_7( this, Invocation.method( #describeElement, [name], {#style: style}, ), ), ) as _i5.DiagnosticsNode); @override _i5.DiagnosticsNode describeWidget( String? name, { _i5.DiagnosticsTreeStyle? style = _i5.DiagnosticsTreeStyle.errorProperty, }) => (super.noSuchMethod( Invocation.method( #describeWidget, [name], {#style: style}, ), returnValue: _FakeDiagnosticsNode_7( this, Invocation.method( #describeWidget, [name], {#style: style}, ), ), ) as _i5.DiagnosticsNode); @override List<_i5.DiagnosticsNode> describeMissingAncestor( {required Type? expectedAncestorType}) => (super.noSuchMethod( Invocation.method( #describeMissingAncestor, [], {#expectedAncestorType: expectedAncestorType}, ), returnValue: <_i5.DiagnosticsNode>[], ) as List<_i5.DiagnosticsNode>); @override _i5.DiagnosticsNode describeOwnershipChain(String? name) => (super.noSuchMethod( Invocation.method( #describeOwnershipChain, [name], ), returnValue: _FakeDiagnosticsNode_7( this, Invocation.method( #describeOwnershipChain, [name], ), ), ) as _i5.DiagnosticsNode); } /// A class which mocks [CandlestickChartPainter]. /// /// See the documentation for Mockito's code generation for more information. class MockCandlestickChartPainter extends _i1.Mock implements _i10.CandlestickChartPainter { MockCandlestickChartPainter() { _i1.throwOnMissingStub(this); } @override void paint( _i6.BuildContext? context, _i11.CanvasWrapper? canvasWrapper, _i12.PaintHolder<_i13.CandlestickChartData>? holder, ) => super.noSuchMethod( Invocation.method( #paint, [ context, canvasWrapper, holder, ], ), returnValueForMissingStub: null, ); @override void drawCandlesticks( _i6.BuildContext? context, _i11.CanvasWrapper? canvasWrapper, _i12.PaintHolder<_i13.CandlestickChartData>? holder, ) => super.noSuchMethod( Invocation.method( #drawCandlesticks, [ context, canvasWrapper, holder, ], ), returnValueForMissingStub: null, ); @override void drawTouchTooltips( _i6.BuildContext? context, _i11.CanvasWrapper? canvasWrapper, _i12.PaintHolder<_i13.CandlestickChartData>? holder, ) => super.noSuchMethod( Invocation.method( #drawTouchTooltips, [ context, canvasWrapper, holder, ], ), returnValueForMissingStub: null, ); @override void drawTouchTooltip( _i6.BuildContext? context, _i11.CanvasWrapper? canvasWrapper, _i13.CandlestickTouchTooltipData? tooltipData, _i13.CandlestickSpot? showOnSpot, int? spotIndex, _i12.PaintHolder<_i13.CandlestickChartData>? holder, ) => super.noSuchMethod( Invocation.method( #drawTouchTooltip, [ context, canvasWrapper, tooltipData, showOnSpot, spotIndex, holder, ], ), returnValueForMissingStub: null, ); @override void drawAxisSpotIndicator( _i6.BuildContext? context, _i11.CanvasWrapper? canvasWrapper, _i12.PaintHolder<_i13.CandlestickChartData>? holder, ) => super.noSuchMethod( Invocation.method( #drawAxisSpotIndicator, [ context, canvasWrapper, holder, ], ), returnValueForMissingStub: null, ); @override _i13.CandlestickTouchedSpot? handleTouch( _i2.Offset? localPosition, _i2.Size? viewSize, _i12.PaintHolder<_i13.CandlestickChartData>? holder, ) => (super.noSuchMethod(Invocation.method( #handleTouch, [ localPosition, viewSize, holder, ], )) as _i13.CandlestickTouchedSpot?); @override void drawGrid( _i11.CanvasWrapper? canvasWrapper, _i12.PaintHolder<_i13.CandlestickChartData>? holder, ) => super.noSuchMethod( Invocation.method( #drawGrid, [ canvasWrapper, holder, ], ), returnValueForMissingStub: null, ); @override void drawBackground( _i11.CanvasWrapper? canvasWrapper, _i12.PaintHolder<_i13.CandlestickChartData>? holder, ) => super.noSuchMethod( Invocation.method( #drawBackground, [ canvasWrapper, holder, ], ), returnValueForMissingStub: null, ); @override void drawRangeAnnotation( _i11.CanvasWrapper? canvasWrapper, _i12.PaintHolder<_i13.CandlestickChartData>? holder, ) => super.noSuchMethod( Invocation.method( #drawRangeAnnotation, [ canvasWrapper, holder, ], ), returnValueForMissingStub: null, ); @override void drawExtraLines( _i6.BuildContext? context, _i11.CanvasWrapper? canvasWrapper, _i12.PaintHolder<_i13.CandlestickChartData>? holder, ) => super.noSuchMethod( Invocation.method( #drawExtraLines, [ context, canvasWrapper, holder, ], ), returnValueForMissingStub: null, ); @override void drawHorizontalLines( _i6.BuildContext? context, _i11.CanvasWrapper? canvasWrapper, _i12.PaintHolder<_i13.CandlestickChartData>? holder, _i2.Size? viewSize, ) => super.noSuchMethod( Invocation.method( #drawHorizontalLines, [ context, canvasWrapper, holder, viewSize, ], ), returnValueForMissingStub: null, ); @override void drawVerticalLines( _i6.BuildContext? context, _i11.CanvasWrapper? canvasWrapper, _i12.PaintHolder<_i13.CandlestickChartData>? holder, _i2.Size? viewSize, ) => super.noSuchMethod( Invocation.method( #drawVerticalLines, [ context, canvasWrapper, holder, viewSize, ], ), returnValueForMissingStub: null, ); @override double getPixelX( double? spotX, _i2.Size? viewSize, _i12.PaintHolder<_i13.CandlestickChartData>? holder, ) => (super.noSuchMethod( Invocation.method( #getPixelX, [ spotX, viewSize, holder, ], ), returnValue: 0.0, ) as double); @override double getPixelY( double? spotY, _i2.Size? viewSize, _i12.PaintHolder<_i13.CandlestickChartData>? holder, ) => (super.noSuchMethod( Invocation.method( #getPixelY, [ spotY, viewSize, holder, ], ), returnValue: 0.0, ) as double); @override double getXForPixel( double? pixelX, _i2.Size? viewSize, _i12.PaintHolder<_i13.CandlestickChartData>? holder, ) => (super.noSuchMethod( Invocation.method( #getXForPixel, [ pixelX, viewSize, holder, ], ), returnValue: 0.0, ) as double); @override double getYForPixel( double? pixelY, _i2.Size? viewSize, _i12.PaintHolder<_i13.CandlestickChartData>? holder, ) => (super.noSuchMethod( Invocation.method( #getYForPixel, [ pixelY, viewSize, holder, ], ), returnValue: 0.0, ) as double); @override _i2.Offset getChartCoordinateFromPixel( _i2.Offset? pixelOffset, _i2.Size? viewSize, _i12.PaintHolder<_i13.CandlestickChartData>? holder, ) => (super.noSuchMethod( Invocation.method( #getChartCoordinateFromPixel, [ pixelOffset, viewSize, holder, ], ), returnValue: _FakeOffset_8( this, Invocation.method( #getChartCoordinateFromPixel, [ pixelOffset, viewSize, holder, ], ), ), ) as _i2.Offset); @override double getTooltipLeft( double? dx, double? tooltipWidth, _i13.FLHorizontalAlignment? tooltipHorizontalAlignment, double? tooltipHorizontalOffset, ) => (super.noSuchMethod( Invocation.method( #getTooltipLeft, [ dx, tooltipWidth, tooltipHorizontalAlignment, tooltipHorizontalOffset, ], ), returnValue: 0.0, ) as double); } ================================================ FILE: test/chart/candlestick_chart/candlestick_chart_test.dart ================================================ import 'package:fl_chart/src/chart/base/axis_chart/axis_chart_scaffold_widget.dart'; import 'package:fl_chart/src/chart/base/axis_chart/scale_axis.dart'; import 'package:fl_chart/src/chart/base/axis_chart/transformation_config.dart'; import 'package:fl_chart/src/chart/candlestick_chart/candlestick_chart.dart'; import 'package:fl_chart/src/chart/candlestick_chart/candlestick_chart_data.dart'; import 'package:fl_chart/src/chart/candlestick_chart/candlestick_chart_renderer.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { Widget createTestWidget({ required CandlestickChart chart, }) { return MaterialApp( home: chart, ); } group('CandlestickChart', () { testWidgets('has correct default values', (tester) async { await tester.pumpWidget( createTestWidget( chart: CandlestickChart( CandlestickChartData(), ), ), ); final candlestickChart = tester.widget( find.byType(CandlestickChart), ); expect( candlestickChart.transformationConfig, const FlTransformationConfig(), ); }); testWidgets('passes interaction parameters to AxisChartScaffoldWidget', (tester) async { await tester.pumpWidget( createTestWidget( chart: CandlestickChart( CandlestickChartData(), ), ), ); final axisChartScaffoldWidget = tester.widget( find.byType(AxisChartScaffoldWidget), ); expect( axisChartScaffoldWidget.transformationConfig, const FlTransformationConfig(), ); await tester.pumpAndSettle(); final transformationConfig = FlTransformationConfig( scaleAxis: FlScaleAxis.free, trackpadScrollCausesScale: true, maxScale: 10, minScale: 1.5, transformationController: TransformationController(), ); await tester.pumpWidget( createTestWidget( chart: CandlestickChart( CandlestickChartData(), transformationConfig: transformationConfig, ), ), ); final axisChartScaffoldWidget1 = tester.widget( find.byType(AxisChartScaffoldWidget), ); expect( axisChartScaffoldWidget1.transformationConfig, transformationConfig, ); }); for (final scaleAxis in FlScaleAxis.scalingEnabledAxis) { testWidgets('passes canBeScaled true for $scaleAxis', (tester) async { await tester.pumpWidget( createTestWidget( chart: CandlestickChart( CandlestickChartData(), transformationConfig: FlTransformationConfig( scaleAxis: scaleAxis, ), ), ), ); final candlestickChartLeaf = tester.widget( find.byType(CandlestickChartLeaf), ); expect(candlestickChartLeaf.canBeScaled, true); }); } testWidgets('passes canBeScaled false for FlScaleAxis.none', (tester) async { await tester.pumpWidget( createTestWidget( chart: CandlestickChart( CandlestickChartData(), // This is for test // ignore: avoid_redundant_argument_values transformationConfig: const FlTransformationConfig( // This is for test // ignore: avoid_redundant_argument_values scaleAxis: FlScaleAxis.none, ), ), ), ); final candlestickChartLeaf = tester.widget( find.byType(CandlestickChartLeaf), ); expect(candlestickChartLeaf.canBeScaled, false); }); group('touch gesture', () { testWidgets('does not scale with FlScaleAxis.none', (tester) async { await tester.pumpWidget( createTestWidget( chart: CandlestickChart( CandlestickChartData(), ), ), ); final candlestickChartCenterOffset = tester.getCenter(find.byType(CandlestickChartLeaf)); final scaleStart1 = candlestickChartCenterOffset; final scaleStart2 = candlestickChartCenterOffset; final scaleEnd1 = candlestickChartCenterOffset + const Offset(100, 100); final scaleEnd2 = candlestickChartCenterOffset - const Offset(100, 100); final gesture1 = await tester.startGesture(scaleStart1); final gesture2 = await tester.startGesture(scaleStart2); await tester.pump(); await gesture1.moveTo(scaleEnd1); await gesture2.moveTo(scaleEnd2); await tester.pump(); await gesture1.up(); await gesture2.up(); await tester.pumpAndSettle(); final candlestickChartLeaf = tester.widget( find.byType(CandlestickChartLeaf), ); expect(candlestickChartLeaf.chartVirtualRect, isNull); }); testWidgets('scales freely with FlScaleAxis.free', (tester) async { await tester.pumpWidget( createTestWidget( chart: CandlestickChart( CandlestickChartData(), transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.free, ), ), ), ); final candlestickChartCenterOffset = tester.getCenter(find.byType(CandlestickChartLeaf)); final scaleStart1 = candlestickChartCenterOffset; final scaleStart2 = candlestickChartCenterOffset; final scaleEnd1 = candlestickChartCenterOffset + const Offset(100, 100); final scaleEnd2 = candlestickChartCenterOffset - const Offset(100, 100); final gesture1 = await tester.startGesture(scaleStart1); final gesture2 = await tester.startGesture(scaleStart2); await tester.pump(); await gesture1.moveTo(scaleEnd1); await gesture2.moveTo(scaleEnd2); await tester.pump(); await gesture1.up(); await gesture2.up(); await tester.pumpAndSettle(); final candlestickChartLeaf = tester.widget( find.byType(CandlestickChartLeaf), ); final renderBox = tester.renderObject( find.byType(CandlestickChartLeaf), ); final chartVirtualRect = candlestickChartLeaf.chartVirtualRect!; expect(chartVirtualRect.size, greaterThan(renderBox.size)); expect(chartVirtualRect.left, isNegative); expect(chartVirtualRect.top, isNegative); }); testWidgets('scales horizontally with FlScaleAxis.horizontal', (tester) async { await tester.pumpWidget( createTestWidget( chart: CandlestickChart( CandlestickChartData(), transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.horizontal, ), ), ), ); final chartCenterOffset = tester.getCenter(find.byType(CandlestickChartLeaf)); final scaleStart1 = chartCenterOffset; final scaleStart2 = chartCenterOffset; final scaleEnd1 = chartCenterOffset + const Offset(100, 100); final scaleEnd2 = chartCenterOffset - const Offset(100, 100); final gesture1 = await tester.startGesture(scaleStart1); final gesture2 = await tester.startGesture(scaleStart2); await tester.pump(); await gesture1.moveTo(scaleEnd1); await gesture2.moveTo(scaleEnd2); await tester.pump(); await gesture1.up(); await gesture2.up(); await tester.pumpAndSettle(); final candlestickChartLeaf = tester.widget( find.byType(CandlestickChartLeaf), ); final renderBox = tester.renderObject( find.byType(CandlestickChartLeaf), ); final chartVirtualRect = candlestickChartLeaf.chartVirtualRect!; expect(chartVirtualRect.size.height, renderBox.size.height); expect(chartVirtualRect.size.width, greaterThan(renderBox.size.width)); expect(chartVirtualRect.left, isNegative); expect(chartVirtualRect.top, 0); }); testWidgets('scales vertically with FlScaleAxis.vertical', (tester) async { await tester.pumpWidget( createTestWidget( chart: CandlestickChart( CandlestickChartData(), transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.vertical, ), ), ), ); final chartCenterOffset = tester.getCenter(find.byType(CandlestickChartLeaf)); final scaleStart1 = chartCenterOffset; final scaleStart2 = chartCenterOffset; final scaleEnd1 = chartCenterOffset + const Offset(100, 100); final scaleEnd2 = chartCenterOffset - const Offset(100, 100); final gesture1 = await tester.startGesture(scaleStart1); final gesture2 = await tester.startGesture(scaleStart2); await tester.pump(); await gesture1.moveTo(scaleEnd1); await gesture2.moveTo(scaleEnd2); await tester.pump(); await gesture1.up(); await gesture2.up(); await tester.pumpAndSettle(); final candlestickChartLeaf = tester.widget( find.byType(CandlestickChartLeaf), ); final renderBox = tester.renderObject( find.byType(CandlestickChartLeaf), ); final chartVirtualRect = candlestickChartLeaf.chartVirtualRect!; expect( chartVirtualRect.size.height, greaterThan(renderBox.size.height), ); expect(chartVirtualRect.size.width, renderBox.size.width); expect(chartVirtualRect.left, 0); expect(chartVirtualRect.top, isNegative); }); group('pans', () { testWidgets('only horizontally with FlScaleAxis.horizontal', (tester) async { await tester.pumpWidget( createTestWidget( chart: CandlestickChart( CandlestickChartData(), transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.horizontal, ), ), ), ); final chartCenterOffset = tester.getCenter(find.byType(CandlestickChartLeaf)); final scaleStart1 = chartCenterOffset; final scaleStart2 = chartCenterOffset; final scaleEnd1 = chartCenterOffset + const Offset(100, 100); final scaleEnd2 = chartCenterOffset - const Offset(100, 100); final gesture1 = await tester.startGesture(scaleStart1); final gesture2 = await tester.startGesture(scaleStart2); await gesture1.moveTo(scaleEnd1); await gesture2.moveTo(scaleEnd2); await gesture1.up(); await gesture2.up(); await tester.pumpAndSettle(); final candlestickChartLeafBeforePan = tester.widget( find.byType(CandlestickChartLeaf), ); final chartVirtualRectBeforePan = candlestickChartLeafBeforePan.chartVirtualRect!; expect(chartVirtualRectBeforePan.top, 0); const panOffset = Offset(100, 100); await tester.dragFrom(chartCenterOffset, panOffset); await tester.pumpAndSettle(); final candlestickChartLeafAfterPan = tester.widget( find.byType(CandlestickChartLeaf), ); final chartVirtualRectAfterPan = candlestickChartLeafAfterPan.chartVirtualRect!; expect(chartVirtualRectBeforePan.size, chartVirtualRectAfterPan.size); expect( chartVirtualRectAfterPan.left, greaterThan(chartVirtualRectBeforePan.left), ); expect(chartVirtualRectAfterPan.top, 0); }); testWidgets('only vertically with FlScaleAxis.vertical', (tester) async { await tester.pumpWidget( createTestWidget( chart: CandlestickChart( CandlestickChartData(), transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.vertical, ), ), ), ); final chartCenterOffset = tester.getCenter(find.byType(CandlestickChartLeaf)); final scaleStart1 = chartCenterOffset; final scaleStart2 = chartCenterOffset; final scaleEnd1 = chartCenterOffset + const Offset(100, 100); final scaleEnd2 = chartCenterOffset - const Offset(100, 100); final gesture1 = await tester.startGesture(scaleStart1); final gesture2 = await tester.startGesture(scaleStart2); await gesture1.moveTo(scaleEnd1); await gesture2.moveTo(scaleEnd2); await gesture1.up(); await gesture2.up(); await tester.pumpAndSettle(); final candlestickChartLeafBeforePan = tester.widget( find.byType(CandlestickChartLeaf), ); final chartVirtualRectBeforePan = candlestickChartLeafBeforePan.chartVirtualRect!; expect(chartVirtualRectBeforePan.left, 0); const panOffset = Offset(100, 100); await tester.dragFrom(chartCenterOffset, panOffset); await tester.pumpAndSettle(); final candlestickChartLeafAfterPan = tester.widget( find.byType(CandlestickChartLeaf), ); final chartVirtualRectAfterPan = candlestickChartLeafAfterPan.chartVirtualRect!; expect(chartVirtualRectAfterPan.left, 0); expect( chartVirtualRectAfterPan.top, greaterThan(chartVirtualRectBeforePan.top), ); }); testWidgets('freely with FlScaleAxis.free', (tester) async { await tester.pumpWidget( createTestWidget( chart: CandlestickChart( CandlestickChartData(), transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.free, ), ), ), ); final chartCenterOffset = tester.getCenter(find.byType(CandlestickChartLeaf)); final scaleStart1 = chartCenterOffset; final scaleStart2 = chartCenterOffset; final scaleEnd1 = chartCenterOffset + const Offset(100, 100); final scaleEnd2 = chartCenterOffset - const Offset(100, 100); final gesture1 = await tester.startGesture(scaleStart1); final gesture2 = await tester.startGesture(scaleStart2); await gesture1.moveTo(scaleEnd1); await gesture2.moveTo(scaleEnd2); await gesture1.up(); await gesture2.up(); await tester.pumpAndSettle(); final candlestickChartLeafBeforePan = tester.widget( find.byType(CandlestickChartLeaf), ); final chartVirtualRectBeforePan = candlestickChartLeafBeforePan.chartVirtualRect!; expect(chartVirtualRectBeforePan.top, isNegative); expect(chartVirtualRectBeforePan.left, isNegative); const panOffset = Offset(100, 100); await tester.dragFrom(chartCenterOffset, panOffset); await tester.pumpAndSettle(); final candlestickChartLeafAfterPan = tester.widget( find.byType(CandlestickChartLeaf), ); final chartVirtualRectAfterPan = candlestickChartLeafAfterPan.chartVirtualRect!; expect( chartVirtualRectAfterPan.left, greaterThan(chartVirtualRectBeforePan.left), ); expect( chartVirtualRectAfterPan.top, greaterThan(chartVirtualRectBeforePan.top), ); }); }); }); group('trackpad scroll', () { group('pans', () { testWidgets('only horizontally with FlScaleAxis.horizontal', (tester) async { await tester.pumpWidget( createTestWidget( chart: CandlestickChart( CandlestickChartData(), transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.horizontal, ), ), ), ); final chartCenterOffset = tester.getCenter(find.byType(CandlestickChartLeaf)); final scaleStart1 = chartCenterOffset; final scaleStart2 = chartCenterOffset; final scaleEnd1 = chartCenterOffset + const Offset(100, 100); final scaleEnd2 = chartCenterOffset - const Offset(100, 100); final gesture1 = await tester.startGesture(scaleStart1); final gesture2 = await tester.startGesture(scaleStart2); await tester.pump(); await gesture1.moveTo(scaleEnd1); await gesture2.moveTo(scaleEnd2); await tester.pump(); await gesture1.up(); await gesture2.up(); await tester.pumpAndSettle(); final candlestickChartLeafBeforePan = tester.widget( find.byType(CandlestickChartLeaf), ); final chartVirtualRectBeforePan = candlestickChartLeafBeforePan.chartVirtualRect!; expect(chartVirtualRectBeforePan.top, 0); final pointer = TestPointer(1, PointerDeviceKind.trackpad); const leftAndUp = Offset(-100, -100); await tester.sendEventToBinding(pointer.hover(chartCenterOffset)); await tester.pump(); await tester.sendEventToBinding(pointer.scroll(leftAndUp)); await tester.pump(); final candlestickChartLeafAfterPan = tester.widget( find.byType(CandlestickChartLeaf), ); final chartVirtualRectAfterPan = candlestickChartLeafAfterPan.chartVirtualRect!; expect( chartVirtualRectAfterPan.left, greaterThan(chartVirtualRectBeforePan.left), ); expect(chartVirtualRectAfterPan.top, 0); }); testWidgets('vertically with FlScaleAxis.vertical', (tester) async { await tester.pumpWidget( createTestWidget( chart: CandlestickChart( CandlestickChartData(), transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.vertical, ), ), ), ); final chartCenterOffset = tester.getCenter(find.byType(CandlestickChartLeaf)); final scaleStart1 = chartCenterOffset; final scaleStart2 = chartCenterOffset; final scaleEnd1 = chartCenterOffset + const Offset(100, 100); final scaleEnd2 = chartCenterOffset - const Offset(100, 100); final gesture1 = await tester.startGesture(scaleStart1); final gesture2 = await tester.startGesture(scaleStart2); await tester.pump(); await gesture1.moveTo(scaleEnd1); await gesture2.moveTo(scaleEnd2); await tester.pump(); await gesture1.up(); await gesture2.up(); await tester.pumpAndSettle(); final candlestickChartLeafBeforePan = tester.widget( find.byType(CandlestickChartLeaf), ); final chartVirtualRectBeforePan = candlestickChartLeafBeforePan.chartVirtualRect!; expect(chartVirtualRectBeforePan.left, 0); final pointer = TestPointer(1, PointerDeviceKind.trackpad); const leftAndUp = Offset(-100, -100); await tester.sendEventToBinding(pointer.hover(chartCenterOffset)); await tester.pump(); await tester.sendEventToBinding(pointer.scroll(leftAndUp)); await tester.pump(); final candlestickChartLeafAfterPan = tester.widget( find.byType(CandlestickChartLeaf), ); final chartVirtualRectAfterPan = candlestickChartLeafAfterPan.chartVirtualRect!; expect(chartVirtualRectAfterPan.left, 0); expect( chartVirtualRectAfterPan.top, greaterThan(chartVirtualRectBeforePan.top), ); }); testWidgets('freely with FlScaleAxis.free', (tester) async { await tester.pumpWidget( createTestWidget( chart: CandlestickChart( CandlestickChartData(), transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.free, ), ), ), ); final chartCenterOffset = tester.getCenter(find.byType(CandlestickChartLeaf)); final scaleStart1 = chartCenterOffset; final scaleStart2 = chartCenterOffset; final scaleEnd1 = chartCenterOffset + const Offset(100, 100); final scaleEnd2 = chartCenterOffset - const Offset(100, 100); final gesture1 = await tester.startGesture(scaleStart1); final gesture2 = await tester.startGesture(scaleStart2); await tester.pump(); await gesture1.moveTo(scaleEnd1); await gesture2.moveTo(scaleEnd2); await tester.pump(); await gesture1.up(); await gesture2.up(); await tester.pumpAndSettle(); final candlestickChartLeafBeforePan = tester.widget( find.byType(CandlestickChartLeaf), ); final chartVirtualRectBeforePan = candlestickChartLeafBeforePan.chartVirtualRect!; final pointer = TestPointer(1, PointerDeviceKind.trackpad); const leftAndUp = Offset(-100, -100); await tester.sendEventToBinding(pointer.hover(chartCenterOffset)); await tester.pump(); await tester.sendEventToBinding(pointer.scroll(leftAndUp)); await tester.pump(); final candlestickChartLeafAfterPan = tester.widget( find.byType(CandlestickChartLeaf), ); final chartVirtualRectAfterPan = candlestickChartLeafAfterPan.chartVirtualRect!; expect( chartVirtualRectAfterPan.left, greaterThan(chartVirtualRectBeforePan.left), ); expect( chartVirtualRectAfterPan.top, greaterThan(chartVirtualRectBeforePan.top), ); }); }); testWidgets( 'does not scale with FlScaleAxis.none when ' 'trackpadScrollCausesScale is true', (tester) async { await tester.pumpWidget( createTestWidget( chart: CandlestickChart( CandlestickChartData(), transformationConfig: const FlTransformationConfig( // This is for test // ignore: avoid_redundant_argument_values scaleAxis: FlScaleAxis.none, trackpadScrollCausesScale: true, ), ), ), ); final pointer = TestPointer(1, PointerDeviceKind.trackpad); final chartCenterOffset = tester.getCenter(find.byType(CandlestickChartLeaf)); const scrollAmount = Offset(0, -100); await tester.sendEventToBinding(pointer.hover(chartCenterOffset)); await tester.pump(); await tester.sendEventToBinding(pointer.scroll(scrollAmount)); await tester.pump(); final candlestickChartLeaf = tester.widget( find.byType(CandlestickChartLeaf), ); expect(candlestickChartLeaf.chartVirtualRect, null); }, ); for (final scaleAxis in FlScaleAxis.scalingEnabledAxis) { testWidgets( 'does not scale when trackpadScrollCausesScale is false ' 'for $scaleAxis', (tester) async { await tester.pumpWidget( createTestWidget( chart: CandlestickChart( CandlestickChartData(), transformationConfig: FlTransformationConfig( scaleAxis: scaleAxis, // This is for test // ignore: avoid_redundant_argument_values trackpadScrollCausesScale: false, ), ), ), ); final pointer = TestPointer(1, PointerDeviceKind.trackpad); final chartCenterOffset = tester.getCenter( find.byType(CandlestickChartLeaf), ); const scrollAmount = Offset(0, -100); await tester.sendEventToBinding(pointer.hover(chartCenterOffset)); await tester.pump(); await tester.sendEventToBinding(pointer.scroll(scrollAmount)); await tester.pump(); final candlestickChartLeaf = tester.widget( find.byType(CandlestickChartLeaf), ); expect(candlestickChartLeaf.chartVirtualRect, null); }, ); } testWidgets('scales horizontally with FlScaleAxis.horizontal', (tester) async { await tester.pumpWidget( createTestWidget( chart: CandlestickChart( CandlestickChartData(), transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.horizontal, trackpadScrollCausesScale: true, ), ), ), ); final pointer = TestPointer(1, PointerDeviceKind.trackpad); final chartCenterOffset = tester.getCenter(find.byType(CandlestickChartLeaf)); const scrollAmount = Offset(0, -100); await tester.sendEventToBinding(pointer.hover(chartCenterOffset)); await tester.pump(); await tester.sendEventToBinding(pointer.scroll(scrollAmount)); await tester.pump(); final candlestickChartLeaf = tester.widget( find.byType(CandlestickChartLeaf), ); final renderBox = tester.renderObject( find.byType(CandlestickChartLeaf), ); final chartVirtualRect = candlestickChartLeaf.chartVirtualRect!; expect(chartVirtualRect.size.height, renderBox.size.height); expect(chartVirtualRect.size.width, greaterThan(renderBox.size.width)); expect(chartVirtualRect.left, isNegative); expect(chartVirtualRect.top, 0); }); testWidgets('scales vertically with FlScaleAxis.vertical', (tester) async { await tester.pumpWidget( createTestWidget( chart: CandlestickChart( CandlestickChartData(), transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.vertical, trackpadScrollCausesScale: true, ), ), ), ); final pointer = TestPointer(1, PointerDeviceKind.trackpad); final chartCenterOffset = tester.getCenter(find.byType(CandlestickChartLeaf)); const scrollAmount = Offset(0, -100); await tester.sendEventToBinding(pointer.hover(chartCenterOffset)); await tester.pump(); await tester.sendEventToBinding(pointer.scroll(scrollAmount)); await tester.pump(); final candlestickChartLeaf = tester.widget( find.byType(CandlestickChartLeaf), ); final renderBox = tester.renderObject( find.byType(CandlestickChartLeaf), ); final chartVirtualRect = candlestickChartLeaf.chartVirtualRect!; expect( chartVirtualRect.size.height, greaterThan(renderBox.size.height), ); expect(chartVirtualRect.size.width, renderBox.size.width); expect(chartVirtualRect.left, 0); expect(chartVirtualRect.top, isNegative); }); testWidgets('scales freely with FlScaleAxis.free', (tester) async { await tester.pumpWidget( createTestWidget( chart: CandlestickChart( CandlestickChartData(), transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.free, trackpadScrollCausesScale: true, ), ), ), ); final pointer = TestPointer(1, PointerDeviceKind.trackpad); final chartCenterOffset = tester.getCenter(find.byType(CandlestickChartLeaf)); const scrollAmount = Offset(0, -100); await tester.sendEventToBinding(pointer.hover(chartCenterOffset)); await tester.pump(); await tester.sendEventToBinding(pointer.scroll(scrollAmount)); await tester.pump(); final candlestickChartLeaf = tester .widget(find.byType(CandlestickChartLeaf)); final renderBox = tester.renderObject( find.byType(CandlestickChartLeaf), ); final chartVirtualRect = candlestickChartLeaf.chartVirtualRect!; expect(chartVirtualRect.size, greaterThan(renderBox.size)); expect(chartVirtualRect.left, isNegative); expect(chartVirtualRect.top, isNegative); }); }); }); } ================================================ FILE: test/chart/data_pool.dart ================================================ import 'dart:ui'; import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/chart/base/line.dart'; import 'package:flutter/material.dart'; class MockData { static const color0 = Color(0x00000000); static const color1 = Color(0x11111111); static const color2 = Color(0x22222222); static const color3 = Color(0x33333333); static const color4 = Color(0x44444444); static const color5 = Color(0x55555555); static const color6 = Color(0x66666666); static final path1 = Path() ..moveTo(10, 10) ..lineTo(20, 20) ..arcTo( Rect.fromCenter(center: Offset.zero, width: 44, height: 22), 2, 3, false, ); static final path1Duplicate = Path() ..moveTo(10, 10) ..lineTo(20, 20) ..arcTo( Rect.fromCenter(center: Offset.zero, width: 44, height: 22), 2, 3, false, ); static final path2 = Path() ..moveTo(10, 10) ..lineTo(20, 20) ..arcTo( Rect.fromCenter(center: Offset.zero, width: 44, height: 22.01), 2, 3, false, ); static final path3 = Path() ..moveTo(10, 13) ..lineTo(20, 20) ..arcTo( Rect.fromCenter(center: Offset.zero, width: 44, height: 22), 2, 3, false, ); static final path4 = Path() ..moveTo(24, 13) ..lineTo(20, 20) ..arcTo( Rect.fromCenter(center: Offset.zero, width: 44, height: 22), 2, 3, false, ); static const borderSide1 = BorderSide(color: color1); static const borderSide2 = BorderSide(color: color2, width: 2); static const borderSide3 = BorderSide(color: color3, width: 3); static const borderSide4 = BorderSide(color: color4, width: 4); static const borderSide5 = BorderSide(color: color5, width: 5); static const borderSide6 = BorderSide(color: color6, width: 6); static const TextStyle textStyle1 = TextStyle(color: color1, fontWeight: FontWeight.w100); static const TextStyle textStyle2 = TextStyle(color: color2, fontWeight: FontWeight.w200); static const TextStyle textStyle3 = TextStyle(color: color3, fontWeight: FontWeight.w300); static const TextStyle textStyle4 = TextStyle(color: color4, fontWeight: FontWeight.w400); static const Offset offset1 = Offset(1, 1); static const Offset offset1Duplicate = Offset(1, 1); static const Offset offset2 = Offset(2, 2); static const Offset offset3 = Offset(2, 2); static const Offset offset4 = Offset(4, 4); static const Offset offset5 = Offset(5, 5); static const Offset offset6 = Offset(6, 6); static const size1 = Size(11, 11); static const size2 = Size(22, 22); static final textPainter1 = TextPainter(); static final textPainter2 = TextPainter() ..text = const TextSpan(text: 'test') ..textDirection = TextDirection.ltr ..layout(); static final rect1 = Rect.fromCenter(center: offset1, width: 11, height: 11); static final rect2 = Rect.fromCenter(center: offset2, width: 22, height: 22); static final rRect1 = RRect.fromLTRBR(1, 1, 1, 1, const Radius.circular(11)); static final rRect2 = RRect.fromLTRBR(2, 2, 2, 2, const Radius.circular(22)); static final paint1 = Paint() ..color = color1 ..strokeWidth = 11; static final paint2 = Paint() ..color = color2 ..strokeWidth = 22; static Picture? _picture1; static Picture picture1() { if (_picture1 != null) { return _picture1!; } final recorder1 = PictureRecorder(); Canvas(recorder1).drawLine(offset1, offset2, paint1); _picture1 = recorder1.endRecording(); return _picture1!; } static Image? _image1; static Image image1() { if (_image1 != null) { return _image1!; } _image1 = Image.asset('asdf/asdf'); return _image1!; } static final LineChartBarData lineChartBarData1 = LineChartBarData( dashArray: [0, 1], gradient: const LinearGradient( colors: [Colors.red, Colors.green], stops: [0, 1], begin: Alignment.center, end: Alignment.bottomRight, ), spots: [ flSpot1, flSpot2, ], shadow: shadow1, aboveBarData: barAreaData1, belowBarData: barAreaData2, barWidth: 12, curveSmoothness: 12, dotData: flDotData1, isStrokeCapRound: true, preventCurveOvershootingThreshold: 1.2, showingIndicators: [0, 1], ); static final LineChartBarData lineChartBarData2 = LineChartBarData( dashArray: [0, 1], gradient: const LinearGradient( colors: [Colors.red, Colors.green], stops: [0, 1], begin: Alignment.center, end: Alignment.bottomRight, ), spots: [ flSpot1, flSpot2, ], shadow: shadow2, isStepLineChart: true, aboveBarData: barAreaData1, belowBarData: barAreaData2, barWidth: 12, curveSmoothness: 12, dotData: flDotData1, isStrokeCapRound: true, preventCurveOvershootingThreshold: 1.2, showingIndicators: [0, 4], ); static const FlSpot flSpot0 = FlSpot.zero; static const flSpot1 = FlSpot(1, 1); static const flSpot2 = FlSpot(2, 2); static const flSpot3 = FlSpot(3, 3); static const flSpot4 = FlSpot(4, 4); static const flSpot5 = FlSpot(5, 5); static final horizontalLine0 = HorizontalLine(y: 0, color: color0); static final horizontalLine1 = HorizontalLine(y: 1, color: color1); static final horizontalLine2 = HorizontalLine(y: 2, color: color2); static final horizontalLine3 = HorizontalLine(y: 3, color: color3); static final horizontalLine4 = HorizontalLine(y: 4, color: color4); static final horizontalLine5 = HorizontalLine(y: 5, color: color5); static final verticalLine0 = VerticalLine(x: 0, color: color0); static final verticalLine1 = VerticalLine(x: 1, color: color1); static final verticalLine2 = VerticalLine(x: 2, color: color2); static final verticalLine3 = VerticalLine(x: 3, color: color3); static final verticalLine4 = VerticalLine(x: 4, color: color4); static final verticalLine5 = VerticalLine(x: 5, color: color5); static final horizontalRangeAnnotation0 = HorizontalRangeAnnotation(y1: 0, y2: 1, color: color0); static final horizontalRangeAnnotation1 = HorizontalRangeAnnotation(y1: 1, y2: 2, color: color1); static final horizontalRangeAnnotation2 = HorizontalRangeAnnotation(y1: 2, y2: 3, color: color2); static final horizontalRangeAnnotation3 = HorizontalRangeAnnotation(y1: 3, y2: 4, color: color3); static final horizontalRangeAnnotation4 = HorizontalRangeAnnotation(y1: 4, y2: 5, color: color4); static final verticalRangeAnnotation0 = VerticalRangeAnnotation(x1: 0, x2: 1, color: color0); static final verticalRangeAnnotation1 = VerticalRangeAnnotation(x1: 1, x2: 2, color: color1); static final verticalRangeAnnotation2 = VerticalRangeAnnotation(x1: 2, x2: 3, color: color2); static final verticalRangeAnnotation3 = VerticalRangeAnnotation(x1: 3, x2: 4, color: color3); static final verticalRangeAnnotation4 = VerticalRangeAnnotation(x1: 4, x2: 5, color: color4); static const RadarEntry radarEntry0 = RadarEntry(value: 0); static const RadarEntry radarEntry1 = RadarEntry(value: 1); static const RadarEntry radarEntry2 = RadarEntry(value: 2); static const RadarEntry radarEntry3 = RadarEntry(value: 3); static const RadarEntry radarEntry4 = RadarEntry(value: 4); static final RadarDataSet radarDataSet1 = RadarDataSet( dataEntries: [radarEntry1, radarEntry2, radarEntry3], ); static final RadarDataSet radarDataSet2 = RadarDataSet( dataEntries: [radarEntry3, radarEntry1, radarEntry2], ); static final RadarTouchedSpot radarTouchedSpot = RadarTouchedSpot( radarDataSet1, 0, radarEntry1, 0, flSpot1, offset1, ); static final pieChartSection0 = PieChartSectionData( value: 0, color: color0, radius: 0, ); static final pieChartSection1 = PieChartSectionData( value: 1, color: color1, radius: 1, ); static final pieChartSection2 = PieChartSectionData( value: 2, color: color2, radius: 2, ); static final pieChartSection3 = PieChartSectionData( value: 3, color: color3, radius: 3, ); static final pieChartSection4 = PieChartSectionData( value: 4, color: color4, radius: 4, ); static final scatterSpot0 = ScatterSpot( 0, 0, dotPainter: FlDotCirclePainter(color: color0), ); static final scatterSpot1 = ScatterSpot( 1, 1, dotPainter: FlDotCirclePainter(color: color1), ); static final scatterSpot2 = ScatterSpot( 2, 2, dotPainter: FlDotCirclePainter(color: color2), ); static final scatterSpot3 = ScatterSpot( 3, 3, dotPainter: FlDotCirclePainter(color: color3), ); static final scatterSpot4 = ScatterSpot( 4, 4, dotPainter: FlDotCirclePainter(color: color4), ); static final scatterTouchedSpot = ScatterTouchedSpot(scatterSpot1, 0); static final pieChartSectionData1 = PieChartSectionData(value: 12); static final pieChartSectionData2 = PieChartSectionData(value: 22); static final pieTouchedSection1 = PieTouchedSection( pieChartSectionData1, 0, 12, 33, ); static final lineBarSpot1 = TouchLineBarSpot( lineChartBarData1, 0, lineChartBarData1.spots.first, 0, ); static final lineBarSpot2 = TouchLineBarSpot( MockData.lineChartBarData1, 1, MockData.lineChartBarData1.spots.last, 2, ); static final lineTouchResponse1 = LineTouchResponse( touchLocation: Offset.zero, touchChartCoordinate: Offset.zero, lineBarSpots: [lineBarSpot1, lineBarSpot2], ); static final barChartRodData1 = BarChartRodData(toY: 11); static final barChartRodData2 = BarChartRodData(toY: 22); static final barTouchedSpot = BarTouchedSpot( BarChartGroupData(x: 0, barRods: [barChartRodData1, barChartRodData2]), 0, barChartRodData1, 0, null, -1, flSpot1, offset1, ); static const gradient1 = LinearGradient( colors: [ MockData.color0, MockData.color1, ], ); static final barGroupData0 = BarChartGroupData( x: 0, barRods: [MockData.barChartRodData1, MockData.barChartRodData2], ); static final barGroupData1 = BarChartGroupData( x: 1, barRods: [MockData.barChartRodData1, MockData.barChartRodData2], ); static final barGroupData2 = BarChartGroupData( x: 2, barRods: [MockData.barChartRodData1, MockData.barChartRodData2], ); static final barChartData1 = BarChartData( barGroups: [barGroupData0, barGroupData1, barGroupData2], ); static const sideTitles1 = SideTitles( reservedSize: 10, interval: 23, ); static const sideTitles1Clone = SideTitles( reservedSize: 10, interval: 23, ); static const sideTitles2 = SideTitles( reservedSize: 10, interval: 12, ); static const sideTitles3 = SideTitles( reservedSize: 10, getTitlesWidget: getTitles, interval: 12, ); static const sideTitles4 = SideTitles( reservedSize: 11, showTitles: true, getTitlesWidget: getTitles, interval: 12, ); static const sideTitles5 = SideTitles( reservedSize: 10, getTitlesWidget: getTitles, interval: 43, ); static const sideTitles6 = SideTitles( reservedSize: 10, getTitlesWidget: getTitles, interval: 22, ); static const sideTitleFitInsideData1 = SideTitleFitInsideData( enabled: true, axisPosition: 0, parentAxisSize: 100, distanceFromEdge: 0, ); static const sideTitleFitInsideData1Clone = SideTitleFitInsideData( enabled: true, axisPosition: 0, parentAxisSize: 100, distanceFromEdge: 0, ); static const sideTitleFitInsideData2 = SideTitleFitInsideData( enabled: true, axisPosition: 0, parentAxisSize: 100, distanceFromEdge: 10, ); static const sideTitleFitInsideData3 = SideTitleFitInsideData( enabled: false, axisPosition: 0, parentAxisSize: 100, distanceFromEdge: 0, ); static const sideTitleFitInsideData4 = SideTitleFitInsideData( enabled: true, axisPosition: 200, parentAxisSize: 200, distanceFromEdge: 0, ); static const sideTitleFitInsideData5 = SideTitleFitInsideData( enabled: true, axisPosition: 200, parentAxisSize: 200, distanceFromEdge: 10, ); static const sideTitleFitInsideData6 = SideTitleFitInsideData( enabled: false, axisPosition: 200, parentAxisSize: 200, distanceFromEdge: 0, ); static const widget1 = Text('axis1'); static const widget2 = Text('axis2'); static const widget3 = Text('axis3'); static const widget4 = Text('axis4'); static const widget5 = Text('axis5'); static const axisTitles1 = AxisTitles( axisNameWidget: widget1, sideTitles: sideTitles1, ); static const axisTitles1Clone = AxisTitles( axisNameWidget: widget1, sideTitles: sideTitles1Clone, ); static const axisTitles2 = AxisTitles( axisNameWidget: widget2, sideTitles: sideTitles2, ); static const axisTitles3 = AxisTitles( axisNameWidget: widget3, sideTitles: sideTitles3, ); static const axisTitles4 = AxisTitles( axisNameWidget: widget4, sideTitles: sideTitles4, ); static const axisTitles5 = AxisTitles( axisNameWidget: widget5, axisNameSize: 889, sideTitles: sideTitles4, ); static const flTitlesData1 = FlTitlesData( bottomTitles: axisTitles1, topTitles: axisTitles2, rightTitles: axisTitles3, leftTitles: axisTitles4, ); static const flTitlesData1Clone = FlTitlesData( bottomTitles: axisTitles1Clone, topTitles: axisTitles2, rightTitles: axisTitles3, leftTitles: axisTitles4, ); static const flTitlesData2 = FlTitlesData( topTitles: axisTitles2, rightTitles: axisTitles3, leftTitles: axisTitles4, ); static const flTitlesData3 = FlTitlesData( bottomTitles: axisTitles1, rightTitles: axisTitles3, leftTitles: axisTitles4, ); static const flTitlesData4 = FlTitlesData( bottomTitles: axisTitles1, topTitles: axisTitles2, leftTitles: axisTitles4, ); static const flTitlesData5 = FlTitlesData( bottomTitles: axisTitles1, topTitles: axisTitles2, rightTitles: axisTitles3, ); static const flTitlesData6 = FlTitlesData( show: false, bottomTitles: axisTitles1, topTitles: axisTitles2, rightTitles: axisTitles3, leftTitles: axisTitles4, ); } final VerticalRangeAnnotation verticalRangeAnnotation1 = VerticalRangeAnnotation(color: Colors.green, x2: 12, x1: 12.1); final VerticalRangeAnnotation verticalRangeAnnotation1Clone = VerticalRangeAnnotation(color: Colors.green, x2: 12, x1: 12.1); final HorizontalRangeAnnotation horizontalRangeAnnotation1 = HorizontalRangeAnnotation(color: Colors.green, y2: 12, y1: 12.1); final HorizontalRangeAnnotation horizontalRangeAnnotation1Clone = HorizontalRangeAnnotation(color: Colors.green, y2: 12, y1: 12.1); final RangeAnnotations rangeAnnotations1 = RangeAnnotations( horizontalRangeAnnotations: [ horizontalRangeAnnotation1, horizontalRangeAnnotation1Clone, ], verticalRangeAnnotations: [ verticalRangeAnnotation1, verticalRangeAnnotation1Clone, ], ); final RangeAnnotations rangeAnnotations1Clone = RangeAnnotations( horizontalRangeAnnotations: [ horizontalRangeAnnotation1, horizontalRangeAnnotation1Clone, ], verticalRangeAnnotations: [ verticalRangeAnnotation1, verticalRangeAnnotation1Clone, ], ); final RangeAnnotations rangeAnnotations2 = RangeAnnotations( horizontalRangeAnnotations: [ horizontalRangeAnnotation1Clone, ], verticalRangeAnnotations: [ verticalRangeAnnotation1, verticalRangeAnnotation1Clone, ], ); const FlLine flLine1 = FlLine(color: Colors.green, strokeWidth: 1, dashArray: [1, 2, 3]); const FlLine flLine1Clone = FlLine(color: Colors.green, strokeWidth: 1, dashArray: [1, 2, 3]); bool checkToShowLine(double value) => true; FlLine getDrawingLine(double value) => const FlLine(); const FlSpot flSpot1 = FlSpot(1, 1); final FlSpot flSpot1Clone = flSpot1.copyWith(); const FlSpot flSpot2 = FlSpot(4, 2); final FlSpot flSpot2Clone = flSpot2.copyWith(); const FlSpot nullSpot1 = FlSpot.nullSpot; final FlSpot nullSpot2 = nullSpot1.copyWith(); const FlSpot nullSpot3 = FlSpot.nullSpot; Widget getTitles(double value, TitleMeta meta) => const Text('sallam'); TextStyle getTextStyles(BuildContext context, double value) => const TextStyle(color: Colors.green); const FlGridData flGridData1 = FlGridData( verticalInterval: 12, horizontalInterval: 22, drawVerticalLine: false, checkToShowVerticalLine: checkToShowLine, getDrawingHorizontalLine: getDrawingLine, ); const FlGridData flGridData1Clone = FlGridData( verticalInterval: 12, horizontalInterval: 22, drawVerticalLine: false, checkToShowVerticalLine: checkToShowLine, getDrawingHorizontalLine: getDrawingLine, ); final FlGridData flGridData2 = FlGridData( verticalInterval: 12, horizontalInterval: 22, drawVerticalLine: false, checkToShowVerticalLine: checkToShowLine, getDrawingHorizontalLine: getDrawingLine, getDrawingVerticalLine: (value) => flLine1, ); const FlGridData flGridData3 = FlGridData( verticalInterval: 12, horizontalInterval: 43, drawVerticalLine: false, checkToShowVerticalLine: checkToShowLine, getDrawingHorizontalLine: getDrawingLine, ); const FlGridData flGridData4 = FlGridData( verticalInterval: 12, horizontalInterval: 22, drawVerticalLine: false, getDrawingHorizontalLine: getDrawingLine, ); const FlGridData flGridData5 = FlGridData( verticalInterval: 12, horizontalInterval: 22, checkToShowVerticalLine: checkToShowLine, getDrawingHorizontalLine: getDrawingLine, ); final FlBorderData borderData1 = FlBorderData( show: true, border: Border.all(color: Colors.green), ); final FlBorderData borderData1Clone = FlBorderData( show: true, border: Border.all(color: Colors.green), ); final FlBorderData borderData2 = FlBorderData( show: true, border: Border.all(color: Colors.green.withValues(alpha: 0.5)), ); bool checkToShowSpotLine(FlSpot spot) => true; const BarAreaSpotsLine barAreaSpotsLine1 = BarAreaSpotsLine(show: true, checkToShowSpotLine: checkToShowSpotLine); const BarAreaSpotsLine barAreaSpotsLine1Clone = BarAreaSpotsLine(show: true, checkToShowSpotLine: checkToShowSpotLine); const BarAreaSpotsLine barAreaSpotsLine2 = BarAreaSpotsLine( show: true, ); final BarAreaData barAreaData1 = BarAreaData( show: true, cutOffY: 12, gradient: const LinearGradient( colors: [Colors.green, Colors.blue], stops: [0, 0.5], begin: Alignment.topLeft, end: Alignment.bottomCenter, ), spotsLine: barAreaSpotsLine1, ); final BarAreaData barAreaData1Clone = BarAreaData( show: true, cutOffY: 12, gradient: const LinearGradient( colors: [Colors.green, Colors.blue], stops: [0, 0.5], begin: Alignment.topLeft, end: Alignment.bottomCenter, ), spotsLine: barAreaSpotsLine1, ); final BarAreaData barAreaData2 = BarAreaData( show: true, cutOffY: 12, gradient: const LinearGradient( colors: [Colors.green, Colors.blue], stops: [0, 0.5], begin: Alignment.topLeft, end: Alignment.bottomCenter, ), spotsLine: barAreaSpotsLine2, ); final BarAreaData barAreaData3 = BarAreaData( show: true, cutOffY: 12, gradient: const LinearGradient( colors: [Colors.green, Colors.blue], stops: [0, 0.6], begin: Alignment.topLeft, end: Alignment.bottomCenter, ), spotsLine: barAreaSpotsLine2, ); final BarAreaData barAreaData4 = BarAreaData( show: true, cutOffY: 12, gradient: const LinearGradient( colors: [Colors.green, Colors.blue], stops: [0], begin: Alignment.topLeft, end: Alignment.bottomCenter, ), spotsLine: barAreaSpotsLine2, ); bool checkToShowDot(FlSpot spot, LineChartBarData barData) => true; FlDotCirclePainter getDotDrawer( FlSpot spot, double percent, LineChartBarData barData, int index, ) => FlDotCirclePainter(radius: 44, strokeWidth: 12); FlDotCirclePainter getDotDrawer5( FlSpot spot, double percent, LineChartBarData barData, int index, ) => FlDotCirclePainter(radius: 44, strokeWidth: 14); FlDotCirclePainter getDotDrawer6( FlSpot spot, double percent, LineChartBarData barData, int index, ) => FlDotCirclePainter(radius: 44.01, strokeWidth: 14); FlDotCirclePainter getDotDrawerTouched( FlSpot spot, double percent, LineChartBarData barData, int index, ) => FlDotCirclePainter(radius: 12, color: Colors.red); FlDotCirclePainter getDotDrawerTouched4( FlSpot spot, double percent, LineChartBarData barData, int index, ) => FlDotCirclePainter(radius: 12); FlDotCirclePainter getDotDrawerTouched6( FlSpot spot, double percent, LineChartBarData barData, int index, ) => FlDotCirclePainter(radius: 12.01, color: Colors.red); const FlDotData flDotData1 = FlDotData( getDotPainter: getDotDrawer, checkToShowDot: checkToShowDot, ); const FlDotData flDotData1Clone = FlDotData( getDotPainter: getDotDrawer, checkToShowDot: checkToShowDot, ); const FlDotData flDotData4 = FlDotData( getDotPainter: getDotDrawer, ); const FlDotData flDotData5 = FlDotData( getDotPainter: getDotDrawer5, ); const FlDotData flDotData6 = FlDotData( getDotPainter: getDotDrawer6, ); const Shadow shadow1 = Shadow( color: Colors.red, blurRadius: 12, ); const Shadow shadow1Clone = Shadow( color: Colors.red, blurRadius: 12, ); const Shadow shadow2 = Shadow( color: Colors.green, blurRadius: 12, ); const Shadow shadow3 = Shadow( color: Colors.red, blurRadius: 14, ); final Shadow shadow4 = Shadow( color: Colors.red.withValues(alpha: 0.5), blurRadius: 12, ); const LineChartStepData lineChartStepData1 = LineChartStepData(); const LineChartStepData lineChartStepData1Clone = LineChartStepData(); const LineChartStepData lineChartStepData2 = LineChartStepData( stepDirection: LineChartStepData.stepDirectionForward, ); final LineChartBarData lineChartBarData1 = LineChartBarData( dashArray: [0, 1], gradient: const LinearGradient( colors: [Colors.red, Colors.green], stops: [0, 1], begin: Alignment.center, end: Alignment.bottomRight, ), spots: [ flSpot1, flSpot2, ], shadow: shadow1, aboveBarData: barAreaData1, belowBarData: barAreaData2, barWidth: 12, curveSmoothness: 12, dotData: flDotData1, isStrokeCapRound: true, preventCurveOvershootingThreshold: 1.2, showingIndicators: [0, 1], errorIndicatorData: const FlErrorIndicatorData( show: false, ), ); final LineChartBarData lineChartBarData1Clone = LineChartBarData( dashArray: [0, 1], gradient: const LinearGradient( colors: [Colors.red, Colors.green], stops: [0, 1], begin: Alignment.center, end: Alignment.bottomRight, ), spots: [ flSpot1Clone, flSpot2, ], shadow: shadow1Clone, aboveBarData: barAreaData1Clone, belowBarData: barAreaData2, barWidth: 12, curveSmoothness: 12, dotData: flDotData1Clone, isStrokeCapRound: true, preventCurveOvershootingThreshold: 1.2, showingIndicators: [0, 1], errorIndicatorData: const FlErrorIndicatorData( show: false, ), ); final LineChartBarData lineChartBarData2 = LineChartBarData( dashArray: [0, 1], gradient: const LinearGradient( colors: [Colors.red, Colors.green], stops: [0, 1], begin: Alignment.center, end: Alignment.bottomRight, ), spots: [ flSpot1, flSpot2, ], shadow: shadow2, isStepLineChart: true, aboveBarData: barAreaData1, belowBarData: barAreaData2, barWidth: 12, curveSmoothness: 12, dotData: flDotData1, isStrokeCapRound: true, preventCurveOvershootingThreshold: 1.2, showingIndicators: [0, 4], ); final LineChartBarData lineChartBarData3 = LineChartBarData( gradient: const LinearGradient( colors: [Colors.red, Colors.green], stops: [0, 1], begin: Alignment.center, end: Alignment.bottomRight, ), spots: [ flSpot1, flSpot2, ], shadow: shadow3, isStepLineChart: true, lineChartStepData: lineChartStepData2, aboveBarData: barAreaData1, belowBarData: barAreaData2, barWidth: 12, curveSmoothness: 12, dotData: flDotData1, isStrokeCapRound: true, preventCurveOvershootingThreshold: 1.2, showingIndicators: [0, 1], ); final LineChartBarData lineChartBarData4 = LineChartBarData( dashArray: [0, 1], gradient: const LinearGradient( colors: [Colors.red, Colors.green], stops: [0, 1], begin: Alignment.center, end: Alignment.bottomRight, ), spots: [ flSpot2, flSpot1, ], shadow: shadow4, lineChartStepData: lineChartStepData2, aboveBarData: barAreaData1, belowBarData: barAreaData2, barWidth: 12, curveSmoothness: 12, dotData: flDotData1, isStrokeCapRound: true, preventCurveOvershootingThreshold: 1.2, showingIndicators: [0, 1], ); final LineChartBarData lineChartBarData5 = LineChartBarData( dashArray: [0, 1], gradient: const LinearGradient( colors: [Colors.red, Colors.green], stops: [0, 1], begin: Alignment.center, end: Alignment.bottomRight, ), spots: [ flSpot1, flSpot2, ], aboveBarData: barAreaData2, belowBarData: barAreaData1, barWidth: 12, curveSmoothness: 12, dotData: flDotData1, isStrokeCapRound: true, preventCurveOvershootingThreshold: 1.2, showingIndicators: [0, 1], ); final LineChartBarData lineChartBarData6 = LineChartBarData( dashArray: [0, 1], gradient: const LinearGradient( colors: [Colors.red, Colors.green], stops: [0, 1], begin: Alignment.center, end: Alignment.bottomRight, ), spots: [ flSpot1, flSpot2, ], aboveBarData: barAreaData1, belowBarData: barAreaData2, barWidth: 12, curveSmoothness: 12, dotData: flDotData1, isStrokeCapRound: true, preventCurveOvershootingThreshold: 1.2, showingIndicators: [0, 1], ); final LineChartBarData lineChartBarData7 = LineChartBarData( dashArray: [0, 1], gradient: LinearGradient( colors: [Colors.red, Colors.green.withValues(alpha: 0.4)], stops: const [0, 1], begin: Alignment.center, end: Alignment.bottomRight, ), spots: [ flSpot1, flSpot2, ], aboveBarData: barAreaData1, belowBarData: barAreaData2, barWidth: 12, curveSmoothness: 12, dotData: flDotData1, isStrokeCapRound: true, preventCurveOvershootingThreshold: 1.2, showingIndicators: [0, 1], ); final LineChartBarData lineChartBarData8 = LineChartBarData( dashArray: [0, 1], gradient: const LinearGradient( colors: [Colors.red, Colors.green], stops: [0, 1], begin: Alignment.center, end: Alignment.bottomRight, ), spots: [ flSpot1, flSpot2, ], aboveBarData: barAreaData1, belowBarData: barAreaData2, barWidth: 12, curveSmoothness: 12.01, dotData: flDotData1, isStrokeCapRound: true, preventCurveOvershootingThreshold: 1.2, showingIndicators: [0, 1], ); final LineChartBarData lineChartBarData9 = LineChartBarData( dashArray: [0, 1], gradient: const LinearGradient( colors: [Colors.red, Colors.green], stops: [0, 1], begin: Alignment.center, end: Alignment.bottomRight, ), spots: [ flSpot1, flSpot2, ], aboveBarData: barAreaData1, belowBarData: barAreaData2, barWidth: 12, curveSmoothness: 12, dotData: flDotData1, isStrokeCapRound: true, preventCurveOverShooting: true, preventCurveOvershootingThreshold: 1.2, showingIndicators: [0, 1], ); final TouchLineBarSpot lineBarSpot1 = TouchLineBarSpot( lineChartBarData1, 0, flSpot1, 0, ); final TouchLineBarSpot lineBarSpot1Clone = TouchLineBarSpot( lineChartBarData1Clone, 0, flSpot1Clone, 0, ); final TouchLineBarSpot lineBarSpot2 = TouchLineBarSpot(lineChartBarData1, 2, flSpot1, 2); final TouchLineBarSpot lineBarSpot3 = TouchLineBarSpot( lineChartBarData1, 100, flSpot1, 2, ); final lineTouchResponse1 = LineTouchResponse( touchLocation: Offset.zero, touchChartCoordinate: Offset.zero, lineBarSpots: [ lineBarSpot1, lineBarSpot2, ], ); final lineTouchResponse1Clone = LineTouchResponse( touchLocation: Offset.zero, touchChartCoordinate: Offset.zero, lineBarSpots: [ lineBarSpot1Clone, lineBarSpot2, ], ); final lineTouchResponse2 = LineTouchResponse( touchLocation: Offset.zero, touchChartCoordinate: Offset.zero, lineBarSpots: [ lineBarSpot2, lineBarSpot1, ], ); final lineTouchResponse3 = LineTouchResponse( touchLocation: Offset.zero, touchChartCoordinate: Offset.zero, lineBarSpots: [], ); final lineTouchResponse4 = LineTouchResponse( touchLocation: Offset.zero, touchChartCoordinate: Offset.zero, lineBarSpots: [ lineBarSpot1, lineBarSpot2, ], ); final lineTouchResponse5 = LineTouchResponse( touchLocation: Offset.zero, touchChartCoordinate: Offset.zero, lineBarSpots: [ lineBarSpot1, lineBarSpot2, ], ); const TouchedSpotIndicatorData touchedSpotIndicatorData1 = TouchedSpotIndicatorData( FlLine( color: Colors.red, dashArray: [], ), FlDotData( getDotPainter: getDotDrawerTouched, checkToShowDot: checkToShowDot, ), ); const TouchedSpotIndicatorData touchedSpotIndicatorData1Clone = TouchedSpotIndicatorData( FlLine( color: Colors.red, dashArray: [], ), FlDotData( getDotPainter: getDotDrawerTouched, checkToShowDot: checkToShowDot, ), ); const TouchedSpotIndicatorData touchedSpotIndicatorData2 = TouchedSpotIndicatorData( FlLine( color: Colors.red, dashArray: [], ), FlDotData( getDotPainter: getDotDrawerTouched, ), ); const TouchedSpotIndicatorData touchedSpotIndicatorData3 = TouchedSpotIndicatorData( FlLine( color: Colors.red, ), FlDotData( getDotPainter: getDotDrawerTouched, checkToShowDot: checkToShowDot, ), ); const TouchedSpotIndicatorData touchedSpotIndicatorData4 = TouchedSpotIndicatorData( FlLine( color: Colors.green, dashArray: [], ), FlDotData( getDotPainter: getDotDrawerTouched4, checkToShowDot: checkToShowDot, ), ); const TouchedSpotIndicatorData touchedSpotIndicatorData5 = TouchedSpotIndicatorData( FlLine( color: Colors.red, dashArray: [], ), FlDotData( getDotPainter: getDotDrawerTouched, checkToShowDot: checkToShowDot, show: false, ), ); const TouchedSpotIndicatorData touchedSpotIndicatorData6 = TouchedSpotIndicatorData( FlLine( color: Colors.red, dashArray: [], ), FlDotData( getDotPainter: getDotDrawerTouched6, checkToShowDot: checkToShowDot, ), ); const LineTooltipItem lineTooltipItem1 = LineTooltipItem('', TextStyle(color: Colors.green)); const LineTooltipItem lineTooltipItem1Clone = LineTooltipItem('', TextStyle(color: Colors.green)); const LineTooltipItem lineTooltipItem2 = LineTooltipItem('ss', TextStyle(color: Colors.green)); const LineTooltipItem lineTooltipItem3 = LineTooltipItem('', TextStyle(color: Colors.blue)); const LineTooltipItem lineTooltipItem4 = LineTooltipItem('', TextStyle(fontSize: 33)); List lineChartGetTooltipItems(List list) { return list.map((s) => lineTooltipItem1).toList(); } Color lineChartGetGreenColor(LineBarSpot touchedSpot) { return Colors.green; } Color lineChartGetRedColor(LineBarSpot touchedSpot) { return Colors.red; } final LineTouchTooltipData lineTouchTooltipData1 = LineTouchTooltipData( tooltipPadding: const EdgeInsets.all(0.1), getTooltipColor: lineChartGetGreenColor, maxContentWidth: 12, getTooltipItems: lineChartGetTooltipItems, fitInsideHorizontally: true, tooltipBorderRadius: BorderRadius.circular(12), tooltipMargin: 33, tooltipBorder: const BorderSide(color: Colors.red), ); final LineTouchTooltipData lineTouchTooltipData1Clone = LineTouchTooltipData( tooltipPadding: const EdgeInsets.all(0.1), getTooltipColor: lineChartGetGreenColor, maxContentWidth: 12, getTooltipItems: lineChartGetTooltipItems, fitInsideHorizontally: true, tooltipBorderRadius: BorderRadius.circular(12), tooltipMargin: 33, tooltipBorder: const BorderSide(color: Colors.red), ); final LineTouchTooltipData lineTouchTooltipData2 = LineTouchTooltipData( tooltipPadding: const EdgeInsets.all(0.1), getTooltipColor: lineChartGetRedColor, maxContentWidth: 12, getTooltipItems: lineChartGetTooltipItems, fitInsideHorizontally: true, tooltipBorderRadius: BorderRadius.circular(12), tooltipMargin: 33, tooltipBorder: const BorderSide(color: Colors.red), ); final LineTouchTooltipData lineTouchTooltipData3 = LineTouchTooltipData( tooltipPadding: const EdgeInsets.all(0.2), getTooltipColor: lineChartGetGreenColor, maxContentWidth: 12, getTooltipItems: lineChartGetTooltipItems, fitInsideHorizontally: true, tooltipBorderRadius: BorderRadius.circular(12), tooltipMargin: 33, tooltipBorder: const BorderSide(color: Colors.red), tooltipHorizontalAlignment: FLHorizontalAlignment.left, ); final LineTouchTooltipData lineTouchTooltipData4 = LineTouchTooltipData( tooltipPadding: const EdgeInsets.all(0.1), getTooltipColor: lineChartGetGreenColor, maxContentWidth: 13, getTooltipItems: lineChartGetTooltipItems, fitInsideHorizontally: true, tooltipBorderRadius: BorderRadius.circular(12), tooltipMargin: 33, tooltipBorder: const BorderSide(color: Colors.red), tooltipHorizontalAlignment: FLHorizontalAlignment.right, ); final LineTouchTooltipData lineTouchTooltipData5 = LineTouchTooltipData( tooltipPadding: const EdgeInsets.all(0.1), getTooltipColor: lineChartGetGreenColor, maxContentWidth: 12, getTooltipItems: lineChartGetTooltipItems, fitInsideHorizontally: true, tooltipBorderRadius: BorderRadius.circular(12), tooltipMargin: 34, tooltipBorder: const BorderSide(color: Colors.red), tooltipHorizontalOffset: 10, ); final LineTouchTooltipData lineTouchTooltipData6 = LineTouchTooltipData( tooltipPadding: const EdgeInsets.all(0.1), getTooltipColor: lineChartGetGreenColor, maxContentWidth: 12, getTooltipItems: lineChartGetTooltipItems, fitInsideHorizontally: true, tooltipBorderRadius: BorderRadius.circular(12), tooltipMargin: 33, tooltipBorder: const BorderSide(color: Colors.pink), tooltipHorizontalAlignment: FLHorizontalAlignment.left, tooltipHorizontalOffset: -10, ); final LineTouchTooltipData lineTouchTooltipData7 = LineTouchTooltipData( tooltipPadding: const EdgeInsets.all(0.1), getTooltipColor: lineChartGetGreenColor, maxContentWidth: 12, getTooltipItems: lineChartGetTooltipItems, fitInsideHorizontally: true, tooltipBorderRadius: BorderRadius.circular(12), tooltipMargin: 33, tooltipBorder: const BorderSide(color: Colors.red, width: 2), tooltipHorizontalAlignment: FLHorizontalAlignment.right, tooltipHorizontalOffset: 10, ); void lineTouchCallback(FlTouchEvent event, LineTouchResponse? response) {} List getTouchedSpotIndicator( LineChartBarData barData, List indexes, ) => indexes.map((i) => touchedSpotIndicatorData1).toList(); final LineTouchData lineTouchData1 = LineTouchData( touchCallback: lineTouchCallback, getTouchedSpotIndicator: getTouchedSpotIndicator, handleBuiltInTouches: false, touchSpotThreshold: 12, touchTooltipData: lineTouchTooltipData1, ); final LineTouchData lineTouchData1Clone = LineTouchData( touchCallback: lineTouchCallback, getTouchedSpotIndicator: getTouchedSpotIndicator, handleBuiltInTouches: false, touchSpotThreshold: 12, touchTooltipData: lineTouchTooltipData1, ); final LineTouchData lineTouchData2 = LineTouchData( getTouchedSpotIndicator: getTouchedSpotIndicator, handleBuiltInTouches: false, touchSpotThreshold: 12, touchTooltipData: lineTouchTooltipData1, ); final LineTouchData lineTouchData3 = LineTouchData( touchCallback: lineTouchCallback, handleBuiltInTouches: false, touchSpotThreshold: 12, touchTooltipData: lineTouchTooltipData1, ); const LineTouchData lineTouchData4 = LineTouchData( touchCallback: lineTouchCallback, getTouchedSpotIndicator: getTouchedSpotIndicator, handleBuiltInTouches: false, touchSpotThreshold: 12, ); final LineTouchData lineTouchData5 = LineTouchData( touchCallback: lineTouchCallback, getTouchedSpotIndicator: getTouchedSpotIndicator, handleBuiltInTouches: false, touchSpotThreshold: 12.001, touchTooltipData: lineTouchTooltipData1, ); final LineTouchData lineTouchData6 = LineTouchData( touchCallback: lineTouchCallback, getTouchedSpotIndicator: getTouchedSpotIndicator, touchSpotThreshold: 12, touchTooltipData: lineTouchTooltipData1, ); final LineTouchData lineTouchData7 = LineTouchData( touchCallback: lineTouchCallback, getTouchedSpotIndicator: getTouchedSpotIndicator, handleBuiltInTouches: false, touchSpotThreshold: 12, touchTooltipData: lineTouchTooltipData1, getTouchLineEnd: (barData, index) => double.infinity, ); final LineTouchData lineTouchData8 = LineTouchData( touchCallback: lineTouchCallback, getTouchedSpotIndicator: getTouchedSpotIndicator, handleBuiltInTouches: false, touchSpotThreshold: 12, touchTooltipData: lineTouchTooltipData1, longPressDuration: Duration.zero, ); String horizontalLabelResolver(HorizontalLine horizontalLine) => 'test'; String verticalLabelResolver(VerticalLine horizontalLine) => 'test'; final HorizontalLineLabel horizontalLineLabel1 = HorizontalLineLabel( show: true, style: const TextStyle(color: Colors.green), labelResolver: horizontalLabelResolver, alignment: Alignment.topCenter, padding: const EdgeInsets.all(12), direction: LabelDirection.vertical, ); final HorizontalLineLabel horizontalLineLabel1Clone = HorizontalLineLabel( show: true, style: const TextStyle(color: Colors.green), labelResolver: horizontalLabelResolver, alignment: Alignment.topCenter, padding: const EdgeInsets.all(12), direction: LabelDirection.vertical, ); final HorizontalLineLabel horizontalLineLabel2 = HorizontalLineLabel( style: const TextStyle(color: Colors.green), labelResolver: horizontalLabelResolver, alignment: Alignment.topCenter, padding: const EdgeInsets.all(12), ); final HorizontalLineLabel horizontalLineLabel3 = HorizontalLineLabel( show: true, labelResolver: horizontalLabelResolver, alignment: Alignment.topCenter, padding: const EdgeInsets.all(12), ); final HorizontalLineLabel horizontalLineLabel4 = HorizontalLineLabel( show: true, style: const TextStyle(color: Colors.green), alignment: Alignment.topCenter, padding: const EdgeInsets.all(12), ); final HorizontalLineLabel horizontalLineLabel5 = HorizontalLineLabel( show: true, style: const TextStyle(color: Colors.green), labelResolver: horizontalLabelResolver, alignment: Alignment.bottomRight, padding: const EdgeInsets.all(12), ); final HorizontalLineLabel horizontalLineLabel6 = HorizontalLineLabel( show: true, style: const TextStyle(color: Colors.green), labelResolver: horizontalLabelResolver, alignment: Alignment.topCenter, padding: const EdgeInsets.all(44), ); final HorizontalLineLabel horizontalLineLabel7 = HorizontalLineLabel( style: const TextStyle(color: Colors.green), labelResolver: horizontalLabelResolver, alignment: Alignment.topCenter, padding: const EdgeInsets.all(12), direction: LabelDirection.vertical, ); final VerticalLineLabel verticalLineLabel1 = VerticalLineLabel( show: true, style: const TextStyle(color: Colors.green), labelResolver: verticalLabelResolver, alignment: Alignment.topCenter, padding: const EdgeInsets.all(12), ); final VerticalLineLabel verticalLineLabel1Clone = VerticalLineLabel( show: true, style: const TextStyle(color: Colors.green), labelResolver: verticalLabelResolver, alignment: Alignment.topCenter, padding: const EdgeInsets.all(12), ); final VerticalLineLabel verticalLineLabel2 = VerticalLineLabel( style: const TextStyle(color: Colors.green), labelResolver: verticalLabelResolver, alignment: Alignment.topCenter, padding: const EdgeInsets.all(12), ); final VerticalLineLabel verticalLineLabel3 = VerticalLineLabel( show: true, labelResolver: verticalLabelResolver, alignment: Alignment.topCenter, padding: const EdgeInsets.all(12), ); final VerticalLineLabel verticalLineLabel4 = VerticalLineLabel( show: true, style: const TextStyle(color: Colors.green), alignment: Alignment.topCenter, padding: const EdgeInsets.all(12), ); final VerticalLineLabel verticalLineLabel5 = VerticalLineLabel( show: true, style: const TextStyle(color: Colors.green), labelResolver: verticalLabelResolver, padding: const EdgeInsets.all(12), ); final VerticalLineLabel verticalLineLabel6 = VerticalLineLabel( show: true, style: const TextStyle(color: Colors.green), labelResolver: verticalLabelResolver, alignment: Alignment.topCenter, padding: const EdgeInsets.all(44), ); final VerticalLineLabel verticalLineLabel7 = VerticalLineLabel( style: const TextStyle(color: Colors.green), labelResolver: verticalLabelResolver, alignment: Alignment.topCenter, padding: const EdgeInsets.all(12), direction: LabelDirection.vertical, ); final HorizontalLine horizontalLine1 = HorizontalLine( y: 12, color: Colors.red, dashArray: [0, 1], strokeWidth: 21, label: horizontalLineLabel1, ); final HorizontalLine horizontalLine1Clone = HorizontalLine( y: 12, color: Colors.red, dashArray: [0, 1], strokeWidth: 21, label: horizontalLineLabel1, ); final HorizontalLine horizontalLine2 = HorizontalLine( y: 12, color: Colors.red, dashArray: [0, 1, 3], strokeWidth: 21, label: horizontalLineLabel1, ); final HorizontalLine horizontalLine3 = HorizontalLine( y: 12, color: Colors.red, dashArray: [0, 1], strokeWidth: 22, label: horizontalLineLabel1, ); final HorizontalLine horizontalLine4 = HorizontalLine( y: 12, color: Colors.red, dashArray: [1, 0], strokeWidth: 21, label: horizontalLineLabel1, ); final HorizontalLine horizontalLine5 = HorizontalLine( y: 33, color: Colors.red, dashArray: [0, 1], strokeWidth: 21, label: horizontalLineLabel1, ); final HorizontalLine horizontalLine6 = HorizontalLine( y: 12, color: Colors.green, dashArray: [0, 1], strokeWidth: 21, label: horizontalLineLabel1, ); final HorizontalLine horizontalLine7 = HorizontalLine( y: 12, color: Colors.red, dashArray: [0, 1], strokeWidth: 21, label: horizontalLineLabel2, ); final HorizontalLine horizontalLine8 = HorizontalLine( y: 12, color: Colors.red, strokeWidth: 21, label: horizontalLineLabel1, ); final HorizontalLine horizontalLine9 = HorizontalLine( y: 12, color: Colors.red, dashArray: [0, 12, 44], strokeWidth: 21, label: horizontalLineLabel1, ); final VerticalLine verticalLine1 = VerticalLine( x: 12, color: Colors.red, dashArray: [0, 1], strokeWidth: 21, label: verticalLineLabel1, ); final VerticalLine verticalLine1Clone = VerticalLine( x: 12, color: Colors.red, dashArray: [0, 1], strokeWidth: 21, label: verticalLineLabel1, ); final VerticalLine verticalLine2 = VerticalLine( x: 12, color: Colors.green, dashArray: [0, 1], strokeWidth: 21, label: verticalLineLabel1, ); final VerticalLine verticalLine3 = VerticalLine( x: 12, color: Colors.red, dashArray: [0, 1], strokeWidth: 22, label: verticalLineLabel1, ); final VerticalLine verticalLine4 = VerticalLine( x: 12, color: Colors.red, dashArray: [1, 0], strokeWidth: 21, label: verticalLineLabel1, ); final VerticalLine verticalLine5 = VerticalLine( x: 33, color: Colors.red, dashArray: [0, 1], strokeWidth: 21, label: verticalLineLabel1, ); final VerticalLine verticalLine6 = VerticalLine( x: 12, color: Colors.green, dashArray: [0, 1], strokeWidth: 21, label: verticalLineLabel1, ); final VerticalLine verticalLine7 = VerticalLine( x: 12, color: Colors.red, dashArray: [0, 1], strokeWidth: 21, label: verticalLineLabel2, ); final VerticalLine verticalLine8 = VerticalLine( x: 12, color: Colors.red, strokeWidth: 21, label: verticalLineLabel1, ); final VerticalLine verticalLine9 = VerticalLine( x: 12, color: Colors.red, dashArray: [0, 12, 44], strokeWidth: 21, label: verticalLineLabel1, ); final ExtraLinesData extraLinesData1 = ExtraLinesData( horizontalLines: [ horizontalLine1, horizontalLine2, horizontalLine3, ], verticalLines: [ verticalLine1, verticalLine2, verticalLine3, ], extraLinesOnTop: false, ); final ExtraLinesData extraLinesData1Clone = ExtraLinesData( horizontalLines: [ horizontalLine1Clone, horizontalLine2, horizontalLine3, ], verticalLines: [ verticalLine1Clone, verticalLine2, verticalLine3, ], extraLinesOnTop: false, ); final ExtraLinesData extraLinesData2 = ExtraLinesData( horizontalLines: [ horizontalLine3, horizontalLine1, horizontalLine2, ], verticalLines: [ verticalLine3, verticalLine1, verticalLine2, ], extraLinesOnTop: false, ); final ExtraLinesData extraLinesData3 = ExtraLinesData( horizontalLines: [ horizontalLine1, horizontalLine2, ], verticalLines: [ verticalLine1, verticalLine2, ], extraLinesOnTop: false, ); final ExtraLinesData extraLinesData4 = ExtraLinesData( horizontalLines: [ horizontalLine1, horizontalLine2, horizontalLine3, ], extraLinesOnTop: false, ); final ExtraLinesData extraLinesData5 = ExtraLinesData( verticalLines: [ verticalLine1, verticalLine2, verticalLine3, ], extraLinesOnTop: false, ); final ExtraLinesData extraLinesData6 = ExtraLinesData( horizontalLines: [ horizontalLine1, horizontalLine2, horizontalLine3, ], verticalLines: [ verticalLine1, verticalLine2, verticalLine3, ], ); final SizedPicture sizedPicture1 = SizedPicture( PictureRecorder().endRecording(), 10, 30, ); final SizedPicture sizedPicture1Clone = SizedPicture( PictureRecorder().endRecording(), 10, 30, ); final SizedPicture sizedPicture2 = SizedPicture( PictureRecorder().endRecording(), 11, 30, ); final SizedPicture sizedPicture3 = SizedPicture( PictureRecorder().endRecording(), 10, 32, ); final SizedPicture sizedPicture4 = SizedPicture( PictureRecorder().endRecording(), 442, 30, ); final BetweenBarsData betweenBarsData1 = BetweenBarsData( fromIndex: 1, toIndex: 2, gradient: const LinearGradient( begin: Alignment(1, 3), end: Alignment(4, 1), stops: [1, 2, 3], colors: [Colors.green, Colors.blue, Colors.red], ), ); final BetweenBarsData betweenBarsData1Clone = BetweenBarsData( fromIndex: 1, toIndex: 2, gradient: const LinearGradient( begin: Alignment(1, 3), end: Alignment(4, 1), stops: [1, 2, 3], colors: [Colors.green, Colors.blue, Colors.red], ), ); final BetweenBarsData betweenBarsData2 = BetweenBarsData( fromIndex: 2, toIndex: 2, gradient: const LinearGradient( begin: Alignment(1, 3), end: Alignment(4, 1), stops: [1, 2, 3], colors: [Colors.green, Colors.blue, Colors.red], ), ); final BetweenBarsData betweenBarsData3 = BetweenBarsData( fromIndex: 1, toIndex: 1, gradient: const LinearGradient( begin: Alignment(1, 4), end: Alignment(4, 1), stops: [1, 2, 3], colors: [Colors.green, Colors.blue, Colors.red], ), ); final BetweenBarsData betweenBarsData4 = BetweenBarsData( fromIndex: 1, toIndex: 2, gradient: const LinearGradient( begin: Alignment(1, 3), end: Alignment(5, 1), stops: [1, 2, 3], colors: [Colors.green, Colors.blue, Colors.red], ), ); final BetweenBarsData betweenBarsData5 = BetweenBarsData( fromIndex: 1, toIndex: 2, gradient: const LinearGradient( begin: Alignment(1, 3), end: Alignment(4, 1), colors: [Colors.green, Colors.blue, Colors.red], ), ); final BetweenBarsData betweenBarsData6 = BetweenBarsData( fromIndex: 1, toIndex: 2, gradient: const LinearGradient( begin: Alignment(1, 3), end: Alignment(4, 1), stops: [1, 2, 3], colors: [Colors.green, Colors.blue], ), ); final BetweenBarsData betweenBarsData7 = BetweenBarsData( fromIndex: 1, toIndex: 2, gradient: const LinearGradient( begin: Alignment(1, 22), end: Alignment(4, 1), stops: [1, 2, 3], colors: [Colors.green, Colors.blue], ), ); final BetweenBarsData betweenBarsData8 = BetweenBarsData( fromIndex: 1, toIndex: 2, gradient: const LinearGradient( begin: Alignment(1, 3), end: Alignment(4, 1), stops: [1, 2, 3], colors: [], ), ); final ShowingTooltipIndicators showingTooltipIndicator1 = ShowingTooltipIndicators( [lineBarSpot1, lineBarSpot2], ); final ShowingTooltipIndicators showingTooltipIndicator1Clone = ShowingTooltipIndicators( [lineBarSpot1, lineBarSpot2], ); const ShowingTooltipIndicators showingTooltipIndicator2 = ShowingTooltipIndicators( [], ); final ShowingTooltipIndicators showingTooltipIndicator3 = ShowingTooltipIndicators( [lineBarSpot2], ); final ShowingTooltipIndicators showingTooltipIndicator4 = ShowingTooltipIndicators( [lineBarSpot2, lineBarSpot1], ); final ShowingTooltipIndicators showingTooltipIndicator5 = ShowingTooltipIndicators( [lineBarSpot1, lineBarSpot2, lineBarSpot2], ); final LineChartData lineChartData1 = LineChartData( borderData: borderData1, lineTouchData: lineTouchData1, showingTooltipIndicators: [ showingTooltipIndicator1, showingTooltipIndicator2, ], backgroundColor: Colors.red, maxY: 23, rangeAnnotations: rangeAnnotations1, gridData: flGridData1, titlesData: MockData.flTitlesData1, lineBarsData: [lineChartBarData1, lineChartBarData2, lineChartBarData3], betweenBarsData: [betweenBarsData1, betweenBarsData2, betweenBarsData3], extraLinesData: extraLinesData1, maxX: 23, minX: 11, minY: 43, ); final LineChartData lineChartData1Clone = LineChartData( borderData: borderData1Clone, lineTouchData: lineTouchData1Clone, showingTooltipIndicators: [ showingTooltipIndicator1Clone, showingTooltipIndicator2, ], backgroundColor: Colors.red, maxY: 23, rangeAnnotations: rangeAnnotations1, gridData: flGridData1Clone, titlesData: MockData.flTitlesData1Clone, lineBarsData: [lineChartBarData1Clone, lineChartBarData2, lineChartBarData3], betweenBarsData: [betweenBarsData1Clone, betweenBarsData2, betweenBarsData3], extraLinesData: extraLinesData1Clone, maxX: 23, minX: 11, minY: 43, ); final LineChartData lineChartData2 = LineChartData( lineTouchData: lineTouchData1, showingTooltipIndicators: [ showingTooltipIndicator1, showingTooltipIndicator2, ], backgroundColor: Colors.red, maxY: 23, rangeAnnotations: rangeAnnotations1, gridData: flGridData1, titlesData: MockData.flTitlesData1, lineBarsData: [lineChartBarData1, lineChartBarData2, lineChartBarData3], betweenBarsData: [betweenBarsData1, betweenBarsData2, betweenBarsData3], extraLinesData: extraLinesData1, maxX: 23, minX: 11, minY: 43, ); final LineChartData lineChartData3 = LineChartData( borderData: borderData1, lineTouchData: lineTouchData2, showingTooltipIndicators: [ showingTooltipIndicator1, showingTooltipIndicator2, ], backgroundColor: Colors.red, maxY: 23, rangeAnnotations: rangeAnnotations1, gridData: flGridData1, titlesData: MockData.flTitlesData1, lineBarsData: [lineChartBarData1, lineChartBarData2, lineChartBarData3], betweenBarsData: [betweenBarsData1, betweenBarsData2, betweenBarsData3], extraLinesData: extraLinesData1, maxX: 23, minX: 11, minY: 43, ); final LineChartData lineChartData4 = LineChartData( borderData: borderData1, lineTouchData: lineTouchData1, showingTooltipIndicators: [], backgroundColor: Colors.red, maxY: 23, rangeAnnotations: rangeAnnotations1, gridData: flGridData1, titlesData: MockData.flTitlesData1, lineBarsData: [lineChartBarData1, lineChartBarData2, lineChartBarData3], betweenBarsData: [betweenBarsData1, betweenBarsData2, betweenBarsData3], extraLinesData: extraLinesData1, maxX: 23, minX: 11, minY: 43, ); final LineChartData lineChartData5 = LineChartData( borderData: borderData1, lineTouchData: lineTouchData1, showingTooltipIndicators: [ showingTooltipIndicator2, showingTooltipIndicator1, ], backgroundColor: Colors.red, maxY: 23, rangeAnnotations: rangeAnnotations1, gridData: flGridData1, titlesData: MockData.flTitlesData1, lineBarsData: [lineChartBarData1, lineChartBarData2, lineChartBarData3], betweenBarsData: [betweenBarsData1, betweenBarsData2, betweenBarsData3], extraLinesData: extraLinesData1, maxX: 23, minX: 11, minY: 43, ); final LineChartData lineChartData6 = LineChartData( borderData: borderData1, lineTouchData: lineTouchData1, backgroundColor: Colors.red, maxY: 23, rangeAnnotations: rangeAnnotations1, gridData: flGridData1, titlesData: MockData.flTitlesData1, lineBarsData: [lineChartBarData1, lineChartBarData2, lineChartBarData3], betweenBarsData: [betweenBarsData1, betweenBarsData2, betweenBarsData3], extraLinesData: extraLinesData1, maxX: 23, minX: 11, minY: 43, ); final LineChartData lineChartData7 = LineChartData( borderData: borderData1, lineTouchData: lineTouchData1, showingTooltipIndicators: [ showingTooltipIndicator1, showingTooltipIndicator2, ], backgroundColor: Colors.red, maxY: 23, rangeAnnotations: rangeAnnotations1, gridData: flGridData1, titlesData: MockData.flTitlesData2, lineBarsData: [lineChartBarData1, lineChartBarData2, lineChartBarData3], betweenBarsData: [betweenBarsData1, betweenBarsData2, betweenBarsData3], extraLinesData: extraLinesData1, maxX: 23, minX: 11, minY: 43, ); final LineChartData lineChartData8 = LineChartData( borderData: borderData1, lineTouchData: lineTouchData1, showingTooltipIndicators: [ showingTooltipIndicator1, showingTooltipIndicator2, ], clipData: const FlClipData.all(), backgroundColor: Colors.red, maxY: 23, rangeAnnotations: rangeAnnotations1, gridData: flGridData1, titlesData: MockData.flTitlesData1, lineBarsData: [lineChartBarData1, lineChartBarData2, lineChartBarData3], betweenBarsData: [betweenBarsData1, betweenBarsData2, betweenBarsData3], extraLinesData: extraLinesData1, maxX: 23, minX: 11, minY: 43, ); final LineChartData lineChartData9 = LineChartData( borderData: borderData1, lineTouchData: lineTouchData1, showingTooltipIndicators: [ showingTooltipIndicator1, showingTooltipIndicator2, ], backgroundColor: Colors.red.withValues(alpha: 0.2), maxY: 23, rangeAnnotations: rangeAnnotations1, gridData: flGridData1, titlesData: MockData.flTitlesData1, lineBarsData: [lineChartBarData1, lineChartBarData2, lineChartBarData3], betweenBarsData: [betweenBarsData1, betweenBarsData2, betweenBarsData3], extraLinesData: extraLinesData1, maxX: 23, minX: 11, minY: 43, ); final LineChartData lineChartData10 = LineChartData( borderData: borderData1, lineTouchData: lineTouchData1, showingTooltipIndicators: [ showingTooltipIndicator1, showingTooltipIndicator2, ], backgroundColor: Colors.red, maxY: 24, rangeAnnotations: rangeAnnotations1, gridData: flGridData1, titlesData: MockData.flTitlesData1, lineBarsData: [lineChartBarData1, lineChartBarData2, lineChartBarData3], betweenBarsData: [betweenBarsData1, betweenBarsData2, betweenBarsData3], extraLinesData: extraLinesData1, maxX: 23, minX: 11, minY: 43, ); final LineChartData lineChartData11 = LineChartData( borderData: borderData1, lineTouchData: lineTouchData1, showingTooltipIndicators: [ showingTooltipIndicator1, showingTooltipIndicator2, ], backgroundColor: Colors.red, maxY: 23, gridData: flGridData1, titlesData: MockData.flTitlesData1, lineBarsData: [lineChartBarData1, lineChartBarData2, lineChartBarData3], betweenBarsData: [betweenBarsData1, betweenBarsData2, betweenBarsData3], extraLinesData: extraLinesData1, maxX: 23, minX: 11, minY: 43, ); final LineChartData lineChartData12 = LineChartData( borderData: borderData1, lineTouchData: lineTouchData1, showingTooltipIndicators: [ showingTooltipIndicator1, showingTooltipIndicator2, ], backgroundColor: Colors.red, maxY: 23, rangeAnnotations: rangeAnnotations1, gridData: flGridData2, titlesData: MockData.flTitlesData1, lineBarsData: [lineChartBarData1, lineChartBarData2, lineChartBarData3], betweenBarsData: [betweenBarsData1, betweenBarsData2, betweenBarsData3], extraLinesData: extraLinesData1, maxX: 23, minX: 11, minY: 43, ); final LineChartData lineChartData13 = LineChartData( borderData: borderData1, lineTouchData: lineTouchData1, showingTooltipIndicators: [ showingTooltipIndicator1, showingTooltipIndicator2, ], backgroundColor: Colors.red, maxY: 23, rangeAnnotations: rangeAnnotations1, gridData: flGridData1, titlesData: MockData.flTitlesData3, lineBarsData: [lineChartBarData1, lineChartBarData2, lineChartBarData3], betweenBarsData: [betweenBarsData1, betweenBarsData2, betweenBarsData3], extraLinesData: extraLinesData1, maxX: 23, minX: 11, minY: 43, ); final LineChartData lineChartData14 = LineChartData( borderData: borderData1, lineTouchData: lineTouchData1, showingTooltipIndicators: [ showingTooltipIndicator1, showingTooltipIndicator2, ], backgroundColor: Colors.red, maxY: 23, rangeAnnotations: rangeAnnotations1, gridData: flGridData1, titlesData: MockData.flTitlesData1, lineBarsData: [lineChartBarData2, lineChartBarData3, lineChartBarData1], betweenBarsData: [betweenBarsData1, betweenBarsData2, betweenBarsData3], extraLinesData: extraLinesData1, maxX: 23, minX: 11, minY: 43, ); final LineChartData lineChartData15 = LineChartData( borderData: borderData1, lineTouchData: lineTouchData1, showingTooltipIndicators: [ showingTooltipIndicator1, showingTooltipIndicator2, ], backgroundColor: Colors.red, maxY: 23, rangeAnnotations: rangeAnnotations1, gridData: flGridData1, titlesData: MockData.flTitlesData1, betweenBarsData: [betweenBarsData1, betweenBarsData2, betweenBarsData3], extraLinesData: extraLinesData1, maxX: 23, minX: 11, minY: 43, ); final LineChartData lineChartData16 = LineChartData( borderData: borderData1, lineTouchData: lineTouchData1, showingTooltipIndicators: [ showingTooltipIndicator1, showingTooltipIndicator2, ], backgroundColor: Colors.red, maxY: 23, rangeAnnotations: rangeAnnotations1, gridData: flGridData1, titlesData: MockData.flTitlesData1, lineBarsData: [lineChartBarData1, lineChartBarData2, lineChartBarData3], betweenBarsData: [betweenBarsData2, betweenBarsData3, betweenBarsData1], extraLinesData: extraLinesData1, maxX: 23, minX: 11, minY: 43, ); final LineChartData lineChartData17 = LineChartData( borderData: borderData1, lineTouchData: lineTouchData1, showingTooltipIndicators: [ showingTooltipIndicator1, showingTooltipIndicator2, ], backgroundColor: Colors.red, maxY: 23, rangeAnnotations: rangeAnnotations1, gridData: flGridData1, titlesData: MockData.flTitlesData1, lineBarsData: [lineChartBarData1, lineChartBarData2, lineChartBarData3], betweenBarsData: [betweenBarsData1, betweenBarsData2, betweenBarsData3], extraLinesData: extraLinesData2, maxX: 23, minX: 11, minY: 43, ); final LineChartData lineChartData18 = LineChartData( borderData: borderData1, lineTouchData: lineTouchData1, showingTooltipIndicators: [ showingTooltipIndicator1, showingTooltipIndicator2, ], backgroundColor: Colors.red, maxY: 23, rangeAnnotations: rangeAnnotations1, gridData: flGridData1, titlesData: MockData.flTitlesData1, lineBarsData: [lineChartBarData1, lineChartBarData2, lineChartBarData3], betweenBarsData: [betweenBarsData1, betweenBarsData2, betweenBarsData3], extraLinesData: extraLinesData1, maxX: 23.01, minX: 11, minY: 43, ); final LineChartData lineChartData19 = LineChartData( borderData: borderData1, lineTouchData: lineTouchData1, showingTooltipIndicators: [ showingTooltipIndicator1, showingTooltipIndicator2, ], backgroundColor: Colors.red, maxY: 23, rangeAnnotations: rangeAnnotations1, gridData: flGridData1, titlesData: MockData.flTitlesData1, lineBarsData: [lineChartBarData1, lineChartBarData2, lineChartBarData3], betweenBarsData: [betweenBarsData1, betweenBarsData2, betweenBarsData3], extraLinesData: extraLinesData1, maxX: 23, minX: 44, minY: 43, ); final LineChartData lineChartData20 = LineChartData( borderData: borderData1, lineTouchData: lineTouchData1, showingTooltipIndicators: [ showingTooltipIndicator1, showingTooltipIndicator2, ], backgroundColor: Colors.red, maxY: 23, rangeAnnotations: rangeAnnotations1, gridData: flGridData1, titlesData: MockData.flTitlesData1, lineBarsData: [lineChartBarData1, lineChartBarData2, lineChartBarData3], betweenBarsData: [betweenBarsData1, betweenBarsData2, betweenBarsData3], extraLinesData: extraLinesData1, maxX: 23, minX: 11, minY: 302, ); final LineChartData lineChartData21 = LineChartData( borderData: borderData1, rotationQuarterTurns: 1, lineTouchData: lineTouchData1, showingTooltipIndicators: [ showingTooltipIndicator1, showingTooltipIndicator2, ], backgroundColor: Colors.red, maxY: 23, rangeAnnotations: rangeAnnotations1, gridData: flGridData1, titlesData: MockData.flTitlesData1, lineBarsData: [lineChartBarData1, lineChartBarData2, lineChartBarData3], betweenBarsData: [betweenBarsData1, betweenBarsData2, betweenBarsData3], extraLinesData: extraLinesData1, maxX: 23, minX: 11, minY: 302, ); final PieChartData pieChartData1 = PieChartData( borderData: FlBorderData(show: false, border: Border.all()), startDegreeOffset: 0, sections: [ PieChartSectionData(value: 12, color: Colors.red), PieChartSectionData(value: 22, color: Colors.green), ], centerSpaceColor: Colors.white, centerSpaceRadius: 12, pieTouchData: PieTouchData( enabled: false, ), sectionsSpace: 44, titleSunbeamLayout: false, ); final PieChartData pieChartData1Clone = pieChartData1.copyWith(); bool gridCheckToShowLine(double value) => true; FlLine gridGetDrawingLine(double value) => const FlLine(); ScatterTooltipItem? scatterChartGetTooltipItems(ScatterSpot spots) { return ScatterTooltipItem( 'check', textStyle: const TextStyle(color: Colors.blue), bottomMargin: 23, ); } Color scatterChartGetTooltipGreenColor(ScatterSpot spots) { return Colors.green; //Color } Color scatterChartGetTooltipRedColor(ScatterSpot spots) { return Colors.red; //Color } final ScatterSpot scatterSpot1 = ScatterSpot(1, 40); final ScatterSpot scatterSpot1Clone = ScatterSpot(1, 40); final ScatterSpot scatterSpot2 = ScatterSpot(-4, -8); final ScatterSpot scatterSpot2Clone = scatterSpot2.copyWith(); final ScatterSpot scatterSpot3 = ScatterSpot(-14, 5); final ScatterSpot scatterSpot4 = ScatterSpot(-0, 0); String getLabel(int spotIndex, ScatterSpot spot) => 'label'; TextStyle getLabelTextStyle(int spotIndex, ScatterSpot spot) => const TextStyle(color: Colors.green); final ScatterChartData scatterChartData1 = ScatterChartData( minY: 0, maxY: 12, maxX: 22, minX: 11, gridData: const FlGridData( show: false, getDrawingHorizontalLine: gridGetDrawingLine, getDrawingVerticalLine: gridGetDrawingLine, checkToShowHorizontalLine: gridCheckToShowLine, checkToShowVerticalLine: gridCheckToShowLine, drawVerticalLine: false, horizontalInterval: 33, verticalInterval: 1, ), backgroundColor: Colors.black, clipData: const FlClipData.none(), borderData: FlBorderData( show: true, border: Border.all( color: Colors.white, ), ), scatterSpots: [ ScatterSpot( 0, 0, show: false, dotPainter: FlDotCirclePainter(radius: 33, color: Colors.yellow), ), ScatterSpot( 2, 2, show: false, renderPriority: 10, dotPainter: FlDotCirclePainter(radius: 11, color: Colors.purple), ), ScatterSpot( 1, 2, show: false, renderPriority: -1, dotPainter: FlDotCirclePainter(radius: 11, color: Colors.white), ), ], scatterTouchData: ScatterTouchData( enabled: true, touchTooltipData: ScatterTouchTooltipData( getTooltipItems: scatterChartGetTooltipItems, fitInsideHorizontally: true, fitInsideVertically: false, maxContentWidth: 33, getTooltipColor: (touchedSpot) => Colors.white, tooltipPadding: const EdgeInsets.all(23), tooltipBorderRadius: BorderRadius.circular(534), ), handleBuiltInTouches: false, touchCallback: scatterTouchCallback, touchSpotThreshold: 12, ), showingTooltipIndicators: [0, 1, 2], titlesData: const FlTitlesData( leftTitles: AxisTitles( axisNameSize: 33, axisNameWidget: MockData.widget1, ), rightTitles: AxisTitles( axisNameSize: 1326, axisNameWidget: MockData.widget3, sideTitles: SideTitles(reservedSize: 500, showTitles: true), ), topTitles: AxisTitles( axisNameSize: 34, axisNameWidget: MockData.widget4, ), bottomTitles: AxisTitles( axisNameSize: 22, axisNameWidget: MockData.widget2, ), ), scatterLabelSettings: ScatterLabelSettings( showLabel: true, getLabelTextStyleFunction: getLabelTextStyle, getLabelFunction: getLabel, ), ); final ScatterChartData scatterChartData1Clone = scatterChartData1.copyWith(); final scatterTouchTooltipData1 = ScatterTouchTooltipData( tooltipBorderRadius: BorderRadius.circular(23), tooltipPadding: const EdgeInsets.all(11), getTooltipColor: scatterChartGetTooltipGreenColor, maxContentWidth: 33, fitInsideVertically: true, fitInsideHorizontally: false, getTooltipItems: scatterChartGetTooltipItems, tooltipBorder: const BorderSide(color: Colors.red), ); final scatterTouchTooltipData1Clone = ScatterTouchTooltipData( tooltipBorderRadius: BorderRadius.circular(23), tooltipPadding: const EdgeInsets.all(11), getTooltipColor: scatterChartGetTooltipGreenColor, maxContentWidth: 33, fitInsideVertically: true, fitInsideHorizontally: false, getTooltipItems: scatterChartGetTooltipItems, tooltipBorder: const BorderSide(color: Colors.red), ); final scatterTouchTooltipData2 = ScatterTouchTooltipData( tooltipBorderRadius: BorderRadius.circular(23), tooltipPadding: const EdgeInsets.all(11), getTooltipColor: scatterChartGetTooltipGreenColor, maxContentWidth: 33, fitInsideVertically: true, fitInsideHorizontally: false, getTooltipItems: scatterChartGetTooltipItems, tooltipBorder: const BorderSide(color: Colors.blue), tooltipHorizontalAlignment: FLHorizontalAlignment.left, ); final scatterTouchTooltipData3 = ScatterTouchTooltipData( tooltipBorderRadius: BorderRadius.circular(23), tooltipPadding: const EdgeInsets.all(11), getTooltipColor: scatterChartGetTooltipGreenColor, maxContentWidth: 33, fitInsideVertically: true, fitInsideHorizontally: false, getTooltipItems: scatterChartGetTooltipItems, tooltipBorder: const BorderSide(color: Colors.red, width: 2), tooltipHorizontalAlignment: FLHorizontalAlignment.right, tooltipHorizontalOffset: 10, ); final BarChartRodStackItem barChartRodStackItem1 = BarChartRodStackItem( 1, 2, Colors.green, label: 'Test Label', labelStyle: const TextStyle( color: Colors.white, fontSize: 14, fontWeight: FontWeight.bold, ), ); final BarChartRodStackItem barChartRodStackItem1Clone = barChartRodStackItem1.copyWith(); final BarChartRodStackItem barChartRodStackItem2 = BarChartRodStackItem( 2, 3, Colors.green, ); final BarChartRodStackItem barChartRodStackItem3 = BarChartRodStackItem( 3, 4, Colors.green, label: 'This is a very long label that might not fit', labelStyle: const TextStyle(fontSize: 12), ); final BarChartRodStackItem barChartRodStackItem4 = BarChartRodStackItem( 8, 10, Colors.purple, label: 'Label with emojis 🎯 & symbols!', labelStyle: const TextStyle(fontWeight: FontWeight.bold), ); final BackgroundBarChartRodData backgroundBarChartRodData1 = BackgroundBarChartRodData( toY: 21, color: Colors.blue, show: true, ); final BackgroundBarChartRodData backgroundBarChartRodData1Clone = BackgroundBarChartRodData( toY: 21, color: Colors.blue, show: true, ); final BackgroundBarChartRodData backgroundBarChartRodData2 = BackgroundBarChartRodData( toY: 44, color: Colors.red, show: true, ); final BackgroundBarChartRodData backgroundBarChartRodData3 = BackgroundBarChartRodData( toY: 44, color: Colors.green, show: true, ); final BarChartRodData barChartRodData1 = BarChartRodData( color: Colors.red, toY: 12, width: 32, borderRadius: const BorderRadius.all(Radius.circular(12)), rodStackItems: [ barChartRodStackItem1, barChartRodStackItem2, barChartRodStackItem3, barChartRodStackItem4, ], backDrawRodData: backgroundBarChartRodData1, ); final BarChartRodData barChartRodData1Clone = barChartRodData1.copyWith( rodStackItems: [ barChartRodStackItem1Clone, barChartRodStackItem2, barChartRodStackItem3, barChartRodStackItem4, ], ); final BarChartRodData barChartRodData2 = BarChartRodData( color: Colors.red, toY: 1132, width: 32, borderRadius: const BorderRadius.all(Radius.circular(12)), rodStackItems: [ barChartRodStackItem1, barChartRodStackItem2, barChartRodStackItem3, barChartRodStackItem4, ], backDrawRodData: backgroundBarChartRodData1, ); final BarChartRodData barChartRodData3 = BarChartRodData( color: Colors.green, toY: 12, width: 32, borderRadius: const BorderRadius.all(Radius.circular(12)), rodStackItems: [ barChartRodStackItem2, ], backDrawRodData: backgroundBarChartRodData1, ); final BarChartRodData barChartRodData4 = BarChartRodData( color: Colors.red, toY: 12, width: 32, borderRadius: const BorderRadius.all(Radius.circular(12)), rodStackItems: [ barChartRodStackItem2, barChartRodStackItem1, barChartRodStackItem4, barChartRodStackItem3, ], backDrawRodData: backgroundBarChartRodData1, ); final BarChartRodData barChartRodData5 = BarChartRodData( color: Colors.red, toY: 12, width: 55, borderRadius: const BorderRadius.all(Radius.circular(12)), rodStackItems: [ barChartRodStackItem1, barChartRodStackItem2, barChartRodStackItem4, barChartRodStackItem3, ], backDrawRodData: backgroundBarChartRodData1, ); final BarChartRodData barChartRodData6 = BarChartRodData( color: Colors.red, toY: 12, width: 32, borderRadius: const BorderRadius.all(Radius.circular(12)), backDrawRodData: backgroundBarChartRodData1, ); final BarChartRodData barChartRodData7 = BarChartRodData( color: Colors.red, toY: 12, width: 32, borderRadius: const BorderRadius.all(Radius.circular(12)), rodStackItems: [ barChartRodStackItem1, barChartRodStackItem2, barChartRodStackItem4, barChartRodStackItem3, ], backDrawRodData: backgroundBarChartRodData2, ); final BarChartRodData barChartRodData8 = BarChartRodData( color: Colors.red, toY: 12, width: 32, borderRadius: const BorderRadius.all(Radius.circular(14)), rodStackItems: [ barChartRodStackItem1, barChartRodStackItem2, barChartRodStackItem4, barChartRodStackItem3, ], backDrawRodData: backgroundBarChartRodData1, ); final BarChartGroupData barChartGroupData1 = BarChartGroupData( x: 12, showingTooltipIndicators: [0, 1, 2], barRods: [ barChartRodData1, barChartRodData2, barChartRodData3, barChartRodData4, ], barsSpace: 23, ); final BarChartGroupData barChartGroupData1Clone = BarChartGroupData( x: 12, showingTooltipIndicators: [0, 1, 2], barRods: [ barChartRodData1Clone, barChartRodData2, barChartRodData3, barChartRodData4, ], barsSpace: 23, ); final BarChartGroupData barChartGroupData2 = BarChartGroupData( x: 13, showingTooltipIndicators: [0, 1, 2], barRods: [ barChartRodData1, barChartRodData2, barChartRodData3, barChartRodData4, ], barsSpace: 23, ); final BarChartGroupData barChartGroupData3 = BarChartGroupData( x: 12, showingTooltipIndicators: [0, 1], barRods: [ barChartRodData1, barChartRodData2, barChartRodData3, barChartRodData4, ], barsSpace: 23, ); final BarChartGroupData barChartGroupData4 = BarChartGroupData( x: 12, showingTooltipIndicators: [0, 1, 2], barRods: [ barChartRodData1, barChartRodData2, barChartRodData4, ], barsSpace: 23, ); final BarChartGroupData barChartGroupData5 = BarChartGroupData( x: 12, showingTooltipIndicators: [0, 1, 2], barsSpace: 23, ); final BarChartGroupData barChartGroupData6 = BarChartGroupData( x: 12, showingTooltipIndicators: [0, 1, 2], barRods: [ barChartRodData2, barChartRodData3, barChartRodData4, barChartRodData1, ], barsSpace: 23, ); final BarChartGroupData barChartGroupData7 = BarChartGroupData( x: 12, showingTooltipIndicators: [0, 1, 2], barRods: [ barChartRodData1, barChartRodData2, barChartRodData3, barChartRodData4, ], barsSpace: 44, ); final BarChartGroupData barChartGroupData8 = BarChartGroupData( x: 12, barRods: [ barChartRodData1, barChartRodData2, barChartRodData3, barChartRodData4, ], barsSpace: 23, ); final BarChartGroupData barChartGroupData9 = BarChartGroupData( x: 12, showingTooltipIndicators: [0, 1, 2], barRods: [ barChartRodData1, barChartRodData2, barChartRodData3, barChartRodData4, ], barsSpace: 0, ); final BarTouchedSpot barTouchedSpot1 = BarTouchedSpot( barChartGroupData1, 1, barChartRodData1, 2, barChartRodStackItem1, 1, flSpot1, Offset.zero, ); final BarTouchedSpot barTouchedSpot1Clone = BarTouchedSpot( barChartGroupData1Clone, 1, barChartRodData1Clone, 2, barChartRodStackItem1Clone, 1, flSpot1Clone, Offset.zero, ); final BarTouchedSpot barTouchedSpot2 = BarTouchedSpot( barChartGroupData2, 1, barChartRodData1, 2, barChartRodStackItem2, 2, flSpot1, Offset.zero, ); final BarTouchedSpot barTouchedSpot3 = BarTouchedSpot( barChartGroupData1, 1, barChartRodData2, 2, barChartRodStackItem2, 2, flSpot1, Offset.zero, ); final BarTouchedSpot barTouchedSpot4 = BarTouchedSpot( barChartGroupData1, 2, barChartRodData1, 2, barChartRodStackItem2, 2, flSpot1, Offset.zero, ); final BarTouchedSpot barTouchedSpot5 = BarTouchedSpot( barChartGroupData1, 1, barChartRodData1, 3, barChartRodStackItem2, 2, flSpot1, Offset.zero, ); final BarTouchedSpot barTouchedSpot6 = BarTouchedSpot( barChartGroupData1, 1, barChartRodData1, 2, barChartRodStackItem2, 2, flSpot2, Offset.zero, ); final BarTouchedSpot barTouchedSpot7 = BarTouchedSpot( barChartGroupData1, 1, barChartRodData1, 2, barChartRodStackItem2, 2, flSpot1, const Offset(1, 10), ); final barTouchResponse1 = BarTouchResponse( touchLocation: Offset.zero, touchChartCoordinate: Offset.zero, spot: barTouchedSpot1, ); final barTouchResponse1Clone = BarTouchResponse( touchLocation: Offset.zero, touchChartCoordinate: Offset.zero, spot: barTouchedSpot1Clone, ); final barTouchResponse2 = BarTouchResponse( touchLocation: Offset.zero, touchChartCoordinate: Offset.zero, spot: barTouchedSpot2, ); final BarTooltipItem barTooltipItem1 = BarTooltipItem( 'pashmam 1', const TextStyle(color: Colors.red), ); final BarTooltipItem barTooltipItem1Clone = BarTooltipItem( 'pashmam 1', const TextStyle(color: Colors.red), ); final BarTooltipItem barTooltipItem2 = BarTooltipItem( 'pashmam 2', const TextStyle(color: Colors.red), ); final BarTooltipItem barTooltipItem3 = BarTooltipItem( 'pashmam 1', const TextStyle(color: Colors.green), ); final BarTooltipItem barTooltipItem4 = BarTooltipItem( 'null', const TextStyle(color: Colors.red), ); final BarTooltipItem barTooltipItem5 = BarTooltipItem( 'pashmam 1', const TextStyle(fontSize: 85), ); BarTooltipItem getTooltipItem( BarChartGroupData group, int groupIndex, BarChartRodData rod, int rodIndex, ) { const textStyle = TextStyle( color: Colors.black, fontWeight: FontWeight.bold, fontSize: 14, ); return BarTooltipItem(rod.toY.toString(), textStyle); } Color getTooltipGreenColor( BarChartGroupData group, ) { return Colors.green; } Color getTooltipBlueColor( BarChartGroupData group, ) { return Colors.blue; } final BarTouchTooltipData barTouchTooltipData1 = BarTouchTooltipData( tooltipBorderRadius: BorderRadius.circular(12), fitInsideVertically: false, fitInsideHorizontally: true, maxContentWidth: 23, getTooltipColor: getTooltipGreenColor, tooltipPadding: const EdgeInsets.all(23), getTooltipItem: getTooltipItem, tooltipMargin: 12, tooltipBorder: const BorderSide(color: Colors.red), ); final BarTouchTooltipData barTouchTooltipData1Clone = BarTouchTooltipData( tooltipBorderRadius: BorderRadius.circular(12), fitInsideVertically: false, fitInsideHorizontally: true, maxContentWidth: 23, getTooltipColor: getTooltipGreenColor, tooltipPadding: const EdgeInsets.all(23), getTooltipItem: getTooltipItem, tooltipMargin: 12, tooltipBorder: const BorderSide(color: Colors.red), ); final BarTouchTooltipData barTouchTooltipData2 = BarTouchTooltipData( tooltipBorderRadius: BorderRadius.circular(13), fitInsideVertically: false, fitInsideHorizontally: true, maxContentWidth: 23, getTooltipColor: getTooltipGreenColor, tooltipPadding: const EdgeInsets.all(23), getTooltipItem: getTooltipItem, tooltipMargin: 12, tooltipBorder: const BorderSide(color: Colors.red), tooltipHorizontalAlignment: FLHorizontalAlignment.center, ); final BarTouchTooltipData barTouchTooltipData3 = BarTouchTooltipData( tooltipBorderRadius: BorderRadius.circular(12), fitInsideVertically: true, fitInsideHorizontally: true, maxContentWidth: 23, getTooltipColor: getTooltipGreenColor, tooltipPadding: const EdgeInsets.all(23), getTooltipItem: getTooltipItem, tooltipMargin: 12, tooltipBorder: const BorderSide(color: Colors.red), tooltipHorizontalAlignment: FLHorizontalAlignment.left, ); final BarTouchTooltipData barTouchTooltipData4 = BarTouchTooltipData( tooltipBorderRadius: BorderRadius.circular(12), fitInsideVertically: false, fitInsideHorizontally: false, maxContentWidth: 23, getTooltipColor: getTooltipGreenColor, tooltipPadding: const EdgeInsets.all(23), getTooltipItem: getTooltipItem, tooltipMargin: 12, tooltipBorder: const BorderSide(color: Colors.red), tooltipHorizontalAlignment: FLHorizontalAlignment.right, ); final BarTouchTooltipData barTouchTooltipData5 = BarTouchTooltipData( tooltipBorderRadius: BorderRadius.circular(12), fitInsideVertically: false, fitInsideHorizontally: true, maxContentWidth: 23.00001, getTooltipColor: getTooltipGreenColor, tooltipPadding: const EdgeInsets.all(23), getTooltipItem: getTooltipItem, tooltipMargin: 12, tooltipBorder: const BorderSide(color: Colors.red), tooltipHorizontalAlignment: FLHorizontalAlignment.center, tooltipHorizontalOffset: 10, ); final BarTouchTooltipData barTouchTooltipData6 = BarTouchTooltipData( tooltipBorderRadius: BorderRadius.circular(12), fitInsideVertically: false, fitInsideHorizontally: true, maxContentWidth: 23, getTooltipColor: getTooltipBlueColor, tooltipPadding: const EdgeInsets.all(23), getTooltipItem: getTooltipItem, tooltipMargin: 12, tooltipBorder: const BorderSide(color: Colors.red), tooltipHorizontalAlignment: FLHorizontalAlignment.left, tooltipHorizontalOffset: -10, ); final BarTouchTooltipData barTouchTooltipData7 = BarTouchTooltipData( tooltipBorderRadius: BorderRadius.circular(12), fitInsideVertically: false, fitInsideHorizontally: true, maxContentWidth: 23, getTooltipColor: getTooltipGreenColor, getTooltipItem: getTooltipItem, tooltipMargin: 12, tooltipBorder: const BorderSide(color: Colors.red), tooltipHorizontalAlignment: FLHorizontalAlignment.right, tooltipHorizontalOffset: 10, ); final BarTouchTooltipData barTouchTooltipData8 = BarTouchTooltipData( tooltipBorderRadius: BorderRadius.circular(12), fitInsideVertically: false, fitInsideHorizontally: true, maxContentWidth: 23, getTooltipColor: getTooltipGreenColor, tooltipPadding: const EdgeInsets.all(23), tooltipMargin: 12, tooltipBorder: const BorderSide(color: Colors.red), ); final BarTouchTooltipData barTouchTooltipData9 = BarTouchTooltipData( tooltipBorderRadius: BorderRadius.circular(12), fitInsideVertically: false, fitInsideHorizontally: true, maxContentWidth: 23, getTooltipColor: getTooltipGreenColor, tooltipPadding: const EdgeInsets.all(23), getTooltipItem: getTooltipItem, tooltipMargin: 333, tooltipBorder: const BorderSide(color: Colors.red), ); final BarTouchTooltipData barTouchTooltipData10 = BarTouchTooltipData( tooltipBorderRadius: BorderRadius.circular(12), fitInsideVertically: false, fitInsideHorizontally: true, maxContentWidth: 23, getTooltipColor: getTooltipGreenColor, tooltipPadding: const EdgeInsets.all(23), getTooltipItem: getTooltipItem, tooltipMargin: 12, tooltipBorder: const BorderSide(color: Colors.blue), ); final BarTouchTooltipData barTouchTooltipData11 = BarTouchTooltipData( tooltipBorderRadius: BorderRadius.circular(12), fitInsideVertically: false, fitInsideHorizontally: true, maxContentWidth: 23, getTooltipColor: getTooltipGreenColor, tooltipPadding: const EdgeInsets.all(23), getTooltipItem: getTooltipItem, tooltipMargin: 12, tooltipBorder: const BorderSide(color: Colors.red, width: 2), ); void barTouchCallback(FlTouchEvent event, BarTouchResponse? response) {} void scatterTouchCallback(FlTouchEvent event, ScatterTouchResponse? response) {} final BarTouchData barTouchData1 = BarTouchData( touchTooltipData: barTouchTooltipData1, handleBuiltInTouches: false, touchCallback: barTouchCallback, enabled: false, allowTouchBarBackDraw: true, touchExtraThreshold: const EdgeInsets.all(12), ); final BarTouchData barTouchData1Clone = BarTouchData( touchTooltipData: barTouchTooltipData1Clone, handleBuiltInTouches: false, touchCallback: barTouchCallback, enabled: false, allowTouchBarBackDraw: true, touchExtraThreshold: const EdgeInsets.all(12), ); const BarTouchData barTouchData2 = BarTouchData( handleBuiltInTouches: false, touchCallback: barTouchCallback, enabled: false, allowTouchBarBackDraw: true, touchExtraThreshold: EdgeInsets.all(12), ); final BarTouchData barTouchData3 = BarTouchData( touchTooltipData: barTouchTooltipData1, handleBuiltInTouches: true, touchCallback: barTouchCallback, enabled: false, allowTouchBarBackDraw: true, touchExtraThreshold: const EdgeInsets.all(12), ); final BarTouchData barTouchData4 = BarTouchData( touchTooltipData: barTouchTooltipData2, handleBuiltInTouches: false, touchCallback: barTouchCallback, enabled: false, allowTouchBarBackDraw: true, touchExtraThreshold: const EdgeInsets.all(12), ); final BarTouchData barTouchData5 = BarTouchData( touchTooltipData: barTouchTooltipData1, handleBuiltInTouches: false, enabled: false, allowTouchBarBackDraw: true, touchExtraThreshold: const EdgeInsets.all(12), ); final BarTouchData barTouchData6 = BarTouchData( touchTooltipData: barTouchTooltipData1, handleBuiltInTouches: false, touchCallback: barTouchCallback, enabled: true, allowTouchBarBackDraw: true, touchExtraThreshold: const EdgeInsets.all(12), ); final BarTouchData barTouchData7 = BarTouchData( touchTooltipData: barTouchTooltipData1, handleBuiltInTouches: false, touchCallback: barTouchCallback, enabled: false, allowTouchBarBackDraw: false, touchExtraThreshold: const EdgeInsets.all(12), ); final BarTouchData barTouchData8 = BarTouchData( touchTooltipData: barTouchTooltipData1, handleBuiltInTouches: false, touchCallback: barTouchCallback, enabled: false, allowTouchBarBackDraw: true, ); final BarTouchData barTouchData9 = BarTouchData( touchTooltipData: barTouchTooltipData1, handleBuiltInTouches: false, touchCallback: barTouchCallback, enabled: false, allowTouchBarBackDraw: true, touchExtraThreshold: const EdgeInsets.only(left: 12), ); final BarTouchData barTouchData10 = BarTouchData( touchTooltipData: barTouchTooltipData1, handleBuiltInTouches: false, touchCallback: barTouchCallback, enabled: false, allowTouchBarBackDraw: true, touchExtraThreshold: const EdgeInsets.all(12), longPressDuration: Duration.zero, ); final BarChartData barChartData1 = BarChartData( minY: 12, titlesData: MockData.flTitlesData1, gridData: flGridData1, rangeAnnotations: rangeAnnotations1, maxY: 23, backgroundColor: Colors.green, borderData: borderData1, alignment: BarChartAlignment.spaceAround, barGroups: [ barChartGroupData1, barChartGroupData2, barChartGroupData3, ], barTouchData: barTouchData1, groupsSpace: 23, extraLinesData: extraLinesData1, ); final BarChartData barChartData1Clone = barChartData1.copyWith( titlesData: MockData.flTitlesData1Clone, gridData: flGridData1Clone, borderData: borderData1Clone, barTouchData: barTouchData1Clone, rangeAnnotations: rangeAnnotations1Clone, ); final BarChartData barChartData2 = barChartData1.copyWith( minY: 11, ); final BarChartData barChartData3 = barChartData1.copyWith( titlesData: MockData.flTitlesData2, ); final BarChartData barChartData4 = barChartData1.copyWith( gridData: flGridData2, ); final BarChartData barChartData5 = barChartData1.copyWith( rangeAnnotations: rangeAnnotations2, ); final BarChartData barChartData6 = barChartData1.copyWith( maxY: 52345, ); final BarChartData barChartData7 = barChartData1.copyWith( backgroundColor: Colors.red, ); final BarChartData barChartData8 = barChartData1.copyWith( titlesData: MockData.flTitlesData3, ); final BarChartData barChartData9 = barChartData1.copyWith( borderData: borderData2, ); final BarChartData barChartData10 = barChartData1.copyWith( alignment: BarChartAlignment.center, ); final BarChartData barChartData11 = barChartData1.copyWith( barGroups: [], ); final BarChartData barChartData12 = barChartData1.copyWith( barGroups: [ barChartGroupData3, barChartGroupData1, barChartGroupData2, ], ); final BarChartData barChartData13 = barChartData1.copyWith( barTouchData: barTouchData2, ); final BarChartData barChartData14 = barChartData1.copyWith( groupsSpace: 444, ); final BarChartData barChartData15 = barChartData1.copyWith( extraLinesData: extraLinesData2, ); final RadarDataSet radarDataSet1 = RadarDataSet( dataEntries: const [ RadarEntry(value: 0), RadarEntry(value: 1), RadarEntry(value: 2), RadarEntry(value: 3), RadarEntry(value: 4), ], borderColor: Colors.blue, borderWidth: 3, entryRadius: 3, fillColor: Colors.grey, ); final RadarDataSet radarDataSet1Clone = radarDataSet1.copyWith(); final RadarDataSet radarDataSet2 = RadarDataSet( dataEntries: const [ RadarEntry(value: 10), RadarEntry(value: 9), RadarEntry(value: 8), RadarEntry(value: 7), RadarEntry(value: 6), ], borderColor: Colors.red, borderWidth: 5, entryRadius: 5, fillColor: Colors.black, ); final RadarTouchData radarTouchData1 = RadarTouchData( enabled: true, touchCallback: radarTouchCallback, touchSpotThreshold: 12, ); final RadarTouchData radarTouchData2 = RadarTouchData( enabled: false, touchCallback: radarTouchCallback, touchSpotThreshold: 5, ); final RadarTouchData radarTouchData1Clone = radarTouchData1; void radarTouchCallback(FlTouchEvent event, RadarTouchResponse? response) {} final radarTouchedSpot1 = RadarTouchedSpot( radarDataSet1, 0, radarDataSet1.dataEntries.first, 0, flSpot1, Offset.zero, ); final RadarTouchedSpot radarTouchedSpotClone1 = radarTouchedSpot1; final radarTouchedSpot2 = RadarTouchedSpot( radarDataSet2, 0, radarDataSet1.dataEntries.first, 0, flSpot1, Offset.zero, ); final radarTouchedSpot3 = RadarTouchedSpot( radarDataSet1, 1, radarDataSet1.dataEntries.first, 0, flSpot1, Offset.zero, ); final radarTouchedSpot4 = RadarTouchedSpot( radarDataSet1, 0, radarDataSet1.dataEntries.last, 0, flSpot1, Offset.zero, ); final radarTouchedSpot5 = RadarTouchedSpot( radarDataSet1, 0, radarDataSet1.dataEntries.first, 1, flSpot1, Offset.zero, ); final radarTouchedSpot6 = RadarTouchedSpot( radarDataSet1, 0, radarDataSet1.dataEntries.first, 0, flSpot2, Offset.zero, ); final radarTouchedSpot7 = RadarTouchedSpot( radarDataSet1, 0, radarDataSet1.dataEntries.first, 0, flSpot1, const Offset(3, 5), ); final RadarChartData radarChartData1 = RadarChartData( dataSets: [radarDataSet1], radarBackgroundColor: Colors.yellow, radarBorderData: const BorderSide(color: Colors.purple, width: 5), borderData: borderData1, gridBorderData: const BorderSide(color: Colors.blue, width: 2), getTitle: (index, angle) => RadarChartTitle(text: 'testTitle', angle: angle), titlePositionPercentageOffset: 0.2, titleTextStyle: const TextStyle(color: Colors.white, fontSize: 12), radarTouchData: radarTouchData1, tickCount: 5, tickBorderData: const BorderSide(width: 4), ticksTextStyle: const TextStyle(color: Colors.white, fontSize: 12), ); final RadarChartData radarChartData1Clone = radarChartData1.copyWith(); final RadarChartData radarChartData2 = RadarChartData( dataSets: [radarDataSet2], radarBackgroundColor: Colors.blue, radarBorderData: const BorderSide(color: Colors.pink, width: 3), borderData: borderData1, gridBorderData: const BorderSide(color: Colors.red, width: 3), getTitle: (index, angle) => RadarChartTitle(text: 'testTitle2', angle: angle), titlePositionPercentageOffset: 0.5, titleTextStyle: const TextStyle(color: Colors.black, fontSize: 5), radarTouchData: radarTouchData2, tickCount: 1, tickBorderData: const BorderSide(color: Colors.pink, width: 2), ticksTextStyle: const TextStyle(color: Colors.purple, fontSize: 10), ); const Line line1 = Line(Offset.zero, Offset(10, 10)); const Line line2 = Line(Offset(-4, -12), Offset(6, 8)); const Line line3 = Line(Offset(18, -1), Offset.zero); const Line line4 = Line(Offset(8, 8), Offset(-4, -22)); const Line line5 = Line(Offset(2, 3), Offset(5, 8)); const TextStyle textStyle1 = TextStyle(color: Colors.red, fontWeight: FontWeight.bold); const TextStyle textStyle2 = TextStyle(color: Colors.green, fontWeight: FontWeight.w200); const TextSpan textSpan1 = TextSpan(text: 'faketext1', style: textStyle1); const TextSpan textSpan2 = TextSpan(text: 'faketext2', style: textStyle2); final DefaultTextStyle defaultTextStyle1 = DefaultTextStyle( style: const TextStyle(), child: Container(), ); final candlestickTouchData1 = CandlestickTouchData( enabled: false, ); final candlestickSpot1 = CandlestickSpot( x: 0, open: 10, high: 100, low: 0, close: 20, ); final CandlestickSpot candlestickSpot1Clone = candlestickSpot1.copyWith(); final candlestickSpot2 = CandlestickSpot( x: 10, open: 30, high: 110, low: 10, close: 20, ); final CandlestickSpot candlestickSpot2Clone = candlestickSpot2.copyWith(); final candlestickSpot3 = CandlestickSpot( x: 20, open: 30, high: 120, low: 20, close: 40, ); final candlestickSpot4 = CandlestickSpot( x: 30, open: 40, high: 130, low: 30, close: 50, ); final candlestickSpot5 = CandlestickSpot( x: -50, open: -40, high: -130, low: -30, close: -50, ); final candleStickChartData1 = CandlestickChartData( candlestickSpots: [ candlestickSpot1, candlestickSpot2, candlestickSpot3, candlestickSpot4, ], candlestickPainter: DefaultCandlestickPainter(), titlesData: MockData.flTitlesData1, candlestickTouchData: candlestickTouchData1, showingTooltipIndicators: [0, 1, 2], gridData: flGridData1, borderData: borderData1, minX: 0, maxX: 1000, minY: 0, maxY: 1000, baselineX: 0, baselineY: 0, backgroundColor: Colors.white, rangeAnnotations: rangeAnnotations1, clipData: const FlClipData.none(), ); final CandlestickChartData candleStickChartData1Clone = candleStickChartData1.copyWith(); final candleStickChartData2 = CandlestickChartData( candlestickSpots: [ candlestickSpot1, candlestickSpot2, candlestickSpot3, candlestickSpot4, ], candlestickPainter: DefaultCandlestickPainter(), titlesData: MockData.flTitlesData2, candlestickTouchData: candlestickTouchData1, showingTooltipIndicators: [1, 2, 3], gridData: flGridData1, borderData: borderData1, minX: 0, maxX: 1000, minY: 0, maxY: 1000, baselineX: 0, baselineY: 0, backgroundColor: Colors.white, rangeAnnotations: rangeAnnotations1, clipData: const FlClipData.all(), rotationQuarterTurns: 1, ); Color candlestickChartGetTooltipRedColor(CandlestickSpot spots) => Colors.red; Color candlestickChartGetTooltipGreenColor(CandlestickSpot spots) => Colors.green; CandlestickTooltipItem? candlestickChartGetTooltipItems( FlCandlestickPainter painter, CandlestickSpot touchedSpot, int spotIndex, ) { return CandlestickTooltipItem( 'check', textStyle: const TextStyle(color: Colors.blue), bottomMargin: 23, ); } final CandlestickTouchTooltipData candlestickTouchTooltipData1 = CandlestickTouchTooltipData( tooltipBorderRadius: const BorderRadius.all(Radius.circular(23)), tooltipPadding: const EdgeInsets.all(11), getTooltipColor: candlestickChartGetTooltipGreenColor, maxContentWidth: 33, fitInsideVertically: true, fitInsideHorizontally: false, getTooltipItems: candlestickChartGetTooltipItems, tooltipBorder: const BorderSide(color: Colors.red), ); final CandlestickTouchTooltipData candlestickTouchTooltipData1Clone = CandlestickTouchTooltipData( tooltipBorderRadius: const BorderRadius.all(Radius.circular(23)), tooltipPadding: const EdgeInsets.all(11), getTooltipColor: candlestickChartGetTooltipGreenColor, maxContentWidth: 33, fitInsideVertically: true, fitInsideHorizontally: false, getTooltipItems: candlestickChartGetTooltipItems, tooltipBorder: const BorderSide(color: Colors.red), ); final CandlestickTouchTooltipData candlestickTouchTooltipData2 = CandlestickTouchTooltipData( tooltipBorderRadius: const BorderRadius.all(Radius.circular(23)), tooltipPadding: const EdgeInsets.all(11), getTooltipColor: candlestickChartGetTooltipGreenColor, maxContentWidth: 33, fitInsideVertically: true, fitInsideHorizontally: false, getTooltipItems: candlestickChartGetTooltipItems, tooltipBorder: const BorderSide(color: Colors.blue), tooltipHorizontalAlignment: FLHorizontalAlignment.left, ); final CandlestickTouchTooltipData candlestickTouchTooltipData3 = CandlestickTouchTooltipData( tooltipBorderRadius: const BorderRadius.all(Radius.circular(23)), tooltipPadding: const EdgeInsets.all(11), getTooltipColor: candlestickChartGetTooltipGreenColor, maxContentWidth: 33, fitInsideVertically: true, fitInsideHorizontally: false, getTooltipItems: candlestickChartGetTooltipItems, tooltipBorder: const BorderSide(color: Colors.red, width: 2), tooltipHorizontalAlignment: FLHorizontalAlignment.right, tooltipHorizontalOffset: 10, ); final candlestickTouchedSpot1 = CandlestickTouchedSpot(candlestickSpot1, 0); ================================================ FILE: test/chart/line_chart/line_chart_data_test.dart ================================================ import 'package:fl_chart/fl_chart.dart'; import 'package:flutter_test/flutter_test.dart'; import '../data_pool.dart'; void main() { group('LineChart data equality check', () { test('LineChartBarData equality test', () { expect(lineChartBarData1 == lineChartBarData1Clone, true); expect(lineChartBarData1 == lineChartBarData2, false); expect(lineChartBarData1 == lineChartBarData3, false); expect(lineChartBarData1 == lineChartBarData4, false); expect(lineChartBarData1 == lineChartBarData5, false); expect(lineChartBarData1 == lineChartBarData6, false); expect(lineChartBarData1 == lineChartBarData7, false); expect(lineChartBarData1 == lineChartBarData8, false); expect(lineChartBarData1 == lineChartBarData9, false); }); test('LineChartBarData late init values test', () { final bar = LineChartBarData( spots: const [ FlSpot(1, 1), FlSpot(2, 4), FlSpot(-3, 8), FlSpot(2, -5), FlSpot(9, 0), ], ); expect(bar.mostLeftSpot, const FlSpot(-3, 8)); expect(bar.mostTopSpot, const FlSpot(-3, 8)); expect(bar.mostRightSpot, const FlSpot(9, 0)); expect(bar.mostBottomSpot, const FlSpot(2, -5)); }); test('BarAreaData equality test', () { expect(barAreaData1 == barAreaData1Clone, true); expect(barAreaData1 == barAreaData2, false); expect(barAreaData1 == barAreaData3, false); expect(barAreaData1 == barAreaData4, false); }); test('BetweenBarsData equality test', () { expect(betweenBarsData1 == betweenBarsData1Clone, true); expect(betweenBarsData1 == betweenBarsData2, false); expect(betweenBarsData1 == betweenBarsData3, false); expect(betweenBarsData1 == betweenBarsData4, false); expect(betweenBarsData1 == betweenBarsData5, false); expect(betweenBarsData1 == betweenBarsData6, false); expect(betweenBarsData1 == betweenBarsData7, false); expect(betweenBarsData1 == betweenBarsData8, false); }); test('BarAreaSpotsLine equality test', () { expect(barAreaSpotsLine1 == barAreaSpotsLine1Clone, true); expect(barAreaSpotsLine1 == barAreaSpotsLine2, false); }); test('FlDotData equality test', () { expect(flDotData1 == flDotData1Clone, true); expect(flDotData1 == flDotData4, false); expect(flDotData1 == flDotData5, false); expect(flDotData1 == flDotData6, false); }); test('HorizontalLine equality test', () { expect(horizontalLine1 == horizontalLine1Clone, true); expect(horizontalLine1 == horizontalLine2, false); expect(horizontalLine1 == horizontalLine3, false); expect(horizontalLine1 == horizontalLine4, false); expect(horizontalLine1 == horizontalLine5, false); expect(horizontalLine1 == horizontalLine6, false); expect(horizontalLine1 == horizontalLine7, false); expect(horizontalLine1 == horizontalLine8, false); expect(horizontalLine1 == horizontalLine9, false); }); test('VerticalLine equality test', () { expect(verticalLine1 == verticalLine1Clone, true); expect(verticalLine1 == verticalLine2, false); expect(verticalLine1 == verticalLine3, false); expect(verticalLine1 == verticalLine4, false); expect(verticalLine1 == verticalLine5, false); expect(verticalLine1 == verticalLine6, false); expect(verticalLine1 == verticalLine7, false); expect(verticalLine1 == verticalLine8, false); expect(verticalLine1 == verticalLine9, false); }); test('HorizontalLineLabel equality test', () { expect(horizontalLineLabel1 == horizontalLineLabel1Clone, true); expect(horizontalLineLabel1 == horizontalLineLabel2, false); expect(horizontalLineLabel1 == horizontalLineLabel3, false); expect(horizontalLineLabel1 == horizontalLineLabel4, false); expect(horizontalLineLabel1 == horizontalLineLabel5, false); expect(horizontalLineLabel1 == horizontalLineLabel6, false); expect(horizontalLineLabel1 == horizontalLineLabel7, false); }); test('VerticalLineLabel equality test', () { expect(verticalLineLabel1 == verticalLineLabel1Clone, true); expect(verticalLineLabel1 == verticalLineLabel2, false); expect(verticalLineLabel1 == verticalLineLabel3, false); expect(verticalLineLabel1 == verticalLineLabel4, false); expect(verticalLineLabel1 == verticalLineLabel5, false); expect(verticalLineLabel1 == verticalLineLabel6, false); expect(verticalLineLabel1 == verticalLineLabel7, false); }); test('ExtraLinesData equality test', () { expect(extraLinesData1 == extraLinesData1Clone, true); expect(extraLinesData1 == extraLinesData2, false); expect(extraLinesData1 == extraLinesData3, false); expect(extraLinesData1 == extraLinesData4, false); expect(extraLinesData1 == extraLinesData5, false); expect(extraLinesData1 == extraLinesData6, false); }); test('LineTouchData equality test', () { expect(lineTouchData1 == lineTouchData1Clone, true); expect(lineTouchData1 == lineTouchData2, false); expect(lineTouchData1 == lineTouchData3, false); expect(lineTouchData1 == lineTouchData4, false); expect(lineTouchData1 == lineTouchData5, false); expect(lineTouchData1 == lineTouchData6, false); expect(lineTouchData1 == lineTouchData7, false); expect(lineTouchData1 == lineTouchData8, false); }); test('LineTouchTooltipData equality test', () { expect(lineTouchTooltipData1 == lineTouchTooltipData1Clone, true); expect(lineTouchTooltipData1 == lineTouchTooltipData2, false); expect(lineTouchTooltipData1 == lineTouchTooltipData3, false); expect(lineTouchTooltipData1 == lineTouchTooltipData4, false); expect(lineTouchTooltipData1 == lineTouchTooltipData5, false); expect(lineTouchTooltipData1 == lineTouchTooltipData6, false); expect(lineTouchTooltipData1 == lineTouchTooltipData7, false); }); test('LineBarSpot equality test', () { expect(lineBarSpot1 == lineBarSpot1Clone, true); expect(lineBarSpot1 == lineBarSpot2, false); expect(lineBarSpot1 == lineBarSpot3, false); }); test('LineTooltipItem equality test', () { expect(lineTooltipItem1 == lineTooltipItem1Clone, true); expect(lineTooltipItem1 == lineTooltipItem2, false); expect(lineTooltipItem1 == lineTooltipItem3, false); expect(lineTooltipItem1 == lineTooltipItem4, false); }); test('TouchedSpotIndicatorData equality test', () { expect(touchedSpotIndicatorData1 == touchedSpotIndicatorData1Clone, true); expect(touchedSpotIndicatorData1 == touchedSpotIndicatorData2, false); expect(touchedSpotIndicatorData1 == touchedSpotIndicatorData3, false); expect(touchedSpotIndicatorData1 == touchedSpotIndicatorData4, false); expect(touchedSpotIndicatorData1 == touchedSpotIndicatorData5, false); expect(touchedSpotIndicatorData1 == touchedSpotIndicatorData6, false); }); test('ShowingTooltipIndicator equality test', () { expect(showingTooltipIndicator1 == showingTooltipIndicator1Clone, true); expect(showingTooltipIndicator1 == showingTooltipIndicator2, false); expect(showingTooltipIndicator1 == showingTooltipIndicator3, false); expect(showingTooltipIndicator1 == showingTooltipIndicator4, false); expect(showingTooltipIndicator1 == showingTooltipIndicator5, false); }); test('LineTouchResponse equality test', () { expect(lineTouchResponse1 == lineTouchResponse1Clone, false); expect(lineTouchResponse1 == lineTouchResponse2, false); expect(lineTouchResponse1 == lineTouchResponse3, false); expect(lineTouchResponse1 == lineTouchResponse4, false); expect(lineTouchResponse1 == lineTouchResponse5, false); }); test('LineChartData equality test', () { expect(lineChartData1 == lineChartData1Clone, true); expect(lineChartData1 == lineChartData2, false); expect(lineChartData1 == lineChartData3, false); expect(lineChartData1 == lineChartData4, false); expect(lineChartData1 == lineChartData5, false); expect(lineChartData1 == lineChartData6, false); expect(lineChartData1 == lineChartData7, false); expect(lineChartData1 == lineChartData8, false); expect(lineChartData1 == lineChartData9, false); expect(lineChartData1 == lineChartData10, false); expect(lineChartData1 == lineChartData11, false); expect(lineChartData1 == lineChartData12, false); expect(lineChartData1 == lineChartData13, false); expect(lineChartData1 == lineChartData14, false); expect(lineChartData1 == lineChartData15, false); expect(lineChartData1 == lineChartData16, false); expect(lineChartData1 == lineChartData17, false); expect(lineChartData1 == lineChartData18, false); expect(lineChartData1 == lineChartData19, false); expect(lineChartData1 == lineChartData20, false); expect(lineChartData1 == lineChartData21, false); expect( lineChartData21 == lineChartData21.copyWith( rotationQuarterTurns: 2, ), false, ); expect( lineChartData21 == lineChartData21.copyWith( rotationQuarterTurns: 1, ), true, ); }); }); } ================================================ FILE: test/chart/line_chart/line_chart_helper_test.dart ================================================ import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/chart/line_chart/line_chart_helper.dart'; import 'package:flutter_test/flutter_test.dart'; import '../data_pool.dart'; void main() { group('Check caching of LineChartHelper.calculateMaxAxisValues', () { test('Test validity 1', () { final lineChartHelper = LineChartHelper(); final lineBars = [lineChartBarData1, lineChartBarData2]; final (minX, maxX, minY, maxY) = lineChartHelper.calculateMaxAxisValues(lineBars); expect(minX, 1); expect(maxX, 4); expect(minY, 1); expect(maxY, 2); }); test('Test validity 2', () { final lineChartHelper = LineChartHelper(); final lineBars = [ lineChartBarData1.copyWith( spots: const [ FlSpot(3, 4), FlSpot(-3, 50), FlSpot(14, -10), ], ), ]; final (minX, maxX, minY, maxY) = lineChartHelper.calculateMaxAxisValues(lineBars); expect(minX, -3); expect(maxX, 14); expect(minY, -10); expect(maxY, 50); }); test('Test equality', () { final lineChartHelper = LineChartHelper(); final lineBars = [lineChartBarData1, lineChartBarData2]; final lineBarsClone = [lineChartBarData1Clone, lineChartBarData2]; final result1 = lineChartHelper.calculateMaxAxisValues(lineBars); final result2 = lineChartHelper.calculateMaxAxisValues(lineBarsClone); expect(result1, result2); }); test('Test null spot 1', () { final lineChartHelper = LineChartHelper(); final lineBars = [ LineChartBarData( spots: [ FlSpot.nullSpot, FlSpot.nullSpot, FlSpot.nullSpot, FlSpot.nullSpot, ], ), ]; expect(lineChartHelper.calculateMaxAxisValues(lineBars), (0, 0, 0, 0)); }); test('Test null spot 2', () { final lineChartHelper = LineChartHelper(); final lineBars = [ LineChartBarData( spots: [ FlSpot.nullSpot, const FlSpot(-1, 5), FlSpot.nullSpot, const FlSpot(4, -3), ], ), ]; expect(lineChartHelper.calculateMaxAxisValues(lineBars), (-1, 4, -3, 5)); }); }); } ================================================ FILE: test/chart/line_chart/line_chart_painter_test.dart ================================================ import 'dart:math' as math; import 'dart:ui' as ui show Gradient; import 'dart:ui'; import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart'; import 'package:fl_chart/src/chart/line_chart/line_chart_helper.dart'; import 'package:fl_chart/src/chart/line_chart/line_chart_painter.dart'; import 'package:fl_chart/src/extensions/path_extension.dart'; import 'package:fl_chart/src/utils/canvas_wrapper.dart'; import 'package:fl_chart/src/utils/utils.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import '../data_pool.dart'; import 'line_chart_painter_test.mocks.dart'; @GenerateMocks([Canvas, CanvasWrapper, BuildContext, Utils, LineChartPainter]) @GenerateNiceMocks([MockSpec()]) void main() { group('paint()', () { test('test 1', () { const viewSize = Size(400, 400); final bar1 = LineChartBarData( spots: const [ FlSpot(0, 4), FlSpot(1, 3), FlSpot(2, 2), FlSpot(3, 1), FlSpot(4, 0), ], showingIndicators: [ 0, 2, 3, ], ); final bar2 = LineChartBarData( spots: const [ FlSpot(0, 5), FlSpot(1, 3), FlSpot(2, 2), FlSpot(3, 5), FlSpot(4, 0), ], ); final lineChartBarsData = [bar1, bar2]; final (minX, maxX, minY, maxY) = LineChartHelper().calculateMaxAxisValues( lineChartBarsData, ); final data = LineChartData( minX: minX, maxX: maxX, minY: minY, maxY: maxY, lineBarsData: lineChartBarsData, clipData: const FlClipData.all(), extraLinesData: ExtraLinesData( horizontalLines: [ HorizontalLine(y: 1), ], verticalLines: [ VerticalLine(x: 4), ], ), betweenBarsData: [ BetweenBarsData(fromIndex: 0, toIndex: 1), ], showingTooltipIndicators: [ ShowingTooltipIndicators([ LineBarSpot(bar1, 0, bar1.spots.first), LineBarSpot(bar2, 1, bar2.spots.first), ]), ], lineTouchData: LineTouchData( getTouchedSpotIndicator: (barData, spotIndexes) { return spotIndexes.asMap().entries.map((entry) { final i = entry.key; if (i == 0) { return null; } return const TouchedSpotIndicatorData( FlLine(color: MockData.color0), FlDotData(), ); }).toList(); }, ), ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockUtils = MockUtils(); Utils.changeInstance(mockUtils); when(mockUtils.getThemeAwareTextStyle(any, any)) .thenAnswer((realInvocation) => textStyle1); when(mockUtils.calculateRotationOffset(any, any)) .thenAnswer((realInvocation) => Offset.zero); when(mockUtils.convertRadiusToSigma(any)) .thenAnswer((realInvocation) => 4.0); when(mockUtils.getEfficientInterval(any, any)) .thenAnswer((realInvocation) => 1.0); when(mockUtils.getBestInitialIntervalValue(any, any, any)) .thenAnswer((realInvocation) => 1.0); final mockBuildContext = MockBuildContext(); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); lineChartPainter.paint( mockBuildContext, mockCanvasWrapper, holder, ); verify(mockCanvasWrapper.clipRect(any)).called(1); verify(mockCanvasWrapper.drawDot(any, any, any)).called(12); verify(mockCanvasWrapper.drawPath(any, any)).called(3); }); test('test 2', () { const viewSize = Size(400, 400); final bar1 = LineChartBarData( spots: const [ FlSpot(0, 4), FlSpot(1, 3), FlSpot(2, 2), FlSpot(3, 1), FlSpot(4, 0), ], ); final bar2 = LineChartBarData( spots: const [ FlSpot(0, 2), FlSpot(1, 5), FlSpot(2, 1), FlSpot(3, 2), FlSpot(4, 3), ], ); final lineChartBarsData = [bar1, bar2]; final (minX, maxX, minY, maxY) = LineChartHelper().calculateMaxAxisValues( lineChartBarsData, ); final data = LineChartData( minX: minX, maxX: maxX, minY: minY, maxY: maxY, lineBarsData: lineChartBarsData, clipData: const FlClipData.all(), lineTouchData: LineTouchData( getTouchedSpotIndicator: (barData, spotIndexes) { return List.generate( spotIndexes.length + 1, (index) { return const TouchedSpotIndicatorData( FlLine(color: MockData.color0), FlDotData(), ); }, ).toList(); }, ), extraLinesData: ExtraLinesData( horizontalLines: [ HorizontalLine(y: 1), ], verticalLines: [ VerticalLine(x: 4), ], extraLinesOnTop: false, ), showingTooltipIndicators: [ ShowingTooltipIndicators([ LineBarSpot(bar1, 0, bar1.spots[0]), LineBarSpot(bar1, 0, bar1.spots[2]), LineBarSpot(bar2, 1, bar1.spots[2]), LineBarSpot(bar2, 1, bar1.spots[3]), LineBarSpot(bar2, 1, bar1.spots[4]), ]), ], ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockUtils = MockUtils(); Utils.changeInstance(mockUtils); when(mockUtils.getThemeAwareTextStyle(any, any)) .thenAnswer((realInvocation) => textStyle1); when(mockUtils.calculateRotationOffset(any, any)) .thenAnswer((realInvocation) => Offset.zero); when(mockUtils.convertRadiusToSigma(any)) .thenAnswer((realInvocation) => 4.0); when(mockUtils.getEfficientInterval(any, any)) .thenAnswer((realInvocation) => 1.0); when(mockUtils.getBestInitialIntervalValue(any, any, any)) .thenAnswer((realInvocation) => 1.0); final mockBuildContext = MockBuildContext(); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); Object? exception; try { lineChartPainter.paint( mockBuildContext, mockCanvasWrapper, holder, ); } catch (e) { exception = e; } expect(exception != null, true); }); test('test 3 minY == maxY', () { const viewSize = Size(400, 400); final bar1 = LineChartBarData( spots: const [ FlSpot(0, 4), FlSpot(1, 3), FlSpot(2, 2), FlSpot(3, 1), FlSpot(4, 0), ], showingIndicators: [ 0, 2, 3, ], ); final bar2 = LineChartBarData( spots: const [ FlSpot(0, 5), FlSpot(1, 3), FlSpot(2, 2), FlSpot(3, 5), FlSpot(4, 0), ], ); final lineChartBarsData = [bar1, bar2]; final (minX, maxX, minY, maxY) = LineChartHelper().calculateMaxAxisValues( lineChartBarsData, ); final data = LineChartData( minX: minX, maxX: maxX, minY: minY, maxY: minY, lineBarsData: lineChartBarsData, clipData: const FlClipData.all(), extraLinesData: ExtraLinesData( horizontalLines: [ HorizontalLine(y: 1), ], verticalLines: [ VerticalLine(x: 4), ], ), betweenBarsData: [ BetweenBarsData(fromIndex: 0, toIndex: 1), ], showingTooltipIndicators: [ ShowingTooltipIndicators([ LineBarSpot(bar1, 0, bar1.spots.first), LineBarSpot(bar2, 1, bar2.spots.first), ]), ], lineTouchData: LineTouchData( getTouchedSpotIndicator: (barData, spotIndexes) { return spotIndexes.asMap().entries.map((entry) { final i = entry.key; if (i == 0) { return null; } return const TouchedSpotIndicatorData( FlLine(color: MockData.color0), FlDotData(), ); }).toList(); }, ), ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockUtils = MockUtils(); Utils.changeInstance(mockUtils); when(mockUtils.getThemeAwareTextStyle(any, any)) .thenAnswer((realInvocation) => textStyle1); when(mockUtils.calculateRotationOffset(any, any)) .thenAnswer((realInvocation) => Offset.zero); when(mockUtils.convertRadiusToSigma(any)) .thenAnswer((realInvocation) => 4.0); when(mockUtils.getEfficientInterval(any, any)) .thenAnswer((realInvocation) => 1.0); when(mockUtils.getBestInitialIntervalValue(any, any, any)) .thenAnswer((realInvocation) => 1.0); final mockBuildContext = MockBuildContext(); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); lineChartPainter.paint( mockBuildContext, mockCanvasWrapper, holder, ); verify(mockCanvasWrapper.clipRect(any)).called(1); verify(mockCanvasWrapper.drawDot(any, any, any)).called(12); verify(mockCanvasWrapper.drawPath(any, any)).called(3); }); }); group('clipToBorder()', () { test('test 1', () { const viewSize = Size(400, 400); final data = LineChartData(); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); lineChartPainter.clipToBorder( mockCanvasWrapper, holder, ); final verifyResult = verify(mockCanvasWrapper.clipRect(captureAny)); final rect = verifyResult.captured.single as Rect; verifyResult.called(1); expect(rect.left, 0); expect(rect.top, 0); expect(rect.width, 400); expect(rect.height, 400); }); test('test 2', () { const viewSize = Size(400, 400); final data = LineChartData( titlesData: const FlTitlesData( leftTitles: AxisTitles( sideTitles: SideTitles(showTitles: true, reservedSize: 10), ), topTitles: AxisTitles( sideTitles: SideTitles(showTitles: true, reservedSize: 20), ), rightTitles: AxisTitles( sideTitles: SideTitles(showTitles: true, reservedSize: 30), ), bottomTitles: AxisTitles( sideTitles: SideTitles(showTitles: true, reservedSize: 40), ), ), borderData: FlBorderData(show: true, border: Border.all(width: 8)), clipData: const FlClipData( top: false, bottom: false, left: true, right: true, ), ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); lineChartPainter.clipToBorder( mockCanvasWrapper, holder, ); final verifyResult = verify(mockCanvasWrapper.clipRect(captureAny)); final rect = verifyResult.captured.single as Rect; verifyResult.called(1); expect(rect.left, 4); expect(rect.top, 0); expect(rect.right, 396); expect(rect.bottom, 400); }); test('test 3', () { const viewSize = Size(400, 400); final data = LineChartData( titlesData: const FlTitlesData( leftTitles: AxisTitles( sideTitles: SideTitles(showTitles: true, reservedSize: 10), ), topTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: 40, ), ), rightTitles: AxisTitles( sideTitles: SideTitles(showTitles: true, reservedSize: 30), ), bottomTitles: AxisTitles( sideTitles: SideTitles(showTitles: true, reservedSize: 40), ), ), borderData: FlBorderData(show: true, border: Border.all(width: 8)), clipData: const FlClipData( top: true, bottom: true, left: true, right: true, ), ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); lineChartPainter.clipToBorder( mockCanvasWrapper, holder, ); final verifyResult = verify(mockCanvasWrapper.clipRect(captureAny)); final rect = verifyResult.captured.single as Rect; verifyResult.called(1); expect(rect.left, 4); expect(rect.top, 4); expect(rect.right, 396); expect(rect.bottom, 396); }); }); group('drawBarLine()', () { test('test 1', () { const viewSize = Size(400, 400); final barData = LineChartBarData( spots: const [ flSpot1, flSpot2, FlSpot(20, 11), FlSpot(11, 11), ], ); final data = LineChartData(lineBarsData: [barData]); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); lineChartPainter.drawBarLine( mockCanvasWrapper, barData, holder, ); verify(mockCanvasWrapper.drawPath(any, any)).called(1); }); test('test 2', () { const viewSize = Size(400, 400); final barData = LineChartBarData( spots: const [ flSpot1, flSpot2, FlSpot.nullSpot, FlSpot(20, 11), FlSpot(11, 11), ], ); final data = LineChartData(lineBarsData: [barData]); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); lineChartPainter.drawBarLine( mockCanvasWrapper, barData, holder, ); verify(mockCanvasWrapper.drawPath(any, any)).called(2); }); test('test 3', () { const viewSize = Size(400, 400); final barData = LineChartBarData( spots: const [ flSpot1, flSpot2, FlSpot(20, 11), FlSpot(11, 11), ], ); final data = LineChartData( lineBarsData: [barData], ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); lineChartPainter.drawBarLine( mockCanvasWrapper, barData, holder, ); final verificationResult = verify(mockCanvasWrapper.drawPath(any, captureAny)); final paint = verificationResult.captured.single as Paint; verificationResult.called(1); expect( paint.color, isSameColorAs(barData.gradient?.colors.first ?? barData.color!), ); }); }); group('drawBetweenBarsArea()', () { test('test 1', () { const viewSize = Size(400, 400); final barData = LineChartBarData( spots: const [ flSpot1, flSpot2, FlSpot(20, 11), FlSpot(11, 11), ], ); final barData2 = LineChartBarData( spots: const [ flSpot2, flSpot1, FlSpot(20, 11), FlSpot(11, 11), ], ); final betweenBarData = BetweenBarsData( fromIndex: 0, toIndex: 1, color: const Color(0xFFFF0000), ); final data = LineChartData( lineBarsData: [ barData, barData2, ], betweenBarsData: [ betweenBarData, ], ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); lineChartPainter.drawBetweenBarsArea( mockCanvasWrapper, data, betweenBarData, holder, ); final verifyResult = verifyInOrder([ mockCanvasWrapper.saveLayer(const Rect.fromLTWH(0, 0, 400, 400), any), mockCanvasWrapper.drawPath(any, captureAny), mockCanvasWrapper.restore(), ]); final paint = verifyResult[1].captured.first as Paint; expect(paint.shader, null); expect(paint.color, const Color(0xFFFF0000)); }); }); group('drawDots()', () { test('test 1', () { const viewSize = Size(400, 400); final barData = LineChartBarData(); final data = LineChartData( lineBarsData: [ barData, ], ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); lineChartPainter.drawDots( mockCanvasWrapper, barData, holder, ); verifyNever(mockCanvasWrapper.drawDot(any, any, any)); }); test('test 2', () { const viewSize = Size(400, 400); final barData = LineChartBarData( spots: const [FlSpot(1, 1)], dotData: const FlDotData(show: false), ); final data = LineChartData( lineBarsData: [ barData, ], ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); lineChartPainter.drawDots( mockCanvasWrapper, barData, holder, ); verifyNever(mockCanvasWrapper.drawDot(any, any, any)); }); test('test 3', () { const viewSize = Size(400, 400); final barData = LineChartBarData( spots: const [ FlSpot(1, 1), FlSpot(2, 2), FlSpot(3, 3), FlSpot(4, 4), FlSpot.nullSpot, FlSpot(5, 5), ], ); final data = LineChartData( lineBarsData: [ barData, ], ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); lineChartPainter.drawDots( mockCanvasWrapper, barData, holder, ); verify(mockCanvasWrapper.drawDot(any, any, any)).called(5); }); test('test 4', () { const viewSize = Size(100, 100); final barData = LineChartBarData( spots: const [ FlSpot(1, 1), FlSpot(2, 2), FlSpot(3, 3), FlSpot(4, 4), FlSpot.nullSpot, FlSpot(5, 5), ], ); final data = LineChartData( minX: 0, maxX: 10, minY: 0, maxY: 10, lineBarsData: [ barData, ], titlesData: const FlTitlesData(show: false), ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); lineChartPainter.drawDots( mockCanvasWrapper, barData, holder, ); verifyInOrder([ mockCanvasWrapper.drawDot( any, const FlSpot(1, 1), const Offset(10, 90), ), mockCanvasWrapper.drawDot( any, const FlSpot(2, 2), const Offset(20, 80), ), mockCanvasWrapper.drawDot( any, const FlSpot(3, 3), const Offset(30, 70), ), mockCanvasWrapper.drawDot( any, const FlSpot(4, 4), const Offset(40, 60), ), mockCanvasWrapper.drawDot( any, const FlSpot(5, 5), const Offset(50, 50), ), ]); }); }); group('drawErrorIndicatorData()', () { test('test - not showing error indicators', () { const viewSize = Size(400, 400); final barData = LineChartBarData( spots: [ const FlSpot( 1, 1, xError: FlErrorRange(lowerBy: 1, upperBy: 1), ), ], errorIndicatorData: const FlErrorIndicatorData( show: false, ), ); final data = LineChartData( lineBarsData: [ barData, ], ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); lineChartPainter.drawErrorIndicatorData( mockCanvasWrapper, barData, holder, ); verifyNever( mockCanvasWrapper.drawErrorIndicator(any, any, any, any, any), ); }); test('test 2 - showing error indicators with single call', () { const viewSize = Size(400, 400); final barData = LineChartBarData( spots: [ const FlSpot( 1, 1, xError: FlErrorRange(lowerBy: 1, upperBy: 1), ), ], ); final data = LineChartData( lineBarsData: [ barData, ], ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); lineChartPainter.drawErrorIndicatorData( mockCanvasWrapper, barData, holder, ); verify( mockCanvasWrapper.drawErrorIndicator(any, any, any, any, any), ).called(1); }); test('test 3 - different values for different spots', () { const viewSize = Size(400, 400); final colors = [ Colors.red, Colors.green, Colors.blue, Colors.yellow, Colors.purple, ]; final spots = [ const FlSpot(1, 1, xError: FlErrorRange.symmetric(1)), const FlSpot(2, 2, xError: FlErrorRange.symmetric(2)), const FlSpot(3, 3, xError: FlErrorRange.symmetric(3)), const FlSpot(4, 2, xError: FlErrorRange.symmetric(2)), const FlSpot(5, 1, xError: FlErrorRange.symmetric(1)), ]; final barData = LineChartBarData( spots: spots, errorIndicatorData: FlErrorIndicatorData( painter: (input) => FlSimpleErrorPainter( lineColor: colors[input.spotIndex], lineWidth: input.spotIndex.toDouble(), capLength: 10, crossAlignment: input.spotIndex / spots.length, showErrorTexts: true, errorTextDirection: TextDirection.rtl, errorTextStyle: TextStyle( color: colors[input.spotIndex], fontSize: input.spot.y, ), ), ), ); final data = LineChartData( lineBarsData: [ barData, ], ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); lineChartPainter.drawErrorIndicatorData( mockCanvasWrapper, barData, holder, ); final result = verify( mockCanvasWrapper.drawErrorIndicator(captureAny, any, any, any, any), )..called(5); for (var i = 0; i < result.captured.length; i++) { final captured = result.captured[i] as FlSimpleErrorPainter; expect(captured.lineColor, colors[i]); expect(captured.lineWidth, i.toDouble()); expect(captured.capLength, 10); expect(captured.crossAlignment, i / spots.length); expect(captured.showErrorTexts, true); expect(captured.errorTextDirection, TextDirection.rtl); expect(captured.errorTextStyle.color, colors[i]); expect(captured.errorTextStyle.fontSize, spots[i].y); } verifyNever(mockCanvasWrapper.drawText(any, any, any)); }); test('test 4 - asymmetric yError maps lowerBy downward and upperBy upward', () { const viewSize = Size(400, 400); final barData = LineChartBarData( spots: [ const FlSpot( 5, 5, yError: FlErrorRange(lowerBy: 1, upperBy: 3), ), ], ); final data = LineChartData( minY: 0, maxY: 10, lineBarsData: [barData], ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); lineChartPainter.drawErrorIndicatorData( mockCanvasWrapper, barData, holder, ); final result = verify( mockCanvasWrapper.drawErrorIndicator(any, any, any, captureAny, any), )..called(1); final rect = result.captured[0] as Rect; // getPixelY(y) = 400 - (y/10)*400 // spot y=5 -> pixel 200 // upper bound y=8 -> pixel 80, relative top = 80 - 200 = -120 // lower bound y=4 -> pixel 240, relative bottom = 240 - 200 = 40 expect(rect.top, -120); expect(rect.bottom, 40); }); }); group('drawTouchedSpotsIndicator()', () { List getDrawingInfo(LineChartData data) { final lineIndexDrawingInfo = []; /// draw each line independently on the chart for (var i = 0; i < data.lineBarsData.length; i++) { final barData = data.lineBarsData[i]; if (!barData.show) { continue; } final indicatorsData = data.lineTouchData .getTouchedSpotIndicator(barData, barData.showingIndicators); if (indicatorsData.length != barData.showingIndicators.length) { throw Exception( 'indicatorsData and touchedSpotOffsets size should be same', ); } for (var j = 0; j < barData.showingIndicators.length; j++) { final indicatorData = indicatorsData[j]; final index = barData.showingIndicators[j]; final spot = barData.spots[index]; if (indicatorData == null) { continue; } lineIndexDrawingInfo.add( LineIndexDrawingInfo(barData, i, spot, index, indicatorData), ); } } return lineIndexDrawingInfo; } test('test 1', () { const viewSize = Size(400, 400); final lineChartBarData = LineChartBarData(); final data = LineChartData( lineBarsData: [lineChartBarData], showingTooltipIndicators: [], ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); lineChartPainter.drawTouchedSpotsIndicator( mockCanvasWrapper, getDrawingInfo(data), holder, ); verifyNever(mockCanvasWrapper.drawPath(any, any)); }); test('test 2', () { const viewSize = Size(400, 400); const spot1 = FlSpot(1, 1); const spot2 = FlSpot(2, 2); const spot3 = FlSpot(3, 3); final lineChartBarData = LineChartBarData( spots: const [spot1, spot2, spot3], showingIndicators: [0, 1], ); final data = LineChartData( lineBarsData: [lineChartBarData], lineTouchData: LineTouchData( getTouchedSpotIndicator: (barData, spotIndexes) { return spotIndexes.asMap().entries.map((e) { final index = e.key; final color = index == 0 ? const Color(0xFF00FF00) : const Color(0xFF0000FF); final strokeWidth = index == 0 ? 8.0 : 12.0; return TouchedSpotIndicatorData( FlLine(color: color, strokeWidth: strokeWidth), const FlDotData(show: false), ); }).toList(); }, ), ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final results = >[]; when( mockCanvasWrapper.drawDashedLine( captureAny, captureAny, captureAny, any, ), ).thenAnswer((inv) { results.add({ 'from': inv.positionalArguments[0] as Offset, 'to': inv.positionalArguments[1] as Offset, 'paint_color': (inv.positionalArguments[2] as Paint).color, 'paint_stroke_width': (inv.positionalArguments[2] as Paint).strokeWidth, }); }); lineChartPainter.drawTouchedSpotsIndicator( mockCanvasWrapper, getDrawingInfo(data), holder, ); expect(results.length, 2); expect(results[0]['paint_color'], const Color(0xFF0000FF)); expect(results[0]['paint_stroke_width'], 12); expect(results[1]['paint_color'], const Color(0xFF00FF00)); expect(results[1]['paint_stroke_width'], 8.0); }); test('test 3', () { const viewSize = Size(400, 400); const spot1 = FlSpot(1, 1); const spot2 = FlSpot(2, 2); const spot3 = FlSpot(3, 3); final lineChartBarData = LineChartBarData( spots: const [spot1, spot2, spot3], showingIndicators: [0, 1], ); final data = LineChartData( lineBarsData: [lineChartBarData], lineTouchData: LineTouchData( getTouchedSpotIndicator: (barData, spotIndexes) { return spotIndexes.asMap().entries.map((e) { final index = e.key; final color = index == 0 ? const Color(0xFF00FF00) : const Color(0xFF0000FF); final strokeWidth = index == 0 ? 8.0 : 12.0; return TouchedSpotIndicatorData( FlLine(color: color, strokeWidth: strokeWidth), const FlDotData(), ); }).toList(); }, ), ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final results = >[]; when( mockCanvasWrapper.drawDashedLine( captureAny, captureAny, captureAny, any, ), ).thenAnswer((inv) { results.add({ 'from': inv.positionalArguments[0] as Offset, 'to': inv.positionalArguments[1] as Offset, 'paint_color': (inv.positionalArguments[2] as Paint).color, 'paint_stroke_width': (inv.positionalArguments[2] as Paint).strokeWidth, }); }); lineChartPainter.drawTouchedSpotsIndicator( mockCanvasWrapper, getDrawingInfo(data), holder, ); expect(results.length, 2); expect(results[0]['paint_color'], const Color(0xFF0000FF)); expect(results[0]['paint_stroke_width'], 12); expect(results[1]['paint_color'], const Color(0xFF00FF00)); expect(results[1]['paint_stroke_width'], 8.0); verify(mockCanvasWrapper.drawDot(any, any, any)).called(2); }); }); group('generateBarPath()', () { test('test 1', () { const viewSize = Size(100, 100); final lineChartBarData = LineChartBarData( spots: const [ FlSpot.zero, FlSpot(5, 5), FlSpot(10, 0), ], ); final data = LineChartData( minY: 0, maxY: 10, minX: 0, maxX: 10, lineBarsData: [lineChartBarData], showingTooltipIndicators: [], titlesData: const FlTitlesData(show: false), ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final path = lineChartPainter.generateBarPath( viewSize, lineChartBarData, lineChartBarData.spots, holder, ); final iterator = path.computeMetrics().iterator; PathMetric? firstMetric; PathMetric? lastMetric; while (iterator.moveNext()) { firstMetric ??= iterator.current; lastMetric = iterator.current; } final tangent1 = firstMetric!.getTangentForOffset(firstMetric.length / 8); final degrees1 = tangent1!.angle * (180 / math.pi); expect(degrees1, 45.0); final tangent = lastMetric!.getTangentForOffset( (lastMetric.length / 8) * 7, ); final degrees = tangent!.angle * (180 / math.pi); expect(degrees, -45.0); }); test('test 2', () { const viewSize = Size(100, 100); final lineChartBarData = LineChartBarData( spots: const [ FlSpot.zero, FlSpot(5, 5), FlSpot(10, 0), ], isStepLineChart: true, ); final data = LineChartData( minY: 0, maxY: 10, minX: 0, maxX: 10, lineBarsData: [lineChartBarData], showingTooltipIndicators: [], titlesData: const FlTitlesData(show: false), ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final path = lineChartPainter.generateBarPath( viewSize, lineChartBarData, lineChartBarData.spots, holder, ); final iterator = path.computeMetrics().iterator; PathMetric? firstMetric; PathMetric? lastMetric; while (iterator.moveNext()) { firstMetric ??= iterator.current; lastMetric = iterator.current; } final tangent1 = firstMetric!.getTangentForOffset(firstMetric.length / 4); final degrees1 = tangent1!.angle * (180 / math.pi); expect(degrees1, 90.0); final tangent2 = lastMetric!.getTangentForOffset( (lastMetric.length / 4) * 3, ); final degrees2 = tangent2!.angle * (180 / math.pi); expect(degrees2, -90.0); }); }); group('generateNormalBarPath()', () { test('test 1', () { const viewSize = Size(100, 100); final lineChartBarData = LineChartBarData( spots: const [ FlSpot.zero, FlSpot(5, 5), FlSpot(10, 0), ], ); final data = LineChartData( minY: 0, maxY: 10, minX: 0, maxX: 10, lineBarsData: [lineChartBarData], showingTooltipIndicators: [], titlesData: const FlTitlesData(show: false), ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final path = lineChartPainter.generateNormalBarPath( viewSize, lineChartBarData, lineChartBarData.spots, holder, ); final iterator = path.computeMetrics().iterator; PathMetric? firstMetric; PathMetric? lastMetric; while (iterator.moveNext()) { firstMetric ??= iterator.current; lastMetric = iterator.current; } final tangent1 = firstMetric!.getTangentForOffset(firstMetric.length / 8); final degrees1 = tangent1!.angle * (180 / math.pi); expect(degrees1, 45.0); final tangent = lastMetric!.getTangentForOffset( (lastMetric.length / 8) * 7, ); final degrees = tangent!.angle * (180 / math.pi); expect(degrees, -45.0); }); }); group('generateStepBarPath()', () { test('test 1', () { const viewSize = Size(100, 100); final lineChartBarData = LineChartBarData( spots: const [ FlSpot.zero, FlSpot(5, 5), FlSpot(10, 0), ], isStepLineChart: true, ); final data = LineChartData( minY: 0, maxY: 10, minX: 0, maxX: 10, lineBarsData: [lineChartBarData], showingTooltipIndicators: [], titlesData: const FlTitlesData(show: false), ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final path = lineChartPainter.generateStepBarPath( viewSize, lineChartBarData, lineChartBarData.spots, holder, ); final iterator = path.computeMetrics().iterator; PathMetric? firstMetric; PathMetric? lastMetric; while (iterator.moveNext()) { firstMetric ??= iterator.current; lastMetric = iterator.current; } final tangent1 = firstMetric!.getTangentForOffset(firstMetric.length / 4); final degrees1 = tangent1!.angle * (180 / math.pi); expect(degrees1, 90.0); final tangent2 = lastMetric!.getTangentForOffset( (lastMetric.length / 4) * 3, ); final degrees2 = tangent2!.angle * (180 / math.pi); expect(degrees2, -90.0); }); }); group('generateBelowBarPath()', () { test('test 1', () { const viewSize = Size(100, 100); const barSpots = [ FlSpot(1, 9), FlSpot(5, 5), FlSpot(8, 9), ]; final lineChartBarData = LineChartBarData( spots: barSpots, isStepLineChart: true, ); final data = LineChartData( minY: 0, maxY: 10, minX: 0, maxX: 10, lineBarsData: [lineChartBarData], showingTooltipIndicators: [], titlesData: const FlTitlesData(show: false), ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final barPath = Path() ..moveTo(10, 10) ..lineTo(50, 50) ..lineTo(80, 10); final belowBarPath = lineChartPainter.generateBelowBarPath( viewSize, lineChartBarData, barPath, barSpots, holder, ); expect(belowBarPath.getBounds().bottom, 100); expect(belowBarPath.getBounds().left, 10); expect(belowBarPath.getBounds().right, 80); expect(belowBarPath.getBounds().top, 10); }); test('test 2', () { const viewSize = Size(100, 100); const barSpots = [ FlSpot(1, 9), FlSpot(5, 5), FlSpot(8, 9), ]; final lineChartBarData = LineChartBarData( spots: barSpots, isStepLineChart: true, belowBarData: BarAreaData( cutOffY: 4, applyCutOffY: true, ), ); final data = LineChartData( minY: 0, maxY: 10, minX: 0, maxX: 10, lineBarsData: [lineChartBarData], showingTooltipIndicators: [], titlesData: const FlTitlesData(show: false), ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final barPath = Path() ..moveTo(10, 10) ..lineTo(50, 50) ..lineTo(80, 10); final belowBarPath = lineChartPainter.generateBelowBarPath( viewSize, lineChartBarData, barPath, barSpots, holder, ); expect(belowBarPath.getBounds().bottom, 60); expect(belowBarPath.getBounds().left, 10); expect(belowBarPath.getBounds().right, 80); expect(belowBarPath.getBounds().top, 10); }); }); group('generateAboveBarPath()', () { test('test 1', () { const viewSize = Size(100, 100); const barSpots = [ FlSpot(1, 9), FlSpot(5, 5), FlSpot(8, 9), ]; final lineChartBarData = LineChartBarData( spots: barSpots, isStepLineChart: true, ); final data = LineChartData( minY: 0, maxY: 10, minX: 0, maxX: 10, lineBarsData: [lineChartBarData], showingTooltipIndicators: [], titlesData: const FlTitlesData(show: false), ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final barPath = Path() ..moveTo(10, 10) ..lineTo(50, 50) ..lineTo(80, 10); final belowBarPath = lineChartPainter.generateAboveBarPath( viewSize, lineChartBarData, barPath, barSpots, holder, ); expect(belowBarPath.getBounds().bottom, 50); expect(belowBarPath.getBounds().left, 10); expect(belowBarPath.getBounds().right, 80); expect(belowBarPath.getBounds().top, 0); }); }); group('drawBelowBar()', () { test('test 1', () { const viewSize = Size(100, 100); const barSpots = [ FlSpot(1, 9), FlSpot(8, 9), ]; final lineChartBarData = LineChartBarData( spots: barSpots, isStepLineChart: true, belowBarData: BarAreaData( show: true, gradient: const LinearGradient( colors: [Color(0xFFFF0000), Color(0xFF00FF00)], ), ), ); final data = LineChartData( minY: 0, maxY: 10, minX: 0, maxX: 10, lineBarsData: [lineChartBarData], showingTooltipIndicators: [], titlesData: const FlTitlesData(show: false), ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final belowBarPath = Path() ..moveTo(10, 10) ..lineTo(80, 10); final filletAboveBarPath = Path() ..moveTo(10, 10) ..lineTo(80, 10) ..lineTo(80, 0) ..lineTo(10, 0) ..lineTo(10, 10); lineChartPainter.drawBelowBar( mockCanvasWrapper, belowBarPath, filletAboveBarPath, holder, lineChartBarData, ); final result = verify(mockCanvasWrapper.drawPath(belowBarPath, captureAny)) ..called(1); final paint = result.captured.single as Paint; expect(paint.color, const Color(0xFF000000)); expect(paint.shader is ui.Gradient, true); }); test('test 2', () { const viewSize = Size(100, 100); const barSpots = [ FlSpot(1, 9), FlSpot(8, 9), ]; final lineChartBarData = LineChartBarData( spots: barSpots, isStepLineChart: true, belowBarData: BarAreaData( show: true, gradient: const LinearGradient( colors: [Color(0xFFFF0000), Color(0xFF00FF00)], ), applyCutOffY: true, cutOffY: 8, spotsLine: const BarAreaSpotsLine( show: true, applyCutOffY: false, flLineStyle: FlLine( color: Color(0x00F0F0F0), strokeWidth: 18, ), ), ), ); final data = LineChartData( minY: 0, maxY: 10, minX: 0, maxX: 10, lineBarsData: [lineChartBarData], showingTooltipIndicators: [], titlesData: const FlTitlesData(show: false), ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final belowBarPath = Path() ..moveTo(10, 10) ..lineTo(80, 10); final filletAboveBarPath = Path() ..moveTo(10, 10) ..lineTo(80, 10) ..lineTo(80, 0) ..lineTo(10, 0) ..lineTo(10, 10); final results = >[]; when( mockCanvasWrapper.drawDashedLine( captureAny, captureAny, captureAny, any, ), ).thenAnswer((inv) { results.add({ 'from': inv.positionalArguments[0] as Offset, 'to': inv.positionalArguments[1] as Offset, 'paint_color': (inv.positionalArguments[2] as Paint).color, 'paint_stroke_width': (inv.positionalArguments[2] as Paint).strokeWidth, }); }); lineChartPainter.drawBelowBar( mockCanvasWrapper, belowBarPath, filletAboveBarPath, holder, lineChartBarData, ); verify( mockCanvasWrapper.saveLayer( Rect.fromLTWH(0, 0, viewSize.width, viewSize.height), any, ), ).called(1); final result = verify(mockCanvasWrapper.drawPath(belowBarPath, captureAny)) ..called(1); final paint = result.captured.single as Paint; expect(paint.color, const Color(0xFF000000)); expect(paint.shader is ui.Gradient, true); final result2 = verify(mockCanvasWrapper.drawPath(filletAboveBarPath, captureAny)) ..called(1); final paint2 = result2.captured.single as Paint; expect(paint2.color, const Color(0x00000000)); expect(paint2.blendMode, BlendMode.dstIn); expect(paint2.style, PaintingStyle.fill); verify(mockCanvasWrapper.restore()).called(1); expect(results.length, 2); for (final item in results) { expect((item['paint_color'] as Color).a, 0); expect(item['paint_stroke_width'], 18); } }); }); group('drawAboveBar()', () { test('test 1', () { const viewSize = Size(100, 100); const barSpots = [ FlSpot(1, 9), FlSpot(8, 9), ]; final lineChartBarData = LineChartBarData( spots: barSpots, isStepLineChart: true, aboveBarData: BarAreaData( show: true, gradient: const LinearGradient( colors: [Color(0xFFFF0000), Color(0xFF00FF00)], ), ), ); final data = LineChartData( minY: 0, maxY: 10, minX: 0, maxX: 10, lineBarsData: [lineChartBarData], showingTooltipIndicators: [], titlesData: const FlTitlesData(show: false), ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final aboveBarPath = Path() ..moveTo(10, 10) ..lineTo(80, 10); final filledBelowBarPath = Path() ..moveTo(10, 10) ..lineTo(80, 10) ..lineTo(80, 0) ..lineTo(10, 0) ..lineTo(10, 10); lineChartPainter.drawAboveBar( mockCanvasWrapper, aboveBarPath, filledBelowBarPath, holder, lineChartBarData, ); final result = verify(mockCanvasWrapper.drawPath(aboveBarPath, captureAny)) ..called(1); final paint = result.captured.single as Paint; expect(paint.color, const Color(0xFF000000)); expect(paint.shader is ui.Gradient, true); }); test('test 2', () { const viewSize = Size(100, 100); const barSpots = [ FlSpot(1, 9), FlSpot(8, 9), ]; final lineChartBarData = LineChartBarData( spots: barSpots, isStepLineChart: true, aboveBarData: BarAreaData( show: true, gradient: const LinearGradient( colors: [Color(0xFFFF0000), Color(0xFF00FF00)], ), applyCutOffY: true, cutOffY: 8, spotsLine: const BarAreaSpotsLine( show: true, applyCutOffY: false, flLineStyle: FlLine( color: Color(0x00F0F0F0), strokeWidth: 18, ), ), ), ); final data = LineChartData( minY: 0, maxY: 10, minX: 0, maxX: 10, lineBarsData: [lineChartBarData], showingTooltipIndicators: [], titlesData: const FlTitlesData(show: false), ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final aboveBarPath = Path() ..moveTo(10, 10) ..lineTo(80, 10); final filledBelowBarPath = Path() ..moveTo(10, 10) ..lineTo(80, 10) ..lineTo(80, 0) ..lineTo(10, 0) ..lineTo(10, 10); final results = >[]; when( mockCanvasWrapper.drawDashedLine( captureAny, captureAny, captureAny, any, ), ).thenAnswer((inv) { results.add({ 'from': inv.positionalArguments[0] as Offset, 'to': inv.positionalArguments[1] as Offset, 'paint_color': (inv.positionalArguments[2] as Paint).color, 'paint_stroke_width': (inv.positionalArguments[2] as Paint).strokeWidth, }); }); lineChartPainter.drawAboveBar( mockCanvasWrapper, aboveBarPath, filledBelowBarPath, holder, lineChartBarData, ); verify( mockCanvasWrapper.saveLayer( Rect.fromLTWH(0, 0, viewSize.width, viewSize.height), any, ), ).called(1); final result = verify(mockCanvasWrapper.drawPath(aboveBarPath, captureAny)) ..called(1); final paint = result.captured.single as Paint; expect(paint.color, const Color(0xFF000000)); expect(paint.shader is ui.Gradient, true); final result2 = verify(mockCanvasWrapper.drawPath(filledBelowBarPath, captureAny)) ..called(1); final paint2 = result2.captured.single as Paint; expect(paint2.color, const Color(0x00000000)); expect(paint2.blendMode, BlendMode.dstIn); expect(paint2.style, PaintingStyle.fill); verify(mockCanvasWrapper.restore()).called(1); expect(results.length, 2); for (final item in results) { expect((item['paint_color'] as Color).a, 0); expect(item['paint_stroke_width'], 18); } }); }); group('drawBetweenBar()', () { test('test 1', () { const viewSize = Size(100, 100); const barSpots1 = [ FlSpot(1, 9), FlSpot(8, 9), ]; const barSpots2 = [ FlSpot(1, 5), FlSpot(8, 5), ]; final lineChartBarData1 = LineChartBarData( spots: barSpots1, isStepLineChart: true, aboveBarData: BarAreaData( show: true, gradient: const LinearGradient( colors: [Color(0xFFFF0000), Color(0xFF00FF00)], ), ), ); final lineChartBarData2 = LineChartBarData( spots: barSpots2, isStepLineChart: true, ); final betweenBarData1 = BetweenBarsData( fromIndex: 0, toIndex: 1, color: const Color(0xFFFF0000), ); final data = LineChartData( minY: 0, maxY: 10, minX: 0, maxX: 10, lineBarsData: [lineChartBarData1, lineChartBarData2], showingTooltipIndicators: [], titlesData: const FlTitlesData(show: false), betweenBarsData: [betweenBarData1], ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final aboveBarPath = Path() ..moveTo(10, 10) ..lineTo(80, 10); lineChartPainter.drawBetweenBar( mockCanvasWrapper, aboveBarPath, betweenBarData1, MockData.rect1, holder, ); verify( mockCanvasWrapper.saveLayer( Rect.fromLTWH(0, 0, viewSize.width, viewSize.height), any, ), ); final result = verify(mockCanvasWrapper.drawPath(aboveBarPath, captureAny)) ..called(1); final painter = result.captured.single as Paint; expect(painter.color, const Color(0xFFFF0000)); verify(mockCanvasWrapper.restore()); }); }); group('drawBarShadow()', () { test('test 1', () { const barSpots1 = [ FlSpot(1, 9), FlSpot(8, 9), ]; final lineChartBarData1 = LineChartBarData( spots: barSpots1, isStepLineChart: true, shadow: const Shadow(color: Color(0x0000FF00)), ); final lineChartPainter = LineChartPainter(); final mockCanvasWrapper = MockCanvasWrapper(); final barPath = Path() ..moveTo(10, 10) ..lineTo(80, 10); lineChartPainter.drawBarShadow( mockCanvasWrapper, barPath, lineChartBarData1, ); verifyNever(mockCanvasWrapper.drawPath(any, any)); }); test('test 2', () { const viewSize = Size(100, 100); const barSpots1 = [ FlSpot(1, 9), FlSpot(8, 9), ]; final lineChartBarData1 = LineChartBarData( spots: barSpots1, barWidth: 80, isStrokeCapRound: true, isStepLineChart: true, shadow: const Shadow( color: Color(0x0100FF00), offset: Offset(10, 15), blurRadius: 10, ), ); final lineChartPainter = LineChartPainter(); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final barPath = Path() ..moveTo(10, 10) ..lineTo(80, 10); lineChartPainter.drawBarShadow( mockCanvasWrapper, barPath, lineChartBarData1, ); final result = verify(mockCanvasWrapper.drawPath(captureAny, captureAny)) ..called(1); final path = result.captured[0] as Path; expect(path.getBounds(), barPath.shift(const Offset(10, 15)).getBounds()); final paint = result.captured[1] as Paint; expect(paint.color, isSameColorAs(const Color(0x0100FF00))); expect(paint.shader, null); expect(paint.strokeWidth, 80); expect( paint.maskFilter.toString(), MaskFilter.blur(BlurStyle.normal, Utils().convertRadiusToSigma(10)) .toString(), ); expect(paint.strokeCap, StrokeCap.round); }); }); group('drawBar()', () { test('test 1', () { const viewSize = Size(100, 100); const barSpots1 = [ FlSpot(1, 9), FlSpot(8, 9), ]; final lineChartBarData1 = LineChartBarData( show: false, spots: barSpots1, barWidth: 80, isStrokeCapRound: true, isStepLineChart: true, shadow: const Shadow( color: Color(0x0100FF00), offset: Offset(10, 15), blurRadius: 10, ), ); final data = LineChartData( minY: 0, maxY: 10, minX: 0, maxX: 10, lineBarsData: [lineChartBarData1], showingTooltipIndicators: [], titlesData: const FlTitlesData(show: false), ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final barPath = Path() ..moveTo(10, 10) ..lineTo(80, 10); lineChartPainter.drawBar( mockCanvasWrapper, barPath, lineChartBarData1, holder, ); verifyNever(mockCanvasWrapper.drawPath(any, any)); }); test('test 2', () { const viewSize = Size(100, 100); const barSpots1 = [ FlSpot(1, 9), FlSpot(8, 9), ]; final lineChartBarData1 = LineChartBarData( spots: barSpots1, barWidth: 80, isStrokeCapRound: true, isStepLineChart: true, color: const Color(0xF0F0F0F0), shadow: const Shadow( color: Color(0x0100FF00), offset: Offset(10, 15), blurRadius: 10, ), ); final data = LineChartData( minY: 0, maxY: 10, minX: 0, maxX: 10, lineBarsData: [lineChartBarData1], showingTooltipIndicators: [], titlesData: const FlTitlesData(show: false), ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final barPath = Path() ..moveTo(10, 10) ..lineTo(80, 10); lineChartPainter.drawBar( mockCanvasWrapper, barPath, lineChartBarData1, holder, ); final result = verify(mockCanvasWrapper.drawPath(captureAny, captureAny)) ..called(1); final drewPath = result.captured[0] as Path; expect(drewPath, barPath); final paint = result.captured[1] as Paint; expect(paint.color, isSameColorAs(const Color(0xF0F0F0F0))); expect(paint.shader, null); expect(paint.maskFilter, null); expect(paint.strokeWidth, 80); }); test('test 3', () { const viewSize = Size(100, 100); const barSpots1 = [ FlSpot(1, 9), FlSpot(8, 9), ]; final mockLinearGradient = MockLinearGradient(); final lineChartBarData1 = LineChartBarData( spots: barSpots1, barWidth: 80, isStrokeCapRound: true, isStepLineChart: true, gradient: mockLinearGradient, dashArray: [1, 2, 3], ); final data = LineChartData( minY: 0, maxY: 10, minX: 0, maxX: 10, lineBarsData: [lineChartBarData1], showingTooltipIndicators: [], titlesData: const FlTitlesData(show: false), ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); // just to provide a dummy Shader, don't care about its value provideDummy( const LinearGradient(colors: [Color(0xF0F0F0F0), Color(0x0100FF00)]) .createShader(Rect.zero), ); final barPath = Path() ..moveTo(10, 10) ..lineTo(80, 10); lineChartPainter.drawBar( mockCanvasWrapper, barPath, lineChartBarData1, holder, ); final result = verify(mockCanvasWrapper.drawPath(captureAny, captureAny)) ..called(1); final drewPath = result.captured[0] as Path; expect( drewPath.computeMetrics().length, barPath.toDashedPath([1, 2, 3]).computeMetrics().length, ); final paint = result.captured[1] as Paint; expect(paint.shader != null, true); expect(paint.maskFilter, null); expect(paint.strokeWidth, 80); final createShaderWithRectAroundTheLine = verify(mockLinearGradient.createShader(captureAny))..called(1); expect( createShaderWithRectAroundTheLine.captured.single, const Rect.fromLTRB(10, 10, 80, 10), ); lineChartPainter.drawBar( mockCanvasWrapper, barPath, lineChartBarData1.copyWith( gradientArea: LineChartGradientArea.wholeChart, ), holder, ); final createShaderWithRectWholeChart = verify(mockLinearGradient.createShader(captureAny))..called(1); expect( createShaderWithRectWholeChart.captured.single, Offset.zero & viewSize, ); }); }); group('drawExtraLines()', () { test('test 1', () { const viewSize = Size(100, 100); final data = LineChartData( minY: 0, maxY: 10, minX: 0, maxX: 10, titlesData: const FlTitlesData(show: false), ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final mockBuildContext = MockBuildContext(); lineChartPainter.drawExtraLines( mockBuildContext, mockCanvasWrapper, holder, ); verifyNever(mockCanvasWrapper.drawDashedLine(any, any, any, captureAny)); }); test('test 2', () { const viewSize = Size(100, 100); final data = LineChartData( minY: 0, maxY: 10, minX: 0, maxX: 10, titlesData: const FlTitlesData(show: false), ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final mockBuildContext = MockBuildContext(); lineChartPainter.drawExtraLines( mockBuildContext, mockCanvasWrapper, holder, ); verifyNever(mockCanvasWrapper.drawDashedLine(any, any, any, captureAny)); }); test('test 3', () { const viewSize = Size(100, 100); final data = LineChartData( minY: 0, maxY: 10, minX: 0, maxX: 10, titlesData: const FlTitlesData(show: false), extraLinesData: ExtraLinesData( horizontalLines: [ HorizontalLine( y: 1, color: const Color(0x11111111), strokeWidth: 11, ), HorizontalLine( y: 2, color: const Color(0x22222222), strokeWidth: 22, ), ], verticalLines: [ VerticalLine(x: 4, color: const Color(0x33333333), strokeWidth: 33), VerticalLine(x: 5, color: const Color(0x44444444), strokeWidth: 44), ], ), ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final mockBuildContext = MockBuildContext(); final results = >[]; when( mockCanvasWrapper.drawDashedLine( captureAny, captureAny, captureAny, any, ), ).thenAnswer((inv) { results.add({ 'from': inv.positionalArguments[0] as Offset, 'to': inv.positionalArguments[1] as Offset, 'paint_color': (inv.positionalArguments[2] as Paint).color, 'paint_stroke_width': (inv.positionalArguments[2] as Paint).strokeWidth, }); }); lineChartPainter.drawExtraLines( mockBuildContext, mockCanvasWrapper, holder, ); expect(results.length, 4); expect( results[0]['paint_color'], isSameColorAs(const Color(0x11111111)), ); expect(results[0]['paint_stroke_width'], 11); expect(results[0]['from'], const Offset(0, 90)); expect(results[0]['to'], const Offset(100, 90)); expect( results[1]['paint_color'], isSameColorAs(const Color(0x22222222)), ); expect(results[1]['paint_stroke_width'], 22); expect(results[1]['from'], const Offset(0, 80)); expect(results[1]['to'], const Offset(100, 80)); expect( results[2]['paint_color'], isSameColorAs(const Color(0x33333333)), ); expect(results[2]['paint_stroke_width'], 33); expect(results[2]['from'], const Offset(40, 0)); expect(results[2]['to'], const Offset(40, 100)); expect( results[3]['paint_color'], isSameColorAs(const Color(0x44444444)), ); expect(results[3]['paint_stroke_width'], 44); expect(results[3]['from'], const Offset(50, 0)); expect(results[3]['to'], const Offset(50, 100)); }); test('should draw horizontal lines at max and min', () { const viewSize = Size(100, 100); final data = LineChartData( minY: 0, maxY: 10, minX: 0, maxX: 10, titlesData: const FlTitlesData(show: false), extraLinesData: ExtraLinesData( horizontalLines: [ HorizontalLine( y: 0, color: Colors.cyanAccent, dashArray: [12, 22], ), HorizontalLine( y: 10, color: Colors.cyanAccent, dashArray: [12, 22], ), ], extraLinesOnTop: false, ), ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final mockBuildContext = MockBuildContext(); lineChartPainter.drawExtraLines( mockBuildContext, mockCanvasWrapper, holder, ); verify( mockCanvasWrapper.drawDashedLine( any, any, argThat( const TypeMatcher().having( (p0) => p0.color, 'colors match', isSameColorAs(Colors.cyanAccent), ), ), holder.data.extraLinesData.horizontalLines[0].dashArray, ), ).called(2); }); test('should draw vertical lines at max and min', () { const viewSize = Size(100, 100); final data = LineChartData( minY: 0, maxY: 10, minX: 0, maxX: 10, titlesData: const FlTitlesData(show: false), extraLinesData: ExtraLinesData( verticalLines: [ VerticalLine( x: 0, color: Colors.cyanAccent, dashArray: [12, 22], ), VerticalLine( x: 10, color: Colors.cyanAccent, dashArray: [12, 22], ), ], ), ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final mockBuildContext = MockBuildContext(); lineChartPainter.drawExtraLines( mockBuildContext, mockCanvasWrapper, holder, ); verify( mockCanvasWrapper.drawDashedLine( any, any, argThat( const TypeMatcher().having( (p0) => p0.color, 'colors match', isSameColorAs(Colors.cyanAccent), ), ), holder.data.extraLinesData.verticalLines[0].dashArray, ), ).called(2); }); test('should not draw extra lines beyond chart max and min', () { const viewSize = Size(100, 100); final data = LineChartData( minY: -1, maxY: 10, minX: -1, maxX: 10, titlesData: const FlTitlesData(show: false), extraLinesData: ExtraLinesData( verticalLines: [ VerticalLine( x: -1.1, color: Colors.cyanAccent, dashArray: [12, 22], ), VerticalLine( x: 11, color: Colors.cyanAccent, dashArray: [12, 22], ), ], horizontalLines: [ HorizontalLine( y: -1.1, color: Colors.cyanAccent, dashArray: [12, 22], ), HorizontalLine( y: 11, color: Colors.cyanAccent, dashArray: [12, 22], ), ], ), ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final mockBuildContext = MockBuildContext(); lineChartPainter.drawExtraLines( mockBuildContext, mockCanvasWrapper, holder, ); verifyNever( mockCanvasWrapper.drawDashedLine( any, any, argThat( const TypeMatcher().having( (p0) => p0.color, 'colors match', isSameColorAs(Colors.cyanAccent), ), ), holder.data.extraLinesData.verticalLines[0].dashArray, ), ); }); test('test lines label', () { const viewSize = Size(100, 100); String horizontalLabelResolver(HorizontalLine line) { return 'test'; } String verticalLabelResolver(VerticalLine line) { return 'test'; } final data = LineChartData( minY: 0, maxY: 10, minX: 0, maxX: 10, extraLinesData: ExtraLinesData( verticalLines: [ VerticalLine( x: 3, label: VerticalLineLabel( show: true, labelResolver: verticalLabelResolver, ), ), VerticalLine( x: 6, label: VerticalLineLabel( show: true, labelResolver: verticalLabelResolver, direction: LabelDirection.vertical, ), ), VerticalLine( x: 9, label: VerticalLineLabel( show: true, labelResolver: verticalLabelResolver, direction: LabelDirection.verticalMirrored, ), ), ], horizontalLines: [ HorizontalLine( y: 3, label: HorizontalLineLabel( show: true, labelResolver: horizontalLabelResolver, ), ), HorizontalLine( y: 6, label: HorizontalLineLabel( show: true, labelResolver: horizontalLabelResolver, direction: LabelDirection.vertical, ), ), HorizontalLine( y: 9, label: HorizontalLineLabel( show: true, labelResolver: horizontalLabelResolver, direction: LabelDirection.verticalMirrored, ), ), ], ), ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final mockBuildContext = MockBuildContext(); lineChartPainter.drawExtraLines( mockBuildContext, mockCanvasWrapper, holder, ); final result1 = verify(mockCanvasWrapper.drawText(any, captureAny)) ..called(2); final result2 = verify(mockCanvasWrapper.drawVerticalText(any, captureAny)) ..called(2); final offset1 = result1.captured[0] as Offset; final offset2 = result1.captured[1] as Offset; final offset3 = result2.captured[0] as Offset; final offset4 = result2.captured[1] as Offset; expect(offset1, const Offset(6, 50)); expect(offset2, const Offset(36, 80)); expect(offset3, const Offset(20, -22)); expect(offset4, const Offset(80, 38)); }); test( 'should restore canvas before drawing extra lines and clip after ' 'when chart virtual rect is provided', () { const viewSize = Size(100, 100); final data = LineChartData( minY: 0, maxY: 10, minX: 0, maxX: 10, titlesData: const FlTitlesData(show: false), extraLinesData: ExtraLinesData( verticalLines: [ VerticalLine( x: 0, color: Colors.cyanAccent, dashArray: [12, 22], ), VerticalLine( x: 10, color: Colors.cyanAccent, dashArray: [12, 22], ), ], ), ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder( data, data, TextScaler.noScaling, Offset.zero & viewSize, ); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final mockBuildContext = MockBuildContext(); lineChartPainter.drawExtraLines( mockBuildContext, mockCanvasWrapper, holder, ); final viewRect = Offset.zero & viewSize; verifyInOrder([ mockCanvasWrapper.restore(), mockCanvasWrapper.drawDashedLine( any, any, argThat( const TypeMatcher().having( (p0) => p0.color, 'colors match', isSameColorAs(Colors.cyanAccent), ), ), holder.data.extraLinesData.verticalLines[0].dashArray, ), mockCanvasWrapper.saveLayer( viewRect, any, ), mockCanvasWrapper.clipRect(viewRect), ]); }); }); group('drawTouchTooltip()', () { test('test 1', () { const viewSize = Size(100, 100); final barData = LineChartBarData( spots: const [ FlSpot(1, 1), FlSpot(2, 2), FlSpot(3, 3), FlSpot(4, 4), FlSpot.nullSpot, FlSpot(5, 5), ], ); final tooltipData = LineTouchTooltipData( getTooltipColor: (touchedSpot) => const Color(0x11111111), tooltipBorderRadius: const BorderRadius.only( topLeft: Radius.circular(10), topRight: Radius.circular(8), ), rotateAngle: 43, maxContentWidth: 100, tooltipMargin: 12, tooltipPadding: const EdgeInsets.all(12), fitInsideHorizontally: true, fitInsideVertically: true, getTooltipItems: (touchedSpots) { return touchedSpots .map((e) => LineTooltipItem(e.barIndex.toString(), textStyle1)) .toList(); }, tooltipBorder: const BorderSide(color: Color(0x11111111), width: 2), ); final data = LineChartData( minY: 0, maxY: 10, minX: 0, maxX: 10, titlesData: const FlTitlesData(show: false), lineTouchData: LineTouchData( touchTooltipData: tooltipData, ), ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final mockBuildContext = MockBuildContext(); final mockUtils = MockUtils(); Utils.changeInstance(mockUtils); when(mockUtils.getThemeAwareTextStyle(any, any)) .thenAnswer((realInvocation) => textStyle1); when(mockUtils.calculateRotationOffset(any, any)) .thenAnswer((realInvocation) => Offset.zero); when( mockCanvasWrapper.drawRotated( size: anyNamed('size'), rotationOffset: anyNamed('rotationOffset'), drawOffset: anyNamed('drawOffset'), angle: anyNamed('angle'), drawCallback: anyNamed('drawCallback'), ), ).thenAnswer((realInvocation) { final callback = realInvocation .namedArguments[const Symbol('drawCallback')] as DrawCallback; callback(); }); lineChartPainter.drawTouchTooltip( mockBuildContext, mockCanvasWrapper, tooltipData, barData.spots.first, ShowingTooltipIndicators([ LineBarSpot( barData, 0, barData.spots.first, ), ]), holder, ); final result1 = verify(mockCanvasWrapper.drawRRect(captureAny, captureAny)) ..called(2); final rRect = result1.captured[0] as RRect; final paint = result1.captured[1] as Paint; expect( rRect, RRect.fromLTRBAndCorners( 0, 40, 38, 78, topLeft: const Radius.circular(10), topRight: const Radius.circular(8), ), ); expect(paint.color, isSameColorAs(const Color(0x11111111))); final rRectBorder = result1.captured[2] as RRect; final paintBorder = result1.captured[3] as Paint; expect( rRectBorder, RRect.fromLTRBAndCorners( 0, 40, 38, 78, topLeft: const Radius.circular(10), topRight: const Radius.circular(8), ), ); expect(paintBorder.color, isSameColorAs(const Color(0x11111111))); expect(paintBorder.strokeWidth, 2); final result2 = verify(mockCanvasWrapper.drawText(captureAny, captureAny)) ..called(1); final textPainter = result2.captured[0] as TextPainter; final drawOffset = result2.captured[1] as Offset; expect((textPainter.text as TextSpan?)!.text, '0'); expect((textPainter.text as TextSpan?)!.style, textStyle1); expect(drawOffset, const Offset(12, 52)); }); test('test 2', () { const viewSize = Size(100, 100); final barData = LineChartBarData( spots: const [ FlSpot(1, 1), FlSpot(2, 2), FlSpot(3, 3), FlSpot(4, 4), FlSpot.nullSpot, FlSpot(5, 5), ], ); final tooltipData = LineTouchTooltipData( getTooltipColor: (touchedSpot) => const Color(0x11111111), tooltipBorderRadius: BorderRadius.circular(12), rotateAngle: 43, maxContentWidth: 100, tooltipMargin: 12, tooltipHorizontalAlignment: FLHorizontalAlignment.left, tooltipPadding: const EdgeInsets.all(12), fitInsideVertically: true, getTooltipItems: (touchedSpots) { return touchedSpots .map((e) => LineTooltipItem(e.barIndex.toString(), textStyle1)) .toList(); }, tooltipBorder: const BorderSide(color: Color(0x11111111), width: 2), ); final data = LineChartData( minY: 0, maxY: 10, minX: 0, maxX: 10, titlesData: const FlTitlesData(show: false), lineTouchData: LineTouchData( touchTooltipData: tooltipData, ), ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final mockBuildContext = MockBuildContext(); final mockUtils = MockUtils(); Utils.changeInstance(mockUtils); when(mockUtils.getThemeAwareTextStyle(any, any)) .thenAnswer((realInvocation) => textStyle1); when(mockUtils.calculateRotationOffset(any, any)) .thenAnswer((realInvocation) => Offset.zero); when( mockCanvasWrapper.drawRotated( size: anyNamed('size'), rotationOffset: anyNamed('rotationOffset'), drawOffset: anyNamed('drawOffset'), angle: anyNamed('angle'), drawCallback: anyNamed('drawCallback'), ), ).thenAnswer((realInvocation) { final callback = realInvocation .namedArguments[const Symbol('drawCallback')] as DrawCallback; callback(); }); lineChartPainter.drawTouchTooltip( mockBuildContext, mockCanvasWrapper, tooltipData, barData.spots.first, ShowingTooltipIndicators([ LineBarSpot( barData, 0, barData.spots.first, ), ]), holder, ); final result1 = verify(mockCanvasWrapper.drawRRect(captureAny, captureAny)) ..called(2); final rRect = result1.captured[0] as RRect; final paint = result1.captured[1] as Paint; expect( rRect, RRect.fromLTRBR(-28, 40, 10, 78, const Radius.circular(12)), ); expect(paint.color, isSameColorAs(const Color(0x11111111))); final rRectBorder = result1.captured[2] as RRect; final paintBorder = result1.captured[3] as Paint; expect( rRectBorder, RRect.fromLTRBR(-28, 40, 10, 78, const Radius.circular(12)), ); expect(paintBorder.color, isSameColorAs(const Color(0x11111111))); expect(paintBorder.strokeWidth, 2); final result2 = verify(mockCanvasWrapper.drawText(captureAny, captureAny)) ..called(1); final textPainter = result2.captured[0] as TextPainter; final drawOffset = result2.captured[1] as Offset; expect((textPainter.text as TextSpan?)!.text, '0'); expect((textPainter.text as TextSpan?)!.style, textStyle1); expect(drawOffset, const Offset(-16, 52)); }); test('test 3', () { const viewSize = Size(100, 100); final barData = LineChartBarData( spots: const [ FlSpot(1, 1), FlSpot(2, 2), FlSpot(3, 3), FlSpot(4, 4), FlSpot.nullSpot, FlSpot(5, 5), ], ); final tooltipData = LineTouchTooltipData( getTooltipColor: (touchedSpot) => const Color(0x11111111), tooltipBorderRadius: BorderRadius.circular(12), rotateAngle: 43, maxContentWidth: 100, tooltipMargin: 12, tooltipHorizontalAlignment: FLHorizontalAlignment.right, tooltipPadding: const EdgeInsets.all(12), fitInsideVertically: true, getTooltipItems: (touchedSpots) { return touchedSpots .map((e) => LineTooltipItem(e.barIndex.toString(), textStyle1)) .toList(); }, tooltipBorder: const BorderSide(color: Color(0x11111111), width: 2), ); final data = LineChartData( minY: 0, maxY: 10, minX: 0, maxX: 10, titlesData: const FlTitlesData(show: false), lineTouchData: LineTouchData( touchTooltipData: tooltipData, ), ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final mockBuildContext = MockBuildContext(); final mockUtils = MockUtils(); Utils.changeInstance(mockUtils); when(mockUtils.getThemeAwareTextStyle(any, any)) .thenAnswer((realInvocation) => textStyle1); when(mockUtils.calculateRotationOffset(any, any)) .thenAnswer((realInvocation) => Offset.zero); when( mockCanvasWrapper.drawRotated( size: anyNamed('size'), rotationOffset: anyNamed('rotationOffset'), drawOffset: anyNamed('drawOffset'), angle: anyNamed('angle'), drawCallback: anyNamed('drawCallback'), ), ).thenAnswer((realInvocation) { final callback = realInvocation .namedArguments[const Symbol('drawCallback')] as DrawCallback; callback(); }); lineChartPainter.drawTouchTooltip( mockBuildContext, mockCanvasWrapper, tooltipData, barData.spots.first, ShowingTooltipIndicators([ LineBarSpot( barData, 0, barData.spots.first, ), ]), holder, ); final result1 = verify(mockCanvasWrapper.drawRRect(captureAny, captureAny)) ..called(2); final rRect = result1.captured[0] as RRect; final paint = result1.captured[1] as Paint; expect( rRect, RRect.fromLTRBR(10, 40, 48, 78, const Radius.circular(12)), ); expect(paint.color, isSameColorAs(const Color(0x11111111))); final rRectBorder = result1.captured[2] as RRect; final paintBorder = result1.captured[3] as Paint; expect( rRectBorder, RRect.fromLTRBR(10, 40, 48, 78, const Radius.circular(12)), ); expect(paintBorder.color, isSameColorAs(const Color(0x11111111))); expect(paintBorder.strokeWidth, 2); final result2 = verify(mockCanvasWrapper.drawText(captureAny, captureAny)) ..called(1); final textPainter = result2.captured[0] as TextPainter; final drawOffset = result2.captured[1] as Offset; expect((textPainter.text as TextSpan?)!.text, '0'); expect((textPainter.text as TextSpan?)!.style, textStyle1); expect(drawOffset, const Offset(22, 52)); }); test('test 4 - rotated chart with rotationQuarterTurns 2', () { const viewSize = Size(100, 100); final barData1 = LineChartBarData( spots: const [ FlSpot(1, 1), FlSpot(2, 2), FlSpot(3, 3), FlSpot(4, 4), FlSpot.nullSpot, FlSpot(5, 5), ], ); final barData2 = LineChartBarData( spots: const [ FlSpot(1, 6), FlSpot(2, 7), FlSpot(3, 8), FlSpot(4, 9), FlSpot.nullSpot, FlSpot(5, 10), ], ); final tooltipData = LineTouchTooltipData( getTooltipColor: (touchedSpot) => const Color(0x11111111), tooltipBorderRadius: BorderRadius.circular(12), rotateAngle: 43, maxContentWidth: 100, tooltipMargin: 12, tooltipHorizontalAlignment: FLHorizontalAlignment.right, tooltipPadding: const EdgeInsets.all(12), fitInsideVertically: true, getTooltipItems: (touchedSpots) { return touchedSpots .map((e) => LineTooltipItem(e.barIndex.toString(), textStyle1)) .toList(); }, tooltipBorder: const BorderSide(color: Color(0x11111111), width: 2), ); final data = LineChartData( rotationQuarterTurns: 2, minY: 0, maxY: 10, minX: 0, maxX: 10, titlesData: const FlTitlesData(show: false), lineTouchData: LineTouchData( touchTooltipData: tooltipData, ), ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final mockBuildContext = MockBuildContext(); final mockUtils = MockUtils(); Utils.changeInstance(mockUtils); when(mockUtils.getThemeAwareTextStyle(any, any)) .thenAnswer((realInvocation) => textStyle1); when(mockUtils.calculateRotationOffset(any, any)) .thenAnswer((realInvocation) => Offset.zero); when( mockCanvasWrapper.drawRotated( size: anyNamed('size'), rotationOffset: anyNamed('rotationOffset'), drawOffset: anyNamed('drawOffset'), angle: anyNamed('angle'), drawCallback: anyNamed('drawCallback'), ), ).thenAnswer((realInvocation) { final callback = realInvocation .namedArguments[const Symbol('drawCallback')] as DrawCallback; callback(); }); lineChartPainter.drawTouchTooltip( mockBuildContext, mockCanvasWrapper, tooltipData, barData2.spots.first, ShowingTooltipIndicators([ LineBarSpot( barData1, 0, barData1.spots.first, ), LineBarSpot( barData2, 1, barData2.spots.first, ), ]), holder, ); final result1 = verify(mockCanvasWrapper.drawRRect(captureAny, captureAny)) ..called(2); final rRect = result1.captured[0] as RRect; final paint = result1.captured[1] as Paint; expect( rRect, RRect.fromLTRBR(10, 0, 48, 56, const Radius.circular(12)), ); expect(paint.color, isSameColorAs(const Color(0x11111111))); final rRectBorder = result1.captured[2] as RRect; final paintBorder = result1.captured[3] as Paint; expect( rRectBorder, RRect.fromLTRBR(10, 0, 48, 56, const Radius.circular(12)), ); expect(paintBorder.color, isSameColorAs(const Color(0x11111111))); expect(paintBorder.strokeWidth, 2); final result2 = verify(mockCanvasWrapper.drawText(captureAny, captureAny)) ..called(2); final textPainter1 = result2.captured[0] as TextPainter; final drawOffset1 = result2.captured[1] as Offset; final textPainter2 = result2.captured[2] as TextPainter; expect((textPainter1.text as TextSpan?)!.text, '1'); expect((textPainter1.text as TextSpan?)!.style, textStyle1); expect((textPainter2.text as TextSpan?)!.text, '0'); expect((textPainter2.text as TextSpan?)!.style, textStyle1); expect(drawOffset1, const Offset(22, 12)); }); test('does not draw tooltip if it is outside of the chart virtual rect', () { const viewSize = Size(100, 100); final chartVirtualRect = Offset.zero & const Size(200, 200); final barData = LineChartBarData( spots: const [ FlSpot(1, 1), FlSpot(2, 2), FlSpot(3, 3), FlSpot(4, 4), FlSpot.nullSpot, FlSpot(5, 5), ], ); final tooltipData = LineTouchTooltipData( getTooltipColor: (touchedSpot) => const Color(0x11111111), tooltipBorderRadius: BorderRadius.circular(12), rotateAngle: 43, maxContentWidth: 100, tooltipMargin: 12, tooltipHorizontalAlignment: FLHorizontalAlignment.right, tooltipPadding: const EdgeInsets.all(12), fitInsideVertically: true, getTooltipItems: (touchedSpots) { return touchedSpots .map((e) => LineTooltipItem(e.barIndex.toString(), textStyle1)) .toList(); }, tooltipBorder: const BorderSide(color: Color(0x11111111), width: 2), ); final data = LineChartData( minY: 0, maxY: 10, minX: 0, maxX: 10, titlesData: const FlTitlesData(show: false), lineTouchData: LineTouchData( touchTooltipData: tooltipData, ), ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder( data, data, TextScaler.noScaling, chartVirtualRect, ); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final mockBuildContext = MockBuildContext(); final mockUtils = MockUtils(); Utils.changeInstance(mockUtils); when(mockUtils.getThemeAwareTextStyle(any, any)) .thenAnswer((realInvocation) => textStyle1); when(mockUtils.calculateRotationOffset(any, any)) .thenAnswer((realInvocation) => Offset.zero); lineChartPainter.drawTouchTooltip( mockBuildContext, mockCanvasWrapper, tooltipData, barData.spots.first, ShowingTooltipIndicators([ LineBarSpot( barData, 0, barData.spots.first, ), ]), holder, ); verifyNever( mockCanvasWrapper.drawRotated( size: anyNamed('size'), rotationOffset: anyNamed('rotationOffset'), drawOffset: anyNamed('drawOffset'), angle: anyNamed('angle'), drawCallback: anyNamed('drawCallback'), ), ); }); test( 'takes dotHeight into account when deciding if tooltip should be drawn', () { const viewSize = Size(100, 100); const dotRadius = 4.0; const smallerDotRadius = 3.0; const dotStrokeWidth = 1.0; final barData = LineChartBarData( spots: const [ FlSpot(1, 1), FlSpot(2, 2), FlSpot(3, 3), FlSpot(4, 4), FlSpot.nullSpot, FlSpot(5, 5), ], ); final tooltipData = LineTouchTooltipData( getTooltipColor: (touchedSpot) => const Color(0x11111111), tooltipBorderRadius: BorderRadius.circular(12), rotateAngle: 43, maxContentWidth: 100, tooltipMargin: 12, tooltipHorizontalAlignment: FLHorizontalAlignment.right, tooltipPadding: const EdgeInsets.all(12), fitInsideVertically: true, getTooltipItems: (touchedSpots) { return touchedSpots .map((e) => LineTooltipItem(e.barIndex.toString(), textStyle1)) .toList(); }, tooltipBorder: const BorderSide(color: Color(0x11111111), width: 2), ); final data = LineChartData( minY: 0, maxY: 10, minX: 0, maxX: 10, titlesData: const FlTitlesData(show: false), lineBarsData: [barData], lineTouchData: LineTouchData( touchTooltipData: tooltipData, getTouchedSpotIndicator: (barData, spotIndexes) => [ TouchedSpotIndicatorData( const FlLine(color: Colors.red, strokeWidth: 1), FlDotData( getDotPainter: ( spot, xPercentage, bar, index, { size, }) => FlDotCirclePainter( color: Colors.red, // smaller first dot ensures we're actually iterating over // the painters to get the largest dot height radius: index == 0 ? smallerDotRadius : dotRadius, strokeWidth: dotStrokeWidth, ), ), ), ], ), ); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final mockBuildContext = MockBuildContext(); final mockUtils = MockUtils(); Utils.changeInstance(mockUtils); when(mockUtils.getThemeAwareTextStyle(any, any)) .thenAnswer((realInvocation) => textStyle1); when(mockUtils.calculateRotationOffset(any, any)) .thenAnswer((realInvocation) => Offset.zero); final lineChartPainter = LineChartPainter(); const dotHeight = (dotRadius + dotStrokeWidth) * 2; const dotXOffset = 20.0; const scaledSize = Size(200, 100); const dotVisibleXOffset = dotXOffset + (dotHeight / 2); final chartVirtualRect = const Offset(-dotVisibleXOffset, 0) & scaledSize; final indicators = ShowingTooltipIndicators([ LineBarSpot( barData, 0, barData.spots.first, ), LineBarSpot( barData, 0, barData.spots[1], ), ]); final holder = PaintHolder( data, data, TextScaler.noScaling, chartVirtualRect, ); lineChartPainter.drawTouchTooltip( mockBuildContext, mockCanvasWrapper, tooltipData, barData.spots.first, indicators, holder, ); verify( mockCanvasWrapper.drawRotated( size: anyNamed('size'), rotationOffset: anyNamed('rotationOffset'), drawOffset: anyNamed('drawOffset'), angle: anyNamed('angle'), drawCallback: anyNamed('drawCallback'), ), ).called(3); const dotHiddenXOffset = dotXOffset + (dotHeight / 2) + 0.1; final chartVirtualRect2 = const Offset(-dotHiddenXOffset, 0) & scaledSize; final holder2 = PaintHolder( data, data, TextScaler.noScaling, chartVirtualRect2, ); lineChartPainter.drawTouchTooltip( mockBuildContext, mockCanvasWrapper, tooltipData, barData.spots.first, indicators, holder2, ); verifyNever( mockCanvasWrapper.drawRotated( size: anyNamed('size'), rotationOffset: anyNamed('rotationOffset'), drawOffset: anyNamed('drawOffset'), angle: anyNamed('angle'), drawCallback: anyNamed('drawCallback'), ), ); }, ); }); group('getBarLineXLength()', () { test('test 1', () { const viewSize = Size(100, 100); final barData = LineChartBarData( spots: const [ FlSpot(1, 1), FlSpot(2, 2), FlSpot(3, 3), FlSpot(4, 4), FlSpot.nullSpot, FlSpot(5, 5), ], ); final data = LineChartData( minY: 0, maxY: 10, minX: 0, maxX: 10, ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final result = lineChartPainter.getBarLineXLength( barData, viewSize, holder, ); expect(result, 40); }); }); group('handleTouch()', () { test('test 1', () { const viewSize = Size(100, 100); final lineChartBarData1 = LineChartBarData( show: false, spots: const [ FlSpot(1, 1), FlSpot(4, 1), FlSpot(6, 1), FlSpot(8, 1), ], barWidth: 80, isStrokeCapRound: true, isStepLineChart: true, shadow: const Shadow( color: Color(0x0100FF00), offset: Offset(10, 15), blurRadius: 10, ), ); final lineChartBarData2 = LineChartBarData( show: false, spots: const [ FlSpot(1.1, 2), FlSpot(2, 2), FlSpot(3.5, 2), FlSpot(4.3, 2), ], barWidth: 80, isStrokeCapRound: true, isStepLineChart: true, shadow: const Shadow( color: Color(0x0100FF00), offset: Offset(10, 15), blurRadius: 10, ), ); final data = LineChartData( minY: 0, maxY: 10, minX: 0, maxX: 10, lineBarsData: [lineChartBarData1, lineChartBarData2], showingTooltipIndicators: [], titlesData: const FlTitlesData(show: false), lineTouchData: const LineTouchData( touchSpotThreshold: 0.5, ), ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final touchResponse = lineChartPainter.handleTouch(const Offset(35, 0), viewSize, holder); expect(touchResponse, null); }); test('test 2', () { const viewSize = Size(100, 100); final lineChartBarData1 = LineChartBarData( spots: const [ FlSpot(1, 1), FlSpot(4, 1), FlSpot(6, 1), FlSpot(8, 1), ], barWidth: 80, isStrokeCapRound: true, isStepLineChart: true, shadow: const Shadow( color: Color(0x0100FF00), offset: Offset(10, 15), blurRadius: 10, ), ); final lineChartBarData2 = LineChartBarData( spots: const [ FlSpot(1.1, 2), FlSpot(2, 2), FlSpot(3.5, 2), FlSpot(4.3, 2), ], barWidth: 80, isStrokeCapRound: true, isStepLineChart: true, shadow: const Shadow( color: Color(0x0100FF00), offset: Offset(10, 15), blurRadius: 10, ), ); final data = LineChartData( minY: 0, maxY: 10, minX: 0, maxX: 10, lineBarsData: [lineChartBarData1, lineChartBarData2], showingTooltipIndicators: [], titlesData: const FlTitlesData(show: false), lineTouchData: const LineTouchData( touchSpotThreshold: 5, ), ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); expect( lineChartPainter .handleTouch(const Offset(30, 0), viewSize, holder)! .length, 1, ); expect( lineChartPainter.handleTouch( const Offset(29.99, 0), viewSize, holder, ), null, ); expect( lineChartPainter .handleTouch(const Offset(10, 0), viewSize, holder)! .length, 2, ); }); test('test 3', () { const viewSize = Size(100, 100); final lineChartBarData1 = LineChartBarData( spots: const [ FlSpot(1, 1), FlSpot(2, 1), FlSpot(3, 1), FlSpot(8, 1), ], barWidth: 80, isStrokeCapRound: true, isStepLineChart: true, shadow: const Shadow( color: Color(0x0100FF00), offset: Offset(10, 15), blurRadius: 10, ), ); final lineChartBarData2 = LineChartBarData( spots: const [ FlSpot(1.3, 1), FlSpot(2, 1), FlSpot(3, 1), FlSpot(4, 1), ], barWidth: 80, isStrokeCapRound: true, isStepLineChart: true, shadow: const Shadow( color: Color(0x0100FF00), offset: Offset(10, 15), blurRadius: 10, ), ); final data = LineChartData( minY: 0, maxY: 10, minX: 0, maxX: 10, lineBarsData: [lineChartBarData1, lineChartBarData2], showingTooltipIndicators: [], titlesData: const FlTitlesData(show: false), lineTouchData: const LineTouchData( touchSpotThreshold: 5, ), ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final result1 = lineChartPainter.handleTouch(const Offset(11, 0), viewSize, holder)!; expect(result1[0].barIndex, 0); expect(result1[1].barIndex, 1); final result2 = lineChartPainter.handleTouch(const Offset(12, 0), viewSize, holder)!; expect(result2[0].barIndex, 1); expect(result2[1].barIndex, 0); }); }); group('getNearestTouchedSpot()', () { test('test 1', () { const viewSize = Size(100, 100); final lineChartBarData1 = LineChartBarData( show: false, spots: const [ FlSpot(1, 1), FlSpot(4, 1), FlSpot(6, 1), FlSpot(8, 1), ], barWidth: 80, isStrokeCapRound: true, isStepLineChart: true, shadow: const Shadow( color: Color(0x0100FF00), offset: Offset(10, 15), blurRadius: 10, ), ); final lineChartBarData2 = LineChartBarData( show: false, spots: const [ FlSpot(1.1, 2), FlSpot(2, 2), FlSpot(3.5, 2), FlSpot(4.3, 2), ], barWidth: 80, isStrokeCapRound: true, isStepLineChart: true, shadow: const Shadow( color: Color(0x0100FF00), offset: Offset(10, 15), blurRadius: 10, ), ); final data = LineChartData( minY: 0, maxY: 10, minX: 0, maxX: 10, lineBarsData: [lineChartBarData1, lineChartBarData2], showingTooltipIndicators: [], titlesData: const FlTitlesData(show: false), lineTouchData: const LineTouchData( touchSpotThreshold: 0.5, ), ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final touchResponse = lineChartPainter.getNearestTouchedSpot( viewSize, const Offset(35, 0), data.lineBarsData[0], 0, holder, ); expect(touchResponse, null); final touchResponse2 = lineChartPainter.getNearestTouchedSpot( viewSize, const Offset(35, 0), data.lineBarsData[0], 0, holder, ); expect(touchResponse2, null); }); test('test 2', () { const viewSize = Size(100, 100); final lineChartBarData1 = LineChartBarData( spots: const [ FlSpot(1, 1), FlSpot(4, 1), FlSpot(6, 1), FlSpot(8, 1), ], barWidth: 80, isStrokeCapRound: true, isStepLineChart: true, shadow: const Shadow( color: Color(0x0100FF00), offset: Offset(10, 15), blurRadius: 10, ), ); final lineChartBarData2 = LineChartBarData( spots: const [ FlSpot(1.1, 2), FlSpot(2, 2), FlSpot(3.5, 2), FlSpot(4.3, 2), ], barWidth: 80, isStrokeCapRound: true, isStepLineChart: true, shadow: const Shadow( color: Color(0x0100FF00), offset: Offset(10, 15), blurRadius: 10, ), ); final data = LineChartData( minY: 0, maxY: 10, minX: 0, maxX: 10, lineBarsData: [lineChartBarData1, lineChartBarData2], showingTooltipIndicators: [], titlesData: const FlTitlesData(show: false), lineTouchData: const LineTouchData( touchSpotThreshold: 5, ), ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); expect( lineChartPainter.getNearestTouchedSpot( viewSize, const Offset(30, 0), data.lineBarsData[0], 0, holder, ), null, ); final result1 = lineChartPainter.getNearestTouchedSpot( viewSize, const Offset(30, 0), data.lineBarsData[1], 1, holder, ); expect(result1!.barIndex, 1); expect(result1.spotIndex, 2); expect( lineChartPainter.getNearestTouchedSpot( viewSize, const Offset(29.99, 0), data.lineBarsData[0], 0, holder, ), null, ); expect( lineChartPainter.getNearestTouchedSpot( viewSize, const Offset(29.99, 0), data.lineBarsData[1], 1, holder, ), null, ); final result2 = lineChartPainter.getNearestTouchedSpot( viewSize, const Offset(10, 0), data.lineBarsData[0], 0, holder, ); expect(result2!.barIndex, 0); expect(result2.spotIndex, 0); final result3 = lineChartPainter.getNearestTouchedSpot( viewSize, const Offset(10, 0), data.lineBarsData[1], 1, holder, ); expect(result3!.barIndex, 1); expect(result3.spotIndex, 0); }); test('test 3', () { const viewSize = Size(100, 100); final lineChartBarData1 = LineChartBarData( spots: const [ FlSpot(1, 1), FlSpot(4, 1), FlSpot(6, 4), FlSpot(8, 1), ], barWidth: 80, isStrokeCapRound: true, isStepLineChart: true, shadow: const Shadow( color: Color(0x0100FF00), offset: Offset(10, 15), blurRadius: 10, ), ); final lineChartBarData2 = LineChartBarData( spots: const [ FlSpot(1.1, 4), FlSpot(2, 4), FlSpot(3.5, 1), FlSpot(4.3, 4), ], barWidth: 80, isStrokeCapRound: true, isStepLineChart: true, shadow: const Shadow( color: Color(0x0100FF00), offset: Offset(10, 15), blurRadius: 10, ), ); final data = LineChartData( minY: 0, maxY: 10, minX: 0, maxX: 10, lineBarsData: [lineChartBarData1, lineChartBarData2], showingTooltipIndicators: [], titlesData: const FlTitlesData(show: false), lineTouchData: LineTouchData( distanceCalculator: (a, b) { final dx = a.dx - b.dx; final dy = a.dy - b.dy; return math.sqrt(dx * dx + dy * dy); }, touchSpotThreshold: 5, ), ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); expect( lineChartPainter.getNearestTouchedSpot( viewSize, const Offset(30, 0), data.lineBarsData[0], 0, holder, ), null, ); final result1 = lineChartPainter.getNearestTouchedSpot( viewSize, const Offset(60, 65), data.lineBarsData[0], 0, holder, ); expect(result1!.barIndex, 0); expect(result1.spotIndex, 2); expect( lineChartPainter.getNearestTouchedSpot( viewSize, const Offset(60, 65.01), data.lineBarsData[0], 0, holder, ), null, ); expect( lineChartPainter.getNearestTouchedSpot( viewSize, const Offset(29.99, 0), data.lineBarsData[1], 1, holder, ), null, ); final result2 = lineChartPainter.getNearestTouchedSpot( viewSize, const Offset(63.5, 63.5), data.lineBarsData[0], 0, holder, ); expect(result2!.barIndex, 0); expect(result2.spotIndex, 2); }); }); group('drawGrid()', () { test('test 1 - none', () { const viewSize = Size(20, 100); final data = LineChartData( minY: 0, maxY: 10, minX: 0, maxX: 10, titlesData: const FlTitlesData(show: false), gridData: const FlGridData( show: false, horizontalInterval: 2, ), ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final mockUtils = MockUtils(); Utils.changeInstance(mockUtils); when(mockUtils.getBestInitialIntervalValue(any, any, any)) .thenAnswer((realInvocation) => 0); lineChartPainter.drawGrid(mockCanvasWrapper, holder); verifyNever(mockCanvasWrapper.drawDashedLine(any, any, any, any)); }); test('test 2 - horizontal', () { const viewSize = Size(20, 100); final data = LineChartData( minY: 0, maxY: 10, minX: 0, maxX: 10, titlesData: const FlTitlesData(show: false), gridData: FlGridData( drawVerticalLine: false, horizontalInterval: 2, checkToShowHorizontalLine: (value) => value != 2 && value != 8, getDrawingHorizontalLine: (value) { if (value == 4) { return const FlLine( color: MockData.color1, strokeWidth: 11, dashArray: [1, 1], ); } else if (value == 6) { return const FlLine( color: MockData.color2, strokeWidth: 22, dashArray: [2, 2], ); } else { throw StateError("We shouldn't draw these lines"); } }, ), ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final mockUtils = MockUtils(); Utils.changeInstance(mockUtils); when(mockUtils.getBestInitialIntervalValue(any, any, any)) .thenAnswer((realInvocation) => 0); final results = >[]; when( mockCanvasWrapper.drawDashedLine( captureAny, captureAny, captureAny, captureAny, ), ).thenAnswer((inv) { results.add({ 'from': inv.positionalArguments[0] as Offset, 'to': inv.positionalArguments[1] as Offset, 'paint_color': (inv.positionalArguments[2] as Paint).color, 'paint_stroke_width': (inv.positionalArguments[2] as Paint).strokeWidth, 'dash_array': inv.positionalArguments[3] as List, }); }); lineChartPainter.drawGrid(mockCanvasWrapper, holder); expect(results.length, 2); expect(results[0]['from'], const Offset(0, 60)); expect(results[0]['to'], const Offset(20, 60)); expect(results[0]['paint_color'], isSameColorAs(MockData.color1)); expect(results[0]['paint_stroke_width'], 11); expect(results[0]['dash_array'], [1, 1]); expect(results[1]['from'], const Offset(0, 40)); expect(results[1]['to'], const Offset(20, 40)); expect(results[1]['paint_color'], isSameColorAs(MockData.color2)); expect(results[1]['paint_stroke_width'], 22); expect(results[1]['dash_array'], [2, 2]); }); test('test 3 - vertical', () { const viewSize = Size(100, 20); final data = LineChartData( minY: 0, maxY: 10, minX: 0, maxX: 10, titlesData: const FlTitlesData(show: false), gridData: FlGridData( drawHorizontalLine: false, verticalInterval: 2, checkToShowVerticalLine: (value) => value != 2 && value != 8, getDrawingVerticalLine: (value) { if (value == 4) { return const FlLine( color: MockData.color1, strokeWidth: 11, dashArray: [1, 1], ); } else if (value == 6) { return const FlLine( color: MockData.color2, strokeWidth: 22, dashArray: [2, 2], ); } else { throw StateError("We shouldn't draw these lines"); } }, ), ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final mockUtils = MockUtils(); Utils.changeInstance(mockUtils); when(mockUtils.getBestInitialIntervalValue(any, any, any)) .thenAnswer((realInvocation) => 0); final results = >[]; when( mockCanvasWrapper.drawDashedLine( captureAny, captureAny, captureAny, captureAny, ), ).thenAnswer((inv) { results.add({ 'from': inv.positionalArguments[0] as Offset, 'to': inv.positionalArguments[1] as Offset, 'paint_color': (inv.positionalArguments[2] as Paint).color, 'paint_stroke_width': (inv.positionalArguments[2] as Paint).strokeWidth, 'dash_array': inv.positionalArguments[3] as List, }); }); lineChartPainter.drawGrid(mockCanvasWrapper, holder); expect(results.length, 2); expect(results[0]['from'], const Offset(40, 0)); expect(results[0]['to'], const Offset(40, 20)); expect(results[0]['paint_color'], isSameColorAs(MockData.color1)); expect(results[0]['paint_stroke_width'], 11); expect(results[0]['dash_array'], [1, 1]); expect(results[1]['from'], const Offset(60, 0)); expect(results[1]['to'], const Offset(60, 20)); expect(results[1]['paint_color'], isSameColorAs(MockData.color2)); expect(results[1]['paint_stroke_width'], 22); expect(results[1]['dash_array'], [2, 2]); }); test('test 4 - both', () { const viewSize = Size(100, 20); final data = LineChartData( minY: 0, maxY: 10, minX: 0, maxX: 10, ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final mockUtils = MockUtils(); Utils.changeInstance(mockUtils); when(mockUtils.getEfficientInterval(any, any)) .thenAnswer((realInvocation) => 3); when(mockUtils.getBestInitialIntervalValue(any, any, any)) .thenAnswer((realInvocation) => 0); lineChartPainter.drawGrid(mockCanvasWrapper, holder); verify(mockCanvasWrapper.drawDashedLine(any, any, any, any)).called(6); }); }); group('drawBackground()', () { test('test 1', () { const viewSize = Size(20, 100); final data = LineChartData( minY: 0, maxY: 10, minX: 0, maxX: 10, titlesData: const FlTitlesData(show: false), backgroundColor: MockData.color1.withValues(alpha: 0), ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); lineChartPainter.drawBackground(mockCanvasWrapper, holder); verifyNever(mockCanvasWrapper.drawRect(any, any)); }); test('test 2', () { const viewSize = Size(20, 100); final data = LineChartData( minY: 0, maxY: 10, minX: 0, maxX: 10, titlesData: const FlTitlesData(show: false), backgroundColor: MockData.color1, ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); lineChartPainter.drawBackground(mockCanvasWrapper, holder); final result = verify( mockCanvasWrapper.drawRect( const Rect.fromLTRB(0, 0, 20, 100), captureAny, ), ); expect(result.callCount, 1); expect( (result.captured.single as Paint).color, isSameColorAs(MockData.color1), ); }); }); group('drawRangeAnnotation()', () { test('test 1 - none', () { const viewSize = Size(20, 100); final data = LineChartData( minY: 0, maxY: 10, minX: 0, maxX: 10, titlesData: const FlTitlesData(show: false), ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); lineChartPainter.drawRangeAnnotation(mockCanvasWrapper, holder); verifyNever(mockCanvasWrapper.drawRect(any, any)); }); test('test 2 - horizontal', () { const viewSize = Size(20, 100); final data = LineChartData( minY: 0, maxY: 10, minX: 0, maxX: 10, titlesData: const FlTitlesData(show: false), rangeAnnotations: RangeAnnotations( horizontalRangeAnnotations: [ HorizontalRangeAnnotation(y1: 4, y2: 10, color: MockData.color1), HorizontalRangeAnnotation(y1: 12, y2: 14, color: MockData.color2), ], ), ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final results = >[]; when(mockCanvasWrapper.drawRect(captureAny, captureAny)) .thenAnswer((inv) { results.add({ 'rect': inv.positionalArguments[0] as Rect, 'paint_color': (inv.positionalArguments[1] as Paint).color, }); }); lineChartPainter.drawRangeAnnotation(mockCanvasWrapper, holder); expect(results.length, 2); expect(results[0]['rect'], const Rect.fromLTRB(0, 0, 20, 60)); expect(results[0]['paint_color'], isSameColorAs(MockData.color1)); expect(results[1]['rect'], const Rect.fromLTRB(0, -40, 20, -20)); expect(results[1]['paint_color'], isSameColorAs(MockData.color2)); }); test('test 3 - vertical', () { const viewSize = Size(20, 100); final data = LineChartData( minY: 0, maxY: 10, minX: 0, maxX: 10, titlesData: const FlTitlesData(show: false), rangeAnnotations: RangeAnnotations( verticalRangeAnnotations: [ VerticalRangeAnnotation(x1: 1, x2: 2, color: MockData.color1), VerticalRangeAnnotation(x1: 4, x2: 5, color: MockData.color2), ], ), ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final results = >[]; when(mockCanvasWrapper.drawRect(captureAny, captureAny)) .thenAnswer((inv) { results.add({ 'rect': inv.positionalArguments[0] as Rect, 'paint_color': (inv.positionalArguments[1] as Paint).color, }); }); lineChartPainter.drawRangeAnnotation(mockCanvasWrapper, holder); expect(results.length, 2); expect(results[0]['rect'], const Rect.fromLTRB(2, 0, 4, 100)); expect(results[0]['paint_color'], isSameColorAs(MockData.color1)); expect(results[1]['rect'], const Rect.fromLTRB(8, 0, 10, 100)); expect(results[1]['paint_color'], isSameColorAs(MockData.color2)); }); test('test 4 - both', () { const viewSize = Size(20, 100); final data = LineChartData( minY: 0, maxY: 10, minX: 0, maxX: 10, titlesData: const FlTitlesData(show: false), rangeAnnotations: RangeAnnotations( horizontalRangeAnnotations: [ HorizontalRangeAnnotation(y1: 4, y2: 10, color: MockData.color1), HorizontalRangeAnnotation(y1: 12, y2: 14, color: MockData.color2), ], verticalRangeAnnotations: [ VerticalRangeAnnotation(x1: 1, x2: 2, color: MockData.color1), VerticalRangeAnnotation(x1: 4, x2: 5, color: MockData.color2), ], ), ); final lineChartPainter = LineChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); lineChartPainter.drawRangeAnnotation(mockCanvasWrapper, holder); verify(mockCanvasWrapper.drawRect(captureAny, captureAny)).called(4); }); }); } ================================================ FILE: test/chart/line_chart/line_chart_painter_test.mocks.dart ================================================ // Mocks generated by Mockito 5.4.6 from annotations // in fl_chart/test/chart/line_chart/line_chart_painter_test.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:typed_data' as _i5; import 'dart:ui' as _i2; import 'package:fl_chart/fl_chart.dart' as _i7; import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart' as _i11; import 'package:fl_chart/src/chart/line_chart/line_chart_painter.dart' as _i10; import 'package:fl_chart/src/utils/canvas_wrapper.dart' as _i6; import 'package:fl_chart/src/utils/utils.dart' as _i8; import 'package:flutter/cupertino.dart' as _i3; import 'package:flutter/foundation.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; import 'package:mockito/src/dummies.dart' as _i9; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values // ignore_for_file: avoid_setters_without_getters // ignore_for_file: comment_references // ignore_for_file: deprecated_member_use // ignore_for_file: deprecated_member_use_from_same_package // ignore_for_file: implementation_imports // ignore_for_file: invalid_use_of_visible_for_testing_member // ignore_for_file: must_be_immutable // ignore_for_file: prefer_const_constructors // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class // ignore_for_file: invalid_use_of_internal_member class _FakeRect_0 extends _i1.SmartFake implements _i2.Rect { _FakeRect_0( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeCanvas_1 extends _i1.SmartFake implements _i2.Canvas { _FakeCanvas_1( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeSize_2 extends _i1.SmartFake implements _i2.Size { _FakeSize_2( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeWidget_3 extends _i1.SmartFake implements _i3.Widget { _FakeWidget_3( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info}) => super.toString(); } class _FakeInheritedWidget_4 extends _i1.SmartFake implements _i3.InheritedWidget { _FakeInheritedWidget_4( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info}) => super.toString(); } class _FakeDiagnosticsNode_5 extends _i1.SmartFake implements _i3.DiagnosticsNode { _FakeDiagnosticsNode_5( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({ _i4.TextTreeConfiguration? parentConfiguration, _i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info, }) => super.toString(); } class _FakeOffset_6 extends _i1.SmartFake implements _i2.Offset { _FakeOffset_6( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeBorderSide_7 extends _i1.SmartFake implements _i3.BorderSide { _FakeBorderSide_7( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info}) => super.toString(); } class _FakeTextStyle_8 extends _i1.SmartFake implements _i3.TextStyle { _FakeTextStyle_8( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info}) => super.toString(); } class _FakePath_9 extends _i1.SmartFake implements _i2.Path { _FakePath_9( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeAlignmentGeometry_10 extends _i1.SmartFake implements _i3.AlignmentGeometry { _FakeAlignmentGeometry_10( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeLinearGradient_11 extends _i1.SmartFake implements _i3.LinearGradient { _FakeLinearGradient_11( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } /// A class which mocks [Canvas]. /// /// See the documentation for Mockito's code generation for more information. class MockCanvas extends _i1.Mock implements _i2.Canvas { MockCanvas() { _i1.throwOnMissingStub(this); } @override void save() => super.noSuchMethod( Invocation.method( #save, [], ), returnValueForMissingStub: null, ); @override void saveLayer( _i2.Rect? bounds, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #saveLayer, [ bounds, paint, ], ), returnValueForMissingStub: null, ); @override void restore() => super.noSuchMethod( Invocation.method( #restore, [], ), returnValueForMissingStub: null, ); @override void restoreToCount(int? count) => super.noSuchMethod( Invocation.method( #restoreToCount, [count], ), returnValueForMissingStub: null, ); @override int getSaveCount() => (super.noSuchMethod( Invocation.method( #getSaveCount, [], ), returnValue: 0, ) as int); @override void translate( double? dx, double? dy, ) => super.noSuchMethod( Invocation.method( #translate, [ dx, dy, ], ), returnValueForMissingStub: null, ); @override void scale( double? sx, [ double? sy, ]) => super.noSuchMethod( Invocation.method( #scale, [ sx, sy, ], ), returnValueForMissingStub: null, ); @override void rotate(double? radians) => super.noSuchMethod( Invocation.method( #rotate, [radians], ), returnValueForMissingStub: null, ); @override void skew( double? sx, double? sy, ) => super.noSuchMethod( Invocation.method( #skew, [ sx, sy, ], ), returnValueForMissingStub: null, ); @override void transform(_i5.Float64List? matrix4) => super.noSuchMethod( Invocation.method( #transform, [matrix4], ), returnValueForMissingStub: null, ); @override _i5.Float64List getTransform() => (super.noSuchMethod( Invocation.method( #getTransform, [], ), returnValue: _i5.Float64List(0), ) as _i5.Float64List); @override void clipRect( _i2.Rect? rect, { _i2.ClipOp? clipOp = _i2.ClipOp.intersect, bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipRect, [rect], { #clipOp: clipOp, #doAntiAlias: doAntiAlias, }, ), returnValueForMissingStub: null, ); @override void clipRRect( _i2.RRect? rrect, { bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipRRect, [rrect], {#doAntiAlias: doAntiAlias}, ), returnValueForMissingStub: null, ); @override void clipRSuperellipse( _i2.RSuperellipse? rsuperellipse, { bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipRSuperellipse, [rsuperellipse], {#doAntiAlias: doAntiAlias}, ), returnValueForMissingStub: null, ); @override void clipPath( _i2.Path? path, { bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipPath, [path], {#doAntiAlias: doAntiAlias}, ), returnValueForMissingStub: null, ); @override _i2.Rect getLocalClipBounds() => (super.noSuchMethod( Invocation.method( #getLocalClipBounds, [], ), returnValue: _FakeRect_0( this, Invocation.method( #getLocalClipBounds, [], ), ), ) as _i2.Rect); @override _i2.Rect getDestinationClipBounds() => (super.noSuchMethod( Invocation.method( #getDestinationClipBounds, [], ), returnValue: _FakeRect_0( this, Invocation.method( #getDestinationClipBounds, [], ), ), ) as _i2.Rect); @override void drawColor( _i2.Color? color, _i2.BlendMode? blendMode, ) => super.noSuchMethod( Invocation.method( #drawColor, [ color, blendMode, ], ), returnValueForMissingStub: null, ); @override void drawLine( _i2.Offset? p1, _i2.Offset? p2, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawLine, [ p1, p2, paint, ], ), returnValueForMissingStub: null, ); @override void drawPaint(_i2.Paint? paint) => super.noSuchMethod( Invocation.method( #drawPaint, [paint], ), returnValueForMissingStub: null, ); @override void drawRect( _i2.Rect? rect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRect, [ rect, paint, ], ), returnValueForMissingStub: null, ); @override void drawRRect( _i2.RRect? rrect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRRect, [ rrect, paint, ], ), returnValueForMissingStub: null, ); @override void drawDRRect( _i2.RRect? outer, _i2.RRect? inner, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawDRRect, [ outer, inner, paint, ], ), returnValueForMissingStub: null, ); @override void drawRSuperellipse( _i2.RSuperellipse? rsuperellipse, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRSuperellipse, [ rsuperellipse, paint, ], ), returnValueForMissingStub: null, ); @override void drawOval( _i2.Rect? rect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawOval, [ rect, paint, ], ), returnValueForMissingStub: null, ); @override void drawCircle( _i2.Offset? c, double? radius, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawCircle, [ c, radius, paint, ], ), returnValueForMissingStub: null, ); @override void drawArc( _i2.Rect? rect, double? startAngle, double? sweepAngle, bool? useCenter, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawArc, [ rect, startAngle, sweepAngle, useCenter, paint, ], ), returnValueForMissingStub: null, ); @override void drawPath( _i2.Path? path, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawPath, [ path, paint, ], ), returnValueForMissingStub: null, ); @override void drawImage( _i2.Image? image, _i2.Offset? offset, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawImage, [ image, offset, paint, ], ), returnValueForMissingStub: null, ); @override void drawImageRect( _i2.Image? image, _i2.Rect? src, _i2.Rect? dst, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawImageRect, [ image, src, dst, paint, ], ), returnValueForMissingStub: null, ); @override void drawImageNine( _i2.Image? image, _i2.Rect? center, _i2.Rect? dst, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawImageNine, [ image, center, dst, paint, ], ), returnValueForMissingStub: null, ); @override void drawPicture(_i2.Picture? picture) => super.noSuchMethod( Invocation.method( #drawPicture, [picture], ), returnValueForMissingStub: null, ); @override void drawParagraph( _i2.Paragraph? paragraph, _i2.Offset? offset, ) => super.noSuchMethod( Invocation.method( #drawParagraph, [ paragraph, offset, ], ), returnValueForMissingStub: null, ); @override void drawPoints( _i2.PointMode? pointMode, List<_i2.Offset>? points, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawPoints, [ pointMode, points, paint, ], ), returnValueForMissingStub: null, ); @override void drawRawPoints( _i2.PointMode? pointMode, _i5.Float32List? points, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRawPoints, [ pointMode, points, paint, ], ), returnValueForMissingStub: null, ); @override void drawVertices( _i2.Vertices? vertices, _i2.BlendMode? blendMode, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawVertices, [ vertices, blendMode, paint, ], ), returnValueForMissingStub: null, ); @override void drawAtlas( _i2.Image? atlas, List<_i2.RSTransform>? transforms, List<_i2.Rect>? rects, List<_i2.Color>? colors, _i2.BlendMode? blendMode, _i2.Rect? cullRect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawAtlas, [ atlas, transforms, rects, colors, blendMode, cullRect, paint, ], ), returnValueForMissingStub: null, ); @override void drawRawAtlas( _i2.Image? atlas, _i5.Float32List? rstTransforms, _i5.Float32List? rects, _i5.Int32List? colors, _i2.BlendMode? blendMode, _i2.Rect? cullRect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRawAtlas, [ atlas, rstTransforms, rects, colors, blendMode, cullRect, paint, ], ), returnValueForMissingStub: null, ); @override void drawShadow( _i2.Path? path, _i2.Color? color, double? elevation, bool? transparentOccluder, ) => super.noSuchMethod( Invocation.method( #drawShadow, [ path, color, elevation, transparentOccluder, ], ), returnValueForMissingStub: null, ); } /// A class which mocks [CanvasWrapper]. /// /// See the documentation for Mockito's code generation for more information. class MockCanvasWrapper extends _i1.Mock implements _i6.CanvasWrapper { MockCanvasWrapper() { _i1.throwOnMissingStub(this); } @override _i2.Canvas get canvas => (super.noSuchMethod( Invocation.getter(#canvas), returnValue: _FakeCanvas_1( this, Invocation.getter(#canvas), ), ) as _i2.Canvas); @override _i2.Size get size => (super.noSuchMethod( Invocation.getter(#size), returnValue: _FakeSize_2( this, Invocation.getter(#size), ), ) as _i2.Size); @override void drawRRect( _i2.RRect? rrect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRRect, [ rrect, paint, ], ), returnValueForMissingStub: null, ); @override void save() => super.noSuchMethod( Invocation.method( #save, [], ), returnValueForMissingStub: null, ); @override void restore() => super.noSuchMethod( Invocation.method( #restore, [], ), returnValueForMissingStub: null, ); @override void clipRect( _i2.Rect? rect, { _i2.ClipOp? clipOp = _i2.ClipOp.intersect, bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipRect, [rect], { #clipOp: clipOp, #doAntiAlias: doAntiAlias, }, ), returnValueForMissingStub: null, ); @override void translate( double? dx, double? dy, ) => super.noSuchMethod( Invocation.method( #translate, [ dx, dy, ], ), returnValueForMissingStub: null, ); @override void rotate(double? radius) => super.noSuchMethod( Invocation.method( #rotate, [radius], ), returnValueForMissingStub: null, ); @override void drawPath( _i2.Path? path, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawPath, [ path, paint, ], ), returnValueForMissingStub: null, ); @override void saveLayer( _i2.Rect? bounds, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #saveLayer, [ bounds, paint, ], ), returnValueForMissingStub: null, ); @override void drawPicture(_i2.Picture? picture) => super.noSuchMethod( Invocation.method( #drawPicture, [picture], ), returnValueForMissingStub: null, ); @override void drawImage( _i2.Image? image, _i2.Offset? offset, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawImage, [ image, offset, paint, ], ), returnValueForMissingStub: null, ); @override void clipPath( _i2.Path? path, { bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipPath, [path], {#doAntiAlias: doAntiAlias}, ), returnValueForMissingStub: null, ); @override void drawRect( _i2.Rect? rect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRect, [ rect, paint, ], ), returnValueForMissingStub: null, ); @override void drawLine( _i2.Offset? p1, _i2.Offset? p2, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawLine, [ p1, p2, paint, ], ), returnValueForMissingStub: null, ); @override void drawCircle( _i2.Offset? center, double? radius, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawCircle, [ center, radius, paint, ], ), returnValueForMissingStub: null, ); @override void drawArc( _i2.Rect? rect, double? startAngle, double? sweepAngle, bool? useCenter, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawArc, [ rect, startAngle, sweepAngle, useCenter, paint, ], ), returnValueForMissingStub: null, ); @override void drawText( _i3.TextPainter? tp, _i2.Offset? offset, [ double? rotateAngle, ]) => super.noSuchMethod( Invocation.method( #drawText, [ tp, offset, rotateAngle, ], ), returnValueForMissingStub: null, ); @override void drawVerticalText( _i3.TextPainter? tp, _i2.Offset? offset, [ double? rotateAngle = 90.0, ]) => super.noSuchMethod( Invocation.method( #drawVerticalText, [ tp, offset, rotateAngle, ], ), returnValueForMissingStub: null, ); @override void drawDot( _i7.FlDotPainter? painter, _i7.FlSpot? spot, _i2.Offset? offset, ) => super.noSuchMethod( Invocation.method( #drawDot, [ painter, spot, offset, ], ), returnValueForMissingStub: null, ); @override void drawErrorIndicator( _i7.FlSpotErrorRangePainter? painter, _i7.FlSpot? origin, _i2.Offset? offset, _i2.Rect? errorRelativeRect, _i7.AxisChartData? axisData, ) => super.noSuchMethod( Invocation.method( #drawErrorIndicator, [ painter, origin, offset, errorRelativeRect, axisData, ], ), returnValueForMissingStub: null, ); @override void drawRotated({ required _i2.Size? size, _i2.Offset? rotationOffset = _i2.Offset.zero, _i2.Offset? drawOffset = _i2.Offset.zero, required double? angle, required _i6.DrawCallback? drawCallback, }) => super.noSuchMethod( Invocation.method( #drawRotated, [], { #size: size, #rotationOffset: rotationOffset, #drawOffset: drawOffset, #angle: angle, #drawCallback: drawCallback, }, ), returnValueForMissingStub: null, ); @override void drawDashedLine( _i2.Offset? from, _i2.Offset? to, _i2.Paint? painter, List? dashArray, ) => super.noSuchMethod( Invocation.method( #drawDashedLine, [ from, to, painter, dashArray, ], ), returnValueForMissingStub: null, ); } /// A class which mocks [BuildContext]. /// /// See the documentation for Mockito's code generation for more information. class MockBuildContext extends _i1.Mock implements _i3.BuildContext { MockBuildContext() { _i1.throwOnMissingStub(this); } @override _i3.Widget get widget => (super.noSuchMethod( Invocation.getter(#widget), returnValue: _FakeWidget_3( this, Invocation.getter(#widget), ), ) as _i3.Widget); @override bool get mounted => (super.noSuchMethod( Invocation.getter(#mounted), returnValue: false, ) as bool); @override bool get debugDoingBuild => (super.noSuchMethod( Invocation.getter(#debugDoingBuild), returnValue: false, ) as bool); @override _i3.InheritedWidget dependOnInheritedElement( _i3.InheritedElement? ancestor, { Object? aspect, }) => (super.noSuchMethod( Invocation.method( #dependOnInheritedElement, [ancestor], {#aspect: aspect}, ), returnValue: _FakeInheritedWidget_4( this, Invocation.method( #dependOnInheritedElement, [ancestor], {#aspect: aspect}, ), ), ) as _i3.InheritedWidget); @override void visitAncestorElements(_i3.ConditionalElementVisitor? visitor) => super.noSuchMethod( Invocation.method( #visitAncestorElements, [visitor], ), returnValueForMissingStub: null, ); @override void visitChildElements(_i3.ElementVisitor? visitor) => super.noSuchMethod( Invocation.method( #visitChildElements, [visitor], ), returnValueForMissingStub: null, ); @override void dispatchNotification(_i3.Notification? notification) => super.noSuchMethod( Invocation.method( #dispatchNotification, [notification], ), returnValueForMissingStub: null, ); @override _i3.DiagnosticsNode describeElement( String? name, { _i4.DiagnosticsTreeStyle? style = _i4.DiagnosticsTreeStyle.errorProperty, }) => (super.noSuchMethod( Invocation.method( #describeElement, [name], {#style: style}, ), returnValue: _FakeDiagnosticsNode_5( this, Invocation.method( #describeElement, [name], {#style: style}, ), ), ) as _i3.DiagnosticsNode); @override _i3.DiagnosticsNode describeWidget( String? name, { _i4.DiagnosticsTreeStyle? style = _i4.DiagnosticsTreeStyle.errorProperty, }) => (super.noSuchMethod( Invocation.method( #describeWidget, [name], {#style: style}, ), returnValue: _FakeDiagnosticsNode_5( this, Invocation.method( #describeWidget, [name], {#style: style}, ), ), ) as _i3.DiagnosticsNode); @override List<_i3.DiagnosticsNode> describeMissingAncestor( {required Type? expectedAncestorType}) => (super.noSuchMethod( Invocation.method( #describeMissingAncestor, [], {#expectedAncestorType: expectedAncestorType}, ), returnValue: <_i3.DiagnosticsNode>[], ) as List<_i3.DiagnosticsNode>); @override _i3.DiagnosticsNode describeOwnershipChain(String? name) => (super.noSuchMethod( Invocation.method( #describeOwnershipChain, [name], ), returnValue: _FakeDiagnosticsNode_5( this, Invocation.method( #describeOwnershipChain, [name], ), ), ) as _i3.DiagnosticsNode); } /// A class which mocks [Utils]. /// /// See the documentation for Mockito's code generation for more information. class MockUtils extends _i1.Mock implements _i8.Utils { MockUtils() { _i1.throwOnMissingStub(this); } @override double radians(double? degrees) => (super.noSuchMethod( Invocation.method( #radians, [degrees], ), returnValue: 0.0, ) as double); @override double degrees(double? radians) => (super.noSuchMethod( Invocation.method( #degrees, [radians], ), returnValue: 0.0, ) as double); @override double translateRotatedPosition( double? size, double? degree, ) => (super.noSuchMethod( Invocation.method( #translateRotatedPosition, [ size, degree, ], ), returnValue: 0.0, ) as double); @override _i2.Offset calculateRotationOffset( _i2.Size? size, double? degree, ) => (super.noSuchMethod( Invocation.method( #calculateRotationOffset, [ size, degree, ], ), returnValue: _FakeOffset_6( this, Invocation.method( #calculateRotationOffset, [ size, degree, ], ), ), ) as _i2.Offset); @override _i3.BorderRadius? normalizeBorderRadius( _i3.BorderRadius? borderRadius, double? width, ) => (super.noSuchMethod(Invocation.method( #normalizeBorderRadius, [ borderRadius, width, ], )) as _i3.BorderRadius?); @override _i3.BorderSide normalizeBorderSide( _i3.BorderSide? borderSide, double? width, ) => (super.noSuchMethod( Invocation.method( #normalizeBorderSide, [ borderSide, width, ], ), returnValue: _FakeBorderSide_7( this, Invocation.method( #normalizeBorderSide, [ borderSide, width, ], ), ), ) as _i3.BorderSide); @override double getEfficientInterval( double? axisViewSize, double? diffInAxis, { double? pixelPerInterval = 40.0, }) => (super.noSuchMethod( Invocation.method( #getEfficientInterval, [ axisViewSize, diffInAxis, ], {#pixelPerInterval: pixelPerInterval}, ), returnValue: 0.0, ) as double); @override double roundInterval(double? input) => (super.noSuchMethod( Invocation.method( #roundInterval, [input], ), returnValue: 0.0, ) as double); @override int getFractionDigits(double? value) => (super.noSuchMethod( Invocation.method( #getFractionDigits, [value], ), returnValue: 0, ) as int); @override String formatNumber( double? axisMin, double? axisMax, double? axisValue, ) => (super.noSuchMethod( Invocation.method( #formatNumber, [ axisMin, axisMax, axisValue, ], ), returnValue: _i9.dummyValue( this, Invocation.method( #formatNumber, [ axisMin, axisMax, axisValue, ], ), ), ) as String); @override _i3.TextStyle getThemeAwareTextStyle( _i3.BuildContext? context, _i3.TextStyle? providedStyle, ) => (super.noSuchMethod( Invocation.method( #getThemeAwareTextStyle, [ context, providedStyle, ], ), returnValue: _FakeTextStyle_8( this, Invocation.method( #getThemeAwareTextStyle, [ context, providedStyle, ], ), ), ) as _i3.TextStyle); @override double getBestInitialIntervalValue( double? min, double? max, double? interval, { double? baseline = 0.0, }) => (super.noSuchMethod( Invocation.method( #getBestInitialIntervalValue, [ min, max, interval, ], {#baseline: baseline}, ), returnValue: 0.0, ) as double); @override double convertRadiusToSigma(double? radius) => (super.noSuchMethod( Invocation.method( #convertRadiusToSigma, [radius], ), returnValue: 0.0, ) as double); } /// A class which mocks [LineChartPainter]. /// /// See the documentation for Mockito's code generation for more information. class MockLineChartPainter extends _i1.Mock implements _i10.LineChartPainter { MockLineChartPainter() { _i1.throwOnMissingStub(this); } @override void paint( _i3.BuildContext? context, _i6.CanvasWrapper? canvasWrapper, _i11.PaintHolder<_i7.LineChartData>? holder, ) => super.noSuchMethod( Invocation.method( #paint, [ context, canvasWrapper, holder, ], ), returnValueForMissingStub: null, ); @override void clipToBorder( _i6.CanvasWrapper? canvasWrapper, _i11.PaintHolder<_i7.LineChartData>? holder, ) => super.noSuchMethod( Invocation.method( #clipToBorder, [ canvasWrapper, holder, ], ), returnValueForMissingStub: null, ); @override void drawBarLine( _i6.CanvasWrapper? canvasWrapper, _i7.LineChartBarData? barData, _i11.PaintHolder<_i7.LineChartData>? holder, ) => super.noSuchMethod( Invocation.method( #drawBarLine, [ canvasWrapper, barData, holder, ], ), returnValueForMissingStub: null, ); @override void drawBetweenBarsArea( _i6.CanvasWrapper? canvasWrapper, _i7.LineChartData? data, _i7.BetweenBarsData? betweenBarsData, _i11.PaintHolder<_i7.LineChartData>? holder, ) => super.noSuchMethod( Invocation.method( #drawBetweenBarsArea, [ canvasWrapper, data, betweenBarsData, holder, ], ), returnValueForMissingStub: null, ); @override void drawDots( _i6.CanvasWrapper? canvasWrapper, _i7.LineChartBarData? barData, _i11.PaintHolder<_i7.LineChartData>? holder, ) => super.noSuchMethod( Invocation.method( #drawDots, [ canvasWrapper, barData, holder, ], ), returnValueForMissingStub: null, ); @override void drawErrorIndicatorData( _i6.CanvasWrapper? canvasWrapper, _i7.LineChartBarData? barData, _i11.PaintHolder<_i7.LineChartData>? holder, ) => super.noSuchMethod( Invocation.method( #drawErrorIndicatorData, [ canvasWrapper, barData, holder, ], ), returnValueForMissingStub: null, ); @override void drawTouchedSpotsIndicator( _i6.CanvasWrapper? canvasWrapper, List<_i10.LineIndexDrawingInfo>? lineIndexDrawingInfo, _i11.PaintHolder<_i7.LineChartData>? holder, ) => super.noSuchMethod( Invocation.method( #drawTouchedSpotsIndicator, [ canvasWrapper, lineIndexDrawingInfo, holder, ], ), returnValueForMissingStub: null, ); @override _i2.Path generateBarPath( _i2.Size? viewSize, _i7.LineChartBarData? barData, List<_i7.FlSpot>? barSpots, _i11.PaintHolder<_i7.LineChartData>? holder, { _i2.Path? appendToPath, }) => (super.noSuchMethod( Invocation.method( #generateBarPath, [ viewSize, barData, barSpots, holder, ], {#appendToPath: appendToPath}, ), returnValue: _FakePath_9( this, Invocation.method( #generateBarPath, [ viewSize, barData, barSpots, holder, ], {#appendToPath: appendToPath}, ), ), ) as _i2.Path); @override _i2.Path generateNormalBarPath( _i2.Size? viewSize, _i7.LineChartBarData? barData, List<_i7.FlSpot>? barSpots, _i11.PaintHolder<_i7.LineChartData>? holder, { _i2.Path? appendToPath, }) => (super.noSuchMethod( Invocation.method( #generateNormalBarPath, [ viewSize, barData, barSpots, holder, ], {#appendToPath: appendToPath}, ), returnValue: _FakePath_9( this, Invocation.method( #generateNormalBarPath, [ viewSize, barData, barSpots, holder, ], {#appendToPath: appendToPath}, ), ), ) as _i2.Path); @override _i2.Path generateStepBarPath( _i2.Size? viewSize, _i7.LineChartBarData? barData, List<_i7.FlSpot>? barSpots, _i11.PaintHolder<_i7.LineChartData>? holder, { _i2.Path? appendToPath, }) => (super.noSuchMethod( Invocation.method( #generateStepBarPath, [ viewSize, barData, barSpots, holder, ], {#appendToPath: appendToPath}, ), returnValue: _FakePath_9( this, Invocation.method( #generateStepBarPath, [ viewSize, barData, barSpots, holder, ], {#appendToPath: appendToPath}, ), ), ) as _i2.Path); @override _i2.Path generateBelowBarPath( _i2.Size? viewSize, _i7.LineChartBarData? barData, _i2.Path? barPath, List<_i7.FlSpot>? barSpots, _i11.PaintHolder<_i7.LineChartData>? holder, { bool? fillCompletely = false, }) => (super.noSuchMethod( Invocation.method( #generateBelowBarPath, [ viewSize, barData, barPath, barSpots, holder, ], {#fillCompletely: fillCompletely}, ), returnValue: _FakePath_9( this, Invocation.method( #generateBelowBarPath, [ viewSize, barData, barPath, barSpots, holder, ], {#fillCompletely: fillCompletely}, ), ), ) as _i2.Path); @override _i2.Path generateAboveBarPath( _i2.Size? viewSize, _i7.LineChartBarData? barData, _i2.Path? barPath, List<_i7.FlSpot>? barSpots, _i11.PaintHolder<_i7.LineChartData>? holder, { bool? fillCompletely = false, }) => (super.noSuchMethod( Invocation.method( #generateAboveBarPath, [ viewSize, barData, barPath, barSpots, holder, ], {#fillCompletely: fillCompletely}, ), returnValue: _FakePath_9( this, Invocation.method( #generateAboveBarPath, [ viewSize, barData, barPath, barSpots, holder, ], {#fillCompletely: fillCompletely}, ), ), ) as _i2.Path); @override void drawBelowBar( _i6.CanvasWrapper? canvasWrapper, _i2.Path? belowBarPath, _i2.Path? filledAboveBarPath, _i11.PaintHolder<_i7.LineChartData>? holder, _i7.LineChartBarData? barData, ) => super.noSuchMethod( Invocation.method( #drawBelowBar, [ canvasWrapper, belowBarPath, filledAboveBarPath, holder, barData, ], ), returnValueForMissingStub: null, ); @override void drawAboveBar( _i6.CanvasWrapper? canvasWrapper, _i2.Path? aboveBarPath, _i2.Path? filledBelowBarPath, _i11.PaintHolder<_i7.LineChartData>? holder, _i7.LineChartBarData? barData, ) => super.noSuchMethod( Invocation.method( #drawAboveBar, [ canvasWrapper, aboveBarPath, filledBelowBarPath, holder, barData, ], ), returnValueForMissingStub: null, ); @override void drawBetweenBar( _i6.CanvasWrapper? canvasWrapper, _i2.Path? barPath, _i7.BetweenBarsData? betweenBarsData, _i2.Rect? aroundRect, _i11.PaintHolder<_i7.LineChartData>? holder, ) => super.noSuchMethod( Invocation.method( #drawBetweenBar, [ canvasWrapper, barPath, betweenBarsData, aroundRect, holder, ], ), returnValueForMissingStub: null, ); @override void drawBarShadow( _i6.CanvasWrapper? canvasWrapper, _i2.Path? barPath, _i7.LineChartBarData? barData, ) => super.noSuchMethod( Invocation.method( #drawBarShadow, [ canvasWrapper, barPath, barData, ], ), returnValueForMissingStub: null, ); @override void drawBar( _i6.CanvasWrapper? canvasWrapper, _i2.Path? barPath, _i7.LineChartBarData? barData, _i11.PaintHolder<_i7.LineChartData>? holder, ) => super.noSuchMethod( Invocation.method( #drawBar, [ canvasWrapper, barPath, barData, holder, ], ), returnValueForMissingStub: null, ); @override void drawTouchTooltip( _i3.BuildContext? context, _i6.CanvasWrapper? canvasWrapper, _i7.LineTouchTooltipData? tooltipData, _i7.FlSpot? showOnSpot, _i7.ShowingTooltipIndicators? showingTooltipSpots, _i11.PaintHolder<_i7.LineChartData>? holder, ) => super.noSuchMethod( Invocation.method( #drawTouchTooltip, [ context, canvasWrapper, tooltipData, showOnSpot, showingTooltipSpots, holder, ], ), returnValueForMissingStub: null, ); @override double getBarLineXLength( _i7.LineChartBarData? barData, _i2.Size? chartUsableSize, _i11.PaintHolder<_i7.LineChartData>? holder, ) => (super.noSuchMethod( Invocation.method( #getBarLineXLength, [ barData, chartUsableSize, holder, ], ), returnValue: 0.0, ) as double); @override List<_i7.TouchLineBarSpot>? handleTouch( _i2.Offset? localPosition, _i2.Size? size, _i11.PaintHolder<_i7.LineChartData>? holder, ) => (super.noSuchMethod(Invocation.method( #handleTouch, [ localPosition, size, holder, ], )) as List<_i7.TouchLineBarSpot>?); @override _i7.TouchLineBarSpot? getNearestTouchedSpot( _i2.Size? viewSize, _i2.Offset? touchedPoint, _i7.LineChartBarData? barData, int? barDataPosition, _i11.PaintHolder<_i7.LineChartData>? holder, ) => (super.noSuchMethod(Invocation.method( #getNearestTouchedSpot, [ viewSize, touchedPoint, barData, barDataPosition, holder, ], )) as _i7.TouchLineBarSpot?); @override void drawGrid( _i6.CanvasWrapper? canvasWrapper, _i11.PaintHolder<_i7.LineChartData>? holder, ) => super.noSuchMethod( Invocation.method( #drawGrid, [ canvasWrapper, holder, ], ), returnValueForMissingStub: null, ); @override void drawBackground( _i6.CanvasWrapper? canvasWrapper, _i11.PaintHolder<_i7.LineChartData>? holder, ) => super.noSuchMethod( Invocation.method( #drawBackground, [ canvasWrapper, holder, ], ), returnValueForMissingStub: null, ); @override void drawRangeAnnotation( _i6.CanvasWrapper? canvasWrapper, _i11.PaintHolder<_i7.LineChartData>? holder, ) => super.noSuchMethod( Invocation.method( #drawRangeAnnotation, [ canvasWrapper, holder, ], ), returnValueForMissingStub: null, ); @override void drawExtraLines( _i3.BuildContext? context, _i6.CanvasWrapper? canvasWrapper, _i11.PaintHolder<_i7.LineChartData>? holder, ) => super.noSuchMethod( Invocation.method( #drawExtraLines, [ context, canvasWrapper, holder, ], ), returnValueForMissingStub: null, ); @override void drawHorizontalLines( _i3.BuildContext? context, _i6.CanvasWrapper? canvasWrapper, _i11.PaintHolder<_i7.LineChartData>? holder, _i2.Size? viewSize, ) => super.noSuchMethod( Invocation.method( #drawHorizontalLines, [ context, canvasWrapper, holder, viewSize, ], ), returnValueForMissingStub: null, ); @override void drawVerticalLines( _i3.BuildContext? context, _i6.CanvasWrapper? canvasWrapper, _i11.PaintHolder<_i7.LineChartData>? holder, _i2.Size? viewSize, ) => super.noSuchMethod( Invocation.method( #drawVerticalLines, [ context, canvasWrapper, holder, viewSize, ], ), returnValueForMissingStub: null, ); @override double getPixelX( double? spotX, _i2.Size? viewSize, _i11.PaintHolder<_i7.LineChartData>? holder, ) => (super.noSuchMethod( Invocation.method( #getPixelX, [ spotX, viewSize, holder, ], ), returnValue: 0.0, ) as double); @override double getPixelY( double? spotY, _i2.Size? viewSize, _i11.PaintHolder<_i7.LineChartData>? holder, ) => (super.noSuchMethod( Invocation.method( #getPixelY, [ spotY, viewSize, holder, ], ), returnValue: 0.0, ) as double); @override double getXForPixel( double? pixelX, _i2.Size? viewSize, _i11.PaintHolder<_i7.LineChartData>? holder, ) => (super.noSuchMethod( Invocation.method( #getXForPixel, [ pixelX, viewSize, holder, ], ), returnValue: 0.0, ) as double); @override double getYForPixel( double? pixelY, _i2.Size? viewSize, _i11.PaintHolder<_i7.LineChartData>? holder, ) => (super.noSuchMethod( Invocation.method( #getYForPixel, [ pixelY, viewSize, holder, ], ), returnValue: 0.0, ) as double); @override _i2.Offset getChartCoordinateFromPixel( _i2.Offset? pixelOffset, _i2.Size? viewSize, _i11.PaintHolder<_i7.LineChartData>? holder, ) => (super.noSuchMethod( Invocation.method( #getChartCoordinateFromPixel, [ pixelOffset, viewSize, holder, ], ), returnValue: _FakeOffset_6( this, Invocation.method( #getChartCoordinateFromPixel, [ pixelOffset, viewSize, holder, ], ), ), ) as _i2.Offset); @override double getTooltipLeft( double? dx, double? tooltipWidth, _i7.FLHorizontalAlignment? tooltipHorizontalAlignment, double? tooltipHorizontalOffset, ) => (super.noSuchMethod( Invocation.method( #getTooltipLeft, [ dx, tooltipWidth, tooltipHorizontalAlignment, tooltipHorizontalOffset, ], ), returnValue: 0.0, ) as double); } /// A class which mocks [LinearGradient]. /// /// See the documentation for Mockito's code generation for more information. class MockLinearGradient extends _i1.Mock implements _i3.LinearGradient { @override _i3.AlignmentGeometry get begin => (super.noSuchMethod( Invocation.getter(#begin), returnValue: _FakeAlignmentGeometry_10( this, Invocation.getter(#begin), ), returnValueForMissingStub: _FakeAlignmentGeometry_10( this, Invocation.getter(#begin), ), ) as _i3.AlignmentGeometry); @override _i3.AlignmentGeometry get end => (super.noSuchMethod( Invocation.getter(#end), returnValue: _FakeAlignmentGeometry_10( this, Invocation.getter(#end), ), returnValueForMissingStub: _FakeAlignmentGeometry_10( this, Invocation.getter(#end), ), ) as _i3.AlignmentGeometry); @override _i2.TileMode get tileMode => (super.noSuchMethod( Invocation.getter(#tileMode), returnValue: _i2.TileMode.clamp, returnValueForMissingStub: _i2.TileMode.clamp, ) as _i2.TileMode); @override List<_i2.Color> get colors => (super.noSuchMethod( Invocation.getter(#colors), returnValue: <_i2.Color>[], returnValueForMissingStub: <_i2.Color>[], ) as List<_i2.Color>); @override _i2.Shader createShader( _i2.Rect? rect, { _i2.TextDirection? textDirection, }) => (super.noSuchMethod( Invocation.method( #createShader, [rect], {#textDirection: textDirection}, ), returnValue: _i9.dummyValue<_i2.Shader>( this, Invocation.method( #createShader, [rect], {#textDirection: textDirection}, ), ), returnValueForMissingStub: _i9.dummyValue<_i2.Shader>( this, Invocation.method( #createShader, [rect], {#textDirection: textDirection}, ), ), ) as _i2.Shader); @override _i3.LinearGradient scale(double? factor) => (super.noSuchMethod( Invocation.method( #scale, [factor], ), returnValue: _FakeLinearGradient_11( this, Invocation.method( #scale, [factor], ), ), returnValueForMissingStub: _FakeLinearGradient_11( this, Invocation.method( #scale, [factor], ), ), ) as _i3.LinearGradient); @override _i3.Gradient? lerpFrom( _i3.Gradient? a, double? t, ) => (super.noSuchMethod( Invocation.method( #lerpFrom, [ a, t, ], ), returnValueForMissingStub: null, ) as _i3.Gradient?); @override _i3.Gradient? lerpTo( _i3.Gradient? b, double? t, ) => (super.noSuchMethod( Invocation.method( #lerpTo, [ b, t, ], ), returnValueForMissingStub: null, ) as _i3.Gradient?); @override _i3.LinearGradient withOpacity(double? opacity) => (super.noSuchMethod( Invocation.method( #withOpacity, [opacity], ), returnValue: _FakeLinearGradient_11( this, Invocation.method( #withOpacity, [opacity], ), ), returnValueForMissingStub: _FakeLinearGradient_11( this, Invocation.method( #withOpacity, [opacity], ), ), ) as _i3.LinearGradient); } ================================================ FILE: test/chart/line_chart/line_chart_renderer_test.dart ================================================ import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart'; import 'package:fl_chart/src/chart/line_chart/line_chart_painter.dart'; import 'package:fl_chart/src/chart/line_chart/line_chart_renderer.dart'; import 'package:fl_chart/src/utils/canvas_wrapper.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import '../data_pool.dart'; import 'line_chart_renderer_test.mocks.dart'; @GenerateMocks([Canvas, PaintingContext, BuildContext, LineChartPainter]) void main() { group('LineChartRenderer', () { final data = LineChartData( titlesData: const FlTitlesData( leftTitles: AxisTitles( sideTitles: SideTitles(reservedSize: 20, showTitles: true), ), rightTitles: AxisTitles( sideTitles: SideTitles(reservedSize: 464, showTitles: true), ), topTitles: AxisTitles(), bottomTitles: AxisTitles(), ), ); final targetData = LineChartData( titlesData: const FlTitlesData( leftTitles: AxisTitles( sideTitles: SideTitles(reservedSize: 8, showTitles: true), ), rightTitles: AxisTitles( sideTitles: SideTitles(reservedSize: 20, showTitles: true), ), topTitles: AxisTitles(), bottomTitles: AxisTitles(), ), lineTouchData: const LineTouchData(enabled: false), ); const textScaler = TextScaler.linear(4); final mockBuildContext = MockBuildContext(); final renderLineChart = RenderLineChart( mockBuildContext, data, targetData, textScaler, null, canBeScaled: false, ); final mockPainter = MockLineChartPainter(); final mockPaintingContext = MockPaintingContext(); final mockCanvas = MockCanvas(); const mockSize = Size(44, 44); when(mockPaintingContext.canvas).thenAnswer((realInvocation) => mockCanvas); renderLineChart ..mockTestSize = mockSize ..painter = mockPainter; test('test 1 correct data set', () { expect(renderLineChart.data == data, true); expect(renderLineChart.data == targetData, false); expect(renderLineChart.targetData == targetData, true); expect(renderLineChart.textScaler == textScaler, true); expect(renderLineChart.paintHolder.data == data, true); expect(renderLineChart.paintHolder.targetData == targetData, true); expect(renderLineChart.paintHolder.textScaler == textScaler, true); expect(renderLineChart.hitTestSelf(Offset.zero), false); }); test('test 2 check paint function', () { renderLineChart.paint(mockPaintingContext, const Offset(10, 10)); verify(mockCanvas.save()).called(1); verify(mockCanvas.translate(10, 10)).called(1); final result = verify(mockPainter.paint(any, captureAny, captureAny)); expect(result.callCount, 1); final canvasWrapper = result.captured[0] as CanvasWrapper; expect(canvasWrapper.size, const Size(44, 44)); expect(canvasWrapper.canvas, mockCanvas); final paintHolder = result.captured[1] as PaintHolder; expect(paintHolder.data, data); expect(paintHolder.targetData, targetData); expect(paintHolder.textScaler, textScaler); verify(mockCanvas.restore()).called(1); }); test('test 3 check getResponseAtLocation function', () { final results = >[]; when(mockPainter.handleTouch(captureAny, captureAny, captureAny)) .thenAnswer((inv) { results.add({ 'local_position': inv.positionalArguments[0] as Offset, 'size': inv.positionalArguments[1] as Size, 'paint_holder': inv.positionalArguments[2] as PaintHolder, }); return MockData.lineTouchResponse1.lineBarSpots; }); when(mockPainter.getChartCoordinateFromPixel(any, any, any)) .thenAnswer((_) => const Offset(10, 10)); final touchResponse = renderLineChart.getResponseAtLocation(MockData.offset1); expect( touchResponse.lineBarSpots, MockData.lineTouchResponse1.lineBarSpots, ); expect(touchResponse.touchChartCoordinate, const Offset(10, 10)); expect(results[0]['local_position'] as Offset, MockData.offset1); expect(results[0]['size'] as Size, mockSize); final paintHolder = results[0]['paint_holder'] as PaintHolder; expect(paintHolder.data, data); expect(paintHolder.targetData, targetData); expect(paintHolder.textScaler, textScaler); }); test('test 4 check setters', () { renderLineChart ..data = targetData ..targetData = data ..textScaler = const TextScaler.linear(22); expect(renderLineChart.data, targetData); expect(renderLineChart.targetData, data); expect(renderLineChart.textScaler, const TextScaler.linear(22)); }); test('passes chart virtual rect to paint holder', () { final rect1 = Offset.zero & const Size(100, 100); final renderLineChart = RenderLineChart( mockBuildContext, data, targetData, textScaler, null, canBeScaled: false, ); expect(renderLineChart.chartVirtualRect, isNull); expect(renderLineChart.paintHolder.chartVirtualRect, isNull); renderLineChart.chartVirtualRect = rect1; expect(renderLineChart.chartVirtualRect, rect1); expect(renderLineChart.paintHolder.chartVirtualRect, rect1); }); test('uses canBeScaled', () { final renderLineChart = RenderLineChart( mockBuildContext, data, targetData, textScaler, null, canBeScaled: false, ); expect(renderLineChart.canBeScaled, false); renderLineChart.canBeScaled = true; expect(renderLineChart.canBeScaled, true); }); }); } ================================================ FILE: test/chart/line_chart/line_chart_renderer_test.mocks.dart ================================================ // Mocks generated by Mockito 5.4.6 from annotations // in fl_chart/test/chart/line_chart/line_chart_renderer_test.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:typed_data' as _i7; import 'dart:ui' as _i2; import 'package:fl_chart/fl_chart.dart' as _i13; import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart' as _i12; import 'package:fl_chart/src/chart/line_chart/line_chart_painter.dart' as _i10; import 'package:fl_chart/src/utils/canvas_wrapper.dart' as _i11; import 'package:flutter/foundation.dart' as _i5; import 'package:flutter/gestures.dart' as _i8; import 'package:flutter/material.dart' as _i6; import 'package:flutter/rendering.dart' as _i3; import 'package:flutter/src/rendering/layer.dart' as _i4; import 'package:flutter/src/widgets/notification_listener.dart' as _i9; import 'package:mockito/mockito.dart' as _i1; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values // ignore_for_file: avoid_setters_without_getters // ignore_for_file: comment_references // ignore_for_file: deprecated_member_use // ignore_for_file: deprecated_member_use_from_same_package // ignore_for_file: implementation_imports // ignore_for_file: invalid_use_of_visible_for_testing_member // ignore_for_file: must_be_immutable // ignore_for_file: prefer_const_constructors // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class // ignore_for_file: invalid_use_of_internal_member class _FakeRect_0 extends _i1.SmartFake implements _i2.Rect { _FakeRect_0( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeCanvas_1 extends _i1.SmartFake implements _i2.Canvas { _FakeCanvas_1( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakePaintingContext_2 extends _i1.SmartFake implements _i3.PaintingContext { _FakePaintingContext_2( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeColorFilterLayer_3 extends _i1.SmartFake implements _i4.ColorFilterLayer { _FakeColorFilterLayer_3( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => super.toString(); } class _FakeOpacityLayer_4 extends _i1.SmartFake implements _i4.OpacityLayer { _FakeOpacityLayer_4( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => super.toString(); } class _FakeWidget_5 extends _i1.SmartFake implements _i6.Widget { _FakeWidget_5( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => super.toString(); } class _FakeInheritedWidget_6 extends _i1.SmartFake implements _i6.InheritedWidget { _FakeInheritedWidget_6( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => super.toString(); } class _FakeDiagnosticsNode_7 extends _i1.SmartFake implements _i5.DiagnosticsNode { _FakeDiagnosticsNode_7( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({ _i5.TextTreeConfiguration? parentConfiguration, _i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info, }) => super.toString(); } class _FakePath_8 extends _i1.SmartFake implements _i2.Path { _FakePath_8( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeOffset_9 extends _i1.SmartFake implements _i2.Offset { _FakeOffset_9( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } /// A class which mocks [Canvas]. /// /// See the documentation for Mockito's code generation for more information. class MockCanvas extends _i1.Mock implements _i2.Canvas { MockCanvas() { _i1.throwOnMissingStub(this); } @override void save() => super.noSuchMethod( Invocation.method( #save, [], ), returnValueForMissingStub: null, ); @override void saveLayer( _i2.Rect? bounds, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #saveLayer, [ bounds, paint, ], ), returnValueForMissingStub: null, ); @override void restore() => super.noSuchMethod( Invocation.method( #restore, [], ), returnValueForMissingStub: null, ); @override void restoreToCount(int? count) => super.noSuchMethod( Invocation.method( #restoreToCount, [count], ), returnValueForMissingStub: null, ); @override int getSaveCount() => (super.noSuchMethod( Invocation.method( #getSaveCount, [], ), returnValue: 0, ) as int); @override void translate( double? dx, double? dy, ) => super.noSuchMethod( Invocation.method( #translate, [ dx, dy, ], ), returnValueForMissingStub: null, ); @override void scale( double? sx, [ double? sy, ]) => super.noSuchMethod( Invocation.method( #scale, [ sx, sy, ], ), returnValueForMissingStub: null, ); @override void rotate(double? radians) => super.noSuchMethod( Invocation.method( #rotate, [radians], ), returnValueForMissingStub: null, ); @override void skew( double? sx, double? sy, ) => super.noSuchMethod( Invocation.method( #skew, [ sx, sy, ], ), returnValueForMissingStub: null, ); @override void transform(_i7.Float64List? matrix4) => super.noSuchMethod( Invocation.method( #transform, [matrix4], ), returnValueForMissingStub: null, ); @override _i7.Float64List getTransform() => (super.noSuchMethod( Invocation.method( #getTransform, [], ), returnValue: _i7.Float64List(0), ) as _i7.Float64List); @override void clipRect( _i2.Rect? rect, { _i2.ClipOp? clipOp = _i2.ClipOp.intersect, bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipRect, [rect], { #clipOp: clipOp, #doAntiAlias: doAntiAlias, }, ), returnValueForMissingStub: null, ); @override void clipRRect( _i2.RRect? rrect, { bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipRRect, [rrect], {#doAntiAlias: doAntiAlias}, ), returnValueForMissingStub: null, ); @override void clipRSuperellipse( _i2.RSuperellipse? rsuperellipse, { bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipRSuperellipse, [rsuperellipse], {#doAntiAlias: doAntiAlias}, ), returnValueForMissingStub: null, ); @override void clipPath( _i2.Path? path, { bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipPath, [path], {#doAntiAlias: doAntiAlias}, ), returnValueForMissingStub: null, ); @override _i2.Rect getLocalClipBounds() => (super.noSuchMethod( Invocation.method( #getLocalClipBounds, [], ), returnValue: _FakeRect_0( this, Invocation.method( #getLocalClipBounds, [], ), ), ) as _i2.Rect); @override _i2.Rect getDestinationClipBounds() => (super.noSuchMethod( Invocation.method( #getDestinationClipBounds, [], ), returnValue: _FakeRect_0( this, Invocation.method( #getDestinationClipBounds, [], ), ), ) as _i2.Rect); @override void drawColor( _i2.Color? color, _i2.BlendMode? blendMode, ) => super.noSuchMethod( Invocation.method( #drawColor, [ color, blendMode, ], ), returnValueForMissingStub: null, ); @override void drawLine( _i2.Offset? p1, _i2.Offset? p2, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawLine, [ p1, p2, paint, ], ), returnValueForMissingStub: null, ); @override void drawPaint(_i2.Paint? paint) => super.noSuchMethod( Invocation.method( #drawPaint, [paint], ), returnValueForMissingStub: null, ); @override void drawRect( _i2.Rect? rect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRect, [ rect, paint, ], ), returnValueForMissingStub: null, ); @override void drawRRect( _i2.RRect? rrect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRRect, [ rrect, paint, ], ), returnValueForMissingStub: null, ); @override void drawDRRect( _i2.RRect? outer, _i2.RRect? inner, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawDRRect, [ outer, inner, paint, ], ), returnValueForMissingStub: null, ); @override void drawRSuperellipse( _i2.RSuperellipse? rsuperellipse, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRSuperellipse, [ rsuperellipse, paint, ], ), returnValueForMissingStub: null, ); @override void drawOval( _i2.Rect? rect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawOval, [ rect, paint, ], ), returnValueForMissingStub: null, ); @override void drawCircle( _i2.Offset? c, double? radius, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawCircle, [ c, radius, paint, ], ), returnValueForMissingStub: null, ); @override void drawArc( _i2.Rect? rect, double? startAngle, double? sweepAngle, bool? useCenter, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawArc, [ rect, startAngle, sweepAngle, useCenter, paint, ], ), returnValueForMissingStub: null, ); @override void drawPath( _i2.Path? path, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawPath, [ path, paint, ], ), returnValueForMissingStub: null, ); @override void drawImage( _i2.Image? image, _i2.Offset? offset, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawImage, [ image, offset, paint, ], ), returnValueForMissingStub: null, ); @override void drawImageRect( _i2.Image? image, _i2.Rect? src, _i2.Rect? dst, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawImageRect, [ image, src, dst, paint, ], ), returnValueForMissingStub: null, ); @override void drawImageNine( _i2.Image? image, _i2.Rect? center, _i2.Rect? dst, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawImageNine, [ image, center, dst, paint, ], ), returnValueForMissingStub: null, ); @override void drawPicture(_i2.Picture? picture) => super.noSuchMethod( Invocation.method( #drawPicture, [picture], ), returnValueForMissingStub: null, ); @override void drawParagraph( _i2.Paragraph? paragraph, _i2.Offset? offset, ) => super.noSuchMethod( Invocation.method( #drawParagraph, [ paragraph, offset, ], ), returnValueForMissingStub: null, ); @override void drawPoints( _i2.PointMode? pointMode, List<_i2.Offset>? points, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawPoints, [ pointMode, points, paint, ], ), returnValueForMissingStub: null, ); @override void drawRawPoints( _i2.PointMode? pointMode, _i7.Float32List? points, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRawPoints, [ pointMode, points, paint, ], ), returnValueForMissingStub: null, ); @override void drawVertices( _i2.Vertices? vertices, _i2.BlendMode? blendMode, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawVertices, [ vertices, blendMode, paint, ], ), returnValueForMissingStub: null, ); @override void drawAtlas( _i2.Image? atlas, List<_i2.RSTransform>? transforms, List<_i2.Rect>? rects, List<_i2.Color>? colors, _i2.BlendMode? blendMode, _i2.Rect? cullRect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawAtlas, [ atlas, transforms, rects, colors, blendMode, cullRect, paint, ], ), returnValueForMissingStub: null, ); @override void drawRawAtlas( _i2.Image? atlas, _i7.Float32List? rstTransforms, _i7.Float32List? rects, _i7.Int32List? colors, _i2.BlendMode? blendMode, _i2.Rect? cullRect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRawAtlas, [ atlas, rstTransforms, rects, colors, blendMode, cullRect, paint, ], ), returnValueForMissingStub: null, ); @override void drawShadow( _i2.Path? path, _i2.Color? color, double? elevation, bool? transparentOccluder, ) => super.noSuchMethod( Invocation.method( #drawShadow, [ path, color, elevation, transparentOccluder, ], ), returnValueForMissingStub: null, ); } /// A class which mocks [PaintingContext]. /// /// See the documentation for Mockito's code generation for more information. class MockPaintingContext extends _i1.Mock implements _i3.PaintingContext { MockPaintingContext() { _i1.throwOnMissingStub(this); } @override _i2.Rect get estimatedBounds => (super.noSuchMethod( Invocation.getter(#estimatedBounds), returnValue: _FakeRect_0( this, Invocation.getter(#estimatedBounds), ), ) as _i2.Rect); @override _i2.Canvas get canvas => (super.noSuchMethod( Invocation.getter(#canvas), returnValue: _FakeCanvas_1( this, Invocation.getter(#canvas), ), ) as _i2.Canvas); @override void paintChild( _i3.RenderObject? child, _i2.Offset? offset, ) => super.noSuchMethod( Invocation.method( #paintChild, [ child, offset, ], ), returnValueForMissingStub: null, ); @override void appendLayer(_i4.Layer? layer) => super.noSuchMethod( Invocation.method( #appendLayer, [layer], ), returnValueForMissingStub: null, ); @override _i2.VoidCallback addCompositionCallback(_i4.CompositionCallback? callback) => (super.noSuchMethod( Invocation.method( #addCompositionCallback, [callback], ), returnValue: () {}, ) as _i2.VoidCallback); @override void stopRecordingIfNeeded() => super.noSuchMethod( Invocation.method( #stopRecordingIfNeeded, [], ), returnValueForMissingStub: null, ); @override void setIsComplexHint() => super.noSuchMethod( Invocation.method( #setIsComplexHint, [], ), returnValueForMissingStub: null, ); @override void setWillChangeHint() => super.noSuchMethod( Invocation.method( #setWillChangeHint, [], ), returnValueForMissingStub: null, ); @override void addLayer(_i4.Layer? layer) => super.noSuchMethod( Invocation.method( #addLayer, [layer], ), returnValueForMissingStub: null, ); @override void pushLayer( _i4.ContainerLayer? childLayer, _i3.PaintingContextCallback? painter, _i2.Offset? offset, { _i2.Rect? childPaintBounds, }) => super.noSuchMethod( Invocation.method( #pushLayer, [ childLayer, painter, offset, ], {#childPaintBounds: childPaintBounds}, ), returnValueForMissingStub: null, ); @override _i3.PaintingContext createChildContext( _i4.ContainerLayer? childLayer, _i2.Rect? bounds, ) => (super.noSuchMethod( Invocation.method( #createChildContext, [ childLayer, bounds, ], ), returnValue: _FakePaintingContext_2( this, Invocation.method( #createChildContext, [ childLayer, bounds, ], ), ), ) as _i3.PaintingContext); @override _i4.ClipRectLayer? pushClipRect( bool? needsCompositing, _i2.Offset? offset, _i2.Rect? clipRect, _i3.PaintingContextCallback? painter, { _i2.Clip? clipBehavior = _i2.Clip.hardEdge, _i4.ClipRectLayer? oldLayer, }) => (super.noSuchMethod(Invocation.method( #pushClipRect, [ needsCompositing, offset, clipRect, painter, ], { #clipBehavior: clipBehavior, #oldLayer: oldLayer, }, )) as _i4.ClipRectLayer?); @override _i4.ClipRRectLayer? pushClipRRect( bool? needsCompositing, _i2.Offset? offset, _i2.Rect? bounds, _i2.RRect? clipRRect, _i3.PaintingContextCallback? painter, { _i2.Clip? clipBehavior = _i2.Clip.antiAlias, _i4.ClipRRectLayer? oldLayer, }) => (super.noSuchMethod(Invocation.method( #pushClipRRect, [ needsCompositing, offset, bounds, clipRRect, painter, ], { #clipBehavior: clipBehavior, #oldLayer: oldLayer, }, )) as _i4.ClipRRectLayer?); @override _i4.ClipRSuperellipseLayer? pushClipRSuperellipse( bool? needsCompositing, _i2.Offset? offset, _i2.Rect? bounds, _i2.RSuperellipse? clipRSuperellipse, _i3.PaintingContextCallback? painter, { _i2.Clip? clipBehavior = _i2.Clip.antiAlias, _i4.ClipRSuperellipseLayer? oldLayer, }) => (super.noSuchMethod(Invocation.method( #pushClipRSuperellipse, [ needsCompositing, offset, bounds, clipRSuperellipse, painter, ], { #clipBehavior: clipBehavior, #oldLayer: oldLayer, }, )) as _i4.ClipRSuperellipseLayer?); @override _i4.ClipPathLayer? pushClipPath( bool? needsCompositing, _i2.Offset? offset, _i2.Rect? bounds, _i2.Path? clipPath, _i3.PaintingContextCallback? painter, { _i2.Clip? clipBehavior = _i2.Clip.antiAlias, _i4.ClipPathLayer? oldLayer, }) => (super.noSuchMethod(Invocation.method( #pushClipPath, [ needsCompositing, offset, bounds, clipPath, painter, ], { #clipBehavior: clipBehavior, #oldLayer: oldLayer, }, )) as _i4.ClipPathLayer?); @override _i4.ColorFilterLayer pushColorFilter( _i2.Offset? offset, _i2.ColorFilter? colorFilter, _i3.PaintingContextCallback? painter, { _i4.ColorFilterLayer? oldLayer, }) => (super.noSuchMethod( Invocation.method( #pushColorFilter, [ offset, colorFilter, painter, ], {#oldLayer: oldLayer}, ), returnValue: _FakeColorFilterLayer_3( this, Invocation.method( #pushColorFilter, [ offset, colorFilter, painter, ], {#oldLayer: oldLayer}, ), ), ) as _i4.ColorFilterLayer); @override _i4.TransformLayer? pushTransform( bool? needsCompositing, _i2.Offset? offset, _i8.Matrix4? transform, _i3.PaintingContextCallback? painter, { _i4.TransformLayer? oldLayer, }) => (super.noSuchMethod(Invocation.method( #pushTransform, [ needsCompositing, offset, transform, painter, ], {#oldLayer: oldLayer}, )) as _i4.TransformLayer?); @override _i4.OpacityLayer pushOpacity( _i2.Offset? offset, int? alpha, _i3.PaintingContextCallback? painter, { _i4.OpacityLayer? oldLayer, }) => (super.noSuchMethod( Invocation.method( #pushOpacity, [ offset, alpha, painter, ], {#oldLayer: oldLayer}, ), returnValue: _FakeOpacityLayer_4( this, Invocation.method( #pushOpacity, [ offset, alpha, painter, ], {#oldLayer: oldLayer}, ), ), ) as _i4.OpacityLayer); @override void clipPathAndPaint( _i2.Path? path, _i2.Clip? clipBehavior, _i2.Rect? bounds, _i2.VoidCallback? painter, ) => super.noSuchMethod( Invocation.method( #clipPathAndPaint, [ path, clipBehavior, bounds, painter, ], ), returnValueForMissingStub: null, ); @override void clipRRectAndPaint( _i2.RRect? rrect, _i2.Clip? clipBehavior, _i2.Rect? bounds, _i2.VoidCallback? painter, ) => super.noSuchMethod( Invocation.method( #clipRRectAndPaint, [ rrect, clipBehavior, bounds, painter, ], ), returnValueForMissingStub: null, ); @override void clipRSuperellipseAndPaint( _i2.RSuperellipse? rse, _i2.Clip? clipBehavior, _i2.Rect? bounds, _i2.VoidCallback? painter, ) => super.noSuchMethod( Invocation.method( #clipRSuperellipseAndPaint, [ rse, clipBehavior, bounds, painter, ], ), returnValueForMissingStub: null, ); @override void clipRectAndPaint( _i2.Rect? rect, _i2.Clip? clipBehavior, _i2.Rect? bounds, _i2.VoidCallback? painter, ) => super.noSuchMethod( Invocation.method( #clipRectAndPaint, [ rect, clipBehavior, bounds, painter, ], ), returnValueForMissingStub: null, ); } /// A class which mocks [BuildContext]. /// /// See the documentation for Mockito's code generation for more information. class MockBuildContext extends _i1.Mock implements _i6.BuildContext { MockBuildContext() { _i1.throwOnMissingStub(this); } @override _i6.Widget get widget => (super.noSuchMethod( Invocation.getter(#widget), returnValue: _FakeWidget_5( this, Invocation.getter(#widget), ), ) as _i6.Widget); @override bool get mounted => (super.noSuchMethod( Invocation.getter(#mounted), returnValue: false, ) as bool); @override bool get debugDoingBuild => (super.noSuchMethod( Invocation.getter(#debugDoingBuild), returnValue: false, ) as bool); @override _i6.InheritedWidget dependOnInheritedElement( _i6.InheritedElement? ancestor, { Object? aspect, }) => (super.noSuchMethod( Invocation.method( #dependOnInheritedElement, [ancestor], {#aspect: aspect}, ), returnValue: _FakeInheritedWidget_6( this, Invocation.method( #dependOnInheritedElement, [ancestor], {#aspect: aspect}, ), ), ) as _i6.InheritedWidget); @override void visitAncestorElements(_i6.ConditionalElementVisitor? visitor) => super.noSuchMethod( Invocation.method( #visitAncestorElements, [visitor], ), returnValueForMissingStub: null, ); @override void visitChildElements(_i6.ElementVisitor? visitor) => super.noSuchMethod( Invocation.method( #visitChildElements, [visitor], ), returnValueForMissingStub: null, ); @override void dispatchNotification(_i9.Notification? notification) => super.noSuchMethod( Invocation.method( #dispatchNotification, [notification], ), returnValueForMissingStub: null, ); @override _i5.DiagnosticsNode describeElement( String? name, { _i5.DiagnosticsTreeStyle? style = _i5.DiagnosticsTreeStyle.errorProperty, }) => (super.noSuchMethod( Invocation.method( #describeElement, [name], {#style: style}, ), returnValue: _FakeDiagnosticsNode_7( this, Invocation.method( #describeElement, [name], {#style: style}, ), ), ) as _i5.DiagnosticsNode); @override _i5.DiagnosticsNode describeWidget( String? name, { _i5.DiagnosticsTreeStyle? style = _i5.DiagnosticsTreeStyle.errorProperty, }) => (super.noSuchMethod( Invocation.method( #describeWidget, [name], {#style: style}, ), returnValue: _FakeDiagnosticsNode_7( this, Invocation.method( #describeWidget, [name], {#style: style}, ), ), ) as _i5.DiagnosticsNode); @override List<_i5.DiagnosticsNode> describeMissingAncestor( {required Type? expectedAncestorType}) => (super.noSuchMethod( Invocation.method( #describeMissingAncestor, [], {#expectedAncestorType: expectedAncestorType}, ), returnValue: <_i5.DiagnosticsNode>[], ) as List<_i5.DiagnosticsNode>); @override _i5.DiagnosticsNode describeOwnershipChain(String? name) => (super.noSuchMethod( Invocation.method( #describeOwnershipChain, [name], ), returnValue: _FakeDiagnosticsNode_7( this, Invocation.method( #describeOwnershipChain, [name], ), ), ) as _i5.DiagnosticsNode); } /// A class which mocks [LineChartPainter]. /// /// See the documentation for Mockito's code generation for more information. class MockLineChartPainter extends _i1.Mock implements _i10.LineChartPainter { MockLineChartPainter() { _i1.throwOnMissingStub(this); } @override void paint( _i6.BuildContext? context, _i11.CanvasWrapper? canvasWrapper, _i12.PaintHolder<_i13.LineChartData>? holder, ) => super.noSuchMethod( Invocation.method( #paint, [ context, canvasWrapper, holder, ], ), returnValueForMissingStub: null, ); @override void clipToBorder( _i11.CanvasWrapper? canvasWrapper, _i12.PaintHolder<_i13.LineChartData>? holder, ) => super.noSuchMethod( Invocation.method( #clipToBorder, [ canvasWrapper, holder, ], ), returnValueForMissingStub: null, ); @override void drawBarLine( _i11.CanvasWrapper? canvasWrapper, _i13.LineChartBarData? barData, _i12.PaintHolder<_i13.LineChartData>? holder, ) => super.noSuchMethod( Invocation.method( #drawBarLine, [ canvasWrapper, barData, holder, ], ), returnValueForMissingStub: null, ); @override void drawBetweenBarsArea( _i11.CanvasWrapper? canvasWrapper, _i13.LineChartData? data, _i13.BetweenBarsData? betweenBarsData, _i12.PaintHolder<_i13.LineChartData>? holder, ) => super.noSuchMethod( Invocation.method( #drawBetweenBarsArea, [ canvasWrapper, data, betweenBarsData, holder, ], ), returnValueForMissingStub: null, ); @override void drawDots( _i11.CanvasWrapper? canvasWrapper, _i13.LineChartBarData? barData, _i12.PaintHolder<_i13.LineChartData>? holder, ) => super.noSuchMethod( Invocation.method( #drawDots, [ canvasWrapper, barData, holder, ], ), returnValueForMissingStub: null, ); @override void drawErrorIndicatorData( _i11.CanvasWrapper? canvasWrapper, _i13.LineChartBarData? barData, _i12.PaintHolder<_i13.LineChartData>? holder, ) => super.noSuchMethod( Invocation.method( #drawErrorIndicatorData, [ canvasWrapper, barData, holder, ], ), returnValueForMissingStub: null, ); @override void drawTouchedSpotsIndicator( _i11.CanvasWrapper? canvasWrapper, List<_i10.LineIndexDrawingInfo>? lineIndexDrawingInfo, _i12.PaintHolder<_i13.LineChartData>? holder, ) => super.noSuchMethod( Invocation.method( #drawTouchedSpotsIndicator, [ canvasWrapper, lineIndexDrawingInfo, holder, ], ), returnValueForMissingStub: null, ); @override _i2.Path generateBarPath( _i2.Size? viewSize, _i13.LineChartBarData? barData, List<_i13.FlSpot>? barSpots, _i12.PaintHolder<_i13.LineChartData>? holder, { _i2.Path? appendToPath, }) => (super.noSuchMethod( Invocation.method( #generateBarPath, [ viewSize, barData, barSpots, holder, ], {#appendToPath: appendToPath}, ), returnValue: _FakePath_8( this, Invocation.method( #generateBarPath, [ viewSize, barData, barSpots, holder, ], {#appendToPath: appendToPath}, ), ), ) as _i2.Path); @override _i2.Path generateNormalBarPath( _i2.Size? viewSize, _i13.LineChartBarData? barData, List<_i13.FlSpot>? barSpots, _i12.PaintHolder<_i13.LineChartData>? holder, { _i2.Path? appendToPath, }) => (super.noSuchMethod( Invocation.method( #generateNormalBarPath, [ viewSize, barData, barSpots, holder, ], {#appendToPath: appendToPath}, ), returnValue: _FakePath_8( this, Invocation.method( #generateNormalBarPath, [ viewSize, barData, barSpots, holder, ], {#appendToPath: appendToPath}, ), ), ) as _i2.Path); @override _i2.Path generateStepBarPath( _i2.Size? viewSize, _i13.LineChartBarData? barData, List<_i13.FlSpot>? barSpots, _i12.PaintHolder<_i13.LineChartData>? holder, { _i2.Path? appendToPath, }) => (super.noSuchMethod( Invocation.method( #generateStepBarPath, [ viewSize, barData, barSpots, holder, ], {#appendToPath: appendToPath}, ), returnValue: _FakePath_8( this, Invocation.method( #generateStepBarPath, [ viewSize, barData, barSpots, holder, ], {#appendToPath: appendToPath}, ), ), ) as _i2.Path); @override _i2.Path generateBelowBarPath( _i2.Size? viewSize, _i13.LineChartBarData? barData, _i2.Path? barPath, List<_i13.FlSpot>? barSpots, _i12.PaintHolder<_i13.LineChartData>? holder, { bool? fillCompletely = false, }) => (super.noSuchMethod( Invocation.method( #generateBelowBarPath, [ viewSize, barData, barPath, barSpots, holder, ], {#fillCompletely: fillCompletely}, ), returnValue: _FakePath_8( this, Invocation.method( #generateBelowBarPath, [ viewSize, barData, barPath, barSpots, holder, ], {#fillCompletely: fillCompletely}, ), ), ) as _i2.Path); @override _i2.Path generateAboveBarPath( _i2.Size? viewSize, _i13.LineChartBarData? barData, _i2.Path? barPath, List<_i13.FlSpot>? barSpots, _i12.PaintHolder<_i13.LineChartData>? holder, { bool? fillCompletely = false, }) => (super.noSuchMethod( Invocation.method( #generateAboveBarPath, [ viewSize, barData, barPath, barSpots, holder, ], {#fillCompletely: fillCompletely}, ), returnValue: _FakePath_8( this, Invocation.method( #generateAboveBarPath, [ viewSize, barData, barPath, barSpots, holder, ], {#fillCompletely: fillCompletely}, ), ), ) as _i2.Path); @override void drawBelowBar( _i11.CanvasWrapper? canvasWrapper, _i2.Path? belowBarPath, _i2.Path? filledAboveBarPath, _i12.PaintHolder<_i13.LineChartData>? holder, _i13.LineChartBarData? barData, ) => super.noSuchMethod( Invocation.method( #drawBelowBar, [ canvasWrapper, belowBarPath, filledAboveBarPath, holder, barData, ], ), returnValueForMissingStub: null, ); @override void drawAboveBar( _i11.CanvasWrapper? canvasWrapper, _i2.Path? aboveBarPath, _i2.Path? filledBelowBarPath, _i12.PaintHolder<_i13.LineChartData>? holder, _i13.LineChartBarData? barData, ) => super.noSuchMethod( Invocation.method( #drawAboveBar, [ canvasWrapper, aboveBarPath, filledBelowBarPath, holder, barData, ], ), returnValueForMissingStub: null, ); @override void drawBetweenBar( _i11.CanvasWrapper? canvasWrapper, _i2.Path? barPath, _i13.BetweenBarsData? betweenBarsData, _i2.Rect? aroundRect, _i12.PaintHolder<_i13.LineChartData>? holder, ) => super.noSuchMethod( Invocation.method( #drawBetweenBar, [ canvasWrapper, barPath, betweenBarsData, aroundRect, holder, ], ), returnValueForMissingStub: null, ); @override void drawBarShadow( _i11.CanvasWrapper? canvasWrapper, _i2.Path? barPath, _i13.LineChartBarData? barData, ) => super.noSuchMethod( Invocation.method( #drawBarShadow, [ canvasWrapper, barPath, barData, ], ), returnValueForMissingStub: null, ); @override void drawBar( _i11.CanvasWrapper? canvasWrapper, _i2.Path? barPath, _i13.LineChartBarData? barData, _i12.PaintHolder<_i13.LineChartData>? holder, ) => super.noSuchMethod( Invocation.method( #drawBar, [ canvasWrapper, barPath, barData, holder, ], ), returnValueForMissingStub: null, ); @override void drawTouchTooltip( _i6.BuildContext? context, _i11.CanvasWrapper? canvasWrapper, _i13.LineTouchTooltipData? tooltipData, _i13.FlSpot? showOnSpot, _i13.ShowingTooltipIndicators? showingTooltipSpots, _i12.PaintHolder<_i13.LineChartData>? holder, ) => super.noSuchMethod( Invocation.method( #drawTouchTooltip, [ context, canvasWrapper, tooltipData, showOnSpot, showingTooltipSpots, holder, ], ), returnValueForMissingStub: null, ); @override double getBarLineXLength( _i13.LineChartBarData? barData, _i2.Size? chartUsableSize, _i12.PaintHolder<_i13.LineChartData>? holder, ) => (super.noSuchMethod( Invocation.method( #getBarLineXLength, [ barData, chartUsableSize, holder, ], ), returnValue: 0.0, ) as double); @override List<_i13.TouchLineBarSpot>? handleTouch( _i2.Offset? localPosition, _i2.Size? size, _i12.PaintHolder<_i13.LineChartData>? holder, ) => (super.noSuchMethod(Invocation.method( #handleTouch, [ localPosition, size, holder, ], )) as List<_i13.TouchLineBarSpot>?); @override _i13.TouchLineBarSpot? getNearestTouchedSpot( _i2.Size? viewSize, _i2.Offset? touchedPoint, _i13.LineChartBarData? barData, int? barDataPosition, _i12.PaintHolder<_i13.LineChartData>? holder, ) => (super.noSuchMethod(Invocation.method( #getNearestTouchedSpot, [ viewSize, touchedPoint, barData, barDataPosition, holder, ], )) as _i13.TouchLineBarSpot?); @override void drawGrid( _i11.CanvasWrapper? canvasWrapper, _i12.PaintHolder<_i13.LineChartData>? holder, ) => super.noSuchMethod( Invocation.method( #drawGrid, [ canvasWrapper, holder, ], ), returnValueForMissingStub: null, ); @override void drawBackground( _i11.CanvasWrapper? canvasWrapper, _i12.PaintHolder<_i13.LineChartData>? holder, ) => super.noSuchMethod( Invocation.method( #drawBackground, [ canvasWrapper, holder, ], ), returnValueForMissingStub: null, ); @override void drawRangeAnnotation( _i11.CanvasWrapper? canvasWrapper, _i12.PaintHolder<_i13.LineChartData>? holder, ) => super.noSuchMethod( Invocation.method( #drawRangeAnnotation, [ canvasWrapper, holder, ], ), returnValueForMissingStub: null, ); @override void drawExtraLines( _i6.BuildContext? context, _i11.CanvasWrapper? canvasWrapper, _i12.PaintHolder<_i13.LineChartData>? holder, ) => super.noSuchMethod( Invocation.method( #drawExtraLines, [ context, canvasWrapper, holder, ], ), returnValueForMissingStub: null, ); @override void drawHorizontalLines( _i6.BuildContext? context, _i11.CanvasWrapper? canvasWrapper, _i12.PaintHolder<_i13.LineChartData>? holder, _i2.Size? viewSize, ) => super.noSuchMethod( Invocation.method( #drawHorizontalLines, [ context, canvasWrapper, holder, viewSize, ], ), returnValueForMissingStub: null, ); @override void drawVerticalLines( _i6.BuildContext? context, _i11.CanvasWrapper? canvasWrapper, _i12.PaintHolder<_i13.LineChartData>? holder, _i2.Size? viewSize, ) => super.noSuchMethod( Invocation.method( #drawVerticalLines, [ context, canvasWrapper, holder, viewSize, ], ), returnValueForMissingStub: null, ); @override double getPixelX( double? spotX, _i2.Size? viewSize, _i12.PaintHolder<_i13.LineChartData>? holder, ) => (super.noSuchMethod( Invocation.method( #getPixelX, [ spotX, viewSize, holder, ], ), returnValue: 0.0, ) as double); @override double getPixelY( double? spotY, _i2.Size? viewSize, _i12.PaintHolder<_i13.LineChartData>? holder, ) => (super.noSuchMethod( Invocation.method( #getPixelY, [ spotY, viewSize, holder, ], ), returnValue: 0.0, ) as double); @override double getXForPixel( double? pixelX, _i2.Size? viewSize, _i12.PaintHolder<_i13.LineChartData>? holder, ) => (super.noSuchMethod( Invocation.method( #getXForPixel, [ pixelX, viewSize, holder, ], ), returnValue: 0.0, ) as double); @override double getYForPixel( double? pixelY, _i2.Size? viewSize, _i12.PaintHolder<_i13.LineChartData>? holder, ) => (super.noSuchMethod( Invocation.method( #getYForPixel, [ pixelY, viewSize, holder, ], ), returnValue: 0.0, ) as double); @override _i2.Offset getChartCoordinateFromPixel( _i2.Offset? pixelOffset, _i2.Size? viewSize, _i12.PaintHolder<_i13.LineChartData>? holder, ) => (super.noSuchMethod( Invocation.method( #getChartCoordinateFromPixel, [ pixelOffset, viewSize, holder, ], ), returnValue: _FakeOffset_9( this, Invocation.method( #getChartCoordinateFromPixel, [ pixelOffset, viewSize, holder, ], ), ), ) as _i2.Offset); @override double getTooltipLeft( double? dx, double? tooltipWidth, _i13.FLHorizontalAlignment? tooltipHorizontalAlignment, double? tooltipHorizontalOffset, ) => (super.noSuchMethod( Invocation.method( #getTooltipLeft, [ dx, tooltipWidth, tooltipHorizontalAlignment, tooltipHorizontalOffset, ], ), returnValue: 0.0, ) as double); } ================================================ FILE: test/chart/line_chart/line_chart_test.dart ================================================ import 'package:fl_chart/src/chart/base/axis_chart/axis_chart_scaffold_widget.dart'; import 'package:fl_chart/src/chart/base/axis_chart/scale_axis.dart'; import 'package:fl_chart/src/chart/base/axis_chart/transformation_config.dart'; import 'package:fl_chart/src/chart/line_chart/line_chart.dart'; import 'package:fl_chart/src/chart/line_chart/line_chart_data.dart'; import 'package:fl_chart/src/chart/line_chart/line_chart_renderer.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { Widget createTestWidget({ required LineChart chart, }) { return MaterialApp( home: chart, ); } group('LineChart', () { testWidgets('has correct default values', (tester) async { await tester.pumpWidget( createTestWidget( chart: LineChart( LineChartData(), ), ), ); final lineChart = tester.widget(find.byType(LineChart)); expect(lineChart.transformationConfig, const FlTransformationConfig()); }); testWidgets('passes interaction parameters to AxisChartScaffoldWidget', (tester) async { await tester.pumpWidget( createTestWidget( chart: LineChart( LineChartData(), ), ), ); final axisChartScaffoldWidget = tester.widget( find.byType(AxisChartScaffoldWidget), ); expect( axisChartScaffoldWidget.transformationConfig, const FlTransformationConfig(), ); await tester.pumpAndSettle(); final transformationConfig = FlTransformationConfig( scaleAxis: FlScaleAxis.free, trackpadScrollCausesScale: true, maxScale: 10, minScale: 1.5, transformationController: TransformationController(), ); await tester.pumpWidget( createTestWidget( chart: LineChart( LineChartData(), transformationConfig: transformationConfig, ), ), ); final axisChartScaffoldWidget1 = tester.widget( find.byType(AxisChartScaffoldWidget), ); expect( axisChartScaffoldWidget1.transformationConfig, transformationConfig, ); }); for (final scaleAxis in FlScaleAxis.scalingEnabledAxis) { testWidgets('passes canBeScaled true for $scaleAxis', (tester) async { await tester.pumpWidget( createTestWidget( chart: LineChart( LineChartData(), transformationConfig: FlTransformationConfig( scaleAxis: scaleAxis, ), ), ), ); final lineChartLeaf = tester.widget( find.byType(LineChartLeaf), ); expect(lineChartLeaf.canBeScaled, true); }); } testWidgets('passes canBeScaled false for FlScaleAxis.none', (tester) async { await tester.pumpWidget( createTestWidget( chart: LineChart( LineChartData(), // This is for test // ignore: avoid_redundant_argument_values transformationConfig: const FlTransformationConfig( // This is for test // ignore: avoid_redundant_argument_values scaleAxis: FlScaleAxis.none, ), ), ), ); final lineChartLeaf = tester.widget( find.byType(LineChartLeaf), ); expect(lineChartLeaf.canBeScaled, false); }); group('touch gesture', () { testWidgets('does not scale with FlScaleAxis.none', (tester) async { await tester.pumpWidget( createTestWidget( chart: LineChart( LineChartData(), ), ), ); final lineChartCenterOffset = tester.getCenter( find.byType(LineChartLeaf), ); final scaleStart1 = lineChartCenterOffset; final scaleStart2 = lineChartCenterOffset; final scaleEnd1 = lineChartCenterOffset + const Offset(100, 100); final scaleEnd2 = lineChartCenterOffset - const Offset(100, 100); final gesture1 = await tester.startGesture(scaleStart1); final gesture2 = await tester.startGesture(scaleStart2); await tester.pump(); await gesture1.moveTo(scaleEnd1); await gesture2.moveTo(scaleEnd2); await tester.pump(); await gesture1.up(); await gesture2.up(); await tester.pumpAndSettle(); final lineChartLeaf = tester.widget( find.byType(LineChartLeaf), ); expect(lineChartLeaf.chartVirtualRect, isNull); }); testWidgets('scales freely with FlScaleAxis.free', (tester) async { await tester.pumpWidget( createTestWidget( chart: LineChart( LineChartData(), transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.free, ), ), ), ); final lineChartCenterOffset = tester.getCenter( find.byType(LineChartLeaf), ); final scaleStart1 = lineChartCenterOffset; final scaleStart2 = lineChartCenterOffset; final scaleEnd1 = lineChartCenterOffset + const Offset(100, 100); final scaleEnd2 = lineChartCenterOffset - const Offset(100, 100); final gesture1 = await tester.startGesture(scaleStart1); final gesture2 = await tester.startGesture(scaleStart2); await tester.pump(); await gesture1.moveTo(scaleEnd1); await gesture2.moveTo(scaleEnd2); await tester.pump(); await gesture1.up(); await gesture2.up(); await tester.pumpAndSettle(); final lineChartLeaf = tester.widget( find.byType(LineChartLeaf), ); final renderBox = tester.renderObject( find.byType(LineChartLeaf), ); final chartVirtualRect = lineChartLeaf.chartVirtualRect!; expect(chartVirtualRect.size, greaterThan(renderBox.size)); expect(chartVirtualRect.left, isNegative); expect(chartVirtualRect.top, isNegative); }); testWidgets('scales horizontally with FlScaleAxis.horizontal', (tester) async { await tester.pumpWidget( createTestWidget( chart: LineChart( LineChartData(), transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.horizontal, ), ), ), ); final chartCenterOffset = tester.getCenter(find.byType(LineChartLeaf)); final scaleStart1 = chartCenterOffset; final scaleStart2 = chartCenterOffset; final scaleEnd1 = chartCenterOffset + const Offset(100, 100); final scaleEnd2 = chartCenterOffset - const Offset(100, 100); final gesture1 = await tester.startGesture(scaleStart1); final gesture2 = await tester.startGesture(scaleStart2); await tester.pump(); await gesture1.moveTo(scaleEnd1); await gesture2.moveTo(scaleEnd2); await tester.pump(); await gesture1.up(); await gesture2.up(); await tester.pumpAndSettle(); final lineChartLeaf = tester.widget( find.byType(LineChartLeaf), ); final renderBox = tester.renderObject( find.byType(LineChartLeaf), ); final chartVirtualRect = lineChartLeaf.chartVirtualRect!; expect(chartVirtualRect.size.height, renderBox.size.height); expect(chartVirtualRect.size.width, greaterThan(renderBox.size.width)); expect(chartVirtualRect.left, isNegative); expect(chartVirtualRect.top, 0); }); testWidgets('scales vertically with FlScaleAxis.vertical', (tester) async { await tester.pumpWidget( createTestWidget( chart: LineChart( LineChartData(), transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.vertical, ), ), ), ); final chartCenterOffset = tester.getCenter(find.byType(LineChartLeaf)); final scaleStart1 = chartCenterOffset; final scaleStart2 = chartCenterOffset; final scaleEnd1 = chartCenterOffset + const Offset(100, 100); final scaleEnd2 = chartCenterOffset - const Offset(100, 100); final gesture1 = await tester.startGesture(scaleStart1); final gesture2 = await tester.startGesture(scaleStart2); await tester.pump(); await gesture1.moveTo(scaleEnd1); await gesture2.moveTo(scaleEnd2); await tester.pump(); await gesture1.up(); await gesture2.up(); await tester.pumpAndSettle(); final lineChartLeaf = tester.widget( find.byType(LineChartLeaf), ); final renderBox = tester.renderObject( find.byType(LineChartLeaf), ); final chartVirtualRect = lineChartLeaf.chartVirtualRect!; expect( chartVirtualRect.size.height, greaterThan(renderBox.size.height), ); expect(chartVirtualRect.size.width, renderBox.size.width); expect(chartVirtualRect.left, 0); expect(chartVirtualRect.top, isNegative); }); group('pans', () { testWidgets('only horizontally with FlScaleAxis.horizontal', (tester) async { await tester.pumpWidget( createTestWidget( chart: LineChart( LineChartData(), transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.horizontal, ), ), ), ); final chartCenterOffset = tester.getCenter( find.byType(LineChartLeaf), ); final scaleStart1 = chartCenterOffset; final scaleStart2 = chartCenterOffset; final scaleEnd1 = chartCenterOffset + const Offset(100, 100); final scaleEnd2 = chartCenterOffset - const Offset(100, 100); final gesture1 = await tester.startGesture(scaleStart1); final gesture2 = await tester.startGesture(scaleStart2); await gesture1.moveTo(scaleEnd1); await gesture2.moveTo(scaleEnd2); await gesture1.up(); await gesture2.up(); await tester.pumpAndSettle(); final lineChartLeafBeforePan = tester.widget( find.byType(LineChartLeaf), ); final chartVirtualRectBeforePan = lineChartLeafBeforePan.chartVirtualRect!; expect(chartVirtualRectBeforePan.top, 0); const panOffset = Offset(100, 100); await tester.dragFrom(chartCenterOffset, panOffset); await tester.pumpAndSettle(); final lineChartLeafAfterPan = tester.widget( find.byType(LineChartLeaf), ); final chartVirtualRectAfterPan = lineChartLeafAfterPan.chartVirtualRect!; expect(chartVirtualRectBeforePan.size, chartVirtualRectAfterPan.size); expect( chartVirtualRectAfterPan.left, greaterThan(chartVirtualRectBeforePan.left), ); expect(chartVirtualRectAfterPan.top, 0); }); testWidgets('only vertically with FlScaleAxis.vertical', (tester) async { await tester.pumpWidget( createTestWidget( chart: LineChart( LineChartData(), transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.vertical, ), ), ), ); final chartCenterOffset = tester.getCenter( find.byType(LineChartLeaf), ); final scaleStart1 = chartCenterOffset; final scaleStart2 = chartCenterOffset; final scaleEnd1 = chartCenterOffset + const Offset(100, 100); final scaleEnd2 = chartCenterOffset - const Offset(100, 100); final gesture1 = await tester.startGesture(scaleStart1); final gesture2 = await tester.startGesture(scaleStart2); await gesture1.moveTo(scaleEnd1); await gesture2.moveTo(scaleEnd2); await gesture1.up(); await gesture2.up(); await tester.pumpAndSettle(); final lineChartLeafBeforePan = tester.widget( find.byType(LineChartLeaf), ); final chartVirtualRectBeforePan = lineChartLeafBeforePan.chartVirtualRect!; expect(chartVirtualRectBeforePan.left, 0); const panOffset = Offset(100, 100); await tester.dragFrom(chartCenterOffset, panOffset); await tester.pumpAndSettle(); final lineChartLeafAfterPan = tester.widget( find.byType(LineChartLeaf), ); final chartVirtualRectAfterPan = lineChartLeafAfterPan.chartVirtualRect!; expect(chartVirtualRectAfterPan.left, 0); expect( chartVirtualRectAfterPan.top, greaterThan(chartVirtualRectBeforePan.top), ); }); testWidgets('freely with FlScaleAxis.free', (tester) async { await tester.pumpWidget( createTestWidget( chart: LineChart( LineChartData(), transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.free, ), ), ), ); final chartCenterOffset = tester.getCenter( find.byType(LineChartLeaf), ); final scaleStart1 = chartCenterOffset; final scaleStart2 = chartCenterOffset; final scaleEnd1 = chartCenterOffset + const Offset(100, 100); final scaleEnd2 = chartCenterOffset - const Offset(100, 100); final gesture1 = await tester.startGesture(scaleStart1); final gesture2 = await tester.startGesture(scaleStart2); await gesture1.moveTo(scaleEnd1); await gesture2.moveTo(scaleEnd2); await gesture1.up(); await gesture2.up(); await tester.pumpAndSettle(); final lineChartLeafBeforePan = tester.widget( find.byType(LineChartLeaf), ); final chartVirtualRectBeforePan = lineChartLeafBeforePan.chartVirtualRect!; expect(chartVirtualRectBeforePan.left, isNegative); expect(chartVirtualRectBeforePan.top, isNegative); const panOffset = Offset(100, 100); await tester.dragFrom(chartCenterOffset, panOffset); await tester.pumpAndSettle(); final lineChartLeafAfterPan = tester.widget( find.byType(LineChartLeaf), ); final chartVirtualRectAfterPan = lineChartLeafAfterPan.chartVirtualRect!; expect( chartVirtualRectAfterPan.left, greaterThan(chartVirtualRectBeforePan.left), ); expect( chartVirtualRectAfterPan.top, greaterThan(chartVirtualRectBeforePan.top), ); }); }); }); group('trackpad scroll', () { group('pans', () { testWidgets('only horizontally with FlScaleAxis.horizontal', (tester) async { await tester.pumpWidget( createTestWidget( chart: LineChart( LineChartData(), transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.horizontal, ), ), ), ); final chartCenterOffset = tester.getCenter( find.byType(LineChartLeaf), ); final scaleStart1 = chartCenterOffset; final scaleStart2 = chartCenterOffset; final scaleEnd1 = chartCenterOffset + const Offset(100, 100); final scaleEnd2 = chartCenterOffset - const Offset(100, 100); final gesture1 = await tester.startGesture(scaleStart1); final gesture2 = await tester.startGesture(scaleStart2); await tester.pump(); await gesture1.moveTo(scaleEnd1); await gesture2.moveTo(scaleEnd2); await tester.pump(); await gesture1.up(); await gesture2.up(); await tester.pumpAndSettle(); final lineChartLeafBeforePan = tester.widget( find.byType(LineChartLeaf), ); final chartVirtualRectBeforePan = lineChartLeafBeforePan.chartVirtualRect!; expect(chartVirtualRectBeforePan.top, 0); final pointer = TestPointer(1, PointerDeviceKind.trackpad); const leftAndUp = Offset(-100, -100); await tester.sendEventToBinding(pointer.hover(chartCenterOffset)); await tester.pump(); await tester.sendEventToBinding(pointer.scroll(leftAndUp)); await tester.pump(); final lineChartLeafAfterPan = tester.widget( find.byType(LineChartLeaf), ); final chartVirtualRectAfterPan = lineChartLeafAfterPan.chartVirtualRect!; expect( chartVirtualRectAfterPan.left, greaterThan(chartVirtualRectBeforePan.left), ); expect(chartVirtualRectAfterPan.top, 0); }); testWidgets('vertically with FlScaleAxis.vertical', (tester) async { await tester.pumpWidget( createTestWidget( chart: LineChart( LineChartData(), transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.vertical, ), ), ), ); final chartCenterOffset = tester.getCenter( find.byType(LineChartLeaf), ); final scaleStart1 = chartCenterOffset; final scaleStart2 = chartCenterOffset; final scaleEnd1 = chartCenterOffset + const Offset(100, 100); final scaleEnd2 = chartCenterOffset - const Offset(100, 100); final gesture1 = await tester.startGesture(scaleStart1); final gesture2 = await tester.startGesture(scaleStart2); await tester.pump(); await gesture1.moveTo(scaleEnd1); await gesture2.moveTo(scaleEnd2); await tester.pump(); await gesture1.up(); await gesture2.up(); await tester.pumpAndSettle(); final lineChartLeafBeforePan = tester.widget( find.byType(LineChartLeaf), ); final chartVirtualRectBeforePan = lineChartLeafBeforePan.chartVirtualRect!; expect(chartVirtualRectBeforePan.left, 0); final pointer = TestPointer(1, PointerDeviceKind.trackpad); const leftAndUp = Offset(-100, -100); await tester.sendEventToBinding(pointer.hover(chartCenterOffset)); await tester.pump(); await tester.sendEventToBinding(pointer.scroll(leftAndUp)); await tester.pump(); final lineChartLeafAfterPan = tester.widget( find.byType(LineChartLeaf), ); final chartVirtualRectAfterPan = lineChartLeafAfterPan.chartVirtualRect!; expect(chartVirtualRectAfterPan.left, 0); expect( chartVirtualRectAfterPan.top, greaterThan(chartVirtualRectBeforePan.top), ); }); testWidgets('freely with FlScaleAxis.free', (tester) async { await tester.pumpWidget( createTestWidget( chart: LineChart( LineChartData(), transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.free, ), ), ), ); final chartCenterOffset = tester.getCenter( find.byType(LineChartLeaf), ); final scaleStart1 = chartCenterOffset; final scaleStart2 = chartCenterOffset; final scaleEnd1 = chartCenterOffset + const Offset(100, 100); final scaleEnd2 = chartCenterOffset - const Offset(100, 100); final gesture1 = await tester.startGesture(scaleStart1); final gesture2 = await tester.startGesture(scaleStart2); await tester.pump(); await gesture1.moveTo(scaleEnd1); await gesture2.moveTo(scaleEnd2); await tester.pump(); await gesture1.up(); await gesture2.up(); await tester.pumpAndSettle(); final lineChartLeafBeforePan = tester.widget( find.byType(LineChartLeaf), ); final chartVirtualRectBeforePan = lineChartLeafBeforePan.chartVirtualRect!; final pointer = TestPointer(1, PointerDeviceKind.trackpad); const leftAndUp = Offset(-100, -100); await tester.sendEventToBinding(pointer.hover(chartCenterOffset)); await tester.pump(); await tester.sendEventToBinding(pointer.scroll(leftAndUp)); await tester.pump(); final lineChartLeafAfterPan = tester.widget( find.byType(LineChartLeaf), ); final chartVirtualRectAfterPan = lineChartLeafAfterPan.chartVirtualRect!; expect( chartVirtualRectAfterPan.left, greaterThan(chartVirtualRectBeforePan.left), ); expect( chartVirtualRectAfterPan.top, greaterThan(chartVirtualRectBeforePan.top), ); }); }); testWidgets( 'does not scale with FlScaleAxis.none when ' 'trackpadScrollCausesScale is true', (tester) async { await tester.pumpWidget( createTestWidget( chart: LineChart( LineChartData(), transformationConfig: const FlTransformationConfig( // This is for test // ignore: avoid_redundant_argument_values scaleAxis: FlScaleAxis.none, trackpadScrollCausesScale: true, ), ), ), ); final pointer = TestPointer(1, PointerDeviceKind.trackpad); final chartCenterOffset = tester.getCenter( find.byType(LineChartLeaf), ); const scrollAmount = Offset(0, -100); await tester.sendEventToBinding(pointer.hover(chartCenterOffset)); await tester.pump(); await tester.sendEventToBinding(pointer.scroll(scrollAmount)); await tester.pump(); final lineChartLeaf = tester.widget( find.byType(LineChartLeaf), ); expect(lineChartLeaf.chartVirtualRect, null); }, ); for (final scaleAxis in FlScaleAxis.scalingEnabledAxis) { testWidgets( 'does not scale when trackpadScrollCausesScale is false ' 'for $scaleAxis', (tester) async { await tester.pumpWidget( createTestWidget( chart: LineChart( LineChartData(), transformationConfig: FlTransformationConfig( scaleAxis: scaleAxis, // This is for test // ignore: avoid_redundant_argument_values trackpadScrollCausesScale: false, ), ), ), ); final pointer = TestPointer(1, PointerDeviceKind.trackpad); final chartCenterOffset = tester.getCenter( find.byType(LineChartLeaf), ); const scrollAmount = Offset(0, -100); await tester.sendEventToBinding(pointer.hover(chartCenterOffset)); await tester.pump(); await tester.sendEventToBinding(pointer.scroll(scrollAmount)); await tester.pump(); final lineChartLeaf = tester.widget( find.byType(LineChartLeaf), ); expect(lineChartLeaf.chartVirtualRect, null); }, ); } testWidgets('scales horizontally with FlScaleAxis.horizontal', (tester) async { await tester.pumpWidget( createTestWidget( chart: LineChart( LineChartData(), transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.horizontal, trackpadScrollCausesScale: true, ), ), ), ); final pointer = TestPointer(1, PointerDeviceKind.trackpad); final chartCenterOffset = tester.getCenter(find.byType(LineChartLeaf)); const scrollAmount = Offset(0, -100); await tester.sendEventToBinding(pointer.hover(chartCenterOffset)); await tester.pump(); await tester.sendEventToBinding(pointer.scroll(scrollAmount)); await tester.pump(); final lineChartLeaf = tester.widget( find.byType(LineChartLeaf), ); final renderBox = tester.renderObject( find.byType(LineChartLeaf), ); final chartVirtualRect = lineChartLeaf.chartVirtualRect!; expect(chartVirtualRect.size.height, renderBox.size.height); expect(chartVirtualRect.size.width, greaterThan(renderBox.size.width)); expect(chartVirtualRect.left, isNegative); expect(chartVirtualRect.top, 0); }); testWidgets('scales vertically with FlScaleAxis.vertical', (tester) async { await tester.pumpWidget( createTestWidget( chart: LineChart( LineChartData(), transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.vertical, trackpadScrollCausesScale: true, ), ), ), ); final pointer = TestPointer(1, PointerDeviceKind.trackpad); final chartCenterOffset = tester.getCenter(find.byType(LineChartLeaf)); const scrollAmount = Offset(0, -100); await tester.sendEventToBinding(pointer.hover(chartCenterOffset)); await tester.pump(); await tester.sendEventToBinding(pointer.scroll(scrollAmount)); await tester.pump(); final lineChartLeaf = tester.widget( find.byType(LineChartLeaf), ); final renderBox = tester.renderObject( find.byType(LineChartLeaf), ); final chartVirtualRect = lineChartLeaf.chartVirtualRect!; expect( chartVirtualRect.size.height, greaterThan(renderBox.size.height), ); expect(chartVirtualRect.size.width, renderBox.size.width); expect(chartVirtualRect.left, 0); expect(chartVirtualRect.top, isNegative); }); testWidgets('scales freely with FlScaleAxis.free', (tester) async { await tester.pumpWidget( createTestWidget( chart: LineChart( LineChartData(), transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.free, trackpadScrollCausesScale: true, ), ), ), ); final pointer = TestPointer(1, PointerDeviceKind.trackpad); final chartCenterOffset = tester.getCenter(find.byType(LineChartLeaf)); const scrollAmount = Offset(0, -100); await tester.sendEventToBinding(pointer.hover(chartCenterOffset)); await tester.pump(); await tester.sendEventToBinding(pointer.scroll(scrollAmount)); await tester.pump(); final lineChartLeaf = tester.widget(find.byType(LineChartLeaf)); final renderBox = tester.renderObject( find.byType(LineChartLeaf), ); final chartVirtualRect = lineChartLeaf.chartVirtualRect!; expect(chartVirtualRect.size, greaterThan(renderBox.size)); expect(chartVirtualRect.left, isNegative); expect(chartVirtualRect.top, isNegative); }); }); }); } ================================================ FILE: test/chart/pie_chart/pie_chart_data_test.dart ================================================ import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import '../data_pool.dart'; void main() { group('PieChart data equality check', () { test('PieChartData equality test', () { expect(pieChartData1 == pieChartData1Clone, true); expect( pieChartData1 == pieChartData1Clone.copyWith( borderData: FlBorderData( show: false, border: Border.all(), ), ), true, ); expect( pieChartData1 == pieChartData1Clone.copyWith( borderData: FlBorderData( show: true, border: Border.all(), ), ), false, ); expect( pieChartData1 == pieChartData1Clone.copyWith( startDegreeOffset: 33, ), false, ); expect( pieChartData1 == PieChartData( borderData: FlBorderData( show: false, border: Border.all(), ), startDegreeOffset: 0, centerSpaceColor: Colors.white, centerSpaceRadius: 12, pieTouchData: PieTouchData( enabled: false, ), sectionsSpace: 44, ), false, ); expect( pieChartData1 == pieChartData1Clone.copyWith( sections: [], ), false, ); expect( pieChartData1 == pieChartData1Clone.copyWith( sections: [ PieChartSectionData(value: 12, color: Colors.red), PieChartSectionData(value: 22, color: Colors.green), ], ), true, ); expect( pieChartData1 == pieChartData1Clone.copyWith( sections: [ PieChartSectionData(value: 12, color: Colors.red), PieChartSectionData( value: 22, color: Colors.green.withValues(alpha: 0.99), ), ], ), false, ); expect( pieChartData1 == pieChartData1Clone.copyWith( sections: [ PieChartSectionData(value: 22, color: Colors.green), PieChartSectionData(value: 12, color: Colors.red), ], ), false, ); expect( pieChartData1 == pieChartData1Clone.copyWith( centerSpaceColor: Colors.cyan, ), false, ); expect( pieChartData1 == pieChartData1Clone.copyWith( centerSpaceRadius: 44, ), false, ); expect( pieChartData1 == pieChartData1Clone.copyWith( pieTouchData: PieTouchData(), ), false, ); expect( pieChartData1 == pieChartData1Clone.copyWith( sectionsSpace: 44.000001, ), false, ); expect( pieChartData1 == pieChartData1Clone.copyWith( titleSunbeamLayout: true, ), false, ); }); test('PieTouchData equality test', () { final sample1 = PieTouchData( touchCallback: (event, response) {}, enabled: true, ); final sample2 = PieTouchData( enabled: true, ); expect(sample1 == sample2, false); final disabled = PieTouchData( enabled: false, ); expect(sample1 == disabled, false); final zeroLongPressDuration = PieTouchData( enabled: true, longPressDuration: Duration.zero, ); expect(sample1 == zeroLongPressDuration, false); }); }); } ================================================ FILE: test/chart/pie_chart/pie_chart_helper_test.dart ================================================ import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/chart/pie_chart/pie_chart_helper.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { test('Test List.toWidgets()', () { final widgets1 = [ PieChartSectionData(value: 1), PieChartSectionData(value: 2), PieChartSectionData(value: 3), ].toWidgets(); expect(widgets1, List.empty()); final widgets2 = [ PieChartSectionData(value: 1), PieChartSectionData(value: 2, badgeWidget: const Text('asdf')), PieChartSectionData(value: 3), ].toWidgets(); expect(widgets2[0] is Container, true); expect(widgets2[1] is Text, true); expect(widgets2[2] is Container, true); final widgets3 = [ PieChartSectionData(value: 1, badgeWidget: const Text('1')), PieChartSectionData(value: 2, badgeWidget: const Text('2')), PieChartSectionData(value: 3, badgeWidget: const Text('3')), ].toWidgets(); expect((widgets3[0] as Text).data, '1'); expect((widgets3[1] as Text).data, '2'); expect((widgets3[2] as Text).data, '3'); }); } ================================================ FILE: test/chart/pie_chart/pie_chart_painter_test.dart ================================================ import 'dart:math' as math; import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart'; import 'package:fl_chart/src/chart/base/line.dart'; import 'package:fl_chart/src/chart/pie_chart/pie_chart_painter.dart'; import 'package:fl_chart/src/utils/canvas_wrapper.dart'; import 'package:fl_chart/src/utils/utils.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/physics.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import '../../helper_methods.dart'; import '../data_pool.dart'; import 'pie_chart_painter_test.mocks.dart'; @GenerateMocks([Canvas, CanvasWrapper, BuildContext, Utils]) void main() { const tolerance = 0.001; group('paint()', () { test('test 1', () { final utilsMainInstance = Utils(); const viewSize = Size(400, 400); final data = PieChartData( sections: [ PieChartSectionData( value: 10, ), PieChartSectionData( value: 20, ), PieChartSectionData( value: 30, ), ], ); final pieChartPainter = PieChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockUtils = MockUtils(); Utils.changeInstance(mockUtils); when(mockUtils.getThemeAwareTextStyle(any, any)) .thenAnswer((realInvocation) => textStyle1); when(mockUtils.radians(any)).thenAnswer((realInvocation) => 12); final mockBuildContext = MockBuildContext(); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); pieChartPainter.paint( mockBuildContext, mockCanvasWrapper, holder, ); verify(mockCanvasWrapper.drawPath(any, any)).called(3); Utils.changeInstance(utilsMainInstance); }); }); group('calculateSectionsAngle()', () { test('test 1', () { final sections = [ PieChartSectionData(value: 10), PieChartSectionData(value: 20), PieChartSectionData(value: 30), PieChartSectionData(value: 40), ]; expect( PieChartPainter().calculateSectionsAngle(sections, 100), [36, 72, 108, 144], ); }); test('test 2', () { final sections = [ PieChartSectionData(value: 10), PieChartSectionData(value: 10), PieChartSectionData(value: 10), PieChartSectionData(value: 10), ]; expect( PieChartPainter().calculateSectionsAngle(sections, 40), [90, 90, 90, 90], ); }); }); group('drawCenterSpace()', () { test('test 1', () { const viewSize = Size(200, 200); final data = PieChartData( centerSpaceColor: MockData.color1, ); final barChartPainter = PieChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); barChartPainter.drawCenterSpace(mockCanvasWrapper, 10, holder); final result = verify( mockCanvasWrapper.drawCircle(const Offset(100, 100), 10, captureAny), ); expect(result.callCount, 1); expect( (result.captured.first as Paint).color, isSameColorAs(MockData.color1), ); }); }); group('drawTexts()', () { test('test 1', () { final utilsMainInstance = Utils(); const viewSize = Size(200, 200); final data = PieChartData( sections: List.generate(2, (i) { return PieChartSectionData( value: 10, title: '$i%', ); }), titleSunbeamLayout: true, ); final pieChartPainter = PieChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockBuildContext = MockBuildContext(); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final mockUtils = MockUtils(); Utils.changeInstance(mockUtils); when(mockUtils.getThemeAwareTextStyle(any, any)) .thenAnswer((realInvocation) => textStyle1); when(mockUtils.radians(any)).thenAnswer((realInvocation) => 12); final centerRadius = pieChartPainter.calculateCenterRadius( viewSize, holder, ); pieChartPainter.drawTexts( mockBuildContext, mockCanvasWrapper, holder, centerRadius, ); final results = verifyInOrder([ mockCanvasWrapper.drawText(any, any, captureAny), mockCanvasWrapper.drawText(any, any, captureAny), ]); expect(results[0].captured.single, -90); expect(results[1].captured.single, 90); Utils.changeInstance(utilsMainInstance); }); }); group('drawSections()', () { test('test 1', () { const viewSize = Size(200, 200); const radius = 30.0; const centerSpace = 10.0; final sections = [ PieChartSectionData( color: MockData.color2, radius: radius, value: 10, borderSide: const BorderSide( color: MockData.color3, width: 3, ), ), ]; final data = PieChartData( sections: sections, ); final barChartPainter = PieChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); barChartPainter.drawSections(mockCanvasWrapper, [360], 10, holder); final rect = Rect.fromCircle( center: viewSize.center(Offset.zero), radius: radius + centerSpace, ); final results = verifyInOrder([ mockCanvasWrapper.saveLayer( rect, any, ), mockCanvasWrapper.drawCircle( const Offset(100, 100), 10 + 30, captureAny, ), mockCanvasWrapper.drawCircle( const Offset(100, 100), 10, captureAny, ), mockCanvasWrapper.restore(), ]); final result = results[1]; expect(result.callCount, 1); expect( (result.captured.single as Paint).color, isSameColorAs(MockData.color2), ); expect((result.captured.single as Paint).style, PaintingStyle.fill); final result2 = verify( mockCanvasWrapper.drawCircle( const Offset(100, 100), 10 + (3 / 2), captureAny, ), ); expect(result2.callCount, 1); expect( (result2.captured.single as Paint).color, isSameColorAs(MockData.color3), ); expect((result2.captured.single as Paint).strokeWidth, 3); expect((result2.captured.single as Paint).style, PaintingStyle.stroke); }); test('test 2', () { const viewSize = Size(200, 200); final data = PieChartData( centerSpaceColor: MockData.color1, sectionsSpace: 10, sections: [ PieChartSectionData(color: MockData.color1, value: 1), PieChartSectionData(color: MockData.color2, value: 2), PieChartSectionData(color: MockData.color3, value: 3), PieChartSectionData(color: MockData.color4, value: 4), ], ); final barChartPainter = PieChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final results = >[]; when(mockCanvasWrapper.drawPath(captureAny, captureAny)) .thenAnswer((inv) { final paint = inv.positionalArguments[1] as Paint; results.add({ 'path': inv.positionalArguments[0] as Path, 'paint_color': paint.color, 'paint_style': paint.style, }); }); barChartPainter.drawSections( mockCanvasWrapper, [36, 72, 108, 144], 10, holder, ); verifyNever(mockCanvasWrapper.drawCircle(any, any, any)); expect(results.length, 4); final path0 = barChartPainter.generateSectionPath( data.sections[0], 10, 0, 36, const Offset(100, 100), 10, ); expect( HelperMethods.equalsPaths(results[0]['path'] as Path, path0), true, ); expect( results[0]['paint_color'] as Color, isSameColorAs(MockData.color1), ); expect(results[0]['paint_style'] as PaintingStyle, PaintingStyle.fill); final path1 = barChartPainter.generateSectionPath( data.sections[1], 10, 36, 72, const Offset(100, 100), 10, ); expect( HelperMethods.equalsPaths(results[1]['path'] as Path, path1), true, ); expect( results[1]['paint_color'] as Color, isSameColorAs(MockData.color2), ); expect(results[1]['paint_style'] as PaintingStyle, PaintingStyle.fill); final path2 = barChartPainter.generateSectionPath( data.sections[2], 10, 108, 108, const Offset(100, 100), 10, ); expect( HelperMethods.equalsPaths(results[2]['path'] as Path, path2), true, ); expect( results[2]['paint_color'] as Color, isSameColorAs(MockData.color3), ); expect(results[2]['paint_style'] as PaintingStyle, PaintingStyle.fill); final path3 = barChartPainter.generateSectionPath( data.sections[3], 10, 216, 144, const Offset(100, 100), 10, ); expect( HelperMethods.equalsPaths(results[3]['path'] as Path, path3), true, ); expect( results[3]['paint_color'] as Color, isSameColorAs(MockData.color4), ); expect(results[3]['paint_style'] as PaintingStyle, PaintingStyle.fill); }); }); group('generateSectionPath()', () { test('test 1', () { final data = PieChartData( centerSpaceColor: MockData.color1, sectionsSpace: 10, sections: [ PieChartSectionData(color: MockData.color1, value: 1), PieChartSectionData(color: MockData.color2, value: 2), PieChartSectionData(color: MockData.color3, value: 3), PieChartSectionData(color: MockData.color4, value: 4), ], ); final barChartPainter = PieChartPainter(); final path0 = barChartPainter.generateSectionPath( data.sections[0], 10, 0, 36, const Offset(100, 100), 10, ); final path0Length = path0 .computeMetrics() .toList() .map((e) => e.length) .reduce((a, b) => a + b); expect(path0Length, 90.08028411865234); final path1 = barChartPainter.generateSectionPath( data.sections[1], 10, 36, 72, const Offset(100, 100), 10, ); final path1Length = path1 .computeMetrics() .toList() .map((e) => e.length) .reduce((a, b) => a + b); expect(path1Length, 136.93048095703125); final path2 = barChartPainter.generateSectionPath( data.sections[2], 10, 108, 108, const Offset(100, 100), 10, ); final path2Length = path2 .computeMetrics() .toList() .map((e) => e.length) .reduce((a, b) => a + b); expect(path2Length, closeTo(174.6013, tolerance)); final path3 = barChartPainter.generateSectionPath( data.sections[3], 10, 216, 144, const Offset(100, 100), 10, ); final path3Length = path3 .computeMetrics() .toList() .map((e) => e.length) .reduce((a, b) => a + b); expect(path3Length, 212.1544189453125); }); test('test 2', () { final data = PieChartData( centerSpaceColor: MockData.color1, sectionsSpace: 0, sections: [ PieChartSectionData(color: MockData.color1, value: 1), PieChartSectionData(color: MockData.color2, value: 2), PieChartSectionData(color: MockData.color3, value: 3), PieChartSectionData(color: MockData.color4, value: 4), ], ); final barChartPainter = PieChartPainter(); final path0 = barChartPainter.generateSectionPath( data.sections[0], 0, 0, 36, const Offset(100, 100), 10, ); final path0Length = path0 .computeMetrics() .toList() .map((e) => e.length) .reduce((a, b) => a + b); expect(path0Length, 117.56398010253906); final path1 = barChartPainter.generateSectionPath( data.sections[1], 0, 36, 72, const Offset(100, 100), 10, ); final path1Length = path1 .computeMetrics() .toList() .map((e) => e.length) .reduce((a, b) => a + b); expect(path1Length, 155.1278076171875); final path2 = barChartPainter.generateSectionPath( data.sections[2], 0, 108, 108, const Offset(100, 100), 10, ); final path2Length = path2 .computeMetrics() .toList() .map((e) => e.length) .reduce((a, b) => a + b); expect(path2Length, closeTo(192.8401, tolerance)); final path3 = barChartPainter.generateSectionPath( data.sections[3], 0, 216, 144, const Offset(100, 100), 10, ); final path3Length = path3 .computeMetrics() .toList() .map((e) => e.length) .reduce((a, b) => a + b); expect(nearEqual(path3Length, 230.37237548828125, 0.0001), true); }); test('test 3', () { final data = PieChartData( centerSpaceColor: MockData.color1, sectionsSpace: 0, sections: [ PieChartSectionData(color: MockData.color1, value: 1), PieChartSectionData(color: MockData.color2, value: 2), PieChartSectionData(color: MockData.color3, value: 3), PieChartSectionData(color: MockData.color4, value: 4), ], ); final barChartPainter = PieChartPainter(); final path0 = barChartPainter.generateSectionPath( data.sections[0], 0, 0, 36, const Offset(100, 100), 3, ); final path0Length = path0 .computeMetrics() .toList() .map((e) => e.length) .reduce((a, b) => a + b); expect(path0Length, 108.80243682861328); final path1 = barChartPainter.generateSectionPath( data.sections[1], 0, 36, 72, const Offset(100, 100), 4, ); final path1Length = path1 .computeMetrics() .toList() .map((e) => e.length) .reduce((a, b) => a + b); expect(path1Length, 140.05465698242188); final path2 = barChartPainter.generateSectionPath( data.sections[2], 0, 108, 108, const Offset(100, 100), 5, ); final path2Length = path2 .computeMetrics() .toList() .map((e) => e.length) .reduce((a, b) => a + b); expect(path2Length, 173.86875915527344); final path3 = barChartPainter.generateSectionPath( data.sections[3], 0, 216, 144, const Offset(100, 100), 6, ); final path3Length = path3 .computeMetrics() .toList() .map((e) => e.length) .reduce((a, b) => a + b); expect(path3Length, 210.1807098388672); }); test('test 4 with cornerRadius matches rounded path when no space', () { const center = Offset(100, 100); const centerRadius = 10.0; const tempAngle = 36.0; const sectionDegree = 144.0; final section = PieChartSectionData( color: MockData.color1, value: 1, radius: 40, cornerRadius: 10, ); final barChartPainter = PieChartPainter(); final generatedPath = barChartPainter.generateSectionPath( section, 0, tempAngle, sectionDegree, center, centerRadius, ); final startRadians = Utils().radians(tempAngle); final sweepRadians = Utils().radians(sectionDegree); final expectedRoundedPath = barChartPainter.generateRoundedSectionPath( section, startRadians, sweepRadians, center, centerRadius, Rect.fromCircle(center: center, radius: centerRadius + section.radius), Rect.fromCircle(center: center, radius: centerRadius), ); expect( HelperMethods.equalsPaths(generatedPath, expectedRoundedPath), true, ); }); test('test 5 with cornerRadius and sectionSpace trims rounded path', () { const center = Offset(100, 100); const centerRadius = 10.0; const tempAngle = 36.0; const sectionDegree = 144.0; final section = PieChartSectionData( color: MockData.color1, value: 1, radius: 40, cornerRadius: 10, ); final barChartPainter = PieChartPainter(); final pathWithoutSpace = barChartPainter.generateSectionPath( section, 0, tempAngle, sectionDegree, center, centerRadius, ); final pathWithSpace = barChartPainter.generateSectionPath( section, 10, tempAngle, sectionDegree, center, centerRadius, ); expect(HelperMethods.equalsPaths(pathWithSpace, pathWithoutSpace), false); final withSpaceMetrics = pathWithSpace.computeMetrics().toList(); expect(withSpaceMetrics.isNotEmpty, true); }); }); group('generateRoundedSectionPath()', () { test('test 1 uses standard path when cornerRadius <= 1', () { const center = Offset(100, 100); const centerRadius = 10.0; const startRadians = 0.4; const sweepRadians = 1.3; final section = PieChartSectionData( value: 10, radius: 40, cornerRadius: 1, ); final barChartPainter = PieChartPainter(); final sectionRadiusRect = Rect.fromCircle( center: center, radius: centerRadius + section.radius, ); final centerRadiusRect = Rect.fromCircle( center: center, radius: centerRadius, ); final result = barChartPainter.generateRoundedSectionPath( section, startRadians, sweepRadians, center, centerRadius, sectionRadiusRect, centerRadiusRect, ); const endRadians = startRadians + sweepRadians; final innerStart = center + Offset(math.cos(startRadians), math.sin(startRadians)) * centerRadius; final outerStart = center + Offset(math.cos(startRadians), math.sin(startRadians)) * (centerRadius + section.radius); final innerEnd = center + Offset(math.cos(endRadians), math.sin(endRadians)) * centerRadius; final expected = Path() ..moveTo(innerStart.dx, innerStart.dy) ..lineTo(outerStart.dx, outerStart.dy) ..arcTo(sectionRadiusRect, startRadians, sweepRadians, false) ..lineTo(innerEnd.dx, innerEnd.dy) ..arcTo(centerRadiusRect, endRadians, -sweepRadians, false) ..close(); expect(HelperMethods.equalsPaths(result, expected), true); }); test('test 2 uses inner fallback lines when clampedInnerRadius <= 1', () { const center = Offset(100, 100); const centerRadius = 0.2; const startRadians = 0.0; const sweepRadians = 1.0; final section = PieChartSectionData( value: 10, radius: 40, cornerRadius: 10, ); final barChartPainter = PieChartPainter(); final result = barChartPainter.generateRoundedSectionPath( section, startRadians, sweepRadians, center, centerRadius, Rect.fromCircle(center: center, radius: centerRadius + section.radius), Rect.fromCircle(center: center, radius: centerRadius), ); final metrics = result.computeMetrics().toList(); expect(metrics.isNotEmpty, true); expect(metrics.map((e) => e.length).reduce((a, b) => a + b) > 0, true); }); test('test 3 supports centerRadius == 0 with fallback line branches', () { const center = Offset(100, 100); const centerRadius = 0.0; const startRadians = 0.3; const sweepRadians = 1.2; final section = PieChartSectionData( value: 10, radius: 1, cornerRadius: 10, ); final barChartPainter = PieChartPainter(); final result = barChartPainter.generateRoundedSectionPath( section, startRadians, sweepRadians, center, centerRadius, Rect.fromCircle(center: center, radius: centerRadius + section.radius), Rect.fromCircle(center: center, radius: centerRadius), ); final metrics = result.computeMetrics().toList(); expect(metrics.isNotEmpty, true); expect(result.getBounds().contains(center), true); }); }); group('createRectPathAroundLine()', () { test('test 1', () { final barChartPainter = PieChartPainter(); final path0 = barChartPainter.createRectPathAroundLine( const Line(Offset.zero, Offset(10, 0)), 4, ); final path0Length = path0 .computeMetrics() .toList() .map((e) => e.length) .reduce((a, b) => a + b); expect(path0Length, 32.0); final path1 = barChartPainter.createRectPathAroundLine( const Line(Offset(32, 11), Offset(12, 5)), 66, ); final path1Length = path1 .computeMetrics() .toList() .map((e) => e.length) .reduce((a, b) => a + b); expect(path1Length, 239.76123046875); }); }); group('drawSection()', () { test('test 1', () { const viewSize = Size(200, 200); final data = PieChartData( centerSpaceColor: MockData.color1, sectionsSpace: 10, sections: [ PieChartSectionData(color: MockData.color1, value: 1), PieChartSectionData(color: MockData.color2, value: 2), PieChartSectionData(color: MockData.color3, value: 3), PieChartSectionData(color: MockData.color4, value: 4), ], ); final barChartPainter = PieChartPainter(); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final results = >[]; when(mockCanvasWrapper.drawPath(captureAny, captureAny)) .thenAnswer((inv) { final paint = inv.positionalArguments[1] as Paint; results.add({ 'path': inv.positionalArguments[0] as Path, 'paint_color': paint.color, 'paint_style': paint.style, }); }); barChartPainter ..drawSection( data.sections[0], MockData.path1, mockCanvasWrapper, ) ..drawSection( data.sections[1], MockData.path2, mockCanvasWrapper, ) ..drawSection( data.sections[2], MockData.path3, mockCanvasWrapper, ) ..drawSection( data.sections[3], MockData.path4, mockCanvasWrapper, ); expect(results.length, 4); expect(results[0]['path'] as Path, MockData.path1); expect( results[0]['paint_color'] as Color, isSameColorAs(MockData.color1), ); expect(results[0]['paint_style'] as PaintingStyle, PaintingStyle.fill); expect(results[1]['path'] as Path, MockData.path2); expect( results[1]['paint_color'] as Color, isSameColorAs(MockData.color2), ); expect(results[1]['paint_style'] as PaintingStyle, PaintingStyle.fill); expect(results[2]['path'] as Path, MockData.path3); expect( results[2]['paint_color'] as Color, isSameColorAs(MockData.color3), ); expect(results[2]['paint_style'] as PaintingStyle, PaintingStyle.fill); expect(results[3]['path'] as Path, MockData.path4); expect( results[3]['paint_color'] as Color, isSameColorAs(MockData.color4), ); expect(results[3]['paint_style'] as PaintingStyle, PaintingStyle.fill); }); }); group('drawSectionStroke()', () { test('test 1', () { const viewSize = Size(200, 200); final data = PieChartData( centerSpaceColor: MockData.color1, sectionsSpace: 10, sections: [ PieChartSectionData(color: MockData.color1, value: 1), PieChartSectionData(color: MockData.color2, value: 2), PieChartSectionData(color: MockData.color3, value: 3), PieChartSectionData(color: MockData.color4, value: 4), ], ); final barChartPainter = PieChartPainter(); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final results = >[]; when(mockCanvasWrapper.drawPath(captureAny, captureAny)) .thenAnswer((inv) { final paint = inv.positionalArguments[1] as Paint; results.add({ 'path': inv.positionalArguments[0] as Path, 'paint_color': paint.color, 'paint_style': paint.style, }); }); barChartPainter ..drawSectionStroke( data.sections[0], MockData.path1, mockCanvasWrapper, viewSize, ) ..drawSectionStroke( data.sections[1], MockData.path2, mockCanvasWrapper, viewSize, ) ..drawSectionStroke( data.sections[2], MockData.path3, mockCanvasWrapper, viewSize, ) ..drawSectionStroke( data.sections[3], MockData.path4, mockCanvasWrapper, viewSize, ); verifyNever(mockCanvasWrapper.saveLayer(any, any)); verifyNever(mockCanvasWrapper.clipPath(any)); verifyNever(mockCanvasWrapper.drawPath(any, any)); verifyNever(mockCanvasWrapper.restore()); }); test('test 2', () { const viewSize = Size(200, 200); final data = PieChartData( centerSpaceColor: MockData.color1, sectionsSpace: 10, sections: [ PieChartSectionData( color: MockData.color1, value: 1, borderSide: MockData.borderSide1, ), PieChartSectionData( color: MockData.color2, value: 2, borderSide: MockData.borderSide2, ), PieChartSectionData( color: MockData.color3, value: 3, borderSide: MockData.borderSide3, ), PieChartSectionData( color: MockData.color4, value: 4, borderSide: MockData.borderSide4, ), ], ); final barChartPainter = PieChartPainter(); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final clipPathResults = >[]; when(mockCanvasWrapper.clipPath(captureAny)).thenAnswer((inv) { clipPathResults.add({ 'path': inv.positionalArguments[0] as Path, }); }); final drawPathResults = >[]; when(mockCanvasWrapper.drawPath(captureAny, captureAny)) .thenAnswer((inv) { final paint = inv.positionalArguments[1] as Paint; drawPathResults.add({ 'path': inv.positionalArguments[0] as Path, 'paint_color': paint.color, 'paint_style': paint.style, 'paint_stroke_width': paint.strokeWidth, }); }); barChartPainter ..drawSectionStroke( data.sections[0], MockData.path1, mockCanvasWrapper, viewSize, ) ..drawSectionStroke( data.sections[1], MockData.path2, mockCanvasWrapper, viewSize, ) ..drawSectionStroke( data.sections[2], MockData.path3, mockCanvasWrapper, viewSize, ) ..drawSectionStroke( data.sections[3], MockData.path4, mockCanvasWrapper, viewSize, ); verify( mockCanvasWrapper.saveLayer( Rect.fromLTWH(0, 0, viewSize.width, viewSize.height), any, ), ).called(4); expect(clipPathResults.length, 4); expect(clipPathResults[0]['path'], MockData.path1); expect(clipPathResults[1]['path'], MockData.path2); expect(clipPathResults[2]['path'], MockData.path3); expect(clipPathResults[3]['path'], MockData.path4); expect(drawPathResults.length, 4); expect(drawPathResults[0]['path'], MockData.path1); expect( drawPathResults[0]['paint_color'], isSameColorAs(MockData.color1), ); expect(drawPathResults[0]['paint_style'], PaintingStyle.stroke); expect( drawPathResults[0]['paint_stroke_width'], MockData.borderSide1.width * 2, ); expect(drawPathResults[1]['path'], MockData.path2); expect( drawPathResults[1]['paint_color'], isSameColorAs(MockData.color2), ); expect(drawPathResults[1]['paint_style'], PaintingStyle.stroke); expect( drawPathResults[1]['paint_stroke_width'], MockData.borderSide2.width * 2, ); expect(drawPathResults[2]['path'], MockData.path3); expect( drawPathResults[2]['paint_color'], isSameColorAs(MockData.color3), ); expect(drawPathResults[2]['paint_style'], PaintingStyle.stroke); expect( drawPathResults[2]['paint_stroke_width'], MockData.borderSide3.width * 2, ); expect(drawPathResults[3]['path'], MockData.path4); expect( drawPathResults[3]['paint_color'], isSameColorAs(MockData.color4), ); expect(drawPathResults[3]['paint_style'], PaintingStyle.stroke); expect( drawPathResults[3]['paint_stroke_width'], MockData.borderSide4.width * 2, ); verify(mockCanvasWrapper.restore()).called(4); }); }); group('calculateCenterRadius()', () { test('test 1', () { const viewSize = Size(400, 200); final sections = [ PieChartSectionData( color: MockData.color1, value: 1, borderSide: MockData.borderSide1, showTitle: true, titleStyle: MockData.textStyle1, radius: 11, ), PieChartSectionData( color: MockData.color2, value: 2, borderSide: MockData.borderSide2, showTitle: true, titleStyle: MockData.textStyle2, radius: 22, title: '22-22', ), PieChartSectionData( color: MockData.color3, value: 3, borderSide: MockData.borderSide3, showTitle: false, titleStyle: MockData.textStyle3, radius: 33, ), PieChartSectionData( color: MockData.color4, value: 4, borderSide: MockData.borderSide4, showTitle: true, titleStyle: MockData.textStyle4, radius: 44, ), ]; final barChartPainter = PieChartPainter(); final data1 = PieChartData(sections: sections, centerSpaceRadius: 15); final result1 = barChartPainter.calculateCenterRadius( viewSize, PaintHolder(data1, data1, TextScaler.noScaling), ); expect(result1, 15); final data2 = PieChartData(sections: sections); final result2 = barChartPainter.calculateCenterRadius( viewSize, PaintHolder(data2, data2, TextScaler.noScaling), ); expect(result2, 56); }); }); group('handleTouch()', () { test('test 2', () { const viewSize = Size(200, 200); final data = PieChartData( centerSpaceColor: MockData.color1, sectionsSpace: 10, sections: [ PieChartSectionData( color: MockData.color1, value: 1, borderSide: MockData.borderSide1, radius: 10, ), PieChartSectionData( color: MockData.color2, value: 2, borderSide: MockData.borderSide2, radius: 20, ), PieChartSectionData( color: MockData.color3, value: 3, borderSide: MockData.borderSide3, radius: 30, ), PieChartSectionData( color: MockData.color4, value: 4, borderSide: MockData.borderSide4, radius: 40, ), ], ); final barChartPainter = PieChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); expect( barChartPainter .handleTouch(const Offset(191, 110), viewSize, holder) .touchedSectionIndex, -1, ); expect( barChartPainter .handleTouch(const Offset(156, 110), viewSize, holder) .touchedSectionIndex, -1, ); expect( barChartPainter .handleTouch(const Offset(107, 190), viewSize, holder) .touchedSectionIndex, -1, ); expect( barChartPainter .handleTouch(const Offset(90, 156), viewSize, holder) .touchedSectionIndex, -1, ); expect( barChartPainter .handleTouch(const Offset(53, 131), viewSize, holder) .touchedSectionIndex, -1, ); expect( barChartPainter .handleTouch(const Offset(53, 131), viewSize, holder) .touchedSectionIndex, -1, ); expect( barChartPainter .handleTouch(const Offset(43, 94), viewSize, holder) .touchedSectionIndex, -1, ); expect( barChartPainter .handleTouch(const Offset(36, 57), viewSize, holder) .touchedSectionIndex, -1, ); expect( barChartPainter .handleTouch(const Offset(36, 57), viewSize, holder) .touchedSectionIndex, -1, ); expect( barChartPainter .handleTouch(const Offset(65, 4.3), viewSize, holder) .touchedSectionIndex, -1, ); expect( barChartPainter .handleTouch(const Offset(7, 108), viewSize, holder) .touchedSectionIndex, -1, ); expect( barChartPainter .handleTouch(const Offset(159.76, 135.56), viewSize, holder) .touchedSectionIndex, 0, ); expect( barChartPainter .handleTouch(const Offset(169.35, 108.4), viewSize, holder) .touchedSectionIndex, 0, ); expect( barChartPainter .handleTouch(const Offset(162.32, 109.37), viewSize, holder) .touchedSectionIndex, 0, ); expect( barChartPainter .handleTouch(const Offset(146.67, 144.94), viewSize, holder) .touchedSectionIndex, 1, ); expect( barChartPainter .handleTouch(const Offset(121.06, 160.38), viewSize, holder) .touchedSectionIndex, 1, ); expect( barChartPainter .handleTouch(const Offset(89.66, 163.60), viewSize, holder) .touchedSectionIndex, 1, ); expect( barChartPainter .handleTouch(const Offset(85.04, 177.85), viewSize, holder) .touchedSectionIndex, 1, ); expect( barChartPainter .handleTouch(const Offset(75.2, 158.4), viewSize, holder) .touchedSectionIndex, 2, ); expect( barChartPainter .handleTouch(const Offset(66.2, 177), viewSize, holder) .touchedSectionIndex, 2, ); expect( barChartPainter .handleTouch(const Offset(40.3, 124.8), viewSize, holder) .touchedSectionIndex, 2, ); expect( barChartPainter .handleTouch(const Offset(19.1, 131), viewSize, holder) .touchedSectionIndex, 2, ); expect( barChartPainter .handleTouch(const Offset(19.1, 131), viewSize, holder) .touchedSectionIndex, 2, ); expect( barChartPainter .handleTouch(const Offset(17.7, 83.7), viewSize, holder) .touchedSectionIndex, 2, ); expect( barChartPainter .handleTouch(const Offset(27.8, 59.4), viewSize, holder) .touchedSectionIndex, 2, ); expect( barChartPainter .handleTouch(const Offset(44.1, 75.2), viewSize, holder) .touchedSectionIndex, 2, ); expect( barChartPainter .handleTouch(const Offset(56.1, 55.6), viewSize, holder) .touchedSectionIndex, 3, ); expect( barChartPainter .handleTouch(const Offset(42.1, 46.3), viewSize, holder) .touchedSectionIndex, 3, ); expect( barChartPainter .handleTouch(const Offset(30.9, 38.4), viewSize, holder) .touchedSectionIndex, 3, ); expect( barChartPainter .handleTouch(const Offset(55.3, 17.8), viewSize, holder) .touchedSectionIndex, 3, ); expect( barChartPainter .handleTouch(const Offset(81.2, 39.8), viewSize, holder) .touchedSectionIndex, 3, ); expect( barChartPainter .handleTouch(const Offset(100.5, 4.1), viewSize, holder) .touchedSectionIndex, 3, ); expect( barChartPainter .handleTouch(const Offset(126.7, 40.6), viewSize, holder) .touchedSectionIndex, 3, ); expect( barChartPainter .handleTouch(const Offset(181.8, 51.3), viewSize, holder) .touchedSectionIndex, 3, ); expect( barChartPainter .handleTouch(const Offset(174.5, 40.2), viewSize, holder) .touchedSectionIndex, 3, ); expect( barChartPainter .handleTouch(const Offset(164.5, 91.4), viewSize, holder) .touchedSectionIndex, 3, ); }); test('test 3 with cornerRadius sections', () { const viewSize = Size(200, 200); final data = PieChartData( sectionsSpace: 10, sections: [ PieChartSectionData(value: 1, radius: 10, cornerRadius: 5), PieChartSectionData(value: 2, radius: 20, cornerRadius: 8), PieChartSectionData(value: 3, radius: 30, cornerRadius: 10), PieChartSectionData(value: 4, radius: 40, cornerRadius: 12), ], ); final barChartPainter = PieChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); expect( barChartPainter .handleTouch(const Offset(159.76, 135.56), viewSize, holder) .touchedSectionIndex, 0, ); expect( barChartPainter .handleTouch(const Offset(121.06, 160.38), viewSize, holder) .touchedSectionIndex, 1, ); expect( barChartPainter .handleTouch(const Offset(40.3, 124.8), viewSize, holder) .touchedSectionIndex, 2, ); expect( barChartPainter .handleTouch(const Offset(126.7, 40.6), viewSize, holder) .touchedSectionIndex, 3, ); }); }); group('getBadgeOffsets()', () { test('test 1', () { const viewSize = Size(200, 200); final data = PieChartData( centerSpaceColor: MockData.color1, sectionsSpace: 10, sections: [ PieChartSectionData( color: MockData.color1, value: 1, borderSide: MockData.borderSide1, ), PieChartSectionData( color: MockData.color2, value: 2, borderSide: MockData.borderSide2, ), PieChartSectionData( color: MockData.color3, value: 3, borderSide: MockData.borderSide3, ), PieChartSectionData( color: MockData.color4, value: 4, borderSide: MockData.borderSide4, ), ], ); final barChartPainter = PieChartPainter(); final holder = PaintHolder( data, data, TextScaler.noScaling, ); final result = barChartPainter.getBadgeOffsets(viewSize, holder); expect( result, { 0: const Offset(176.0845213036123, 124.7213595499958), 1: const Offset(124.7213595499958, 176.0845213036123), 2: const Offset(23.915478696387723, 124.7213595499958), 3: const Offset(124.72135954999578, 23.91547869638771), }, ); }); }); } ================================================ FILE: test/chart/pie_chart/pie_chart_painter_test.mocks.dart ================================================ // Mocks generated by Mockito 5.4.6 from annotations // in fl_chart/test/chart/pie_chart/pie_chart_painter_test.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:typed_data' as _i5; import 'dart:ui' as _i2; import 'package:fl_chart/fl_chart.dart' as _i7; import 'package:fl_chart/src/utils/canvas_wrapper.dart' as _i6; import 'package:fl_chart/src/utils/utils.dart' as _i8; import 'package:flutter/cupertino.dart' as _i3; import 'package:flutter/foundation.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; import 'package:mockito/src/dummies.dart' as _i9; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values // ignore_for_file: avoid_setters_without_getters // ignore_for_file: comment_references // ignore_for_file: deprecated_member_use // ignore_for_file: deprecated_member_use_from_same_package // ignore_for_file: implementation_imports // ignore_for_file: invalid_use_of_visible_for_testing_member // ignore_for_file: must_be_immutable // ignore_for_file: prefer_const_constructors // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class // ignore_for_file: invalid_use_of_internal_member class _FakeRect_0 extends _i1.SmartFake implements _i2.Rect { _FakeRect_0( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeCanvas_1 extends _i1.SmartFake implements _i2.Canvas { _FakeCanvas_1( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeSize_2 extends _i1.SmartFake implements _i2.Size { _FakeSize_2( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeWidget_3 extends _i1.SmartFake implements _i3.Widget { _FakeWidget_3( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info}) => super.toString(); } class _FakeInheritedWidget_4 extends _i1.SmartFake implements _i3.InheritedWidget { _FakeInheritedWidget_4( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info}) => super.toString(); } class _FakeDiagnosticsNode_5 extends _i1.SmartFake implements _i3.DiagnosticsNode { _FakeDiagnosticsNode_5( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({ _i4.TextTreeConfiguration? parentConfiguration, _i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info, }) => super.toString(); } class _FakeOffset_6 extends _i1.SmartFake implements _i2.Offset { _FakeOffset_6( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeBorderSide_7 extends _i1.SmartFake implements _i3.BorderSide { _FakeBorderSide_7( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info}) => super.toString(); } class _FakeTextStyle_8 extends _i1.SmartFake implements _i3.TextStyle { _FakeTextStyle_8( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info}) => super.toString(); } /// A class which mocks [Canvas]. /// /// See the documentation for Mockito's code generation for more information. class MockCanvas extends _i1.Mock implements _i2.Canvas { MockCanvas() { _i1.throwOnMissingStub(this); } @override void save() => super.noSuchMethod( Invocation.method( #save, [], ), returnValueForMissingStub: null, ); @override void saveLayer( _i2.Rect? bounds, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #saveLayer, [ bounds, paint, ], ), returnValueForMissingStub: null, ); @override void restore() => super.noSuchMethod( Invocation.method( #restore, [], ), returnValueForMissingStub: null, ); @override void restoreToCount(int? count) => super.noSuchMethod( Invocation.method( #restoreToCount, [count], ), returnValueForMissingStub: null, ); @override int getSaveCount() => (super.noSuchMethod( Invocation.method( #getSaveCount, [], ), returnValue: 0, ) as int); @override void translate( double? dx, double? dy, ) => super.noSuchMethod( Invocation.method( #translate, [ dx, dy, ], ), returnValueForMissingStub: null, ); @override void scale( double? sx, [ double? sy, ]) => super.noSuchMethod( Invocation.method( #scale, [ sx, sy, ], ), returnValueForMissingStub: null, ); @override void rotate(double? radians) => super.noSuchMethod( Invocation.method( #rotate, [radians], ), returnValueForMissingStub: null, ); @override void skew( double? sx, double? sy, ) => super.noSuchMethod( Invocation.method( #skew, [ sx, sy, ], ), returnValueForMissingStub: null, ); @override void transform(_i5.Float64List? matrix4) => super.noSuchMethod( Invocation.method( #transform, [matrix4], ), returnValueForMissingStub: null, ); @override _i5.Float64List getTransform() => (super.noSuchMethod( Invocation.method( #getTransform, [], ), returnValue: _i5.Float64List(0), ) as _i5.Float64List); @override void clipRect( _i2.Rect? rect, { _i2.ClipOp? clipOp = _i2.ClipOp.intersect, bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipRect, [rect], { #clipOp: clipOp, #doAntiAlias: doAntiAlias, }, ), returnValueForMissingStub: null, ); @override void clipRRect( _i2.RRect? rrect, { bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipRRect, [rrect], {#doAntiAlias: doAntiAlias}, ), returnValueForMissingStub: null, ); @override void clipRSuperellipse( _i2.RSuperellipse? rsuperellipse, { bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipRSuperellipse, [rsuperellipse], {#doAntiAlias: doAntiAlias}, ), returnValueForMissingStub: null, ); @override void clipPath( _i2.Path? path, { bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipPath, [path], {#doAntiAlias: doAntiAlias}, ), returnValueForMissingStub: null, ); @override _i2.Rect getLocalClipBounds() => (super.noSuchMethod( Invocation.method( #getLocalClipBounds, [], ), returnValue: _FakeRect_0( this, Invocation.method( #getLocalClipBounds, [], ), ), ) as _i2.Rect); @override _i2.Rect getDestinationClipBounds() => (super.noSuchMethod( Invocation.method( #getDestinationClipBounds, [], ), returnValue: _FakeRect_0( this, Invocation.method( #getDestinationClipBounds, [], ), ), ) as _i2.Rect); @override void drawColor( _i2.Color? color, _i2.BlendMode? blendMode, ) => super.noSuchMethod( Invocation.method( #drawColor, [ color, blendMode, ], ), returnValueForMissingStub: null, ); @override void drawLine( _i2.Offset? p1, _i2.Offset? p2, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawLine, [ p1, p2, paint, ], ), returnValueForMissingStub: null, ); @override void drawPaint(_i2.Paint? paint) => super.noSuchMethod( Invocation.method( #drawPaint, [paint], ), returnValueForMissingStub: null, ); @override void drawRect( _i2.Rect? rect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRect, [ rect, paint, ], ), returnValueForMissingStub: null, ); @override void drawRRect( _i2.RRect? rrect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRRect, [ rrect, paint, ], ), returnValueForMissingStub: null, ); @override void drawDRRect( _i2.RRect? outer, _i2.RRect? inner, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawDRRect, [ outer, inner, paint, ], ), returnValueForMissingStub: null, ); @override void drawRSuperellipse( _i2.RSuperellipse? rsuperellipse, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRSuperellipse, [ rsuperellipse, paint, ], ), returnValueForMissingStub: null, ); @override void drawOval( _i2.Rect? rect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawOval, [ rect, paint, ], ), returnValueForMissingStub: null, ); @override void drawCircle( _i2.Offset? c, double? radius, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawCircle, [ c, radius, paint, ], ), returnValueForMissingStub: null, ); @override void drawArc( _i2.Rect? rect, double? startAngle, double? sweepAngle, bool? useCenter, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawArc, [ rect, startAngle, sweepAngle, useCenter, paint, ], ), returnValueForMissingStub: null, ); @override void drawPath( _i2.Path? path, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawPath, [ path, paint, ], ), returnValueForMissingStub: null, ); @override void drawImage( _i2.Image? image, _i2.Offset? offset, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawImage, [ image, offset, paint, ], ), returnValueForMissingStub: null, ); @override void drawImageRect( _i2.Image? image, _i2.Rect? src, _i2.Rect? dst, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawImageRect, [ image, src, dst, paint, ], ), returnValueForMissingStub: null, ); @override void drawImageNine( _i2.Image? image, _i2.Rect? center, _i2.Rect? dst, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawImageNine, [ image, center, dst, paint, ], ), returnValueForMissingStub: null, ); @override void drawPicture(_i2.Picture? picture) => super.noSuchMethod( Invocation.method( #drawPicture, [picture], ), returnValueForMissingStub: null, ); @override void drawParagraph( _i2.Paragraph? paragraph, _i2.Offset? offset, ) => super.noSuchMethod( Invocation.method( #drawParagraph, [ paragraph, offset, ], ), returnValueForMissingStub: null, ); @override void drawPoints( _i2.PointMode? pointMode, List<_i2.Offset>? points, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawPoints, [ pointMode, points, paint, ], ), returnValueForMissingStub: null, ); @override void drawRawPoints( _i2.PointMode? pointMode, _i5.Float32List? points, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRawPoints, [ pointMode, points, paint, ], ), returnValueForMissingStub: null, ); @override void drawVertices( _i2.Vertices? vertices, _i2.BlendMode? blendMode, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawVertices, [ vertices, blendMode, paint, ], ), returnValueForMissingStub: null, ); @override void drawAtlas( _i2.Image? atlas, List<_i2.RSTransform>? transforms, List<_i2.Rect>? rects, List<_i2.Color>? colors, _i2.BlendMode? blendMode, _i2.Rect? cullRect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawAtlas, [ atlas, transforms, rects, colors, blendMode, cullRect, paint, ], ), returnValueForMissingStub: null, ); @override void drawRawAtlas( _i2.Image? atlas, _i5.Float32List? rstTransforms, _i5.Float32List? rects, _i5.Int32List? colors, _i2.BlendMode? blendMode, _i2.Rect? cullRect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRawAtlas, [ atlas, rstTransforms, rects, colors, blendMode, cullRect, paint, ], ), returnValueForMissingStub: null, ); @override void drawShadow( _i2.Path? path, _i2.Color? color, double? elevation, bool? transparentOccluder, ) => super.noSuchMethod( Invocation.method( #drawShadow, [ path, color, elevation, transparentOccluder, ], ), returnValueForMissingStub: null, ); } /// A class which mocks [CanvasWrapper]. /// /// See the documentation for Mockito's code generation for more information. class MockCanvasWrapper extends _i1.Mock implements _i6.CanvasWrapper { MockCanvasWrapper() { _i1.throwOnMissingStub(this); } @override _i2.Canvas get canvas => (super.noSuchMethod( Invocation.getter(#canvas), returnValue: _FakeCanvas_1( this, Invocation.getter(#canvas), ), ) as _i2.Canvas); @override _i2.Size get size => (super.noSuchMethod( Invocation.getter(#size), returnValue: _FakeSize_2( this, Invocation.getter(#size), ), ) as _i2.Size); @override void drawRRect( _i2.RRect? rrect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRRect, [ rrect, paint, ], ), returnValueForMissingStub: null, ); @override void save() => super.noSuchMethod( Invocation.method( #save, [], ), returnValueForMissingStub: null, ); @override void restore() => super.noSuchMethod( Invocation.method( #restore, [], ), returnValueForMissingStub: null, ); @override void clipRect( _i2.Rect? rect, { _i2.ClipOp? clipOp = _i2.ClipOp.intersect, bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipRect, [rect], { #clipOp: clipOp, #doAntiAlias: doAntiAlias, }, ), returnValueForMissingStub: null, ); @override void translate( double? dx, double? dy, ) => super.noSuchMethod( Invocation.method( #translate, [ dx, dy, ], ), returnValueForMissingStub: null, ); @override void rotate(double? radius) => super.noSuchMethod( Invocation.method( #rotate, [radius], ), returnValueForMissingStub: null, ); @override void drawPath( _i2.Path? path, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawPath, [ path, paint, ], ), returnValueForMissingStub: null, ); @override void saveLayer( _i2.Rect? bounds, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #saveLayer, [ bounds, paint, ], ), returnValueForMissingStub: null, ); @override void drawPicture(_i2.Picture? picture) => super.noSuchMethod( Invocation.method( #drawPicture, [picture], ), returnValueForMissingStub: null, ); @override void drawImage( _i2.Image? image, _i2.Offset? offset, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawImage, [ image, offset, paint, ], ), returnValueForMissingStub: null, ); @override void clipPath( _i2.Path? path, { bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipPath, [path], {#doAntiAlias: doAntiAlias}, ), returnValueForMissingStub: null, ); @override void drawRect( _i2.Rect? rect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRect, [ rect, paint, ], ), returnValueForMissingStub: null, ); @override void drawLine( _i2.Offset? p1, _i2.Offset? p2, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawLine, [ p1, p2, paint, ], ), returnValueForMissingStub: null, ); @override void drawCircle( _i2.Offset? center, double? radius, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawCircle, [ center, radius, paint, ], ), returnValueForMissingStub: null, ); @override void drawArc( _i2.Rect? rect, double? startAngle, double? sweepAngle, bool? useCenter, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawArc, [ rect, startAngle, sweepAngle, useCenter, paint, ], ), returnValueForMissingStub: null, ); @override void drawText( _i3.TextPainter? tp, _i2.Offset? offset, [ double? rotateAngle, ]) => super.noSuchMethod( Invocation.method( #drawText, [ tp, offset, rotateAngle, ], ), returnValueForMissingStub: null, ); @override void drawVerticalText( _i3.TextPainter? tp, _i2.Offset? offset, [ double? rotateAngle = 90.0, ]) => super.noSuchMethod( Invocation.method( #drawVerticalText, [ tp, offset, rotateAngle, ], ), returnValueForMissingStub: null, ); @override void drawDot( _i7.FlDotPainter? painter, _i7.FlSpot? spot, _i2.Offset? offset, ) => super.noSuchMethod( Invocation.method( #drawDot, [ painter, spot, offset, ], ), returnValueForMissingStub: null, ); @override void drawErrorIndicator( _i7.FlSpotErrorRangePainter? painter, _i7.FlSpot? origin, _i2.Offset? offset, _i2.Rect? errorRelativeRect, _i7.AxisChartData? axisData, ) => super.noSuchMethod( Invocation.method( #drawErrorIndicator, [ painter, origin, offset, errorRelativeRect, axisData, ], ), returnValueForMissingStub: null, ); @override void drawRotated({ required _i2.Size? size, _i2.Offset? rotationOffset = _i2.Offset.zero, _i2.Offset? drawOffset = _i2.Offset.zero, required double? angle, required _i6.DrawCallback? drawCallback, }) => super.noSuchMethod( Invocation.method( #drawRotated, [], { #size: size, #rotationOffset: rotationOffset, #drawOffset: drawOffset, #angle: angle, #drawCallback: drawCallback, }, ), returnValueForMissingStub: null, ); @override void drawDashedLine( _i2.Offset? from, _i2.Offset? to, _i2.Paint? painter, List? dashArray, ) => super.noSuchMethod( Invocation.method( #drawDashedLine, [ from, to, painter, dashArray, ], ), returnValueForMissingStub: null, ); } /// A class which mocks [BuildContext]. /// /// See the documentation for Mockito's code generation for more information. class MockBuildContext extends _i1.Mock implements _i3.BuildContext { MockBuildContext() { _i1.throwOnMissingStub(this); } @override _i3.Widget get widget => (super.noSuchMethod( Invocation.getter(#widget), returnValue: _FakeWidget_3( this, Invocation.getter(#widget), ), ) as _i3.Widget); @override bool get mounted => (super.noSuchMethod( Invocation.getter(#mounted), returnValue: false, ) as bool); @override bool get debugDoingBuild => (super.noSuchMethod( Invocation.getter(#debugDoingBuild), returnValue: false, ) as bool); @override _i3.InheritedWidget dependOnInheritedElement( _i3.InheritedElement? ancestor, { Object? aspect, }) => (super.noSuchMethod( Invocation.method( #dependOnInheritedElement, [ancestor], {#aspect: aspect}, ), returnValue: _FakeInheritedWidget_4( this, Invocation.method( #dependOnInheritedElement, [ancestor], {#aspect: aspect}, ), ), ) as _i3.InheritedWidget); @override void visitAncestorElements(_i3.ConditionalElementVisitor? visitor) => super.noSuchMethod( Invocation.method( #visitAncestorElements, [visitor], ), returnValueForMissingStub: null, ); @override void visitChildElements(_i3.ElementVisitor? visitor) => super.noSuchMethod( Invocation.method( #visitChildElements, [visitor], ), returnValueForMissingStub: null, ); @override void dispatchNotification(_i3.Notification? notification) => super.noSuchMethod( Invocation.method( #dispatchNotification, [notification], ), returnValueForMissingStub: null, ); @override _i3.DiagnosticsNode describeElement( String? name, { _i4.DiagnosticsTreeStyle? style = _i4.DiagnosticsTreeStyle.errorProperty, }) => (super.noSuchMethod( Invocation.method( #describeElement, [name], {#style: style}, ), returnValue: _FakeDiagnosticsNode_5( this, Invocation.method( #describeElement, [name], {#style: style}, ), ), ) as _i3.DiagnosticsNode); @override _i3.DiagnosticsNode describeWidget( String? name, { _i4.DiagnosticsTreeStyle? style = _i4.DiagnosticsTreeStyle.errorProperty, }) => (super.noSuchMethod( Invocation.method( #describeWidget, [name], {#style: style}, ), returnValue: _FakeDiagnosticsNode_5( this, Invocation.method( #describeWidget, [name], {#style: style}, ), ), ) as _i3.DiagnosticsNode); @override List<_i3.DiagnosticsNode> describeMissingAncestor( {required Type? expectedAncestorType}) => (super.noSuchMethod( Invocation.method( #describeMissingAncestor, [], {#expectedAncestorType: expectedAncestorType}, ), returnValue: <_i3.DiagnosticsNode>[], ) as List<_i3.DiagnosticsNode>); @override _i3.DiagnosticsNode describeOwnershipChain(String? name) => (super.noSuchMethod( Invocation.method( #describeOwnershipChain, [name], ), returnValue: _FakeDiagnosticsNode_5( this, Invocation.method( #describeOwnershipChain, [name], ), ), ) as _i3.DiagnosticsNode); } /// A class which mocks [Utils]. /// /// See the documentation for Mockito's code generation for more information. class MockUtils extends _i1.Mock implements _i8.Utils { MockUtils() { _i1.throwOnMissingStub(this); } @override double radians(double? degrees) => (super.noSuchMethod( Invocation.method( #radians, [degrees], ), returnValue: 0.0, ) as double); @override double degrees(double? radians) => (super.noSuchMethod( Invocation.method( #degrees, [radians], ), returnValue: 0.0, ) as double); @override double translateRotatedPosition( double? size, double? degree, ) => (super.noSuchMethod( Invocation.method( #translateRotatedPosition, [ size, degree, ], ), returnValue: 0.0, ) as double); @override _i2.Offset calculateRotationOffset( _i2.Size? size, double? degree, ) => (super.noSuchMethod( Invocation.method( #calculateRotationOffset, [ size, degree, ], ), returnValue: _FakeOffset_6( this, Invocation.method( #calculateRotationOffset, [ size, degree, ], ), ), ) as _i2.Offset); @override _i3.BorderRadius? normalizeBorderRadius( _i3.BorderRadius? borderRadius, double? width, ) => (super.noSuchMethod(Invocation.method( #normalizeBorderRadius, [ borderRadius, width, ], )) as _i3.BorderRadius?); @override _i3.BorderSide normalizeBorderSide( _i3.BorderSide? borderSide, double? width, ) => (super.noSuchMethod( Invocation.method( #normalizeBorderSide, [ borderSide, width, ], ), returnValue: _FakeBorderSide_7( this, Invocation.method( #normalizeBorderSide, [ borderSide, width, ], ), ), ) as _i3.BorderSide); @override double getEfficientInterval( double? axisViewSize, double? diffInAxis, { double? pixelPerInterval = 40.0, }) => (super.noSuchMethod( Invocation.method( #getEfficientInterval, [ axisViewSize, diffInAxis, ], {#pixelPerInterval: pixelPerInterval}, ), returnValue: 0.0, ) as double); @override double roundInterval(double? input) => (super.noSuchMethod( Invocation.method( #roundInterval, [input], ), returnValue: 0.0, ) as double); @override int getFractionDigits(double? value) => (super.noSuchMethod( Invocation.method( #getFractionDigits, [value], ), returnValue: 0, ) as int); @override String formatNumber( double? axisMin, double? axisMax, double? axisValue, ) => (super.noSuchMethod( Invocation.method( #formatNumber, [ axisMin, axisMax, axisValue, ], ), returnValue: _i9.dummyValue( this, Invocation.method( #formatNumber, [ axisMin, axisMax, axisValue, ], ), ), ) as String); @override _i3.TextStyle getThemeAwareTextStyle( _i3.BuildContext? context, _i3.TextStyle? providedStyle, ) => (super.noSuchMethod( Invocation.method( #getThemeAwareTextStyle, [ context, providedStyle, ], ), returnValue: _FakeTextStyle_8( this, Invocation.method( #getThemeAwareTextStyle, [ context, providedStyle, ], ), ), ) as _i3.TextStyle); @override double getBestInitialIntervalValue( double? min, double? max, double? interval, { double? baseline = 0.0, }) => (super.noSuchMethod( Invocation.method( #getBestInitialIntervalValue, [ min, max, interval, ], {#baseline: baseline}, ), returnValue: 0.0, ) as double); @override double convertRadiusToSigma(double? radius) => (super.noSuchMethod( Invocation.method( #convertRadiusToSigma, [radius], ), returnValue: 0.0, ) as double); } ================================================ FILE: test/chart/pie_chart/pie_chart_renderer_test.dart ================================================ import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart'; import 'package:fl_chart/src/chart/pie_chart/pie_chart_painter.dart'; import 'package:fl_chart/src/chart/pie_chart/pie_chart_renderer.dart'; import 'package:fl_chart/src/utils/canvas_wrapper.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import '../data_pool.dart'; import 'pie_chart_renderer_test.mocks.dart'; @GenerateMocks([Canvas, PaintingContext, BuildContext, PieChartPainter]) void main() { group('PieChartRenderer', () { final data = PieChartData(); final targetData = PieChartData( centerSpaceRadius: 12, pieTouchData: PieTouchData(enabled: false), ); const textScaler = TextScaler.linear(4); final mockBuildContext = MockBuildContext(); final renderBarChart = RenderPieChart( mockBuildContext, data, targetData, textScaler, ); final mockPainter = MockPieChartPainter(); final mockPaintingContext = MockPaintingContext(); final mockCanvas = MockCanvas(); const mockSize = Size(44, 44); when(mockPaintingContext.canvas).thenAnswer((realInvocation) => mockCanvas); renderBarChart ..mockTestSize = mockSize ..painter = mockPainter; test('test 1 correct data set', () { expect(renderBarChart.data == data, true); expect(renderBarChart.data == targetData, false); expect(renderBarChart.targetData == targetData, true); expect(renderBarChart.textScaler == textScaler, true); expect(renderBarChart.paintHolder.data == data, true); expect(renderBarChart.paintHolder.targetData == targetData, true); expect(renderBarChart.paintHolder.textScaler == textScaler, true); expect(renderBarChart.hitTestSelf(Offset.zero), false); }); test('test 2 check paint function', () { renderBarChart.paint(mockPaintingContext, const Offset(10, 10)); verify(mockCanvas.save()).called(1); verify(mockCanvas.translate(10, 10)).called(1); final result = verify(mockPainter.paint(any, captureAny, captureAny)); expect(result.callCount, 1); final canvasWrapper = result.captured[0] as CanvasWrapper; expect(canvasWrapper.size, const Size(44, 44)); expect(canvasWrapper.canvas, mockCanvas); final paintHolder = result.captured[1] as PaintHolder; expect(paintHolder.data, data); expect(paintHolder.targetData, targetData); expect(paintHolder.textScaler, textScaler); verify(mockCanvas.restore()).called(1); }); test('test 3 check getResponseAtLocation function', () { final results = >[]; when(mockPainter.handleTouch(captureAny, captureAny, captureAny)) .thenAnswer((inv) { results.add({ 'local_position': inv.positionalArguments[0] as Offset, 'size': inv.positionalArguments[1] as Size, 'paint_holder': inv.positionalArguments[2] as PaintHolder, }); return MockData.pieTouchedSection1; }); final touchResponse = renderBarChart.getResponseAtLocation(MockData.offset1); expect(touchResponse.touchedSection, MockData.pieTouchedSection1); expect(results[0]['local_position'] as Offset, MockData.offset1); expect(results[0]['size'] as Size, mockSize); final paintHolder = results[0]['paint_holder'] as PaintHolder; expect(paintHolder.data, data); expect(paintHolder.targetData, targetData); expect(paintHolder.textScaler, textScaler); }); test('test 4 check setters', () { renderBarChart ..data = targetData ..targetData = data ..textScaler = const TextScaler.linear(22); expect(renderBarChart.data, targetData); expect(renderBarChart.targetData, data); expect(renderBarChart.textScaler, const TextScaler.linear(22)); }); }); } ================================================ FILE: test/chart/pie_chart/pie_chart_renderer_test.mocks.dart ================================================ // Mocks generated by Mockito 5.4.6 from annotations // in fl_chart/test/chart/pie_chart/pie_chart_renderer_test.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:typed_data' as _i8; import 'dart:ui' as _i2; import 'package:fl_chart/fl_chart.dart' as _i7; import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart' as _i13; import 'package:fl_chart/src/chart/base/line.dart' as _i14; import 'package:fl_chart/src/chart/pie_chart/pie_chart_painter.dart' as _i11; import 'package:fl_chart/src/utils/canvas_wrapper.dart' as _i12; import 'package:flutter/foundation.dart' as _i5; import 'package:flutter/gestures.dart' as _i9; import 'package:flutter/material.dart' as _i6; import 'package:flutter/rendering.dart' as _i3; import 'package:flutter/src/rendering/layer.dart' as _i4; import 'package:flutter/src/widgets/notification_listener.dart' as _i10; import 'package:mockito/mockito.dart' as _i1; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values // ignore_for_file: avoid_setters_without_getters // ignore_for_file: comment_references // ignore_for_file: deprecated_member_use // ignore_for_file: deprecated_member_use_from_same_package // ignore_for_file: implementation_imports // ignore_for_file: invalid_use_of_visible_for_testing_member // ignore_for_file: must_be_immutable // ignore_for_file: prefer_const_constructors // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class // ignore_for_file: invalid_use_of_internal_member class _FakeRect_0 extends _i1.SmartFake implements _i2.Rect { _FakeRect_0( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeCanvas_1 extends _i1.SmartFake implements _i2.Canvas { _FakeCanvas_1( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakePaintingContext_2 extends _i1.SmartFake implements _i3.PaintingContext { _FakePaintingContext_2( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeColorFilterLayer_3 extends _i1.SmartFake implements _i4.ColorFilterLayer { _FakeColorFilterLayer_3( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => super.toString(); } class _FakeOpacityLayer_4 extends _i1.SmartFake implements _i4.OpacityLayer { _FakeOpacityLayer_4( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => super.toString(); } class _FakeWidget_5 extends _i1.SmartFake implements _i6.Widget { _FakeWidget_5( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => super.toString(); } class _FakeInheritedWidget_6 extends _i1.SmartFake implements _i6.InheritedWidget { _FakeInheritedWidget_6( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => super.toString(); } class _FakeDiagnosticsNode_7 extends _i1.SmartFake implements _i5.DiagnosticsNode { _FakeDiagnosticsNode_7( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({ _i5.TextTreeConfiguration? parentConfiguration, _i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info, }) => super.toString(); } class _FakePath_8 extends _i1.SmartFake implements _i2.Path { _FakePath_8( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakePieTouchedSection_9 extends _i1.SmartFake implements _i7.PieTouchedSection { _FakePieTouchedSection_9( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } /// A class which mocks [Canvas]. /// /// See the documentation for Mockito's code generation for more information. class MockCanvas extends _i1.Mock implements _i2.Canvas { MockCanvas() { _i1.throwOnMissingStub(this); } @override void save() => super.noSuchMethod( Invocation.method( #save, [], ), returnValueForMissingStub: null, ); @override void saveLayer( _i2.Rect? bounds, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #saveLayer, [ bounds, paint, ], ), returnValueForMissingStub: null, ); @override void restore() => super.noSuchMethod( Invocation.method( #restore, [], ), returnValueForMissingStub: null, ); @override void restoreToCount(int? count) => super.noSuchMethod( Invocation.method( #restoreToCount, [count], ), returnValueForMissingStub: null, ); @override int getSaveCount() => (super.noSuchMethod( Invocation.method( #getSaveCount, [], ), returnValue: 0, ) as int); @override void translate( double? dx, double? dy, ) => super.noSuchMethod( Invocation.method( #translate, [ dx, dy, ], ), returnValueForMissingStub: null, ); @override void scale( double? sx, [ double? sy, ]) => super.noSuchMethod( Invocation.method( #scale, [ sx, sy, ], ), returnValueForMissingStub: null, ); @override void rotate(double? radians) => super.noSuchMethod( Invocation.method( #rotate, [radians], ), returnValueForMissingStub: null, ); @override void skew( double? sx, double? sy, ) => super.noSuchMethod( Invocation.method( #skew, [ sx, sy, ], ), returnValueForMissingStub: null, ); @override void transform(_i8.Float64List? matrix4) => super.noSuchMethod( Invocation.method( #transform, [matrix4], ), returnValueForMissingStub: null, ); @override _i8.Float64List getTransform() => (super.noSuchMethod( Invocation.method( #getTransform, [], ), returnValue: _i8.Float64List(0), ) as _i8.Float64List); @override void clipRect( _i2.Rect? rect, { _i2.ClipOp? clipOp = _i2.ClipOp.intersect, bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipRect, [rect], { #clipOp: clipOp, #doAntiAlias: doAntiAlias, }, ), returnValueForMissingStub: null, ); @override void clipRRect( _i2.RRect? rrect, { bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipRRect, [rrect], {#doAntiAlias: doAntiAlias}, ), returnValueForMissingStub: null, ); @override void clipRSuperellipse( _i2.RSuperellipse? rsuperellipse, { bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipRSuperellipse, [rsuperellipse], {#doAntiAlias: doAntiAlias}, ), returnValueForMissingStub: null, ); @override void clipPath( _i2.Path? path, { bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipPath, [path], {#doAntiAlias: doAntiAlias}, ), returnValueForMissingStub: null, ); @override _i2.Rect getLocalClipBounds() => (super.noSuchMethod( Invocation.method( #getLocalClipBounds, [], ), returnValue: _FakeRect_0( this, Invocation.method( #getLocalClipBounds, [], ), ), ) as _i2.Rect); @override _i2.Rect getDestinationClipBounds() => (super.noSuchMethod( Invocation.method( #getDestinationClipBounds, [], ), returnValue: _FakeRect_0( this, Invocation.method( #getDestinationClipBounds, [], ), ), ) as _i2.Rect); @override void drawColor( _i2.Color? color, _i2.BlendMode? blendMode, ) => super.noSuchMethod( Invocation.method( #drawColor, [ color, blendMode, ], ), returnValueForMissingStub: null, ); @override void drawLine( _i2.Offset? p1, _i2.Offset? p2, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawLine, [ p1, p2, paint, ], ), returnValueForMissingStub: null, ); @override void drawPaint(_i2.Paint? paint) => super.noSuchMethod( Invocation.method( #drawPaint, [paint], ), returnValueForMissingStub: null, ); @override void drawRect( _i2.Rect? rect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRect, [ rect, paint, ], ), returnValueForMissingStub: null, ); @override void drawRRect( _i2.RRect? rrect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRRect, [ rrect, paint, ], ), returnValueForMissingStub: null, ); @override void drawDRRect( _i2.RRect? outer, _i2.RRect? inner, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawDRRect, [ outer, inner, paint, ], ), returnValueForMissingStub: null, ); @override void drawRSuperellipse( _i2.RSuperellipse? rsuperellipse, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRSuperellipse, [ rsuperellipse, paint, ], ), returnValueForMissingStub: null, ); @override void drawOval( _i2.Rect? rect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawOval, [ rect, paint, ], ), returnValueForMissingStub: null, ); @override void drawCircle( _i2.Offset? c, double? radius, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawCircle, [ c, radius, paint, ], ), returnValueForMissingStub: null, ); @override void drawArc( _i2.Rect? rect, double? startAngle, double? sweepAngle, bool? useCenter, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawArc, [ rect, startAngle, sweepAngle, useCenter, paint, ], ), returnValueForMissingStub: null, ); @override void drawPath( _i2.Path? path, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawPath, [ path, paint, ], ), returnValueForMissingStub: null, ); @override void drawImage( _i2.Image? image, _i2.Offset? offset, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawImage, [ image, offset, paint, ], ), returnValueForMissingStub: null, ); @override void drawImageRect( _i2.Image? image, _i2.Rect? src, _i2.Rect? dst, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawImageRect, [ image, src, dst, paint, ], ), returnValueForMissingStub: null, ); @override void drawImageNine( _i2.Image? image, _i2.Rect? center, _i2.Rect? dst, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawImageNine, [ image, center, dst, paint, ], ), returnValueForMissingStub: null, ); @override void drawPicture(_i2.Picture? picture) => super.noSuchMethod( Invocation.method( #drawPicture, [picture], ), returnValueForMissingStub: null, ); @override void drawParagraph( _i2.Paragraph? paragraph, _i2.Offset? offset, ) => super.noSuchMethod( Invocation.method( #drawParagraph, [ paragraph, offset, ], ), returnValueForMissingStub: null, ); @override void drawPoints( _i2.PointMode? pointMode, List<_i2.Offset>? points, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawPoints, [ pointMode, points, paint, ], ), returnValueForMissingStub: null, ); @override void drawRawPoints( _i2.PointMode? pointMode, _i8.Float32List? points, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRawPoints, [ pointMode, points, paint, ], ), returnValueForMissingStub: null, ); @override void drawVertices( _i2.Vertices? vertices, _i2.BlendMode? blendMode, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawVertices, [ vertices, blendMode, paint, ], ), returnValueForMissingStub: null, ); @override void drawAtlas( _i2.Image? atlas, List<_i2.RSTransform>? transforms, List<_i2.Rect>? rects, List<_i2.Color>? colors, _i2.BlendMode? blendMode, _i2.Rect? cullRect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawAtlas, [ atlas, transforms, rects, colors, blendMode, cullRect, paint, ], ), returnValueForMissingStub: null, ); @override void drawRawAtlas( _i2.Image? atlas, _i8.Float32List? rstTransforms, _i8.Float32List? rects, _i8.Int32List? colors, _i2.BlendMode? blendMode, _i2.Rect? cullRect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRawAtlas, [ atlas, rstTransforms, rects, colors, blendMode, cullRect, paint, ], ), returnValueForMissingStub: null, ); @override void drawShadow( _i2.Path? path, _i2.Color? color, double? elevation, bool? transparentOccluder, ) => super.noSuchMethod( Invocation.method( #drawShadow, [ path, color, elevation, transparentOccluder, ], ), returnValueForMissingStub: null, ); } /// A class which mocks [PaintingContext]. /// /// See the documentation for Mockito's code generation for more information. class MockPaintingContext extends _i1.Mock implements _i3.PaintingContext { MockPaintingContext() { _i1.throwOnMissingStub(this); } @override _i2.Rect get estimatedBounds => (super.noSuchMethod( Invocation.getter(#estimatedBounds), returnValue: _FakeRect_0( this, Invocation.getter(#estimatedBounds), ), ) as _i2.Rect); @override _i2.Canvas get canvas => (super.noSuchMethod( Invocation.getter(#canvas), returnValue: _FakeCanvas_1( this, Invocation.getter(#canvas), ), ) as _i2.Canvas); @override void paintChild( _i3.RenderObject? child, _i2.Offset? offset, ) => super.noSuchMethod( Invocation.method( #paintChild, [ child, offset, ], ), returnValueForMissingStub: null, ); @override void appendLayer(_i4.Layer? layer) => super.noSuchMethod( Invocation.method( #appendLayer, [layer], ), returnValueForMissingStub: null, ); @override _i2.VoidCallback addCompositionCallback(_i4.CompositionCallback? callback) => (super.noSuchMethod( Invocation.method( #addCompositionCallback, [callback], ), returnValue: () {}, ) as _i2.VoidCallback); @override void stopRecordingIfNeeded() => super.noSuchMethod( Invocation.method( #stopRecordingIfNeeded, [], ), returnValueForMissingStub: null, ); @override void setIsComplexHint() => super.noSuchMethod( Invocation.method( #setIsComplexHint, [], ), returnValueForMissingStub: null, ); @override void setWillChangeHint() => super.noSuchMethod( Invocation.method( #setWillChangeHint, [], ), returnValueForMissingStub: null, ); @override void addLayer(_i4.Layer? layer) => super.noSuchMethod( Invocation.method( #addLayer, [layer], ), returnValueForMissingStub: null, ); @override void pushLayer( _i4.ContainerLayer? childLayer, _i3.PaintingContextCallback? painter, _i2.Offset? offset, { _i2.Rect? childPaintBounds, }) => super.noSuchMethod( Invocation.method( #pushLayer, [ childLayer, painter, offset, ], {#childPaintBounds: childPaintBounds}, ), returnValueForMissingStub: null, ); @override _i3.PaintingContext createChildContext( _i4.ContainerLayer? childLayer, _i2.Rect? bounds, ) => (super.noSuchMethod( Invocation.method( #createChildContext, [ childLayer, bounds, ], ), returnValue: _FakePaintingContext_2( this, Invocation.method( #createChildContext, [ childLayer, bounds, ], ), ), ) as _i3.PaintingContext); @override _i4.ClipRectLayer? pushClipRect( bool? needsCompositing, _i2.Offset? offset, _i2.Rect? clipRect, _i3.PaintingContextCallback? painter, { _i2.Clip? clipBehavior = _i2.Clip.hardEdge, _i4.ClipRectLayer? oldLayer, }) => (super.noSuchMethod(Invocation.method( #pushClipRect, [ needsCompositing, offset, clipRect, painter, ], { #clipBehavior: clipBehavior, #oldLayer: oldLayer, }, )) as _i4.ClipRectLayer?); @override _i4.ClipRRectLayer? pushClipRRect( bool? needsCompositing, _i2.Offset? offset, _i2.Rect? bounds, _i2.RRect? clipRRect, _i3.PaintingContextCallback? painter, { _i2.Clip? clipBehavior = _i2.Clip.antiAlias, _i4.ClipRRectLayer? oldLayer, }) => (super.noSuchMethod(Invocation.method( #pushClipRRect, [ needsCompositing, offset, bounds, clipRRect, painter, ], { #clipBehavior: clipBehavior, #oldLayer: oldLayer, }, )) as _i4.ClipRRectLayer?); @override _i4.ClipRSuperellipseLayer? pushClipRSuperellipse( bool? needsCompositing, _i2.Offset? offset, _i2.Rect? bounds, _i2.RSuperellipse? clipRSuperellipse, _i3.PaintingContextCallback? painter, { _i2.Clip? clipBehavior = _i2.Clip.antiAlias, _i4.ClipRSuperellipseLayer? oldLayer, }) => (super.noSuchMethod(Invocation.method( #pushClipRSuperellipse, [ needsCompositing, offset, bounds, clipRSuperellipse, painter, ], { #clipBehavior: clipBehavior, #oldLayer: oldLayer, }, )) as _i4.ClipRSuperellipseLayer?); @override _i4.ClipPathLayer? pushClipPath( bool? needsCompositing, _i2.Offset? offset, _i2.Rect? bounds, _i2.Path? clipPath, _i3.PaintingContextCallback? painter, { _i2.Clip? clipBehavior = _i2.Clip.antiAlias, _i4.ClipPathLayer? oldLayer, }) => (super.noSuchMethod(Invocation.method( #pushClipPath, [ needsCompositing, offset, bounds, clipPath, painter, ], { #clipBehavior: clipBehavior, #oldLayer: oldLayer, }, )) as _i4.ClipPathLayer?); @override _i4.ColorFilterLayer pushColorFilter( _i2.Offset? offset, _i2.ColorFilter? colorFilter, _i3.PaintingContextCallback? painter, { _i4.ColorFilterLayer? oldLayer, }) => (super.noSuchMethod( Invocation.method( #pushColorFilter, [ offset, colorFilter, painter, ], {#oldLayer: oldLayer}, ), returnValue: _FakeColorFilterLayer_3( this, Invocation.method( #pushColorFilter, [ offset, colorFilter, painter, ], {#oldLayer: oldLayer}, ), ), ) as _i4.ColorFilterLayer); @override _i4.TransformLayer? pushTransform( bool? needsCompositing, _i2.Offset? offset, _i9.Matrix4? transform, _i3.PaintingContextCallback? painter, { _i4.TransformLayer? oldLayer, }) => (super.noSuchMethod(Invocation.method( #pushTransform, [ needsCompositing, offset, transform, painter, ], {#oldLayer: oldLayer}, )) as _i4.TransformLayer?); @override _i4.OpacityLayer pushOpacity( _i2.Offset? offset, int? alpha, _i3.PaintingContextCallback? painter, { _i4.OpacityLayer? oldLayer, }) => (super.noSuchMethod( Invocation.method( #pushOpacity, [ offset, alpha, painter, ], {#oldLayer: oldLayer}, ), returnValue: _FakeOpacityLayer_4( this, Invocation.method( #pushOpacity, [ offset, alpha, painter, ], {#oldLayer: oldLayer}, ), ), ) as _i4.OpacityLayer); @override void clipPathAndPaint( _i2.Path? path, _i2.Clip? clipBehavior, _i2.Rect? bounds, _i2.VoidCallback? painter, ) => super.noSuchMethod( Invocation.method( #clipPathAndPaint, [ path, clipBehavior, bounds, painter, ], ), returnValueForMissingStub: null, ); @override void clipRRectAndPaint( _i2.RRect? rrect, _i2.Clip? clipBehavior, _i2.Rect? bounds, _i2.VoidCallback? painter, ) => super.noSuchMethod( Invocation.method( #clipRRectAndPaint, [ rrect, clipBehavior, bounds, painter, ], ), returnValueForMissingStub: null, ); @override void clipRSuperellipseAndPaint( _i2.RSuperellipse? rse, _i2.Clip? clipBehavior, _i2.Rect? bounds, _i2.VoidCallback? painter, ) => super.noSuchMethod( Invocation.method( #clipRSuperellipseAndPaint, [ rse, clipBehavior, bounds, painter, ], ), returnValueForMissingStub: null, ); @override void clipRectAndPaint( _i2.Rect? rect, _i2.Clip? clipBehavior, _i2.Rect? bounds, _i2.VoidCallback? painter, ) => super.noSuchMethod( Invocation.method( #clipRectAndPaint, [ rect, clipBehavior, bounds, painter, ], ), returnValueForMissingStub: null, ); } /// A class which mocks [BuildContext]. /// /// See the documentation for Mockito's code generation for more information. class MockBuildContext extends _i1.Mock implements _i6.BuildContext { MockBuildContext() { _i1.throwOnMissingStub(this); } @override _i6.Widget get widget => (super.noSuchMethod( Invocation.getter(#widget), returnValue: _FakeWidget_5( this, Invocation.getter(#widget), ), ) as _i6.Widget); @override bool get mounted => (super.noSuchMethod( Invocation.getter(#mounted), returnValue: false, ) as bool); @override bool get debugDoingBuild => (super.noSuchMethod( Invocation.getter(#debugDoingBuild), returnValue: false, ) as bool); @override _i6.InheritedWidget dependOnInheritedElement( _i6.InheritedElement? ancestor, { Object? aspect, }) => (super.noSuchMethod( Invocation.method( #dependOnInheritedElement, [ancestor], {#aspect: aspect}, ), returnValue: _FakeInheritedWidget_6( this, Invocation.method( #dependOnInheritedElement, [ancestor], {#aspect: aspect}, ), ), ) as _i6.InheritedWidget); @override void visitAncestorElements(_i6.ConditionalElementVisitor? visitor) => super.noSuchMethod( Invocation.method( #visitAncestorElements, [visitor], ), returnValueForMissingStub: null, ); @override void visitChildElements(_i6.ElementVisitor? visitor) => super.noSuchMethod( Invocation.method( #visitChildElements, [visitor], ), returnValueForMissingStub: null, ); @override void dispatchNotification(_i10.Notification? notification) => super.noSuchMethod( Invocation.method( #dispatchNotification, [notification], ), returnValueForMissingStub: null, ); @override _i5.DiagnosticsNode describeElement( String? name, { _i5.DiagnosticsTreeStyle? style = _i5.DiagnosticsTreeStyle.errorProperty, }) => (super.noSuchMethod( Invocation.method( #describeElement, [name], {#style: style}, ), returnValue: _FakeDiagnosticsNode_7( this, Invocation.method( #describeElement, [name], {#style: style}, ), ), ) as _i5.DiagnosticsNode); @override _i5.DiagnosticsNode describeWidget( String? name, { _i5.DiagnosticsTreeStyle? style = _i5.DiagnosticsTreeStyle.errorProperty, }) => (super.noSuchMethod( Invocation.method( #describeWidget, [name], {#style: style}, ), returnValue: _FakeDiagnosticsNode_7( this, Invocation.method( #describeWidget, [name], {#style: style}, ), ), ) as _i5.DiagnosticsNode); @override List<_i5.DiagnosticsNode> describeMissingAncestor( {required Type? expectedAncestorType}) => (super.noSuchMethod( Invocation.method( #describeMissingAncestor, [], {#expectedAncestorType: expectedAncestorType}, ), returnValue: <_i5.DiagnosticsNode>[], ) as List<_i5.DiagnosticsNode>); @override _i5.DiagnosticsNode describeOwnershipChain(String? name) => (super.noSuchMethod( Invocation.method( #describeOwnershipChain, [name], ), returnValue: _FakeDiagnosticsNode_7( this, Invocation.method( #describeOwnershipChain, [name], ), ), ) as _i5.DiagnosticsNode); } /// A class which mocks [PieChartPainter]. /// /// See the documentation for Mockito's code generation for more information. class MockPieChartPainter extends _i1.Mock implements _i11.PieChartPainter { MockPieChartPainter() { _i1.throwOnMissingStub(this); } @override void paint( _i6.BuildContext? context, _i12.CanvasWrapper? canvasWrapper, _i13.PaintHolder<_i7.PieChartData>? holder, ) => super.noSuchMethod( Invocation.method( #paint, [ context, canvasWrapper, holder, ], ), returnValueForMissingStub: null, ); @override List calculateSectionsAngle( List<_i7.PieChartSectionData>? sections, double? sumValue, ) => (super.noSuchMethod( Invocation.method( #calculateSectionsAngle, [ sections, sumValue, ], ), returnValue: [], ) as List); @override void drawCenterSpace( _i12.CanvasWrapper? canvasWrapper, double? centerRadius, _i13.PaintHolder<_i7.PieChartData>? holder, ) => super.noSuchMethod( Invocation.method( #drawCenterSpace, [ canvasWrapper, centerRadius, holder, ], ), returnValueForMissingStub: null, ); @override void drawSections( _i12.CanvasWrapper? canvasWrapper, List? sectionsAngle, double? centerRadius, _i13.PaintHolder<_i7.PieChartData>? holder, ) => super.noSuchMethod( Invocation.method( #drawSections, [ canvasWrapper, sectionsAngle, centerRadius, holder, ], ), returnValueForMissingStub: null, ); @override _i2.Path generateSectionPath( _i7.PieChartSectionData? section, double? sectionSpace, double? tempAngle, double? sectionDegree, _i2.Offset? center, double? centerRadius, ) => (super.noSuchMethod( Invocation.method( #generateSectionPath, [ section, sectionSpace, tempAngle, sectionDegree, center, centerRadius, ], ), returnValue: _FakePath_8( this, Invocation.method( #generateSectionPath, [ section, sectionSpace, tempAngle, sectionDegree, center, centerRadius, ], ), ), ) as _i2.Path); @override _i2.Path createRectPathAroundLine( _i14.Line? line, double? width, ) => (super.noSuchMethod( Invocation.method( #createRectPathAroundLine, [ line, width, ], ), returnValue: _FakePath_8( this, Invocation.method( #createRectPathAroundLine, [ line, width, ], ), ), ) as _i2.Path); @override void drawSection( _i7.PieChartSectionData? section, _i2.Path? sectionPath, _i12.CanvasWrapper? canvasWrapper, ) => super.noSuchMethod( Invocation.method( #drawSection, [ section, sectionPath, canvasWrapper, ], ), returnValueForMissingStub: null, ); @override void drawSectionStroke( _i7.PieChartSectionData? section, _i2.Path? sectionPath, _i12.CanvasWrapper? canvasWrapper, _i2.Size? viewSize, ) => super.noSuchMethod( Invocation.method( #drawSectionStroke, [ section, sectionPath, canvasWrapper, viewSize, ], ), returnValueForMissingStub: null, ); @override void drawTexts( _i6.BuildContext? context, _i12.CanvasWrapper? canvasWrapper, _i13.PaintHolder<_i7.PieChartData>? holder, double? centerRadius, ) => super.noSuchMethod( Invocation.method( #drawTexts, [ context, canvasWrapper, holder, centerRadius, ], ), returnValueForMissingStub: null, ); @override double calculateCenterRadius( _i2.Size? viewSize, _i13.PaintHolder<_i7.PieChartData>? holder, ) => (super.noSuchMethod( Invocation.method( #calculateCenterRadius, [ viewSize, holder, ], ), returnValue: 0.0, ) as double); @override _i7.PieTouchedSection handleTouch( _i2.Offset? localPosition, _i2.Size? viewSize, _i13.PaintHolder<_i7.PieChartData>? holder, ) => (super.noSuchMethod( Invocation.method( #handleTouch, [ localPosition, viewSize, holder, ], ), returnValue: _FakePieTouchedSection_9( this, Invocation.method( #handleTouch, [ localPosition, viewSize, holder, ], ), ), ) as _i7.PieTouchedSection); @override Map getBadgeOffsets( _i2.Size? viewSize, _i13.PaintHolder<_i7.PieChartData>? holder, ) => (super.noSuchMethod( Invocation.method( #getBadgeOffsets, [ viewSize, holder, ], ), returnValue: {}, ) as Map); } ================================================ FILE: test/chart/radar_chart/radar_chart_data_test.dart ================================================ import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import '../data_pool.dart'; void main() { group('RadarChart Data equality check', () { test('RadarChartData equality test', () { /// object equality test expect(radarChartData1 == radarChartData1Clone, true); expect( radarChartData1 == radarChartData1Clone.copyWith(dataSets: [radarDataSet2]), false, ); expect( radarChartData1 == radarChartData1Clone.copyWith(radarBackgroundColor: Colors.black), false, ); expect( radarChartData1 == radarChartData1Clone.copyWith( borderData: FlBorderData( show: true, border: Border.all(color: Colors.green), ), ), true, ); expect( radarChartData1 == radarChartData1Clone.copyWith( borderData: FlBorderData( show: false, border: Border.all(), ), ), false, ); expect( radarChartData1 == radarChartData1Clone.copyWith( radarBorderData: const BorderSide( width: 200, color: Colors.red, ), ), false, ); expect( radarChartData1 == radarChartData1Clone.copyWith( radarShape: RadarShape.polygon, ), false, ); expect( radarChartData1 == radarChartData1Clone.copyWith(radarTouchData: radarTouchData2), false, ); expect( radarChartData1 == radarChartData1Clone.copyWith( gridBorderData: const BorderSide( color: Colors.black54, width: 2.1, ), ), false, ); expect( radarChartData1 == radarChartData1Clone.copyWith(tickCount: 8), false, ); expect( radarChartData1 == radarChartData1Clone.copyWith(ticksTextStyle: const TextStyle()), false, ); expect( radarChartData1 == radarChartData1Clone.copyWith( ticksTextStyle: radarChartData2.ticksTextStyle, ), false, ); expect( radarChartData1 == radarChartData1Clone.copyWith( tickBorderData: radarChartData2.tickBorderData, ), false, ); expect( radarChartData1 == radarChartData1Clone.copyWith(titlePositionPercentageOffset: 0.2), true, ); expect( radarChartData1 == radarChartData1Clone.copyWith( titlePositionPercentageOffset: radarChartData2.titlePositionPercentageOffset, ), false, ); expect( radarChartData1 == radarChartData1Clone.copyWith(titleTextStyle: const TextStyle()), false, ); expect( radarChartData1 == radarChartData1Clone.copyWith( titleTextStyle: radarChartData2.titleTextStyle, ), false, ); expect( radarChartData1 == radarChartData1Clone.copyWith( titleTextStyle: radarChartData2.titleTextStyle, ), false, ); }); test('RadarDataSet equality test', () { expect(radarDataSet1 == radarDataSet1Clone, true); expect(radarDataSet1 == radarDataSet2, false); expect( radarDataSet1 == radarDataSet1Clone.copyWith( dataEntries: const [ RadarEntry(value: 5), RadarEntry(value: 5), RadarEntry(value: 5), ], ), false, ); expect( radarDataSet1 == radarDataSet1Clone.copyWith(fillColor: Colors.grey), true, ); expect( radarDataSet1 == radarDataSet1Clone.copyWith(fillColor: Colors.pink), false, ); expect( radarDataSet1 == radarDataSet1Clone.copyWith(borderColor: Colors.blue), true, ); expect( radarDataSet1 == radarDataSet1Clone.copyWith(borderColor: Colors.pink), false, ); expect( radarDataSet1 == radarDataSet1Clone.copyWith(borderWidth: 3), true, ); expect( radarDataSet1 == radarDataSet1Clone.copyWith(borderWidth: 3.2), false, ); expect( radarDataSet1 == radarDataSet1Clone.copyWith(borderWidth: 3.00002), false, ); expect( radarDataSet1 == radarDataSet1Clone.copyWith(entryRadius: 3), true, ); expect( radarDataSet1 == radarDataSet1Clone.copyWith(entryRadius: 3.2), false, ); expect( radarDataSet1 == radarDataSet1Clone.copyWith(entryRadius: 3.002), false, ); }); test('RadarTouchData equality test', () { expect(radarTouchData1 == radarTouchData1Clone, true); expect(radarTouchData1 == radarTouchData2, false); expect( radarTouchData1 == RadarTouchData( enabled: false, touchCallback: radarTouchCallback, touchSpotThreshold: 12, ), false, ); expect( radarTouchData1 == RadarTouchData( enabled: true, touchCallback: radarTouchCallback, touchSpotThreshold: 2, ), false, ); expect( radarTouchData1 == RadarTouchData( enabled: true, touchCallback: (event, value) {}, touchSpotThreshold: 12, ), false, ); expect( radarTouchData1 == RadarTouchData( enabled: true, touchCallback: radarTouchCallback, touchSpotThreshold: 12, longPressDuration: Duration.zero, ), false, ); }); test('RadarTouchedSpot equality test', () { expect(radarTouchedSpot1 == radarTouchedSpotClone1, true); expect(radarTouchedSpot1 == radarTouchedSpot2, false); expect(radarTouchedSpot1 == radarTouchedSpot3, false); expect(radarTouchedSpot1 == radarTouchedSpot4, false); expect(radarTouchedSpot1 == radarTouchedSpot5, false); expect(radarTouchedSpot1 == radarTouchedSpot6, false); expect(radarTouchedSpot1 == radarTouchedSpot7, false); }); }); } ================================================ FILE: test/chart/radar_chart/radar_chart_painter_test.dart ================================================ import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart'; import 'package:fl_chart/src/chart/radar_chart/radar_chart_painter.dart'; import 'package:fl_chart/src/utils/canvas_wrapper.dart'; import 'package:fl_chart/src/utils/utils.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import '../data_pool.dart'; import 'radar_chart_painter_test.mocks.dart'; @GenerateMocks([Canvas, CanvasWrapper, BuildContext, Utils]) void main() { final utilsMainInstance = Utils(); group('paint()', () { test('test 1', () { const viewSize = Size(400, 400); final data = RadarChartData( dataSets: [ RadarDataSet( dataEntries: const [ RadarEntry(value: 12), RadarEntry(value: 11), RadarEntry(value: 10), ], ), RadarDataSet( dataEntries: const [ RadarEntry(value: 2), RadarEntry(value: 2), RadarEntry(value: 2), ], ), RadarDataSet( dataEntries: const [ RadarEntry(value: 4), RadarEntry(value: 4), RadarEntry(value: 4), ], ), ], ); final radarPainter = RadarChartPainter(); final holder = PaintHolder( data, data, TextScaler.noScaling, ); final mockUtils = MockUtils(); Utils.changeInstance(mockUtils); when(mockUtils.getThemeAwareTextStyle(any, any)) .thenAnswer((realInvocation) => textStyle1); when(mockUtils.calculateRotationOffset(any, any)) .thenAnswer((realInvocation) => Offset.zero); when(mockUtils.convertRadiusToSigma(any)) .thenAnswer((realInvocation) => 4.0); when(mockUtils.getEfficientInterval(any, any)) .thenAnswer((realInvocation) => 1.0); when(mockUtils.getBestInitialIntervalValue(any, any, any)) .thenAnswer((realInvocation) => 1.0); when(mockUtils.normalizeBorderRadius(any, any)) .thenAnswer((realInvocation) => BorderRadius.zero); when(mockUtils.normalizeBorderSide(any, any)).thenAnswer( (realInvocation) => const BorderSide(color: MockData.color0), ); final mockBuildContext = MockBuildContext(); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); radarPainter.paint( mockBuildContext, mockCanvasWrapper, holder, ); verify(mockCanvasWrapper.drawCircle(any, any, any)).called(12); verify(mockCanvasWrapper.drawLine(any, any, any)).called(3); Utils.changeInstance(utilsMainInstance); }); }); group('drawTicks()', () { test('test 1', () { const viewSize = Size(400, 300); final data = RadarChartData( dataSets: [ RadarDataSet( dataEntries: [ const RadarEntry(value: 1), const RadarEntry(value: 2), const RadarEntry(value: 3), ], ), RadarDataSet( dataEntries: [ const RadarEntry(value: 3), const RadarEntry(value: 1), const RadarEntry(value: 2), ], ), RadarDataSet( dataEntries: [ const RadarEntry(value: 2), const RadarEntry(value: 3), const RadarEntry(value: 1), ], ), ], radarBorderData: const BorderSide(color: MockData.color6, width: 33), tickBorderData: const BorderSide(color: MockData.color5, width: 55), radarBackgroundColor: MockData.color2, ); final radarChartPainter = RadarChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final mockUtils = MockUtils(); when(mockUtils.getThemeAwareTextStyle(any, any)) .thenReturn(MockData.textStyle1); Utils.changeInstance(mockUtils); final mockContext = MockBuildContext(); final drawCircleResults = >[]; when(mockCanvasWrapper.drawCircle(captureAny, captureAny, captureAny)) .thenAnswer((inv) { drawCircleResults.add({ 'offset': inv.positionalArguments[0] as Offset, 'radius': inv.positionalArguments[1] as double, 'paint_color': (inv.positionalArguments[2] as Paint).color, 'paint_style': (inv.positionalArguments[2] as Paint).style, 'paint_stroke': (inv.positionalArguments[2] as Paint).strokeWidth, }); }); radarChartPainter.drawTicks(mockContext, mockCanvasWrapper, holder); expect(drawCircleResults.length, 3); // Background circle expect(drawCircleResults[0]['offset'], const Offset(200, 150)); expect(drawCircleResults[0]['radius'], 120); expect( drawCircleResults[0]['paint_color'], isSameColorAs(MockData.color2), ); expect(drawCircleResults[0]['paint_style'], PaintingStyle.fill); // Border circle expect(drawCircleResults[1]['offset'], const Offset(200, 150)); expect(drawCircleResults[1]['radius'], 120); expect( drawCircleResults[1]['paint_color'], isSameColorAs(MockData.color6), ); expect(drawCircleResults[1]['paint_stroke'], 33); expect(drawCircleResults[1]['paint_style'], PaintingStyle.stroke); // First Tick expect(drawCircleResults[2]['offset'], const Offset(200, 150)); expect(drawCircleResults[2]['radius'], 60); expect( drawCircleResults[2]['paint_color'], isSameColorAs(MockData.color5), ); expect(drawCircleResults[2]['paint_stroke'], 55); expect(drawCircleResults[2]['paint_style'], PaintingStyle.stroke); final result = verify(mockCanvasWrapper.drawText(captureAny, captureAny)); expect(result.callCount, 1); final tp = result.captured[0] as TextPainter; expect((tp.text as TextSpan?)!.text, '1.0'); expect((tp.text as TextSpan?)!.style, MockData.textStyle1); expect(result.captured[1] as Offset, const Offset(205, 76)); }); test('test 2', () { const viewSize = Size(400, 300); final data = RadarChartData( dataSets: [ RadarDataSet( dataEntries: [ const RadarEntry(value: 1), const RadarEntry(value: 2), const RadarEntry(value: 3), ], ), RadarDataSet( dataEntries: [ const RadarEntry(value: 3), const RadarEntry(value: 1), const RadarEntry(value: 2), ], ), RadarDataSet( dataEntries: [ const RadarEntry(value: 2), const RadarEntry(value: 3), const RadarEntry(value: 1), ], ), ], radarBorderData: const BorderSide(color: MockData.color6, width: 33), radarShape: RadarShape.polygon, tickBorderData: const BorderSide(color: MockData.color5, width: 55), radarBackgroundColor: MockData.color2, ); final radarChartPainter = RadarChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final mockUtils = MockUtils(); when(mockUtils.getThemeAwareTextStyle(any, any)) .thenReturn(MockData.textStyle1); Utils.changeInstance(mockUtils); final mockContext = MockBuildContext(); final drawPathResult = >[]; when(mockCanvasWrapper.drawPath(captureAny, captureAny)) .thenAnswer((inv) { drawPathResult.add({ 'path': inv.positionalArguments[0] as Path, 'paint_color': (inv.positionalArguments[1] as Paint).color, 'paint_stroke': (inv.positionalArguments[1] as Paint).strokeWidth, 'paint_style': (inv.positionalArguments[1] as Paint).style, }); }); radarChartPainter.drawTicks(mockContext, mockCanvasWrapper, holder); expect(drawPathResult.length, 3); // Background circle expect( drawPathResult[0]['paint_color'], isSameColorAs(MockData.color2), ); expect(drawPathResult[0]['paint_stroke'], 0); expect(drawPathResult[0]['paint_style'], PaintingStyle.fill); // Border circle expect( drawPathResult[1]['paint_color'], isSameColorAs(MockData.color6), ); expect(drawPathResult[1]['paint_stroke'], 33); expect(drawPathResult[1]['paint_style'], PaintingStyle.stroke); // First Tick expect( drawPathResult[2]['paint_color'], isSameColorAs(MockData.color5), ); expect(drawPathResult[2]['paint_stroke'], 55); expect(drawPathResult[2]['paint_style'], PaintingStyle.stroke); final result = verify(mockCanvasWrapper.drawText(captureAny, captureAny)); expect(result.callCount, 1); final tp = result.captured[0] as TextPainter; expect((tp.text as TextSpan?)!.text, '1.0'); expect((tp.text as TextSpan?)!.style, MockData.textStyle1); expect(result.captured[1] as Offset, const Offset(205, 76)); }); }); group('drawGrids()', () { test('test 1', () { const viewSize = Size(400, 300); final data = RadarChartData( dataSets: [ RadarDataSet( dataEntries: [ const RadarEntry(value: 1), const RadarEntry(value: 2), const RadarEntry(value: 3), ], ), RadarDataSet( dataEntries: [ const RadarEntry(value: 3), const RadarEntry(value: 1), const RadarEntry(value: 2), ], ), RadarDataSet( dataEntries: [ const RadarEntry(value: 2), const RadarEntry(value: 3), const RadarEntry(value: 1), ], ), ], radarBorderData: const BorderSide(color: MockData.color6, width: 33), tickBorderData: const BorderSide(color: MockData.color5, width: 55), gridBorderData: const BorderSide(color: MockData.color3, width: 3), radarBackgroundColor: MockData.color2, ); final radarChartPainter = RadarChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final mockUtils = MockUtils(); when(mockUtils.getThemeAwareTextStyle(any, any)) .thenReturn(MockData.textStyle1); Utils.changeInstance(mockUtils); final drawLineResults = >[]; when(mockCanvasWrapper.drawLine(captureAny, captureAny, captureAny)) .thenAnswer((inv) { drawLineResults.add({ 'offset_from': inv.positionalArguments[0] as Offset, 'offset_to': inv.positionalArguments[1] as Offset, 'paint_color': (inv.positionalArguments[2] as Paint).color, 'paint_style': (inv.positionalArguments[2] as Paint).style, 'paint_stroke': (inv.positionalArguments[2] as Paint).strokeWidth, }); }); radarChartPainter.drawGrids(mockCanvasWrapper, holder); expect(drawLineResults.length, 3); expect(drawLineResults[0]['offset_from'], const Offset(200, 150)); expect(drawLineResults[0]['offset_to'], const Offset(200, 30)); expect( drawLineResults[0]['paint_color'], isSameColorAs(MockData.color3), ); expect(drawLineResults[0]['paint_style'], PaintingStyle.stroke); expect(drawLineResults[0]['paint_stroke'], 3); expect(drawLineResults[1]['offset_from'], const Offset(200, 150)); expect( drawLineResults[1]['offset_to'], const Offset(303.92304845413264, 209.99999999999997), ); expect( drawLineResults[1]['paint_color'], isSameColorAs(MockData.color3), ); expect(drawLineResults[1]['paint_style'], PaintingStyle.stroke); expect(drawLineResults[1]['paint_stroke'], 3); expect(drawLineResults[2]['offset_from'], const Offset(200, 150)); expect( drawLineResults[2]['offset_to'], const Offset(96.07695154586739, 210.00000000000006), ); expect( drawLineResults[2]['paint_color'], isSameColorAs(MockData.color3), ); expect(drawLineResults[2]['paint_style'], PaintingStyle.stroke); expect(drawLineResults[2]['paint_stroke'], 3); }); }); group('drawGrids()', () { test('test 1', () { const viewSize = Size(400, 300); final data = RadarChartData( dataSets: [ RadarDataSet( dataEntries: [ const RadarEntry(value: 1), const RadarEntry(value: 2), const RadarEntry(value: 3), ], ), RadarDataSet( dataEntries: [ const RadarEntry(value: 3), const RadarEntry(value: 1), const RadarEntry(value: 2), ], ), RadarDataSet( dataEntries: [ const RadarEntry(value: 2), const RadarEntry(value: 3), const RadarEntry(value: 1), ], ), ], titleTextStyle: MockData.textStyle4, radarBorderData: const BorderSide(color: MockData.color6, width: 33), tickBorderData: const BorderSide(color: MockData.color5, width: 55), gridBorderData: const BorderSide(color: MockData.color3, width: 3), radarBackgroundColor: MockData.color2, ); final radarChartPainter = RadarChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final mockUtils = MockUtils(); when(mockUtils.getThemeAwareTextStyle(any, any)).thenAnswer( (realInvocation) => realInvocation.positionalArguments[1] as TextStyle, ); Utils.changeInstance(mockUtils); final mockContext = MockBuildContext(); radarChartPainter.drawTitles(mockContext, mockCanvasWrapper, holder); verifyNever(mockCanvasWrapper.drawText(any, any)); }); test('test 2', () { const viewSize = Size(400, 300); final data = RadarChartData( dataSets: [ RadarDataSet( dataEntries: [ const RadarEntry(value: 1), const RadarEntry(value: 2), const RadarEntry(value: 3), ], ), RadarDataSet( dataEntries: [ const RadarEntry(value: 3), const RadarEntry(value: 1), const RadarEntry(value: 2), ], ), RadarDataSet( dataEntries: [ const RadarEntry(value: 2), const RadarEntry(value: 3), const RadarEntry(value: 1), ], ), ], getTitle: (index, angle) { return RadarChartTitle(text: '$index$index', angle: angle); }, titleTextStyle: MockData.textStyle4, radarBorderData: const BorderSide(color: MockData.color6, width: 33), tickBorderData: const BorderSide(color: MockData.color5, width: 55), gridBorderData: const BorderSide(color: MockData.color3, width: 3), radarBackgroundColor: MockData.color2, ); final radarChartPainter = RadarChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final mockUtils = MockUtils(); when(mockUtils.getThemeAwareTextStyle(any, any)).thenAnswer( (realInvocation) => realInvocation.positionalArguments[1] as TextStyle, ); when(mockUtils.degrees(captureAny)).thenAnswer((inv) { return utilsMainInstance .degrees(inv.positionalArguments.first as double); }); Utils.changeInstance(mockUtils); final mockContext = MockBuildContext(); final results = >[]; when( mockCanvasWrapper.drawRotated( size: anyNamed('size'), rotationOffset: anyNamed('rotationOffset'), drawOffset: anyNamed('drawOffset'), angle: anyNamed('angle'), drawCallback: captureAnyNamed('drawCallback'), ), ).thenAnswer((inv) { (inv.namedArguments[const Symbol('drawCallback')] as void Function())(); }); when(mockCanvasWrapper.drawText(captureAny, captureAny, captureAny)) .thenAnswer((inv) { results.add({ 'tp_text': ((inv.positionalArguments[0] as TextPainter).text as TextSpan?)! .text, 'tp_style': ((inv.positionalArguments[0] as TextPainter).text as TextSpan?)! .style, }); }); radarChartPainter.drawTitles(mockContext, mockCanvasWrapper, holder); expect(results.length, 3); expect(results[0]['tp_text'] as String, '00'); expect(results[0]['tp_style'] as TextStyle, MockData.textStyle4); expect(results[1]['tp_text'] as String, '11'); expect(results[1]['tp_style'] as TextStyle, MockData.textStyle4); expect(results[2]['tp_text'] as String, '22'); expect(results[2]['tp_style'] as TextStyle, MockData.textStyle4); }); }); group('drawDataSets()', () { test('test 1', () { const viewSize = Size(400, 300); final data = RadarChartData( dataSets: [ RadarDataSet( dataEntries: [ const RadarEntry(value: 1), const RadarEntry(value: 2), const RadarEntry(value: 3), ], fillColor: MockData.color1, borderColor: MockData.color3, borderWidth: 3, ), RadarDataSet( dataEntries: [ const RadarEntry(value: 3), const RadarEntry(value: 1), const RadarEntry(value: 2), ], fillColor: MockData.color2, borderColor: MockData.color2, borderWidth: 2, ), RadarDataSet( dataEntries: [ const RadarEntry(value: 2), const RadarEntry(value: 3), const RadarEntry(value: 1), ], fillColor: MockData.color3, borderColor: MockData.color1, borderWidth: 1, ), RadarDataSet( dataEntries: [ const RadarEntry(value: 2), const RadarEntry(value: 3), const RadarEntry(value: 1), ], fillColor: MockData.color1, fillGradient: MockData.gradient1, borderColor: MockData.color1, borderWidth: 1, ), ], getTitle: (index, angle) { return RadarChartTitle(text: '$index$index', angle: angle); }, titleTextStyle: MockData.textStyle4, radarBorderData: const BorderSide(color: MockData.color6, width: 33), tickBorderData: const BorderSide(color: MockData.color5, width: 55), gridBorderData: const BorderSide(color: MockData.color3, width: 3), radarBackgroundColor: MockData.color2, ); final radarChartPainter = RadarChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final mockUtils = MockUtils(); when(mockUtils.getThemeAwareTextStyle(any, any)).thenAnswer( (realInvocation) => realInvocation.positionalArguments[1] as TextStyle, ); Utils.changeInstance(mockUtils); final drawCircleResults = >[]; when(mockCanvasWrapper.drawCircle(captureAny, captureAny, captureAny)) .thenAnswer((inv) { drawCircleResults.add({ 'offset': inv.positionalArguments[0] as Offset, 'radius': inv.positionalArguments[1] as double, 'paint': inv.positionalArguments[2] as Paint, }); }); final drawPathResults = >[]; when(mockCanvasWrapper.drawPath(captureAny, captureAny)) .thenAnswer((inv) { drawPathResults.add({ 'path': inv.positionalArguments[0] as Path, 'paint_color': (inv.positionalArguments[1] as Paint).color, 'paint_shader': (inv.positionalArguments[1] as Paint).shader, 'paint_stroke': (inv.positionalArguments[1] as Paint).strokeWidth, 'paint_style': (inv.positionalArguments[1] as Paint).style, }); }); radarChartPainter.drawDataSets(mockCanvasWrapper, holder); expect(drawCircleResults.length, 12); expect( drawCircleResults[0]['offset'] as Offset, const Offset(200, 90), ); expect(drawCircleResults[0]['radius'] as double, 5); expect( drawCircleResults[1]['offset'] as Offset, const Offset(277.9422863405995, 195), ); expect(drawCircleResults[1]['radius'] as double, 5); expect( drawCircleResults[2]['offset'] as Offset, const Offset(96.07695154586739, 210.00000000000006), ); expect(drawCircleResults[2]['radius'] as double, 5); expect( drawCircleResults[3]['offset'] as Offset, const Offset(200, 30), ); expect(drawCircleResults[3]['radius'] as double, 5); expect( drawCircleResults[4]['offset'] as Offset, const Offset(251.96152422706632, 180), ); expect(drawCircleResults[4]['radius'] as double, 5); expect( drawCircleResults[5]['offset'] as Offset, const Offset(122.05771365940053, 195.00000000000003), ); expect(drawCircleResults[5]['radius'] as double, 5); expect( drawCircleResults[6]['offset'] as Offset, const Offset(200, 60), ); expect(drawCircleResults[6]['radius'] as double, 5); expect( drawCircleResults[7]['offset'] as Offset, const Offset(303.92304845413264, 209.99999999999997), ); expect(drawCircleResults[7]['radius'] as double, 5); expect( drawCircleResults[8]['offset'] as Offset, const Offset(148.03847577293368, 180.00000000000003), ); expect(drawCircleResults[8]['radius'] as double, 5); expect(drawPathResults.length, 8); expect( drawPathResults[0]['paint_color'], isSameColorAs(MockData.color1), ); expect(drawPathResults[0]['paint_style'], PaintingStyle.fill); expect( drawPathResults[1]['paint_color'], isSameColorAs(MockData.color3), ); expect(drawPathResults[1]['paint_stroke'], 3); expect(drawPathResults[1]['paint_style'], PaintingStyle.stroke); expect( drawPathResults[2]['paint_color'], isSameColorAs(MockData.color2), ); expect(drawPathResults[2]['paint_style'], PaintingStyle.fill); expect( drawPathResults[3]['paint_color'], isSameColorAs(MockData.color2), ); expect(drawPathResults[3]['paint_stroke'], 2); expect(drawPathResults[3]['paint_style'], PaintingStyle.stroke); expect( drawPathResults[4]['paint_color'], isSameColorAs(MockData.color3), ); expect(drawPathResults[4]['paint_style'], PaintingStyle.fill); expect( drawPathResults[5]['paint_color'], isSameColorAs(MockData.color1), ); expect(drawPathResults[5]['paint_stroke'], 1); expect(drawPathResults[5]['paint_style'], PaintingStyle.stroke); expect(drawPathResults[6]['paint_shader'], isNotNull); expect(drawPathResults[6]['paint_shader'], isA()); expect(drawPathResults[6]['paint_style'], PaintingStyle.fill); expect(drawPathResults[7]['paint_color'], isSameColorAs(MockData.color1)); expect(drawPathResults[7]['paint_stroke'], 1); expect(drawPathResults[7]['paint_style'], PaintingStyle.stroke); }); }); group('drawTitles()', () { test('rotated titles', () { const viewSize = Size(400, 300); final data = RadarChartData( dataSets: [ RadarDataSet( dataEntries: [ const RadarEntry(value: 1), const RadarEntry(value: 2), const RadarEntry(value: 3), ], ), RadarDataSet( dataEntries: [ const RadarEntry(value: 3), const RadarEntry(value: 1), const RadarEntry(value: 2), ], ), RadarDataSet( dataEntries: [ const RadarEntry(value: 2), const RadarEntry(value: 3), const RadarEntry(value: 1), ], ), ], radarBorderData: const BorderSide(color: MockData.color6, width: 33), tickBorderData: const BorderSide(color: MockData.color5, width: 55), radarBackgroundColor: MockData.color2, getTitle: (index, angle) { return RadarChartTitle(text: '$index-$angle', angle: angle); }, ); final radarChartPainter = RadarChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final mockUtils = MockUtils(); when(mockUtils.getThemeAwareTextStyle(any, any)) .thenReturn(MockData.textStyle1); when(mockUtils.degrees(captureAny)).thenAnswer((inv) { return utilsMainInstance .degrees(inv.positionalArguments.first as double); }); Utils.changeInstance(mockUtils); final mockContext = MockBuildContext(); final drawRotatedResults = >[]; final drawTextResults = >[]; when( mockCanvasWrapper.drawRotated( size: anyNamed('size'), rotationOffset: anyNamed('rotationOffset'), drawOffset: anyNamed('drawOffset'), angle: captureAnyNamed('angle'), drawCallback: captureAnyNamed('drawCallback'), ), ).thenAnswer((inv) { drawRotatedResults.add({ 'angle': inv.namedArguments[const Symbol('angle')], }); (inv.namedArguments[const Symbol('drawCallback')] as void Function())(); }); when(mockCanvasWrapper.drawText(captureAny, captureAny, captureAny)) .thenAnswer((inv) { drawTextResults.add({ 'text': (inv.positionalArguments[0] as TextPainter).text?.toPlainText(), 'angle': inv.positionalArguments[2] as double, }); }); radarChartPainter.drawTitles(mockContext, mockCanvasWrapper, holder); expect(drawRotatedResults.length, 3); expect(drawTextResults.length, 3); // Titles const angle = 360.0 / 3; for (var i = 0; i < drawTextResults.length; i++) { expect(drawRotatedResults[i]['angle'], closeTo(angle * i, 0.001)); expect(drawTextResults[i]['text'], startsWith('$i')); expect(drawTextResults[i]['angle'], 0); } }); test('horizontal titles by default', () { const viewSize = Size(400, 300); final data = RadarChartData( dataSets: [ RadarDataSet( dataEntries: [ const RadarEntry(value: 1), const RadarEntry(value: 2), const RadarEntry(value: 3), ], ), RadarDataSet( dataEntries: [ const RadarEntry(value: 3), const RadarEntry(value: 1), const RadarEntry(value: 2), ], ), RadarDataSet( dataEntries: [ const RadarEntry(value: 2), const RadarEntry(value: 3), const RadarEntry(value: 1), ], ), ], radarBorderData: const BorderSide(color: MockData.color6, width: 33), tickBorderData: const BorderSide(color: MockData.color5, width: 55), radarBackgroundColor: MockData.color2, getTitle: (index, angle) { return RadarChartTitle(text: '$index-$angle'); }, ); final radarChartPainter = RadarChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final mockUtils = MockUtils(); when(mockUtils.getThemeAwareTextStyle(any, any)) .thenReturn(MockData.textStyle1); when(mockUtils.degrees(captureAny)).thenAnswer((inv) { return utilsMainInstance .degrees(inv.positionalArguments.first as double); }); Utils.changeInstance(mockUtils); final mockContext = MockBuildContext(); final drawRotatedResults = >[]; final drawTextResults = >[]; when( mockCanvasWrapper.drawRotated( size: anyNamed('size'), rotationOffset: anyNamed('rotationOffset'), drawOffset: anyNamed('drawOffset'), angle: captureAnyNamed('angle'), drawCallback: captureAnyNamed('drawCallback'), ), ).thenAnswer((inv) { drawRotatedResults.add({ 'angle': inv.namedArguments[const Symbol('angle')], }); (inv.namedArguments[const Symbol('drawCallback')] as void Function())(); }); when(mockCanvasWrapper.drawText(captureAny, captureAny, captureAny)) .thenAnswer((inv) { drawTextResults.add({ 'text': (inv.positionalArguments[0] as TextPainter).text?.toPlainText(), 'angle': inv.positionalArguments[2] as double, }); }); radarChartPainter.drawTitles(mockContext, mockCanvasWrapper, holder); expect(drawRotatedResults.length, 3); expect(drawTextResults.length, 3); // Titles const angle = 360.0 / 3; for (var i = 0; i < drawTextResults.length; i++) { expect(drawRotatedResults[i]['angle'], closeTo(angle * i, 0.001)); expect(drawTextResults[i]['text'], startsWith('$i')); expect(drawTextResults[i]['angle'], closeTo(-angle * i, 0.001)); } }); }); group('handleTouch()', () { test('test 1', () { const viewSize = Size(400, 300); final data = RadarChartData( dataSets: [ RadarDataSet( dataEntries: [ const RadarEntry(value: 1), const RadarEntry(value: 2), const RadarEntry(value: 3), ], ), RadarDataSet( dataEntries: [ const RadarEntry(value: 3), const RadarEntry(value: 1), const RadarEntry(value: 2), ], ), RadarDataSet( dataEntries: [ const RadarEntry(value: 2), const RadarEntry(value: 3), const RadarEntry(value: 1), ], ), ], ); final radarChartPainter = RadarChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final mockUtils = MockUtils(); when(mockUtils.getThemeAwareTextStyle(any, any)).thenAnswer( (realInvocation) => realInvocation.positionalArguments[1] as TextStyle, ); Utils.changeInstance(mockUtils); final drawCircleResults = >[]; when(mockCanvasWrapper.drawCircle(captureAny, captureAny, captureAny)) .thenAnswer((inv) { drawCircleResults.add({ 'offset': inv.positionalArguments[0] as Offset, 'radius': inv.positionalArguments[1] as double, 'paint': inv.positionalArguments[2] as Paint, }); }); final drawPathResults = >[]; when(mockCanvasWrapper.drawPath(captureAny, captureAny)) .thenAnswer((inv) { drawPathResults.add({ 'path': inv.positionalArguments[0] as Path, 'paint_color': (inv.positionalArguments[1] as Paint).color, 'paint_stroke': (inv.positionalArguments[1] as Paint).strokeWidth, 'paint_style': (inv.positionalArguments[1] as Paint).style, }); }); expect( radarChartPainter.handleTouch( const Offset(287.8, 120.3), viewSize, holder, ), null, ); expect( radarChartPainter.handleTouch( const Offset(145.1, 125.4), viewSize, holder, ), null, ); expect( radarChartPainter.handleTouch( const Offset(175.9, 120.8), viewSize, holder, ), null, ); expect( radarChartPainter.handleTouch( const Offset(201.8, 153.7), viewSize, holder, ), null, ); expect( radarChartPainter.handleTouch( const Offset(259.5, 116.3), viewSize, holder, ), null, ); expect( radarChartPainter.handleTouch( const Offset(266.9, 179.3), viewSize, holder, ), null, ); expect( radarChartPainter.handleTouch( const Offset(145, 193.7), viewSize, holder, ), null, ); final result0 = radarChartPainter.handleTouch( const Offset(304.9, 212.9), viewSize, holder, ); expect(result0!.touchedDataSetIndex, 2); expect(result0.touchedRadarEntryIndex, 1); final result1 = radarChartPainter.handleTouch( const Offset(200, 60), viewSize, holder, ); expect(result1!.touchedDataSetIndex, 2); expect(result1.touchedRadarEntryIndex, 0); final result2 = radarChartPainter.handleTouch( const Offset(148, 180), viewSize, holder, ); expect(result2!.touchedDataSetIndex, 2); expect(result2.touchedRadarEntryIndex, 2); final result3 = radarChartPainter.handleTouch( const Offset(270.5, 192.3), viewSize, holder, ); expect(result3!.touchedDataSetIndex, 0); expect(result3.touchedRadarEntryIndex, 1); final result4 = radarChartPainter.handleTouch( const Offset(98.3, 216.8), viewSize, holder, ); expect(result4!.touchedDataSetIndex, 0); expect(result4.touchedRadarEntryIndex, 2); final result5 = radarChartPainter.handleTouch( const Offset(200, 90), viewSize, holder, ); expect(result5!.touchedDataSetIndex, 0); expect(result5.touchedRadarEntryIndex, 0); final result6 = radarChartPainter.handleTouch( const Offset(202.6, 33.5), viewSize, holder, ); expect(result6!.touchedDataSetIndex, 1); expect(result6.touchedRadarEntryIndex, 0); final result7 = radarChartPainter.handleTouch( const Offset(122.1, 195), viewSize, holder, ); expect(result7!.touchedDataSetIndex, 1); expect(result7.touchedRadarEntryIndex, 2); final result8 = radarChartPainter.handleTouch( const Offset(252, 180), viewSize, holder, ); expect(result8!.touchedDataSetIndex, 1); expect(result8.touchedRadarEntryIndex, 1); }); }); group('radarCenterY()', () { test('test 1', () { final painter = RadarChartPainter(); expect(painter.radarCenterY(const Size(200, 400)), 200); expect(painter.radarCenterY(const Size(2314, 400)), 200); }); }); group('radarCenterX()', () { test('test 1', () { final painter = RadarChartPainter(); expect(painter.radarCenterX(const Size(400, 200)), 200); expect(painter.radarCenterX(const Size(400, 2314)), 200); }); }); group('radarRadius()', () { test('test 1', () { final painter = RadarChartPainter(); expect(painter.radarRadius(const Size(400, 200)), 80); expect(painter.radarRadius(const Size(400, 2314)), 160); }); }); group('calculateDataSetsPosition()', () { test('test 1', () { const viewSize = Size(400, 300); final data = RadarChartData( dataSets: [ RadarDataSet( dataEntries: [ const RadarEntry(value: 1), const RadarEntry(value: 2), const RadarEntry(value: 3), ], ), RadarDataSet( dataEntries: [ const RadarEntry(value: 3), const RadarEntry(value: 1), const RadarEntry(value: 2), ], ), RadarDataSet( dataEntries: [ const RadarEntry(value: 2), const RadarEntry(value: 3), const RadarEntry(value: 1), ], ), ], titleTextStyle: MockData.textStyle4, radarBorderData: const BorderSide(color: MockData.color6, width: 33), tickBorderData: const BorderSide(color: MockData.color5, width: 55), gridBorderData: const BorderSide(color: MockData.color3, width: 3), radarBackgroundColor: MockData.color2, ); final radarChartPainter = RadarChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final result = radarChartPainter.calculateDataSetsPosition(viewSize, holder); expect(result.length, 3); expect( result[0].entriesOffset, [ const Offset(200, 90), const Offset(277.9422863405995, 195), const Offset(96.07695154586739, 210.00000000000006), ], ); expect( result[1].entriesOffset, [ const Offset(200, 30), const Offset(251.96152422706632, 180), const Offset(122.05771365940053, 195.00000000000003), ], ); expect( result[2].entriesOffset, [ const Offset(200, 60), const Offset(303.92304845413264, 209.99999999999997), const Offset(148.03847577293368, 180.00000000000003), ], ); }); }); group('getDefaultChartCenterValue()', () { final radarChartPainter = RadarChartPainter(); test('test 1', () { expect(radarChartPainter.getDefaultChartCenterValue(), 0); }); }); group('getChartCenterValue()', () { final radarChartPainter = RadarChartPainter(); final dataSet = RadarDataSet( dataEntries: [ const RadarEntry(value: 15), const RadarEntry(value: 20), const RadarEntry(value: 20), ], ); final dataSetWithSameMaxAndMin = RadarDataSet( dataEntries: [ const RadarEntry(value: 10), const RadarEntry(value: 10), const RadarEntry(value: 10), ], ); final dataWith1Tick = RadarChartData( dataSets: [dataSet], tickCount: 1, ); final dataWith2Ticks = RadarChartData( dataSets: [dataSet], tickCount: 2, ); final dataWith3Ticks = RadarChartData( dataSets: [dataSet], tickCount: 3, ); final dataWithSameMaxAndMin = RadarChartData( dataSets: [dataSetWithSameMaxAndMin], tickCount: 2, ); test('test 1', () { expect(radarChartPainter.getChartCenterValue(dataWith1Tick), 10); expect(radarChartPainter.getChartCenterValue(dataWith2Ticks), 12.5); expect( radarChartPainter.getChartCenterValue(dataWith3Ticks), 13.333333333333334, ); }); test('test 2', () { expect(radarChartPainter.getChartCenterValue(dataWithSameMaxAndMin), 0); }); }); group('getScaledPoint()', () { final radarChartPainter = RadarChartPainter(); final data = RadarChartData( dataSets: [ RadarDataSet( dataEntries: [ const RadarEntry(value: 15), const RadarEntry(value: 20), const RadarEntry(value: 20), ], ), ], tickCount: 2, ); final dataWithSameMaxAndMin = RadarChartData( dataSets: [ RadarDataSet( dataEntries: [ const RadarEntry(value: 10), const RadarEntry(value: 10), const RadarEntry(value: 10), ], ), ], tickCount: 2, ); const radius = 200.0; const point1 = RadarEntry(value: 0); const point2 = RadarEntry(value: 50); const point3 = RadarEntry(value: 150); test('test 1', () { expect( radarChartPainter.getScaledPoint(point1, radius, data), -333.3333333333333, ); expect(radarChartPainter.getScaledPoint(point2, radius, data), 1000.0); expect( radarChartPainter.getScaledPoint(point3, radius, data), 3666.6666666666665, ); }); test('test 2', () { expect( radarChartPainter.getScaledPoint( point1, radius, dataWithSameMaxAndMin, ), 0.0, ); expect( radarChartPainter.getScaledPoint( point2, radius, dataWithSameMaxAndMin, ), 1000.0, ); expect( radarChartPainter.getScaledPoint( point3, radius, dataWithSameMaxAndMin, ), 3000.0, ); }); }); group('getFirstTickValue()', () { final radarChartPainter = RadarChartPainter(); final data = RadarChartData( dataSets: [ RadarDataSet( dataEntries: [ const RadarEntry(value: 15), const RadarEntry(value: 20), const RadarEntry(value: 20), ], ), ], tickCount: 2, ); final dataWithSameMaxAndMin = RadarChartData( dataSets: [ RadarDataSet( dataEntries: [ const RadarEntry(value: 10), const RadarEntry(value: 10), const RadarEntry(value: 10), ], ), ], tickCount: 2, ); test('test 1', () { expect(radarChartPainter.getFirstTickValue(data), 15); }); test('test 2', () { expect( radarChartPainter.getFirstTickValue(dataWithSameMaxAndMin), 3.3333333333333335, ); }); }); group('getSpaceBetweenTicks()', () { final radarChartPainter = RadarChartPainter(); final data = RadarChartData( dataSets: [ RadarDataSet( dataEntries: [ const RadarEntry(value: 15), const RadarEntry(value: 20), const RadarEntry(value: 20), ], ), ], tickCount: 2, ); final dataWithSameMaxAndMin = RadarChartData( dataSets: [ RadarDataSet( dataEntries: [ const RadarEntry(value: 10), const RadarEntry(value: 10), const RadarEntry(value: 10), ], ), ], tickCount: 2, ); test('test 1', () { expect(radarChartPainter.getSpaceBetweenTicks(data), 2.5); }); test('test 2', () { expect( radarChartPainter.getSpaceBetweenTicks(dataWithSameMaxAndMin), 3.3333333333333335, ); }); }); } ================================================ FILE: test/chart/radar_chart/radar_chart_painter_test.mocks.dart ================================================ // Mocks generated by Mockito 5.4.6 from annotations // in fl_chart/test/chart/radar_chart/radar_chart_painter_test.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:typed_data' as _i5; import 'dart:ui' as _i2; import 'package:fl_chart/fl_chart.dart' as _i7; import 'package:fl_chart/src/utils/canvas_wrapper.dart' as _i6; import 'package:fl_chart/src/utils/utils.dart' as _i8; import 'package:flutter/cupertino.dart' as _i3; import 'package:flutter/foundation.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; import 'package:mockito/src/dummies.dart' as _i9; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values // ignore_for_file: avoid_setters_without_getters // ignore_for_file: comment_references // ignore_for_file: deprecated_member_use // ignore_for_file: deprecated_member_use_from_same_package // ignore_for_file: implementation_imports // ignore_for_file: invalid_use_of_visible_for_testing_member // ignore_for_file: must_be_immutable // ignore_for_file: prefer_const_constructors // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class // ignore_for_file: invalid_use_of_internal_member class _FakeRect_0 extends _i1.SmartFake implements _i2.Rect { _FakeRect_0( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeCanvas_1 extends _i1.SmartFake implements _i2.Canvas { _FakeCanvas_1( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeSize_2 extends _i1.SmartFake implements _i2.Size { _FakeSize_2( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeWidget_3 extends _i1.SmartFake implements _i3.Widget { _FakeWidget_3( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info}) => super.toString(); } class _FakeInheritedWidget_4 extends _i1.SmartFake implements _i3.InheritedWidget { _FakeInheritedWidget_4( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info}) => super.toString(); } class _FakeDiagnosticsNode_5 extends _i1.SmartFake implements _i3.DiagnosticsNode { _FakeDiagnosticsNode_5( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({ _i4.TextTreeConfiguration? parentConfiguration, _i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info, }) => super.toString(); } class _FakeOffset_6 extends _i1.SmartFake implements _i2.Offset { _FakeOffset_6( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeBorderSide_7 extends _i1.SmartFake implements _i3.BorderSide { _FakeBorderSide_7( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info}) => super.toString(); } class _FakeTextStyle_8 extends _i1.SmartFake implements _i3.TextStyle { _FakeTextStyle_8( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info}) => super.toString(); } /// A class which mocks [Canvas]. /// /// See the documentation for Mockito's code generation for more information. class MockCanvas extends _i1.Mock implements _i2.Canvas { MockCanvas() { _i1.throwOnMissingStub(this); } @override void save() => super.noSuchMethod( Invocation.method( #save, [], ), returnValueForMissingStub: null, ); @override void saveLayer( _i2.Rect? bounds, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #saveLayer, [ bounds, paint, ], ), returnValueForMissingStub: null, ); @override void restore() => super.noSuchMethod( Invocation.method( #restore, [], ), returnValueForMissingStub: null, ); @override void restoreToCount(int? count) => super.noSuchMethod( Invocation.method( #restoreToCount, [count], ), returnValueForMissingStub: null, ); @override int getSaveCount() => (super.noSuchMethod( Invocation.method( #getSaveCount, [], ), returnValue: 0, ) as int); @override void translate( double? dx, double? dy, ) => super.noSuchMethod( Invocation.method( #translate, [ dx, dy, ], ), returnValueForMissingStub: null, ); @override void scale( double? sx, [ double? sy, ]) => super.noSuchMethod( Invocation.method( #scale, [ sx, sy, ], ), returnValueForMissingStub: null, ); @override void rotate(double? radians) => super.noSuchMethod( Invocation.method( #rotate, [radians], ), returnValueForMissingStub: null, ); @override void skew( double? sx, double? sy, ) => super.noSuchMethod( Invocation.method( #skew, [ sx, sy, ], ), returnValueForMissingStub: null, ); @override void transform(_i5.Float64List? matrix4) => super.noSuchMethod( Invocation.method( #transform, [matrix4], ), returnValueForMissingStub: null, ); @override _i5.Float64List getTransform() => (super.noSuchMethod( Invocation.method( #getTransform, [], ), returnValue: _i5.Float64List(0), ) as _i5.Float64List); @override void clipRect( _i2.Rect? rect, { _i2.ClipOp? clipOp = _i2.ClipOp.intersect, bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipRect, [rect], { #clipOp: clipOp, #doAntiAlias: doAntiAlias, }, ), returnValueForMissingStub: null, ); @override void clipRRect( _i2.RRect? rrect, { bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipRRect, [rrect], {#doAntiAlias: doAntiAlias}, ), returnValueForMissingStub: null, ); @override void clipRSuperellipse( _i2.RSuperellipse? rsuperellipse, { bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipRSuperellipse, [rsuperellipse], {#doAntiAlias: doAntiAlias}, ), returnValueForMissingStub: null, ); @override void clipPath( _i2.Path? path, { bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipPath, [path], {#doAntiAlias: doAntiAlias}, ), returnValueForMissingStub: null, ); @override _i2.Rect getLocalClipBounds() => (super.noSuchMethod( Invocation.method( #getLocalClipBounds, [], ), returnValue: _FakeRect_0( this, Invocation.method( #getLocalClipBounds, [], ), ), ) as _i2.Rect); @override _i2.Rect getDestinationClipBounds() => (super.noSuchMethod( Invocation.method( #getDestinationClipBounds, [], ), returnValue: _FakeRect_0( this, Invocation.method( #getDestinationClipBounds, [], ), ), ) as _i2.Rect); @override void drawColor( _i2.Color? color, _i2.BlendMode? blendMode, ) => super.noSuchMethod( Invocation.method( #drawColor, [ color, blendMode, ], ), returnValueForMissingStub: null, ); @override void drawLine( _i2.Offset? p1, _i2.Offset? p2, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawLine, [ p1, p2, paint, ], ), returnValueForMissingStub: null, ); @override void drawPaint(_i2.Paint? paint) => super.noSuchMethod( Invocation.method( #drawPaint, [paint], ), returnValueForMissingStub: null, ); @override void drawRect( _i2.Rect? rect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRect, [ rect, paint, ], ), returnValueForMissingStub: null, ); @override void drawRRect( _i2.RRect? rrect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRRect, [ rrect, paint, ], ), returnValueForMissingStub: null, ); @override void drawDRRect( _i2.RRect? outer, _i2.RRect? inner, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawDRRect, [ outer, inner, paint, ], ), returnValueForMissingStub: null, ); @override void drawRSuperellipse( _i2.RSuperellipse? rsuperellipse, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRSuperellipse, [ rsuperellipse, paint, ], ), returnValueForMissingStub: null, ); @override void drawOval( _i2.Rect? rect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawOval, [ rect, paint, ], ), returnValueForMissingStub: null, ); @override void drawCircle( _i2.Offset? c, double? radius, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawCircle, [ c, radius, paint, ], ), returnValueForMissingStub: null, ); @override void drawArc( _i2.Rect? rect, double? startAngle, double? sweepAngle, bool? useCenter, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawArc, [ rect, startAngle, sweepAngle, useCenter, paint, ], ), returnValueForMissingStub: null, ); @override void drawPath( _i2.Path? path, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawPath, [ path, paint, ], ), returnValueForMissingStub: null, ); @override void drawImage( _i2.Image? image, _i2.Offset? offset, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawImage, [ image, offset, paint, ], ), returnValueForMissingStub: null, ); @override void drawImageRect( _i2.Image? image, _i2.Rect? src, _i2.Rect? dst, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawImageRect, [ image, src, dst, paint, ], ), returnValueForMissingStub: null, ); @override void drawImageNine( _i2.Image? image, _i2.Rect? center, _i2.Rect? dst, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawImageNine, [ image, center, dst, paint, ], ), returnValueForMissingStub: null, ); @override void drawPicture(_i2.Picture? picture) => super.noSuchMethod( Invocation.method( #drawPicture, [picture], ), returnValueForMissingStub: null, ); @override void drawParagraph( _i2.Paragraph? paragraph, _i2.Offset? offset, ) => super.noSuchMethod( Invocation.method( #drawParagraph, [ paragraph, offset, ], ), returnValueForMissingStub: null, ); @override void drawPoints( _i2.PointMode? pointMode, List<_i2.Offset>? points, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawPoints, [ pointMode, points, paint, ], ), returnValueForMissingStub: null, ); @override void drawRawPoints( _i2.PointMode? pointMode, _i5.Float32List? points, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRawPoints, [ pointMode, points, paint, ], ), returnValueForMissingStub: null, ); @override void drawVertices( _i2.Vertices? vertices, _i2.BlendMode? blendMode, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawVertices, [ vertices, blendMode, paint, ], ), returnValueForMissingStub: null, ); @override void drawAtlas( _i2.Image? atlas, List<_i2.RSTransform>? transforms, List<_i2.Rect>? rects, List<_i2.Color>? colors, _i2.BlendMode? blendMode, _i2.Rect? cullRect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawAtlas, [ atlas, transforms, rects, colors, blendMode, cullRect, paint, ], ), returnValueForMissingStub: null, ); @override void drawRawAtlas( _i2.Image? atlas, _i5.Float32List? rstTransforms, _i5.Float32List? rects, _i5.Int32List? colors, _i2.BlendMode? blendMode, _i2.Rect? cullRect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRawAtlas, [ atlas, rstTransforms, rects, colors, blendMode, cullRect, paint, ], ), returnValueForMissingStub: null, ); @override void drawShadow( _i2.Path? path, _i2.Color? color, double? elevation, bool? transparentOccluder, ) => super.noSuchMethod( Invocation.method( #drawShadow, [ path, color, elevation, transparentOccluder, ], ), returnValueForMissingStub: null, ); } /// A class which mocks [CanvasWrapper]. /// /// See the documentation for Mockito's code generation for more information. class MockCanvasWrapper extends _i1.Mock implements _i6.CanvasWrapper { MockCanvasWrapper() { _i1.throwOnMissingStub(this); } @override _i2.Canvas get canvas => (super.noSuchMethod( Invocation.getter(#canvas), returnValue: _FakeCanvas_1( this, Invocation.getter(#canvas), ), ) as _i2.Canvas); @override _i2.Size get size => (super.noSuchMethod( Invocation.getter(#size), returnValue: _FakeSize_2( this, Invocation.getter(#size), ), ) as _i2.Size); @override void drawRRect( _i2.RRect? rrect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRRect, [ rrect, paint, ], ), returnValueForMissingStub: null, ); @override void save() => super.noSuchMethod( Invocation.method( #save, [], ), returnValueForMissingStub: null, ); @override void restore() => super.noSuchMethod( Invocation.method( #restore, [], ), returnValueForMissingStub: null, ); @override void clipRect( _i2.Rect? rect, { _i2.ClipOp? clipOp = _i2.ClipOp.intersect, bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipRect, [rect], { #clipOp: clipOp, #doAntiAlias: doAntiAlias, }, ), returnValueForMissingStub: null, ); @override void translate( double? dx, double? dy, ) => super.noSuchMethod( Invocation.method( #translate, [ dx, dy, ], ), returnValueForMissingStub: null, ); @override void rotate(double? radius) => super.noSuchMethod( Invocation.method( #rotate, [radius], ), returnValueForMissingStub: null, ); @override void drawPath( _i2.Path? path, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawPath, [ path, paint, ], ), returnValueForMissingStub: null, ); @override void saveLayer( _i2.Rect? bounds, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #saveLayer, [ bounds, paint, ], ), returnValueForMissingStub: null, ); @override void drawPicture(_i2.Picture? picture) => super.noSuchMethod( Invocation.method( #drawPicture, [picture], ), returnValueForMissingStub: null, ); @override void drawImage( _i2.Image? image, _i2.Offset? offset, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawImage, [ image, offset, paint, ], ), returnValueForMissingStub: null, ); @override void clipPath( _i2.Path? path, { bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipPath, [path], {#doAntiAlias: doAntiAlias}, ), returnValueForMissingStub: null, ); @override void drawRect( _i2.Rect? rect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRect, [ rect, paint, ], ), returnValueForMissingStub: null, ); @override void drawLine( _i2.Offset? p1, _i2.Offset? p2, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawLine, [ p1, p2, paint, ], ), returnValueForMissingStub: null, ); @override void drawCircle( _i2.Offset? center, double? radius, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawCircle, [ center, radius, paint, ], ), returnValueForMissingStub: null, ); @override void drawArc( _i2.Rect? rect, double? startAngle, double? sweepAngle, bool? useCenter, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawArc, [ rect, startAngle, sweepAngle, useCenter, paint, ], ), returnValueForMissingStub: null, ); @override void drawText( _i3.TextPainter? tp, _i2.Offset? offset, [ double? rotateAngle, ]) => super.noSuchMethod( Invocation.method( #drawText, [ tp, offset, rotateAngle, ], ), returnValueForMissingStub: null, ); @override void drawVerticalText( _i3.TextPainter? tp, _i2.Offset? offset, [ double? rotateAngle = 90.0, ]) => super.noSuchMethod( Invocation.method( #drawVerticalText, [ tp, offset, rotateAngle, ], ), returnValueForMissingStub: null, ); @override void drawDot( _i7.FlDotPainter? painter, _i7.FlSpot? spot, _i2.Offset? offset, ) => super.noSuchMethod( Invocation.method( #drawDot, [ painter, spot, offset, ], ), returnValueForMissingStub: null, ); @override void drawErrorIndicator( _i7.FlSpotErrorRangePainter? painter, _i7.FlSpot? origin, _i2.Offset? offset, _i2.Rect? errorRelativeRect, _i7.AxisChartData? axisData, ) => super.noSuchMethod( Invocation.method( #drawErrorIndicator, [ painter, origin, offset, errorRelativeRect, axisData, ], ), returnValueForMissingStub: null, ); @override void drawRotated({ required _i2.Size? size, _i2.Offset? rotationOffset = _i2.Offset.zero, _i2.Offset? drawOffset = _i2.Offset.zero, required double? angle, required _i6.DrawCallback? drawCallback, }) => super.noSuchMethod( Invocation.method( #drawRotated, [], { #size: size, #rotationOffset: rotationOffset, #drawOffset: drawOffset, #angle: angle, #drawCallback: drawCallback, }, ), returnValueForMissingStub: null, ); @override void drawDashedLine( _i2.Offset? from, _i2.Offset? to, _i2.Paint? painter, List? dashArray, ) => super.noSuchMethod( Invocation.method( #drawDashedLine, [ from, to, painter, dashArray, ], ), returnValueForMissingStub: null, ); } /// A class which mocks [BuildContext]. /// /// See the documentation for Mockito's code generation for more information. class MockBuildContext extends _i1.Mock implements _i3.BuildContext { MockBuildContext() { _i1.throwOnMissingStub(this); } @override _i3.Widget get widget => (super.noSuchMethod( Invocation.getter(#widget), returnValue: _FakeWidget_3( this, Invocation.getter(#widget), ), ) as _i3.Widget); @override bool get mounted => (super.noSuchMethod( Invocation.getter(#mounted), returnValue: false, ) as bool); @override bool get debugDoingBuild => (super.noSuchMethod( Invocation.getter(#debugDoingBuild), returnValue: false, ) as bool); @override _i3.InheritedWidget dependOnInheritedElement( _i3.InheritedElement? ancestor, { Object? aspect, }) => (super.noSuchMethod( Invocation.method( #dependOnInheritedElement, [ancestor], {#aspect: aspect}, ), returnValue: _FakeInheritedWidget_4( this, Invocation.method( #dependOnInheritedElement, [ancestor], {#aspect: aspect}, ), ), ) as _i3.InheritedWidget); @override void visitAncestorElements(_i3.ConditionalElementVisitor? visitor) => super.noSuchMethod( Invocation.method( #visitAncestorElements, [visitor], ), returnValueForMissingStub: null, ); @override void visitChildElements(_i3.ElementVisitor? visitor) => super.noSuchMethod( Invocation.method( #visitChildElements, [visitor], ), returnValueForMissingStub: null, ); @override void dispatchNotification(_i3.Notification? notification) => super.noSuchMethod( Invocation.method( #dispatchNotification, [notification], ), returnValueForMissingStub: null, ); @override _i3.DiagnosticsNode describeElement( String? name, { _i4.DiagnosticsTreeStyle? style = _i4.DiagnosticsTreeStyle.errorProperty, }) => (super.noSuchMethod( Invocation.method( #describeElement, [name], {#style: style}, ), returnValue: _FakeDiagnosticsNode_5( this, Invocation.method( #describeElement, [name], {#style: style}, ), ), ) as _i3.DiagnosticsNode); @override _i3.DiagnosticsNode describeWidget( String? name, { _i4.DiagnosticsTreeStyle? style = _i4.DiagnosticsTreeStyle.errorProperty, }) => (super.noSuchMethod( Invocation.method( #describeWidget, [name], {#style: style}, ), returnValue: _FakeDiagnosticsNode_5( this, Invocation.method( #describeWidget, [name], {#style: style}, ), ), ) as _i3.DiagnosticsNode); @override List<_i3.DiagnosticsNode> describeMissingAncestor( {required Type? expectedAncestorType}) => (super.noSuchMethod( Invocation.method( #describeMissingAncestor, [], {#expectedAncestorType: expectedAncestorType}, ), returnValue: <_i3.DiagnosticsNode>[], ) as List<_i3.DiagnosticsNode>); @override _i3.DiagnosticsNode describeOwnershipChain(String? name) => (super.noSuchMethod( Invocation.method( #describeOwnershipChain, [name], ), returnValue: _FakeDiagnosticsNode_5( this, Invocation.method( #describeOwnershipChain, [name], ), ), ) as _i3.DiagnosticsNode); } /// A class which mocks [Utils]. /// /// See the documentation for Mockito's code generation for more information. class MockUtils extends _i1.Mock implements _i8.Utils { MockUtils() { _i1.throwOnMissingStub(this); } @override double radians(double? degrees) => (super.noSuchMethod( Invocation.method( #radians, [degrees], ), returnValue: 0.0, ) as double); @override double degrees(double? radians) => (super.noSuchMethod( Invocation.method( #degrees, [radians], ), returnValue: 0.0, ) as double); @override double translateRotatedPosition( double? size, double? degree, ) => (super.noSuchMethod( Invocation.method( #translateRotatedPosition, [ size, degree, ], ), returnValue: 0.0, ) as double); @override _i2.Offset calculateRotationOffset( _i2.Size? size, double? degree, ) => (super.noSuchMethod( Invocation.method( #calculateRotationOffset, [ size, degree, ], ), returnValue: _FakeOffset_6( this, Invocation.method( #calculateRotationOffset, [ size, degree, ], ), ), ) as _i2.Offset); @override _i3.BorderRadius? normalizeBorderRadius( _i3.BorderRadius? borderRadius, double? width, ) => (super.noSuchMethod(Invocation.method( #normalizeBorderRadius, [ borderRadius, width, ], )) as _i3.BorderRadius?); @override _i3.BorderSide normalizeBorderSide( _i3.BorderSide? borderSide, double? width, ) => (super.noSuchMethod( Invocation.method( #normalizeBorderSide, [ borderSide, width, ], ), returnValue: _FakeBorderSide_7( this, Invocation.method( #normalizeBorderSide, [ borderSide, width, ], ), ), ) as _i3.BorderSide); @override double getEfficientInterval( double? axisViewSize, double? diffInAxis, { double? pixelPerInterval = 40.0, }) => (super.noSuchMethod( Invocation.method( #getEfficientInterval, [ axisViewSize, diffInAxis, ], {#pixelPerInterval: pixelPerInterval}, ), returnValue: 0.0, ) as double); @override double roundInterval(double? input) => (super.noSuchMethod( Invocation.method( #roundInterval, [input], ), returnValue: 0.0, ) as double); @override int getFractionDigits(double? value) => (super.noSuchMethod( Invocation.method( #getFractionDigits, [value], ), returnValue: 0, ) as int); @override String formatNumber( double? axisMin, double? axisMax, double? axisValue, ) => (super.noSuchMethod( Invocation.method( #formatNumber, [ axisMin, axisMax, axisValue, ], ), returnValue: _i9.dummyValue( this, Invocation.method( #formatNumber, [ axisMin, axisMax, axisValue, ], ), ), ) as String); @override _i3.TextStyle getThemeAwareTextStyle( _i3.BuildContext? context, _i3.TextStyle? providedStyle, ) => (super.noSuchMethod( Invocation.method( #getThemeAwareTextStyle, [ context, providedStyle, ], ), returnValue: _FakeTextStyle_8( this, Invocation.method( #getThemeAwareTextStyle, [ context, providedStyle, ], ), ), ) as _i3.TextStyle); @override double getBestInitialIntervalValue( double? min, double? max, double? interval, { double? baseline = 0.0, }) => (super.noSuchMethod( Invocation.method( #getBestInitialIntervalValue, [ min, max, interval, ], {#baseline: baseline}, ), returnValue: 0.0, ) as double); @override double convertRadiusToSigma(double? radius) => (super.noSuchMethod( Invocation.method( #convertRadiusToSigma, [radius], ), returnValue: 0.0, ) as double); } ================================================ FILE: test/chart/radar_chart/radar_chart_renderer_test.dart ================================================ import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart'; import 'package:fl_chart/src/chart/radar_chart/radar_chart_painter.dart'; import 'package:fl_chart/src/chart/radar_chart/radar_chart_renderer.dart'; import 'package:fl_chart/src/utils/canvas_wrapper.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import '../data_pool.dart'; import 'radar_chart_renderer_test.mocks.dart'; @GenerateMocks([Canvas, PaintingContext, BuildContext, RadarChartPainter]) void main() { group('RadarChartRenderer', () { final data = RadarChartData( dataSets: [MockData.radarDataSet1], tickCount: 1, ); final targetData = RadarChartData( dataSets: [MockData.radarDataSet2], tickCount: 1, radarTouchData: RadarTouchData(enabled: false), ); const textScaler = TextScaler.linear(4); final mockBuildContext = MockBuildContext(); final renderRadarChart = RenderRadarChart( mockBuildContext, data, targetData, textScaler, ); final mockPainter = MockRadarChartPainter(); final mockPaintingContext = MockPaintingContext(); final mockCanvas = MockCanvas(); const mockSize = Size(44, 44); when(mockPaintingContext.canvas).thenAnswer((realInvocation) => mockCanvas); renderRadarChart ..mockTestSize = mockSize ..painter = mockPainter; test('test 1 correct data set', () { expect(renderRadarChart.data == data, true); expect(renderRadarChart.data == targetData, false); expect(renderRadarChart.targetData == targetData, true); expect(renderRadarChart.textScaler == textScaler, true); expect(renderRadarChart.paintHolder.data == data, true); expect(renderRadarChart.paintHolder.targetData == targetData, true); expect(renderRadarChart.paintHolder.textScaler == textScaler, true); expect(renderRadarChart.hitTestSelf(Offset.zero), false); }); test('test 2 check paint function', () { renderRadarChart.paint(mockPaintingContext, const Offset(10, 10)); verify(mockCanvas.save()).called(1); verify(mockCanvas.translate(10, 10)).called(1); final result = verify(mockPainter.paint(any, captureAny, captureAny)); expect(result.callCount, 1); final canvasWrapper = result.captured[0] as CanvasWrapper; expect(canvasWrapper.size, const Size(44, 44)); expect(canvasWrapper.canvas, mockCanvas); final paintHolder = result.captured[1] as PaintHolder; expect(paintHolder.data, data); expect(paintHolder.targetData, targetData); expect(paintHolder.textScaler, textScaler); verify(mockCanvas.restore()).called(1); }); test('test 3 check getResponseAtLocation function', () { final results = >[]; when(mockPainter.handleTouch(captureAny, captureAny, captureAny)) .thenAnswer((inv) { results.add({ 'local_position': inv.positionalArguments[0] as Offset, 'size': inv.positionalArguments[1] as Size, 'paint_holder': inv.positionalArguments[2] as PaintHolder, }); return MockData.radarTouchedSpot; }); final touchResponse = renderRadarChart.getResponseAtLocation(MockData.offset1); expect(touchResponse.touchedSpot, MockData.radarTouchedSpot); expect(results[0]['local_position'] as Offset, MockData.offset1); expect(results[0]['size'] as Size, mockSize); final paintHolder = results[0]['paint_holder'] as PaintHolder; expect(paintHolder.data, data); expect(paintHolder.targetData, targetData); expect(paintHolder.textScaler, textScaler); }); test('test 4 check setters', () { renderRadarChart ..data = targetData ..targetData = data ..textScaler = const TextScaler.linear(22); expect(renderRadarChart.data, targetData); expect(renderRadarChart.targetData, data); expect(renderRadarChart.textScaler, const TextScaler.linear(22)); }); }); } ================================================ FILE: test/chart/radar_chart/radar_chart_renderer_test.mocks.dart ================================================ // Mocks generated by Mockito 5.4.6 from annotations // in fl_chart/test/chart/radar_chart/radar_chart_renderer_test.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:typed_data' as _i7; import 'dart:ui' as _i2; import 'package:fl_chart/fl_chart.dart' as _i13; import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart' as _i12; import 'package:fl_chart/src/chart/radar_chart/radar_chart_painter.dart' as _i10; import 'package:fl_chart/src/utils/canvas_wrapper.dart' as _i11; import 'package:flutter/foundation.dart' as _i5; import 'package:flutter/gestures.dart' as _i8; import 'package:flutter/material.dart' as _i6; import 'package:flutter/rendering.dart' as _i3; import 'package:flutter/src/rendering/layer.dart' as _i4; import 'package:flutter/src/widgets/notification_listener.dart' as _i9; import 'package:mockito/mockito.dart' as _i1; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values // ignore_for_file: avoid_setters_without_getters // ignore_for_file: comment_references // ignore_for_file: deprecated_member_use // ignore_for_file: deprecated_member_use_from_same_package // ignore_for_file: implementation_imports // ignore_for_file: invalid_use_of_visible_for_testing_member // ignore_for_file: must_be_immutable // ignore_for_file: prefer_const_constructors // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class // ignore_for_file: invalid_use_of_internal_member class _FakeRect_0 extends _i1.SmartFake implements _i2.Rect { _FakeRect_0( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeCanvas_1 extends _i1.SmartFake implements _i2.Canvas { _FakeCanvas_1( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakePaintingContext_2 extends _i1.SmartFake implements _i3.PaintingContext { _FakePaintingContext_2( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeColorFilterLayer_3 extends _i1.SmartFake implements _i4.ColorFilterLayer { _FakeColorFilterLayer_3( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => super.toString(); } class _FakeOpacityLayer_4 extends _i1.SmartFake implements _i4.OpacityLayer { _FakeOpacityLayer_4( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => super.toString(); } class _FakeWidget_5 extends _i1.SmartFake implements _i6.Widget { _FakeWidget_5( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => super.toString(); } class _FakeInheritedWidget_6 extends _i1.SmartFake implements _i6.InheritedWidget { _FakeInheritedWidget_6( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => super.toString(); } class _FakeDiagnosticsNode_7 extends _i1.SmartFake implements _i5.DiagnosticsNode { _FakeDiagnosticsNode_7( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({ _i5.TextTreeConfiguration? parentConfiguration, _i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info, }) => super.toString(); } /// A class which mocks [Canvas]. /// /// See the documentation for Mockito's code generation for more information. class MockCanvas extends _i1.Mock implements _i2.Canvas { MockCanvas() { _i1.throwOnMissingStub(this); } @override void save() => super.noSuchMethod( Invocation.method( #save, [], ), returnValueForMissingStub: null, ); @override void saveLayer( _i2.Rect? bounds, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #saveLayer, [ bounds, paint, ], ), returnValueForMissingStub: null, ); @override void restore() => super.noSuchMethod( Invocation.method( #restore, [], ), returnValueForMissingStub: null, ); @override void restoreToCount(int? count) => super.noSuchMethod( Invocation.method( #restoreToCount, [count], ), returnValueForMissingStub: null, ); @override int getSaveCount() => (super.noSuchMethod( Invocation.method( #getSaveCount, [], ), returnValue: 0, ) as int); @override void translate( double? dx, double? dy, ) => super.noSuchMethod( Invocation.method( #translate, [ dx, dy, ], ), returnValueForMissingStub: null, ); @override void scale( double? sx, [ double? sy, ]) => super.noSuchMethod( Invocation.method( #scale, [ sx, sy, ], ), returnValueForMissingStub: null, ); @override void rotate(double? radians) => super.noSuchMethod( Invocation.method( #rotate, [radians], ), returnValueForMissingStub: null, ); @override void skew( double? sx, double? sy, ) => super.noSuchMethod( Invocation.method( #skew, [ sx, sy, ], ), returnValueForMissingStub: null, ); @override void transform(_i7.Float64List? matrix4) => super.noSuchMethod( Invocation.method( #transform, [matrix4], ), returnValueForMissingStub: null, ); @override _i7.Float64List getTransform() => (super.noSuchMethod( Invocation.method( #getTransform, [], ), returnValue: _i7.Float64List(0), ) as _i7.Float64List); @override void clipRect( _i2.Rect? rect, { _i2.ClipOp? clipOp = _i2.ClipOp.intersect, bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipRect, [rect], { #clipOp: clipOp, #doAntiAlias: doAntiAlias, }, ), returnValueForMissingStub: null, ); @override void clipRRect( _i2.RRect? rrect, { bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipRRect, [rrect], {#doAntiAlias: doAntiAlias}, ), returnValueForMissingStub: null, ); @override void clipRSuperellipse( _i2.RSuperellipse? rsuperellipse, { bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipRSuperellipse, [rsuperellipse], {#doAntiAlias: doAntiAlias}, ), returnValueForMissingStub: null, ); @override void clipPath( _i2.Path? path, { bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipPath, [path], {#doAntiAlias: doAntiAlias}, ), returnValueForMissingStub: null, ); @override _i2.Rect getLocalClipBounds() => (super.noSuchMethod( Invocation.method( #getLocalClipBounds, [], ), returnValue: _FakeRect_0( this, Invocation.method( #getLocalClipBounds, [], ), ), ) as _i2.Rect); @override _i2.Rect getDestinationClipBounds() => (super.noSuchMethod( Invocation.method( #getDestinationClipBounds, [], ), returnValue: _FakeRect_0( this, Invocation.method( #getDestinationClipBounds, [], ), ), ) as _i2.Rect); @override void drawColor( _i2.Color? color, _i2.BlendMode? blendMode, ) => super.noSuchMethod( Invocation.method( #drawColor, [ color, blendMode, ], ), returnValueForMissingStub: null, ); @override void drawLine( _i2.Offset? p1, _i2.Offset? p2, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawLine, [ p1, p2, paint, ], ), returnValueForMissingStub: null, ); @override void drawPaint(_i2.Paint? paint) => super.noSuchMethod( Invocation.method( #drawPaint, [paint], ), returnValueForMissingStub: null, ); @override void drawRect( _i2.Rect? rect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRect, [ rect, paint, ], ), returnValueForMissingStub: null, ); @override void drawRRect( _i2.RRect? rrect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRRect, [ rrect, paint, ], ), returnValueForMissingStub: null, ); @override void drawDRRect( _i2.RRect? outer, _i2.RRect? inner, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawDRRect, [ outer, inner, paint, ], ), returnValueForMissingStub: null, ); @override void drawRSuperellipse( _i2.RSuperellipse? rsuperellipse, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRSuperellipse, [ rsuperellipse, paint, ], ), returnValueForMissingStub: null, ); @override void drawOval( _i2.Rect? rect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawOval, [ rect, paint, ], ), returnValueForMissingStub: null, ); @override void drawCircle( _i2.Offset? c, double? radius, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawCircle, [ c, radius, paint, ], ), returnValueForMissingStub: null, ); @override void drawArc( _i2.Rect? rect, double? startAngle, double? sweepAngle, bool? useCenter, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawArc, [ rect, startAngle, sweepAngle, useCenter, paint, ], ), returnValueForMissingStub: null, ); @override void drawPath( _i2.Path? path, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawPath, [ path, paint, ], ), returnValueForMissingStub: null, ); @override void drawImage( _i2.Image? image, _i2.Offset? offset, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawImage, [ image, offset, paint, ], ), returnValueForMissingStub: null, ); @override void drawImageRect( _i2.Image? image, _i2.Rect? src, _i2.Rect? dst, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawImageRect, [ image, src, dst, paint, ], ), returnValueForMissingStub: null, ); @override void drawImageNine( _i2.Image? image, _i2.Rect? center, _i2.Rect? dst, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawImageNine, [ image, center, dst, paint, ], ), returnValueForMissingStub: null, ); @override void drawPicture(_i2.Picture? picture) => super.noSuchMethod( Invocation.method( #drawPicture, [picture], ), returnValueForMissingStub: null, ); @override void drawParagraph( _i2.Paragraph? paragraph, _i2.Offset? offset, ) => super.noSuchMethod( Invocation.method( #drawParagraph, [ paragraph, offset, ], ), returnValueForMissingStub: null, ); @override void drawPoints( _i2.PointMode? pointMode, List<_i2.Offset>? points, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawPoints, [ pointMode, points, paint, ], ), returnValueForMissingStub: null, ); @override void drawRawPoints( _i2.PointMode? pointMode, _i7.Float32List? points, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRawPoints, [ pointMode, points, paint, ], ), returnValueForMissingStub: null, ); @override void drawVertices( _i2.Vertices? vertices, _i2.BlendMode? blendMode, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawVertices, [ vertices, blendMode, paint, ], ), returnValueForMissingStub: null, ); @override void drawAtlas( _i2.Image? atlas, List<_i2.RSTransform>? transforms, List<_i2.Rect>? rects, List<_i2.Color>? colors, _i2.BlendMode? blendMode, _i2.Rect? cullRect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawAtlas, [ atlas, transforms, rects, colors, blendMode, cullRect, paint, ], ), returnValueForMissingStub: null, ); @override void drawRawAtlas( _i2.Image? atlas, _i7.Float32List? rstTransforms, _i7.Float32List? rects, _i7.Int32List? colors, _i2.BlendMode? blendMode, _i2.Rect? cullRect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRawAtlas, [ atlas, rstTransforms, rects, colors, blendMode, cullRect, paint, ], ), returnValueForMissingStub: null, ); @override void drawShadow( _i2.Path? path, _i2.Color? color, double? elevation, bool? transparentOccluder, ) => super.noSuchMethod( Invocation.method( #drawShadow, [ path, color, elevation, transparentOccluder, ], ), returnValueForMissingStub: null, ); } /// A class which mocks [PaintingContext]. /// /// See the documentation for Mockito's code generation for more information. class MockPaintingContext extends _i1.Mock implements _i3.PaintingContext { MockPaintingContext() { _i1.throwOnMissingStub(this); } @override _i2.Rect get estimatedBounds => (super.noSuchMethod( Invocation.getter(#estimatedBounds), returnValue: _FakeRect_0( this, Invocation.getter(#estimatedBounds), ), ) as _i2.Rect); @override _i2.Canvas get canvas => (super.noSuchMethod( Invocation.getter(#canvas), returnValue: _FakeCanvas_1( this, Invocation.getter(#canvas), ), ) as _i2.Canvas); @override void paintChild( _i3.RenderObject? child, _i2.Offset? offset, ) => super.noSuchMethod( Invocation.method( #paintChild, [ child, offset, ], ), returnValueForMissingStub: null, ); @override void appendLayer(_i4.Layer? layer) => super.noSuchMethod( Invocation.method( #appendLayer, [layer], ), returnValueForMissingStub: null, ); @override _i2.VoidCallback addCompositionCallback(_i4.CompositionCallback? callback) => (super.noSuchMethod( Invocation.method( #addCompositionCallback, [callback], ), returnValue: () {}, ) as _i2.VoidCallback); @override void stopRecordingIfNeeded() => super.noSuchMethod( Invocation.method( #stopRecordingIfNeeded, [], ), returnValueForMissingStub: null, ); @override void setIsComplexHint() => super.noSuchMethod( Invocation.method( #setIsComplexHint, [], ), returnValueForMissingStub: null, ); @override void setWillChangeHint() => super.noSuchMethod( Invocation.method( #setWillChangeHint, [], ), returnValueForMissingStub: null, ); @override void addLayer(_i4.Layer? layer) => super.noSuchMethod( Invocation.method( #addLayer, [layer], ), returnValueForMissingStub: null, ); @override void pushLayer( _i4.ContainerLayer? childLayer, _i3.PaintingContextCallback? painter, _i2.Offset? offset, { _i2.Rect? childPaintBounds, }) => super.noSuchMethod( Invocation.method( #pushLayer, [ childLayer, painter, offset, ], {#childPaintBounds: childPaintBounds}, ), returnValueForMissingStub: null, ); @override _i3.PaintingContext createChildContext( _i4.ContainerLayer? childLayer, _i2.Rect? bounds, ) => (super.noSuchMethod( Invocation.method( #createChildContext, [ childLayer, bounds, ], ), returnValue: _FakePaintingContext_2( this, Invocation.method( #createChildContext, [ childLayer, bounds, ], ), ), ) as _i3.PaintingContext); @override _i4.ClipRectLayer? pushClipRect( bool? needsCompositing, _i2.Offset? offset, _i2.Rect? clipRect, _i3.PaintingContextCallback? painter, { _i2.Clip? clipBehavior = _i2.Clip.hardEdge, _i4.ClipRectLayer? oldLayer, }) => (super.noSuchMethod(Invocation.method( #pushClipRect, [ needsCompositing, offset, clipRect, painter, ], { #clipBehavior: clipBehavior, #oldLayer: oldLayer, }, )) as _i4.ClipRectLayer?); @override _i4.ClipRRectLayer? pushClipRRect( bool? needsCompositing, _i2.Offset? offset, _i2.Rect? bounds, _i2.RRect? clipRRect, _i3.PaintingContextCallback? painter, { _i2.Clip? clipBehavior = _i2.Clip.antiAlias, _i4.ClipRRectLayer? oldLayer, }) => (super.noSuchMethod(Invocation.method( #pushClipRRect, [ needsCompositing, offset, bounds, clipRRect, painter, ], { #clipBehavior: clipBehavior, #oldLayer: oldLayer, }, )) as _i4.ClipRRectLayer?); @override _i4.ClipRSuperellipseLayer? pushClipRSuperellipse( bool? needsCompositing, _i2.Offset? offset, _i2.Rect? bounds, _i2.RSuperellipse? clipRSuperellipse, _i3.PaintingContextCallback? painter, { _i2.Clip? clipBehavior = _i2.Clip.antiAlias, _i4.ClipRSuperellipseLayer? oldLayer, }) => (super.noSuchMethod(Invocation.method( #pushClipRSuperellipse, [ needsCompositing, offset, bounds, clipRSuperellipse, painter, ], { #clipBehavior: clipBehavior, #oldLayer: oldLayer, }, )) as _i4.ClipRSuperellipseLayer?); @override _i4.ClipPathLayer? pushClipPath( bool? needsCompositing, _i2.Offset? offset, _i2.Rect? bounds, _i2.Path? clipPath, _i3.PaintingContextCallback? painter, { _i2.Clip? clipBehavior = _i2.Clip.antiAlias, _i4.ClipPathLayer? oldLayer, }) => (super.noSuchMethod(Invocation.method( #pushClipPath, [ needsCompositing, offset, bounds, clipPath, painter, ], { #clipBehavior: clipBehavior, #oldLayer: oldLayer, }, )) as _i4.ClipPathLayer?); @override _i4.ColorFilterLayer pushColorFilter( _i2.Offset? offset, _i2.ColorFilter? colorFilter, _i3.PaintingContextCallback? painter, { _i4.ColorFilterLayer? oldLayer, }) => (super.noSuchMethod( Invocation.method( #pushColorFilter, [ offset, colorFilter, painter, ], {#oldLayer: oldLayer}, ), returnValue: _FakeColorFilterLayer_3( this, Invocation.method( #pushColorFilter, [ offset, colorFilter, painter, ], {#oldLayer: oldLayer}, ), ), ) as _i4.ColorFilterLayer); @override _i4.TransformLayer? pushTransform( bool? needsCompositing, _i2.Offset? offset, _i8.Matrix4? transform, _i3.PaintingContextCallback? painter, { _i4.TransformLayer? oldLayer, }) => (super.noSuchMethod(Invocation.method( #pushTransform, [ needsCompositing, offset, transform, painter, ], {#oldLayer: oldLayer}, )) as _i4.TransformLayer?); @override _i4.OpacityLayer pushOpacity( _i2.Offset? offset, int? alpha, _i3.PaintingContextCallback? painter, { _i4.OpacityLayer? oldLayer, }) => (super.noSuchMethod( Invocation.method( #pushOpacity, [ offset, alpha, painter, ], {#oldLayer: oldLayer}, ), returnValue: _FakeOpacityLayer_4( this, Invocation.method( #pushOpacity, [ offset, alpha, painter, ], {#oldLayer: oldLayer}, ), ), ) as _i4.OpacityLayer); @override void clipPathAndPaint( _i2.Path? path, _i2.Clip? clipBehavior, _i2.Rect? bounds, _i2.VoidCallback? painter, ) => super.noSuchMethod( Invocation.method( #clipPathAndPaint, [ path, clipBehavior, bounds, painter, ], ), returnValueForMissingStub: null, ); @override void clipRRectAndPaint( _i2.RRect? rrect, _i2.Clip? clipBehavior, _i2.Rect? bounds, _i2.VoidCallback? painter, ) => super.noSuchMethod( Invocation.method( #clipRRectAndPaint, [ rrect, clipBehavior, bounds, painter, ], ), returnValueForMissingStub: null, ); @override void clipRSuperellipseAndPaint( _i2.RSuperellipse? rse, _i2.Clip? clipBehavior, _i2.Rect? bounds, _i2.VoidCallback? painter, ) => super.noSuchMethod( Invocation.method( #clipRSuperellipseAndPaint, [ rse, clipBehavior, bounds, painter, ], ), returnValueForMissingStub: null, ); @override void clipRectAndPaint( _i2.Rect? rect, _i2.Clip? clipBehavior, _i2.Rect? bounds, _i2.VoidCallback? painter, ) => super.noSuchMethod( Invocation.method( #clipRectAndPaint, [ rect, clipBehavior, bounds, painter, ], ), returnValueForMissingStub: null, ); } /// A class which mocks [BuildContext]. /// /// See the documentation for Mockito's code generation for more information. class MockBuildContext extends _i1.Mock implements _i6.BuildContext { MockBuildContext() { _i1.throwOnMissingStub(this); } @override _i6.Widget get widget => (super.noSuchMethod( Invocation.getter(#widget), returnValue: _FakeWidget_5( this, Invocation.getter(#widget), ), ) as _i6.Widget); @override bool get mounted => (super.noSuchMethod( Invocation.getter(#mounted), returnValue: false, ) as bool); @override bool get debugDoingBuild => (super.noSuchMethod( Invocation.getter(#debugDoingBuild), returnValue: false, ) as bool); @override _i6.InheritedWidget dependOnInheritedElement( _i6.InheritedElement? ancestor, { Object? aspect, }) => (super.noSuchMethod( Invocation.method( #dependOnInheritedElement, [ancestor], {#aspect: aspect}, ), returnValue: _FakeInheritedWidget_6( this, Invocation.method( #dependOnInheritedElement, [ancestor], {#aspect: aspect}, ), ), ) as _i6.InheritedWidget); @override void visitAncestorElements(_i6.ConditionalElementVisitor? visitor) => super.noSuchMethod( Invocation.method( #visitAncestorElements, [visitor], ), returnValueForMissingStub: null, ); @override void visitChildElements(_i6.ElementVisitor? visitor) => super.noSuchMethod( Invocation.method( #visitChildElements, [visitor], ), returnValueForMissingStub: null, ); @override void dispatchNotification(_i9.Notification? notification) => super.noSuchMethod( Invocation.method( #dispatchNotification, [notification], ), returnValueForMissingStub: null, ); @override _i5.DiagnosticsNode describeElement( String? name, { _i5.DiagnosticsTreeStyle? style = _i5.DiagnosticsTreeStyle.errorProperty, }) => (super.noSuchMethod( Invocation.method( #describeElement, [name], {#style: style}, ), returnValue: _FakeDiagnosticsNode_7( this, Invocation.method( #describeElement, [name], {#style: style}, ), ), ) as _i5.DiagnosticsNode); @override _i5.DiagnosticsNode describeWidget( String? name, { _i5.DiagnosticsTreeStyle? style = _i5.DiagnosticsTreeStyle.errorProperty, }) => (super.noSuchMethod( Invocation.method( #describeWidget, [name], {#style: style}, ), returnValue: _FakeDiagnosticsNode_7( this, Invocation.method( #describeWidget, [name], {#style: style}, ), ), ) as _i5.DiagnosticsNode); @override List<_i5.DiagnosticsNode> describeMissingAncestor( {required Type? expectedAncestorType}) => (super.noSuchMethod( Invocation.method( #describeMissingAncestor, [], {#expectedAncestorType: expectedAncestorType}, ), returnValue: <_i5.DiagnosticsNode>[], ) as List<_i5.DiagnosticsNode>); @override _i5.DiagnosticsNode describeOwnershipChain(String? name) => (super.noSuchMethod( Invocation.method( #describeOwnershipChain, [name], ), returnValue: _FakeDiagnosticsNode_7( this, Invocation.method( #describeOwnershipChain, [name], ), ), ) as _i5.DiagnosticsNode); } /// A class which mocks [RadarChartPainter]. /// /// See the documentation for Mockito's code generation for more information. class MockRadarChartPainter extends _i1.Mock implements _i10.RadarChartPainter { MockRadarChartPainter() { _i1.throwOnMissingStub(this); } @override set dataSetsPosition(List<_i10.RadarDataSetsPosition>? value) => super.noSuchMethod( Invocation.setter( #dataSetsPosition, value, ), returnValueForMissingStub: null, ); @override void paint( _i6.BuildContext? context, _i11.CanvasWrapper? canvasWrapper, _i12.PaintHolder<_i13.RadarChartData>? holder, ) => super.noSuchMethod( Invocation.method( #paint, [ context, canvasWrapper, holder, ], ), returnValueForMissingStub: null, ); @override double getDefaultChartCenterValue() => (super.noSuchMethod( Invocation.method( #getDefaultChartCenterValue, [], ), returnValue: 0.0, ) as double); @override double getChartCenterValue(_i13.RadarChartData? data) => (super.noSuchMethod( Invocation.method( #getChartCenterValue, [data], ), returnValue: 0.0, ) as double); @override double getScaledPoint( _i13.RadarEntry? point, double? radius, _i13.RadarChartData? data, ) => (super.noSuchMethod( Invocation.method( #getScaledPoint, [ point, radius, data, ], ), returnValue: 0.0, ) as double); @override double getFirstTickValue(_i13.RadarChartData? data) => (super.noSuchMethod( Invocation.method( #getFirstTickValue, [data], ), returnValue: 0.0, ) as double); @override double getSpaceBetweenTicks(_i13.RadarChartData? data) => (super.noSuchMethod( Invocation.method( #getSpaceBetweenTicks, [data], ), returnValue: 0.0, ) as double); @override void drawTicks( _i6.BuildContext? context, _i11.CanvasWrapper? canvasWrapper, _i12.PaintHolder<_i13.RadarChartData>? holder, ) => super.noSuchMethod( Invocation.method( #drawTicks, [ context, canvasWrapper, holder, ], ), returnValueForMissingStub: null, ); @override void drawGrids( _i11.CanvasWrapper? canvasWrapper, _i12.PaintHolder<_i13.RadarChartData>? holder, ) => super.noSuchMethod( Invocation.method( #drawGrids, [ canvasWrapper, holder, ], ), returnValueForMissingStub: null, ); @override void drawTitles( _i6.BuildContext? context, _i11.CanvasWrapper? canvasWrapper, _i12.PaintHolder<_i13.RadarChartData>? holder, ) => super.noSuchMethod( Invocation.method( #drawTitles, [ context, canvasWrapper, holder, ], ), returnValueForMissingStub: null, ); @override void drawDataSets( _i11.CanvasWrapper? canvasWrapper, _i12.PaintHolder<_i13.RadarChartData>? holder, ) => super.noSuchMethod( Invocation.method( #drawDataSets, [ canvasWrapper, holder, ], ), returnValueForMissingStub: null, ); @override _i13.RadarTouchedSpot? handleTouch( _i2.Offset? touchedPoint, _i2.Size? viewSize, _i12.PaintHolder<_i13.RadarChartData>? holder, ) => (super.noSuchMethod(Invocation.method( #handleTouch, [ touchedPoint, viewSize, holder, ], )) as _i13.RadarTouchedSpot?); @override double radarCenterY(_i2.Size? size) => (super.noSuchMethod( Invocation.method( #radarCenterY, [size], ), returnValue: 0.0, ) as double); @override double radarCenterX(_i2.Size? size) => (super.noSuchMethod( Invocation.method( #radarCenterX, [size], ), returnValue: 0.0, ) as double); @override double radarRadius(_i2.Size? size) => (super.noSuchMethod( Invocation.method( #radarRadius, [size], ), returnValue: 0.0, ) as double); @override List<_i10.RadarDataSetsPosition> calculateDataSetsPosition( _i2.Size? viewSize, _i12.PaintHolder<_i13.RadarChartData>? holder, ) => (super.noSuchMethod( Invocation.method( #calculateDataSetsPosition, [ viewSize, holder, ], ), returnValue: <_i10.RadarDataSetsPosition>[], ) as List<_i10.RadarDataSetsPosition>); } ================================================ FILE: test/chart/scatter_chart/scatter_chart_data_test.dart ================================================ import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import '../data_pool.dart'; void main() { group('ScatterChart data equality check', () { test('ScatterChartData equality test', () { expect(scatterChartData1 == scatterChartData1Clone, true); expect( scatterChartData1 == scatterChartData1Clone.copyWith(showingTooltipIndicators: []), false, ); expect( scatterChartData1 == scatterChartData1Clone.copyWith( borderData: FlBorderData( show: true, border: Border.all(color: Colors.green), ), ), false, ); expect( scatterChartData1 == scatterChartData1Clone.copyWith( borderData: FlBorderData( show: true, border: Border.all(color: Colors.white), ), ), true, ); expect( scatterChartData1 == scatterChartData1Clone.copyWith(maxX: 444), false, ); expect( scatterChartData1 == scatterChartData1Clone.copyWith( scatterSpots: [ ScatterSpot( 0, 0, show: false, dotPainter: FlDotCirclePainter( radius: 33, color: Colors.yellow, ), ), ScatterSpot( 2, 2, show: false, renderPriority: 10, dotPainter: FlDotCirclePainter( radius: 11, color: Colors.purple, ), ), ScatterSpot( 1, 2, show: false, renderPriority: -1, dotPainter: FlDotCirclePainter( radius: 11, color: Colors.white, ), ), ], ), true, ); expect( scatterChartData1 == scatterChartData1Clone.copyWith( scatterSpots: [ ScatterSpot( 2, 2, show: false, renderPriority: 10, dotPainter: FlDotCirclePainter( radius: 11, color: Colors.purple, ), ), ScatterSpot( 0, 0, show: false, renderPriority: 0, dotPainter: FlDotCirclePainter( radius: 33, color: Colors.yellow, ), ), ScatterSpot( 1, 2, show: false, renderPriority: -1, dotPainter: FlDotCirclePainter( radius: 11, color: Colors.white, ), ), ], ), false, ); expect( scatterChartData1 == scatterChartData1Clone.copyWith(clipData: const FlClipData.all()), false, ); expect( scatterChartData1 == scatterChartData1Clone.copyWith( gridData: const FlGridData( show: false, getDrawingHorizontalLine: gridGetDrawingLine, getDrawingVerticalLine: gridGetDrawingLine, checkToShowHorizontalLine: gridCheckToShowLine, checkToShowVerticalLine: gridCheckToShowLine, drawVerticalLine: false, horizontalInterval: 33, verticalInterval: 1, ), ), true, ); expect( scatterChartData1 == scatterChartData1Clone.copyWith( gridData: const FlGridData( getDrawingHorizontalLine: gridGetDrawingLine, getDrawingVerticalLine: gridGetDrawingLine, checkToShowHorizontalLine: gridCheckToShowLine, checkToShowVerticalLine: gridCheckToShowLine, drawVerticalLine: false, horizontalInterval: 33, verticalInterval: 1, ), ), false, ); expect( scatterChartData1 == scatterChartData1Clone.copyWith( gridData: FlGridData( show: false, getDrawingHorizontalLine: (value) => const FlLine( color: Colors.green, strokeWidth: 12, dashArray: [1, 2], ), getDrawingVerticalLine: (value) => const FlLine( color: Colors.yellow, strokeWidth: 33, dashArray: [0, 1], ), checkToShowHorizontalLine: (value) => false, checkToShowVerticalLine: (value) => true, drawVerticalLine: false, horizontalInterval: 32, verticalInterval: 1, ), ), false, ); expect( scatterChartData1 == scatterChartData1Clone.copyWith( titlesData: const FlTitlesData( leftTitles: AxisTitles( axisNameSize: 33, axisNameWidget: MockData.widget1, ), rightTitles: AxisTitles( axisNameSize: 1326, axisNameWidget: MockData.widget3, sideTitles: SideTitles(reservedSize: 500, showTitles: true), ), topTitles: AxisTitles( axisNameSize: 34, axisNameWidget: MockData.widget4, ), bottomTitles: AxisTitles( axisNameSize: 22, axisNameWidget: MockData.widget2, ), ), ), true, ); expect( scatterChartData1 == scatterChartData1Clone.copyWith( titlesData: const FlTitlesData( leftTitles: AxisTitles( axisNameSize: 332, axisNameWidget: Text('title 1'), ), rightTitles: AxisTitles( axisNameSize: 1326, axisNameWidget: Text('title 3'), sideTitles: SideTitles(reservedSize: 500, showTitles: true), ), topTitles: AxisTitles( axisNameSize: 34, axisNameWidget: Text('title 4'), ), bottomTitles: AxisTitles( axisNameSize: 22, axisNameWidget: Text('title 2'), ), ), ), false, ); expect( scatterChartData1 == scatterChartData1Clone.copyWith( titlesData: const FlTitlesData( leftTitles: AxisTitles( axisNameSize: 33, axisNameWidget: Text('title 1'), ), rightTitles: AxisTitles( axisNameSize: 1326, axisNameWidget: Text('title 3'), sideTitles: SideTitles(reservedSize: 500, showTitles: true), ), topTitles: AxisTitles( axisNameSize: 34, axisNameWidget: Text('title 4'), ), bottomTitles: AxisTitles( axisNameSize: 22, axisNameWidget: Text('title 2'), sideTitles: SideTitles(showTitles: true), ), ), ), false, ); expect( scatterChartData1 == scatterChartData1Clone.copyWith( titlesData: const FlTitlesData( leftTitles: AxisTitles( axisNameSize: 33, axisNameWidget: Text('title 1'), ), rightTitles: AxisTitles( axisNameSize: 1326, axisNameWidget: Text('title 1'), sideTitles: SideTitles(reservedSize: 500, showTitles: true), ), topTitles: AxisTitles( axisNameSize: 34, axisNameWidget: Text('title 4'), ), bottomTitles: AxisTitles( axisNameSize: 22, axisNameWidget: Text('title 2'), ), ), ), false, ); expect( scatterChartData1 == scatterChartData1Clone.copyWith( titlesData: const FlTitlesData( leftTitles: AxisTitles( axisNameSize: 33, axisNameWidget: Text('title 1'), ), rightTitles: AxisTitles( axisNameSize: 13262, axisNameWidget: Text('title 3'), sideTitles: SideTitles(reservedSize: 500, showTitles: true), ), topTitles: AxisTitles( axisNameSize: 34, axisNameWidget: Text('title 4'), ), bottomTitles: AxisTitles( axisNameSize: 22, axisNameWidget: Text('title 2'), ), ), ), false, ); expect( scatterChartData1 == scatterChartData1Clone.copyWith(showingTooltipIndicators: []), false, ); expect( scatterChartData1 == scatterChartData1Clone .copyWith(showingTooltipIndicators: [2, 1, 0]), false, ); expect( scatterChartData1 == scatterChartData1Clone.copyWith( scatterLabelSettings: ScatterLabelSettings( showLabel: true, getLabelTextStyleFunction: (index, spot) => const TextStyle(color: Colors.green), ), ), false, ); expect( scatterChartData1 == scatterChartData1Clone.copyWith( scatterLabelSettings: ScatterLabelSettings( showLabel: false, getLabelTextStyleFunction: (index, spot) => const TextStyle(color: Colors.red), getLabelFunction: (index, spot) => 'Label - $index', ), ), false, ); expect( scatterChartData1 == scatterChartData1Clone.copyWith( scatterLabelSettings: ScatterLabelSettings( showLabel: true, getLabelTextStyleFunction: (index, spot) => const TextStyle(color: Colors.red), getLabelFunction: (index, spot) => 'Different Label - $index', ), ), false, ); expect( scatterChartData1 == scatterChartData1Clone.copyWith( scatterLabelSettings: ScatterLabelSettings( showLabel: true, getLabelFunction: getLabel, getLabelTextStyleFunction: getLabelTextStyle, ), ), true, ); expect( scatterChartData1 == scatterChartData1Clone.copyWith( scatterLabelSettings: ScatterLabelSettings( showLabel: true, getLabelFunction: getLabel, getLabelTextStyleFunction: getLabelTextStyle, textDirection: TextDirection.rtl, ), ), false, ); }); test('ScatterSpot equality test', () { final scatterSpot = ScatterSpot(0, 1); final scatterSpotClone = ScatterSpot(0, 1); expect(scatterSpot == scatterSpotClone.copyWith(), true); expect(scatterSpot == scatterSpotClone.copyWith(y: 3), false); expect(scatterSpot == scatterSpotClone.copyWith(x: 3), false); }); test('ScatterTouchData equality test', () { final sample = ScatterTouchData( touchTooltipData: const ScatterTouchTooltipData( maxContentWidth: 2, getTooltipColor: scatterChartGetTooltipRedColor, tooltipPadding: EdgeInsets.all(11), ), handleBuiltInTouches: false, touchSpotThreshold: 23, enabled: false, ); final sampleClone = ScatterTouchData( touchTooltipData: const ScatterTouchTooltipData( maxContentWidth: 2, getTooltipColor: scatterChartGetTooltipRedColor, tooltipPadding: EdgeInsets.all(11), ), handleBuiltInTouches: false, touchSpotThreshold: 23, enabled: false, ); expect(sample == sampleClone, true); expect( sample == sampleClone.copyWith( touchCallback: (event, response) {}, ), false, ); expect( sample == sampleClone.copyWith( enabled: true, ), false, ); expect( sample == sampleClone.copyWith( touchSpotThreshold: 22, ), false, ); expect( sample == sampleClone.copyWith( handleBuiltInTouches: true, ), false, ); expect( sample == sampleClone.copyWith( longPressDuration: Duration.zero, ), false, ); }); test('ScatterTouchTooltipData equality test', () { expect(scatterTouchTooltipData1 == scatterTouchTooltipData1Clone, true); expect(scatterTouchTooltipData1 == scatterTouchTooltipData2, false); expect(scatterTouchTooltipData1 == scatterTouchTooltipData3, false); }); test('ScatterTooltipItem equality test', () { final sample1 = ScatterTooltipItem( 'aa', textStyle: const TextStyle(color: Colors.red), bottomMargin: 23, ); final sample2 = ScatterTooltipItem( 'aa', textStyle: const TextStyle(color: Colors.red), bottomMargin: 23, ); expect(sample1 == sample2, true); var changed = ScatterTooltipItem( 'a3a', textStyle: const TextStyle(color: Colors.red), bottomMargin: 23, ); expect(sample1 == changed, false); changed = ScatterTooltipItem( 'aa', textStyle: const TextStyle(color: Colors.green), bottomMargin: 23, ); expect(sample1 == changed, false); changed = ScatterTooltipItem( 'aa', textStyle: const TextStyle(color: Colors.red), bottomMargin: 0, ); expect(sample1 == changed, false); }); test('ScatterLabelSettings equality test', () { final sample1 = ScatterLabelSettings( showLabel: true, getLabelTextStyleFunction: getLabelTextStyle, getLabelFunction: getLabel, ); final sample2 = ScatterLabelSettings( showLabel: true, getLabelTextStyleFunction: getLabelTextStyle, getLabelFunction: getLabel, ); expect(sample1 == sample2, true); var changed = ScatterLabelSettings( showLabel: false, getLabelTextStyleFunction: getLabelTextStyle, getLabelFunction: getLabel, ); expect(sample1 == changed, false); expect(sample1 == changed.copyWith(showLabel: true), true); changed = ScatterLabelSettings( showLabel: true, getLabelTextStyleFunction: getLabelTextStyle, getLabelFunction: (index, spot) => 'Label', ); expect(sample1 == changed, false); changed = ScatterLabelSettings( showLabel: true, getLabelTextStyleFunction: getLabelTextStyle, getLabelFunction: getLabel, textDirection: TextDirection.rtl, ); expect(sample1 == changed, false); }); }); } ================================================ FILE: test/chart/scatter_chart/scatter_chart_helper_test.dart ================================================ import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/chart/scatter_chart/scatter_chart_helper.dart'; import 'package:flutter_test/flutter_test.dart'; import '../data_pool.dart'; void main() { group('Check caching of ScatterChartHelper.calculateMaxAxisValues', () { test('Test validity 1', () { final scatterSpots = [ scatterSpot1, scatterSpot2, scatterSpot3, scatterSpot4, ]; final (minX, maxX, minY, maxY) = ScatterChartHelper.calculateMaxAxisValues(scatterSpots); expect(minX, -14); expect(maxX, 1); expect(minY, -8); expect(maxY, 40); }); test('Test validity 2', () { final scatterSpots = [ ScatterSpot(3, -1), ScatterSpot(-1, 3), ]; final (minX, maxX, minY, maxY) = ScatterChartHelper.calculateMaxAxisValues(scatterSpots); expect(minX, -1); expect(maxX, 3); expect(minY, -1); expect(maxY, 3); }); test('Test validity 3', () { final scatterSpots = []; final (minX, maxX, minY, maxY) = ScatterChartHelper.calculateMaxAxisValues(scatterSpots); expect(minX, 0); expect(maxX, 0); expect(minY, 0); expect(maxY, 0); }); test('Test equality', () { final scatterSpots = [scatterSpot1, scatterSpot2, scatterSpot3]; final scatterSpotsClone = [ scatterSpot1Clone, scatterSpot2Clone, scatterSpot3, ]; final result1 = ScatterChartHelper.calculateMaxAxisValues(scatterSpots); final result2 = ScatterChartHelper.calculateMaxAxisValues(scatterSpotsClone); expect(result1, result2); }); }); } ================================================ FILE: test/chart/scatter_chart/scatter_chart_painter_test.dart ================================================ import 'dart:math' as math; import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart'; import 'package:fl_chart/src/chart/scatter_chart/scatter_chart_painter.dart'; import 'package:fl_chart/src/utils/canvas_wrapper.dart'; import 'package:fl_chart/src/utils/utils.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import '../data_pool.dart'; import 'scatter_chart_painter_test.mocks.dart'; @GenerateMocks([Canvas, CanvasWrapper, BuildContext, Utils]) void main() { group('paint()', () { test('test 1', () { final utilsMainInstance = Utils(); const viewSize = Size(400, 400); final data = ScatterChartData( scatterSpots: [ ScatterSpot(0, 1), ScatterSpot(1, 3), ScatterSpot(3, 4), ], ); final scatterPainter = ScatterChartPainter(); final holder = PaintHolder( data, data, TextScaler.noScaling, ); final mockUtils = MockUtils(); Utils.changeInstance(mockUtils); when(mockUtils.getThemeAwareTextStyle(any, any)) .thenAnswer((realInvocation) => textStyle1); when(mockUtils.calculateRotationOffset(any, any)) .thenAnswer((realInvocation) => Offset.zero); when(mockUtils.convertRadiusToSigma(any)) .thenAnswer((realInvocation) => 4.0); when(mockUtils.getEfficientInterval(any, any)) .thenAnswer((realInvocation) => 1.0); when(mockUtils.getBestInitialIntervalValue(any, any, any)) .thenAnswer((realInvocation) => 1.0); when(mockUtils.normalizeBorderRadius(any, any)) .thenAnswer((realInvocation) => BorderRadius.zero); when(mockUtils.normalizeBorderSide(any, any)).thenAnswer( (realInvocation) => const BorderSide(color: MockData.color0), ); final mockBuildContext = MockBuildContext(); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); scatterPainter.paint( mockBuildContext, mockCanvasWrapper, holder, ); verify(mockCanvasWrapper.drawDot(any, any, any)).called(3); Utils.changeInstance(utilsMainInstance); }); }); group('drawSpots()', () { test('test 1', () { const viewSize = Size(100, 100); final dotPainter1 = FlDotCirclePainter(radius: 18); final dotPainter3 = FlDotCirclePainter(radius: 4); final dotPainter4 = FlDotCirclePainter(radius: 6); final spot1 = ScatterSpot(1, 1, dotPainter: dotPainter1); final spot2 = ScatterSpot(3, 9, show: false); final spot3 = ScatterSpot(8, 2, dotPainter: dotPainter3); final spot4 = ScatterSpot(7, 5, dotPainter: dotPainter4); final data = ScatterChartData( minY: 0, maxY: 10, minX: 0, maxX: 10, scatterSpots: [spot1, spot2, spot3, spot4], titlesData: const FlTitlesData(show: false), clipData: const FlClipData.all(), ); final scatterChartPainter = ScatterChartPainter(); final holder = PaintHolder( data, data, TextScaler.noScaling, ); final mockBuildContext = MockBuildContext(); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenReturn(viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); scatterChartPainter.drawSpots( mockBuildContext, mockCanvasWrapper, holder, ); verify( mockCanvasWrapper.drawDot( dotPainter1, spot1, const Offset(10, 90), ), ).called(1); verify( mockCanvasWrapper.drawDot( dotPainter3, spot3, const Offset(80, 80), ), ).called(1); verify( mockCanvasWrapper.drawDot( dotPainter4, spot4, const Offset(70, 50), ), ).called(1); verifyNever(mockCanvasWrapper.drawText(any, any)); verify(mockCanvasWrapper.clipRect(any)).called(1); }); test('test 2', () { const viewSize = Size(100, 100); final data = ScatterChartData( minY: 0, maxY: 10, minX: 0, maxX: 10, scatterSpots: [ ScatterSpot(1, 1, show: false), ScatterSpot(3, 9, show: false), ScatterSpot(8, 2, show: false), ScatterSpot(7, 5, show: false), ], titlesData: const FlTitlesData(show: false), clipData: const FlClipData.none(), ); final scatterChartPainter = ScatterChartPainter(); final holder = PaintHolder( data, data, TextScaler.noScaling, ); final mockBuildContext = MockBuildContext(); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenReturn(viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); scatterChartPainter.drawSpots( mockBuildContext, mockCanvasWrapper, holder, ); verifyNever(mockCanvasWrapper.drawCircle(any, any, any)); verifyNever(mockCanvasWrapper.clipRect(any)); verifyNever(mockCanvasWrapper.drawText(any, any)); }); test('test 3', () { const viewSize = Size(100, 100); final data = ScatterChartData( minY: 0, maxY: 10, minX: 0, maxX: 10, scatterSpots: [ ScatterSpot(1, 1, dotPainter: FlDotCirclePainter(radius: 18)), ScatterSpot(2, 2, dotPainter: FlDotCirclePainter(radius: 8)), ScatterSpot(3, 9, show: false), ScatterSpot(8, 8, dotPainter: FlDotCirclePainter(radius: 4)), ScatterSpot(7, 5, dotPainter: FlDotCirclePainter(radius: 20)), ScatterSpot(4, 6, dotPainter: FlDotCirclePainter(radius: 24)), ], titlesData: const FlTitlesData(show: false), clipData: const FlClipData.all(), scatterLabelSettings: ScatterLabelSettings( showLabel: true, getLabelTextStyleFunction: (index, spot) => const TextStyle(fontSize: 12), getLabelFunction: (index, spot) { if (index == 5) { return ''; } return 'Label : $index'; }, ), ); final scatterChartPainter = ScatterChartPainter(); final holder = PaintHolder( data, data, TextScaler.noScaling, ); final mockBuildContext = MockBuildContext(); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenReturn(viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); final mockUtils = MockUtils(); Utils.changeInstance(mockUtils); when(mockUtils.getThemeAwareTextStyle(any, any)) .thenReturn(const TextStyle(color: Color(0x00ffffff))); when(mockUtils.calculateRotationOffset(any, any)).thenReturn(Offset.zero); scatterChartPainter.drawSpots( mockBuildContext, mockCanvasWrapper, holder, ); verify( mockCanvasWrapper.drawDot( any, data.scatterSpots[0], const Offset(10, 90), ), ).called(1); verify( mockCanvasWrapper.drawDot( any, data.scatterSpots[1], const Offset(20, 80), ), ).called(1); verify( mockCanvasWrapper.drawDot( any, data.scatterSpots[3], const Offset(80, 20), ), ).called(1); verify( mockCanvasWrapper.drawDot( any, data.scatterSpots[4], const Offset(70, 50), ), ).called(1); verify( mockCanvasWrapper.drawDot( any, data.scatterSpots[5], const Offset(40, 40), ), ).called(1); verify(mockCanvasWrapper.drawText(any, any)).called(4); verify(mockCanvasWrapper.clipRect(any)).called(1); }); }); group('drawTooltips()', () { test('test 1', () { const viewSize = Size(100, 100); final data = ScatterChartData( minY: 0, maxY: 10, minX: 0, maxX: 10, scatterSpots: [ ScatterSpot(1, 1, dotPainter: FlDotCirclePainter(radius: 18)), ScatterSpot(3, 9, show: false), ScatterSpot(8, 2, dotPainter: FlDotCirclePainter(radius: 4)), ScatterSpot(7, 5, dotPainter: FlDotCirclePainter(radius: 6)), ], showingTooltipIndicators: [0, 2, 3], titlesData: const FlTitlesData(show: false), ); final scatterChartPainter = ScatterChartPainter(); final holder = PaintHolder( data, data, TextScaler.noScaling, ); final mockCanvasWrapper = MockCanvasWrapper(); final mockBuildContext = MockBuildContext(); final mockUtils = MockUtils(); Utils.changeInstance(mockUtils); when(mockUtils.getThemeAwareTextStyle(any, any)) .thenReturn(const TextStyle(color: Color(0x00ffffff))); when(mockUtils.calculateRotationOffset(any, any)).thenReturn(Offset.zero); when(mockCanvasWrapper.size).thenReturn(viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); scatterChartPainter.drawTouchTooltips( mockBuildContext, mockCanvasWrapper, holder, ); verify( mockCanvasWrapper.drawRotated( size: anyNamed('size'), rotationOffset: anyNamed('rotationOffset'), drawOffset: anyNamed('drawOffset'), angle: anyNamed('angle'), drawCallback: anyNamed('drawCallback'), ), ).called(3); }); test('test 2', () { const viewSize = Size(100, 100); final data = ScatterChartData( minY: 0, maxY: 10, minX: 0, maxX: 10, scatterSpots: [ ScatterSpot(1, 1, dotPainter: FlDotCirclePainter(radius: 18)), ScatterSpot(3, 9, show: false), ScatterSpot(8, 2, dotPainter: FlDotCirclePainter(radius: 4)), ScatterSpot(7, 5, dotPainter: FlDotCirclePainter(radius: 6)), ], showingTooltipIndicators: [0, 2, 3], scatterTouchData: ScatterTouchData( touchTooltipData: ScatterTouchTooltipData( getTooltipItems: (spot) => null, ), ), titlesData: const FlTitlesData(show: false), ); final scatterChartPainter = ScatterChartPainter(); final holder = PaintHolder( data, data, TextScaler.noScaling, ); final mockCanvasWrapper = MockCanvasWrapper(); final mockBuildContext = MockBuildContext(); final mockUtils = MockUtils(); Utils.changeInstance(mockUtils); when(mockUtils.getThemeAwareTextStyle(any, any)) .thenReturn(const TextStyle(color: Color(0x00ffffff))); when(mockUtils.calculateRotationOffset(any, any)).thenReturn(Offset.zero); when(mockCanvasWrapper.size).thenReturn(viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); scatterChartPainter.drawTouchTooltips( mockBuildContext, mockCanvasWrapper, holder, ); verifyNever( mockCanvasWrapper.drawRotated( size: null, angle: null, drawCallback: () {}, ), ); verifyNever(mockCanvasWrapper.drawRect(any, any)); }); }); group('drawTouchTooltip()', () { test('test 1', () { const viewSize = Size(100, 100); final spot1 = ScatterSpot(1, 1); final data = ScatterChartData( minY: 0, maxY: 10, minX: 0, maxX: 10, scatterSpots: [ spot1, scatterSpot2, scatterSpot3, scatterSpot4, ], showingTooltipIndicators: [0, 2, 3], titlesData: const FlTitlesData(show: false), scatterTouchData: ScatterTouchData( touchTooltipData: ScatterTouchTooltipData( rotateAngle: 18, getTooltipColor: (touchedSpot) => const Color(0xFF00FF00), tooltipBorderRadius: const BorderRadius.only( topLeft: Radius.circular(85), topRight: Radius.circular(8), ), tooltipPadding: const EdgeInsets.all(12), getTooltipItems: (_) { return ScatterTooltipItem( 'faketext', textStyle: textStyle1, textAlign: TextAlign.left, textDirection: TextDirection.rtl, children: [ textSpan2, textSpan1, ], ); }, ), ), ); final scatterChartPainter = ScatterChartPainter(); final holder = PaintHolder( data, data, TextScaler.noScaling, ); final mockCanvasWrapper = MockCanvasWrapper(); final mockBuildContext = MockBuildContext(); final mockUtils = MockUtils(); Utils.changeInstance(mockUtils); when(mockUtils.getThemeAwareTextStyle(any, any)).thenReturn(textStyle2); when(mockUtils.calculateRotationOffset(any, any)).thenReturn(Offset.zero); when(mockCanvasWrapper.size).thenReturn(viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); scatterChartPainter.drawTouchTooltip( mockBuildContext, mockCanvasWrapper, data.scatterTouchData.touchTooltipData, spot1, holder, ); final verificationResult = verify( mockCanvasWrapper.drawRotated( size: anyNamed('size'), drawOffset: anyNamed('drawOffset'), angle: 18, drawCallback: captureAnyNamed('drawCallback'), ), ); final passedDrawCallback = verificationResult.captured.first as DrawCallback; passedDrawCallback(); verificationResult.called(1); final captured2 = verifyInOrder([ mockCanvasWrapper.drawRRect(captureAny, captureAny), mockCanvasWrapper.drawText(captureAny, any), ]).captured; final rRect = captured2[0][0] as RRect; final bgPaint = captured2[0][1] as Paint; final textPainter = captured2[1][0] as TextPainter; expect(rRect.blRadiusX, 0); expect(rRect.blRadiusY, 0); expect(rRect.tlRadiusY, 85); expect(rRect.trRadiusX, 8); expect(bgPaint.color, const Color(0xFF00FF00)); expect( textPainter.text, const TextSpan( style: textStyle2, text: 'faketext', children: [ textSpan2, textSpan1, ], ), ); }); test('test 2', () { const viewSize = Size(100, 100); final spot1 = ScatterSpot(1, 1); final data = ScatterChartData( minY: 0, maxY: 10, minX: 0, maxX: 10, scatterSpots: [ spot1, scatterSpot2, scatterSpot3, scatterSpot4, ], showingTooltipIndicators: [0, 2, 3], titlesData: const FlTitlesData(show: false), scatterTouchData: ScatterTouchData( touchTooltipData: ScatterTouchTooltipData( rotateAngle: 18, getTooltipColor: (touchedSpot) => const Color(0xFFFFFF00), tooltipBorderRadius: BorderRadius.circular(22), fitInsideHorizontally: false, fitInsideVertically: true, tooltipPadding: const EdgeInsets.all(12), tooltipHorizontalAlignment: FLHorizontalAlignment.left, getTooltipItems: (_) { return ScatterTooltipItem( 'faketext', textStyle: textStyle2, textAlign: TextAlign.right, textDirection: TextDirection.ltr, children: [ textSpan1, textSpan2, ], ); }, ), ), ); final scatterChartPainter = ScatterChartPainter(); final holder = PaintHolder( data, data, TextScaler.noScaling, ); final mockCanvasWrapper = MockCanvasWrapper(); final mockBuildContext = MockBuildContext(); final mockUtils = MockUtils(); Utils.changeInstance(mockUtils); when(mockUtils.getThemeAwareTextStyle(any, any)).thenReturn(textStyle1); when(mockUtils.calculateRotationOffset(any, any)).thenReturn(Offset.zero); when(mockCanvasWrapper.size).thenReturn(viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); scatterChartPainter.drawTouchTooltip( mockBuildContext, mockCanvasWrapper, data.scatterTouchData.touchTooltipData, spot1, holder, ); final verificationResult = verify( mockCanvasWrapper.drawRotated( size: anyNamed('size'), drawOffset: anyNamed('drawOffset'), angle: 18, drawCallback: captureAnyNamed('drawCallback'), ), ); final passedDrawCallback = verificationResult.captured.first as DrawCallback; passedDrawCallback(); verificationResult.called(1); final captured2 = verifyInOrder([ mockCanvasWrapper.drawRRect(captureAny, captureAny), mockCanvasWrapper.drawText(captureAny, any), ]).captured; final rRect = captured2[0][0] as RRect; final bgPaint = captured2[0][1] as Paint; final textPainter = captured2[1][0] as TextPainter; expect(rRect.blRadiusX, 22); expect(rRect.tlRadiusY, 22); expect(rRect.left, -134); expect(bgPaint.color, const Color(0xFFFFFF00)); expect( textPainter.text, const TextSpan( style: textStyle1, text: 'faketext', children: [ textSpan1, textSpan2, ], ), ); }); test('test 3', () { const viewSize = Size(100, 100); final spot1 = ScatterSpot(1, 1); final data = ScatterChartData( minY: 0, maxY: 10, minX: 0, maxX: 10, scatterSpots: [ spot1, scatterSpot2, scatterSpot3, scatterSpot4, ], showingTooltipIndicators: [0, 2, 3], titlesData: const FlTitlesData(show: false), scatterTouchData: ScatterTouchData( touchTooltipData: ScatterTouchTooltipData( rotateAngle: 18, getTooltipColor: (touchedSpot) => const Color(0xFFFFFF00), tooltipBorderRadius: BorderRadius.circular(22), fitInsideHorizontally: false, fitInsideVertically: true, tooltipPadding: const EdgeInsets.all(12), tooltipHorizontalAlignment: FLHorizontalAlignment.right, getTooltipItems: (_) { return ScatterTooltipItem( 'faketext', textStyle: textStyle2, textAlign: TextAlign.right, textDirection: TextDirection.ltr, children: [ textSpan1, textSpan2, ], ); }, ), ), ); final scatterChartPainter = ScatterChartPainter(); final holder = PaintHolder( data, data, TextScaler.noScaling, ); final mockCanvasWrapper = MockCanvasWrapper(); final mockBuildContext = MockBuildContext(); final mockUtils = MockUtils(); Utils.changeInstance(mockUtils); when(mockUtils.getThemeAwareTextStyle(any, any)).thenReturn(textStyle1); when(mockUtils.calculateRotationOffset(any, any)).thenReturn(Offset.zero); when(mockCanvasWrapper.size).thenReturn(viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); scatterChartPainter.drawTouchTooltip( mockBuildContext, mockCanvasWrapper, data.scatterTouchData.touchTooltipData, spot1, holder, ); final verificationResult = verify( mockCanvasWrapper.drawRotated( size: anyNamed('size'), drawOffset: anyNamed('drawOffset'), angle: 18, drawCallback: captureAnyNamed('drawCallback'), ), ); final passedDrawCallback = verificationResult.captured.first as DrawCallback; passedDrawCallback(); verificationResult.called(1); final captured2 = verifyInOrder([ mockCanvasWrapper.drawRRect(captureAny, captureAny), mockCanvasWrapper.drawText(captureAny, any), ]).captured; final rRect = captured2[0][0] as RRect; final bgPaint = captured2[0][1] as Paint; final textPainter = captured2[1][0] as TextPainter; expect(rRect.blRadiusX, 22); expect(rRect.tlRadiusY, 22); expect(rRect.left, 10); expect(bgPaint.color, const Color(0xFFFFFF00)); expect( textPainter.text, const TextSpan( style: textStyle1, text: 'faketext', children: [ textSpan1, textSpan2, ], ), ); }); }); group('handleTouch()', () { test('test 1', () { const viewSize = Size(100, 100); final spots = [ ScatterSpot(1, 1), ScatterSpot(2, 4), ScatterSpot(5, 2, dotPainter: FlDotCirclePainter(radius: 0.5)), ScatterSpot(8, 7), ]; final data = ScatterChartData( minY: 0, maxY: 10, minX: 0, maxX: 10, titlesData: const FlTitlesData(show: false), scatterSpots: spots, ); final scatterChartPainter = ScatterChartPainter(); final holder = PaintHolder( data, data, TextScaler.noScaling, ); final touchedSpot = scatterChartPainter.handleTouch( const Offset(10, 90), viewSize, holder, ); expect(touchedSpot!.spot, spots[0]); final touchedSpot2 = scatterChartPainter.handleTouch( const Offset(50, 80), viewSize, holder, ); expect(touchedSpot2!.spot, spots[2]); final touchedSpot3 = scatterChartPainter.handleTouch( const Offset(50.49, 80), viewSize, holder, ); expect(touchedSpot3!.spot, spots[2]); final touchedSpot4 = scatterChartPainter.handleTouch( const Offset(50.5, 80), viewSize, holder, ); expect(touchedSpot4, null); final radius = spots[2].size.width / 2; final touchedSpot5 = scatterChartPainter.handleTouch( Offset( 50 + (math.cos(math.pi / 4) * radius) - 0.01, 80 + (math.sin(math.pi / 4) * radius) - 0.01, ), viewSize, holder, ); expect(touchedSpot5!.spot, spots[2]); final touchedSpot6 = scatterChartPainter.handleTouch( Offset( 50 + (math.cos(math.pi / 4) * radius), 80 + (math.sin(math.pi / 4) * radius), ), viewSize, holder, ); expect(touchedSpot6, null); }); test('test 2', () { const viewSize = Size(100, 100); final spots = [ ScatterSpot(1, 1), ScatterSpot(2, 4), ScatterSpot(5, 2, dotPainter: FlDotCirclePainter(radius: 0.5)), ScatterSpot(8, 7), ]; final data = ScatterChartData( minY: 0, maxY: 10, minX: 0, maxX: 10, titlesData: const FlTitlesData( leftTitles: AxisTitles( axisNameSize: 4, axisNameWidget: Text('ss1'), sideTitles: SideTitles( showTitles: true, reservedSize: 10, ), ), rightTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: 10, ), ), topTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: 6, ), ), bottomTitles: AxisTitles( axisNameSize: 4, axisNameWidget: Text('ss2'), sideTitles: SideTitles( showTitles: true, reservedSize: 6, ), ), ), scatterSpots: spots, ); final scatterChartPainter = ScatterChartPainter(); final holder = PaintHolder( data, data, TextScaler.noScaling, ); final touchedSpot = scatterChartPainter.handleTouch( const Offset(10, 90), viewSize, holder, ); expect(touchedSpot!.spot, spots[0]); final touchedSpot2 = scatterChartPainter.handleTouch( const Offset(50, 80), viewSize, holder, ); expect(touchedSpot2!.spot, spots[2]); final touchedSpot3 = scatterChartPainter.handleTouch( const Offset(50.49, 80), viewSize, holder, ); expect(touchedSpot3!.spot, spots[2]); final touchedSpot4 = scatterChartPainter.handleTouch( const Offset(50.5, 80), viewSize, holder, ); expect(touchedSpot4, null); final radius = spots[2].size.width / 2; final touchedSpot5 = scatterChartPainter.handleTouch( Offset( 50 + (math.cos(math.pi / 4) * radius) - 0.01, 80 + (math.sin(math.pi / 4) * radius) - 0.01, ), viewSize, holder, ); expect(touchedSpot5!.spot, spots[2]); final touchedSpot6 = scatterChartPainter.handleTouch( Offset( 50 + (math.cos(math.pi / 4) * radius), 80 + (math.sin(math.pi / 4) * radius), ), viewSize, holder, ); expect(touchedSpot6, null); }); }); group('drawScatterErrorBars()', () { test('asymmetric yError maps lowerBy downward and upperBy upward', () { const viewSize = Size(400, 400); final data = ScatterChartData( minY: 0, maxY: 10, scatterSpots: [ ScatterSpot( 5, 5, yError: const FlErrorRange(lowerBy: 1, upperBy: 3), ), ], ); final scatterChartPainter = ScatterChartPainter(); final holder = PaintHolder(data, data, TextScaler.noScaling); final mockCanvasWrapper = MockCanvasWrapper(); when(mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); when(mockCanvasWrapper.canvas).thenReturn(MockCanvas()); scatterChartPainter.drawScatterErrorBars( mockCanvasWrapper, holder, ); final result = verify( mockCanvasWrapper.drawErrorIndicator(any, any, any, captureAny, any), )..called(1); final rect = result.captured[0] as Rect; // getPixelY(y) = 400 - (y/10)*400 // spot y=5 -> pixel 200 // upper bound y=8 -> pixel 80, relative top = 80 - 200 = -120 // lower bound y=4 -> pixel 240, relative bottom = 240 - 200 = 40 expect(rect.top, -120); expect(rect.bottom, 40); }); }); } ================================================ FILE: test/chart/scatter_chart/scatter_chart_painter_test.mocks.dart ================================================ // Mocks generated by Mockito 5.4.6 from annotations // in fl_chart/test/chart/scatter_chart/scatter_chart_painter_test.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:typed_data' as _i5; import 'dart:ui' as _i2; import 'package:fl_chart/fl_chart.dart' as _i7; import 'package:fl_chart/src/utils/canvas_wrapper.dart' as _i6; import 'package:fl_chart/src/utils/utils.dart' as _i8; import 'package:flutter/cupertino.dart' as _i3; import 'package:flutter/foundation.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; import 'package:mockito/src/dummies.dart' as _i9; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values // ignore_for_file: avoid_setters_without_getters // ignore_for_file: comment_references // ignore_for_file: deprecated_member_use // ignore_for_file: deprecated_member_use_from_same_package // ignore_for_file: implementation_imports // ignore_for_file: invalid_use_of_visible_for_testing_member // ignore_for_file: must_be_immutable // ignore_for_file: prefer_const_constructors // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class // ignore_for_file: invalid_use_of_internal_member class _FakeRect_0 extends _i1.SmartFake implements _i2.Rect { _FakeRect_0( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeCanvas_1 extends _i1.SmartFake implements _i2.Canvas { _FakeCanvas_1( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeSize_2 extends _i1.SmartFake implements _i2.Size { _FakeSize_2( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeWidget_3 extends _i1.SmartFake implements _i3.Widget { _FakeWidget_3( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info}) => super.toString(); } class _FakeInheritedWidget_4 extends _i1.SmartFake implements _i3.InheritedWidget { _FakeInheritedWidget_4( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info}) => super.toString(); } class _FakeDiagnosticsNode_5 extends _i1.SmartFake implements _i3.DiagnosticsNode { _FakeDiagnosticsNode_5( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({ _i4.TextTreeConfiguration? parentConfiguration, _i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info, }) => super.toString(); } class _FakeOffset_6 extends _i1.SmartFake implements _i2.Offset { _FakeOffset_6( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeBorderSide_7 extends _i1.SmartFake implements _i3.BorderSide { _FakeBorderSide_7( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info}) => super.toString(); } class _FakeTextStyle_8 extends _i1.SmartFake implements _i3.TextStyle { _FakeTextStyle_8( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info}) => super.toString(); } /// A class which mocks [Canvas]. /// /// See the documentation for Mockito's code generation for more information. class MockCanvas extends _i1.Mock implements _i2.Canvas { MockCanvas() { _i1.throwOnMissingStub(this); } @override void save() => super.noSuchMethod( Invocation.method( #save, [], ), returnValueForMissingStub: null, ); @override void saveLayer( _i2.Rect? bounds, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #saveLayer, [ bounds, paint, ], ), returnValueForMissingStub: null, ); @override void restore() => super.noSuchMethod( Invocation.method( #restore, [], ), returnValueForMissingStub: null, ); @override void restoreToCount(int? count) => super.noSuchMethod( Invocation.method( #restoreToCount, [count], ), returnValueForMissingStub: null, ); @override int getSaveCount() => (super.noSuchMethod( Invocation.method( #getSaveCount, [], ), returnValue: 0, ) as int); @override void translate( double? dx, double? dy, ) => super.noSuchMethod( Invocation.method( #translate, [ dx, dy, ], ), returnValueForMissingStub: null, ); @override void scale( double? sx, [ double? sy, ]) => super.noSuchMethod( Invocation.method( #scale, [ sx, sy, ], ), returnValueForMissingStub: null, ); @override void rotate(double? radians) => super.noSuchMethod( Invocation.method( #rotate, [radians], ), returnValueForMissingStub: null, ); @override void skew( double? sx, double? sy, ) => super.noSuchMethod( Invocation.method( #skew, [ sx, sy, ], ), returnValueForMissingStub: null, ); @override void transform(_i5.Float64List? matrix4) => super.noSuchMethod( Invocation.method( #transform, [matrix4], ), returnValueForMissingStub: null, ); @override _i5.Float64List getTransform() => (super.noSuchMethod( Invocation.method( #getTransform, [], ), returnValue: _i5.Float64List(0), ) as _i5.Float64List); @override void clipRect( _i2.Rect? rect, { _i2.ClipOp? clipOp = _i2.ClipOp.intersect, bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipRect, [rect], { #clipOp: clipOp, #doAntiAlias: doAntiAlias, }, ), returnValueForMissingStub: null, ); @override void clipRRect( _i2.RRect? rrect, { bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipRRect, [rrect], {#doAntiAlias: doAntiAlias}, ), returnValueForMissingStub: null, ); @override void clipRSuperellipse( _i2.RSuperellipse? rsuperellipse, { bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipRSuperellipse, [rsuperellipse], {#doAntiAlias: doAntiAlias}, ), returnValueForMissingStub: null, ); @override void clipPath( _i2.Path? path, { bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipPath, [path], {#doAntiAlias: doAntiAlias}, ), returnValueForMissingStub: null, ); @override _i2.Rect getLocalClipBounds() => (super.noSuchMethod( Invocation.method( #getLocalClipBounds, [], ), returnValue: _FakeRect_0( this, Invocation.method( #getLocalClipBounds, [], ), ), ) as _i2.Rect); @override _i2.Rect getDestinationClipBounds() => (super.noSuchMethod( Invocation.method( #getDestinationClipBounds, [], ), returnValue: _FakeRect_0( this, Invocation.method( #getDestinationClipBounds, [], ), ), ) as _i2.Rect); @override void drawColor( _i2.Color? color, _i2.BlendMode? blendMode, ) => super.noSuchMethod( Invocation.method( #drawColor, [ color, blendMode, ], ), returnValueForMissingStub: null, ); @override void drawLine( _i2.Offset? p1, _i2.Offset? p2, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawLine, [ p1, p2, paint, ], ), returnValueForMissingStub: null, ); @override void drawPaint(_i2.Paint? paint) => super.noSuchMethod( Invocation.method( #drawPaint, [paint], ), returnValueForMissingStub: null, ); @override void drawRect( _i2.Rect? rect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRect, [ rect, paint, ], ), returnValueForMissingStub: null, ); @override void drawRRect( _i2.RRect? rrect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRRect, [ rrect, paint, ], ), returnValueForMissingStub: null, ); @override void drawDRRect( _i2.RRect? outer, _i2.RRect? inner, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawDRRect, [ outer, inner, paint, ], ), returnValueForMissingStub: null, ); @override void drawRSuperellipse( _i2.RSuperellipse? rsuperellipse, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRSuperellipse, [ rsuperellipse, paint, ], ), returnValueForMissingStub: null, ); @override void drawOval( _i2.Rect? rect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawOval, [ rect, paint, ], ), returnValueForMissingStub: null, ); @override void drawCircle( _i2.Offset? c, double? radius, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawCircle, [ c, radius, paint, ], ), returnValueForMissingStub: null, ); @override void drawArc( _i2.Rect? rect, double? startAngle, double? sweepAngle, bool? useCenter, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawArc, [ rect, startAngle, sweepAngle, useCenter, paint, ], ), returnValueForMissingStub: null, ); @override void drawPath( _i2.Path? path, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawPath, [ path, paint, ], ), returnValueForMissingStub: null, ); @override void drawImage( _i2.Image? image, _i2.Offset? offset, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawImage, [ image, offset, paint, ], ), returnValueForMissingStub: null, ); @override void drawImageRect( _i2.Image? image, _i2.Rect? src, _i2.Rect? dst, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawImageRect, [ image, src, dst, paint, ], ), returnValueForMissingStub: null, ); @override void drawImageNine( _i2.Image? image, _i2.Rect? center, _i2.Rect? dst, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawImageNine, [ image, center, dst, paint, ], ), returnValueForMissingStub: null, ); @override void drawPicture(_i2.Picture? picture) => super.noSuchMethod( Invocation.method( #drawPicture, [picture], ), returnValueForMissingStub: null, ); @override void drawParagraph( _i2.Paragraph? paragraph, _i2.Offset? offset, ) => super.noSuchMethod( Invocation.method( #drawParagraph, [ paragraph, offset, ], ), returnValueForMissingStub: null, ); @override void drawPoints( _i2.PointMode? pointMode, List<_i2.Offset>? points, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawPoints, [ pointMode, points, paint, ], ), returnValueForMissingStub: null, ); @override void drawRawPoints( _i2.PointMode? pointMode, _i5.Float32List? points, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRawPoints, [ pointMode, points, paint, ], ), returnValueForMissingStub: null, ); @override void drawVertices( _i2.Vertices? vertices, _i2.BlendMode? blendMode, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawVertices, [ vertices, blendMode, paint, ], ), returnValueForMissingStub: null, ); @override void drawAtlas( _i2.Image? atlas, List<_i2.RSTransform>? transforms, List<_i2.Rect>? rects, List<_i2.Color>? colors, _i2.BlendMode? blendMode, _i2.Rect? cullRect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawAtlas, [ atlas, transforms, rects, colors, blendMode, cullRect, paint, ], ), returnValueForMissingStub: null, ); @override void drawRawAtlas( _i2.Image? atlas, _i5.Float32List? rstTransforms, _i5.Float32List? rects, _i5.Int32List? colors, _i2.BlendMode? blendMode, _i2.Rect? cullRect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRawAtlas, [ atlas, rstTransforms, rects, colors, blendMode, cullRect, paint, ], ), returnValueForMissingStub: null, ); @override void drawShadow( _i2.Path? path, _i2.Color? color, double? elevation, bool? transparentOccluder, ) => super.noSuchMethod( Invocation.method( #drawShadow, [ path, color, elevation, transparentOccluder, ], ), returnValueForMissingStub: null, ); } /// A class which mocks [CanvasWrapper]. /// /// See the documentation for Mockito's code generation for more information. class MockCanvasWrapper extends _i1.Mock implements _i6.CanvasWrapper { MockCanvasWrapper() { _i1.throwOnMissingStub(this); } @override _i2.Canvas get canvas => (super.noSuchMethod( Invocation.getter(#canvas), returnValue: _FakeCanvas_1( this, Invocation.getter(#canvas), ), ) as _i2.Canvas); @override _i2.Size get size => (super.noSuchMethod( Invocation.getter(#size), returnValue: _FakeSize_2( this, Invocation.getter(#size), ), ) as _i2.Size); @override void drawRRect( _i2.RRect? rrect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRRect, [ rrect, paint, ], ), returnValueForMissingStub: null, ); @override void save() => super.noSuchMethod( Invocation.method( #save, [], ), returnValueForMissingStub: null, ); @override void restore() => super.noSuchMethod( Invocation.method( #restore, [], ), returnValueForMissingStub: null, ); @override void clipRect( _i2.Rect? rect, { _i2.ClipOp? clipOp = _i2.ClipOp.intersect, bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipRect, [rect], { #clipOp: clipOp, #doAntiAlias: doAntiAlias, }, ), returnValueForMissingStub: null, ); @override void translate( double? dx, double? dy, ) => super.noSuchMethod( Invocation.method( #translate, [ dx, dy, ], ), returnValueForMissingStub: null, ); @override void rotate(double? radius) => super.noSuchMethod( Invocation.method( #rotate, [radius], ), returnValueForMissingStub: null, ); @override void drawPath( _i2.Path? path, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawPath, [ path, paint, ], ), returnValueForMissingStub: null, ); @override void saveLayer( _i2.Rect? bounds, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #saveLayer, [ bounds, paint, ], ), returnValueForMissingStub: null, ); @override void drawPicture(_i2.Picture? picture) => super.noSuchMethod( Invocation.method( #drawPicture, [picture], ), returnValueForMissingStub: null, ); @override void drawImage( _i2.Image? image, _i2.Offset? offset, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawImage, [ image, offset, paint, ], ), returnValueForMissingStub: null, ); @override void clipPath( _i2.Path? path, { bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipPath, [path], {#doAntiAlias: doAntiAlias}, ), returnValueForMissingStub: null, ); @override void drawRect( _i2.Rect? rect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRect, [ rect, paint, ], ), returnValueForMissingStub: null, ); @override void drawLine( _i2.Offset? p1, _i2.Offset? p2, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawLine, [ p1, p2, paint, ], ), returnValueForMissingStub: null, ); @override void drawCircle( _i2.Offset? center, double? radius, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawCircle, [ center, radius, paint, ], ), returnValueForMissingStub: null, ); @override void drawArc( _i2.Rect? rect, double? startAngle, double? sweepAngle, bool? useCenter, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawArc, [ rect, startAngle, sweepAngle, useCenter, paint, ], ), returnValueForMissingStub: null, ); @override void drawText( _i3.TextPainter? tp, _i2.Offset? offset, [ double? rotateAngle, ]) => super.noSuchMethod( Invocation.method( #drawText, [ tp, offset, rotateAngle, ], ), returnValueForMissingStub: null, ); @override void drawVerticalText( _i3.TextPainter? tp, _i2.Offset? offset, [ double? rotateAngle = 90.0, ]) => super.noSuchMethod( Invocation.method( #drawVerticalText, [ tp, offset, rotateAngle, ], ), returnValueForMissingStub: null, ); @override void drawDot( _i7.FlDotPainter? painter, _i7.FlSpot? spot, _i2.Offset? offset, ) => super.noSuchMethod( Invocation.method( #drawDot, [ painter, spot, offset, ], ), returnValueForMissingStub: null, ); @override void drawErrorIndicator( _i7.FlSpotErrorRangePainter? painter, _i7.FlSpot? origin, _i2.Offset? offset, _i2.Rect? errorRelativeRect, _i7.AxisChartData? axisData, ) => super.noSuchMethod( Invocation.method( #drawErrorIndicator, [ painter, origin, offset, errorRelativeRect, axisData, ], ), returnValueForMissingStub: null, ); @override void drawRotated({ required _i2.Size? size, _i2.Offset? rotationOffset = _i2.Offset.zero, _i2.Offset? drawOffset = _i2.Offset.zero, required double? angle, required _i6.DrawCallback? drawCallback, }) => super.noSuchMethod( Invocation.method( #drawRotated, [], { #size: size, #rotationOffset: rotationOffset, #drawOffset: drawOffset, #angle: angle, #drawCallback: drawCallback, }, ), returnValueForMissingStub: null, ); @override void drawDashedLine( _i2.Offset? from, _i2.Offset? to, _i2.Paint? painter, List? dashArray, ) => super.noSuchMethod( Invocation.method( #drawDashedLine, [ from, to, painter, dashArray, ], ), returnValueForMissingStub: null, ); } /// A class which mocks [BuildContext]. /// /// See the documentation for Mockito's code generation for more information. class MockBuildContext extends _i1.Mock implements _i3.BuildContext { MockBuildContext() { _i1.throwOnMissingStub(this); } @override _i3.Widget get widget => (super.noSuchMethod( Invocation.getter(#widget), returnValue: _FakeWidget_3( this, Invocation.getter(#widget), ), ) as _i3.Widget); @override bool get mounted => (super.noSuchMethod( Invocation.getter(#mounted), returnValue: false, ) as bool); @override bool get debugDoingBuild => (super.noSuchMethod( Invocation.getter(#debugDoingBuild), returnValue: false, ) as bool); @override _i3.InheritedWidget dependOnInheritedElement( _i3.InheritedElement? ancestor, { Object? aspect, }) => (super.noSuchMethod( Invocation.method( #dependOnInheritedElement, [ancestor], {#aspect: aspect}, ), returnValue: _FakeInheritedWidget_4( this, Invocation.method( #dependOnInheritedElement, [ancestor], {#aspect: aspect}, ), ), ) as _i3.InheritedWidget); @override void visitAncestorElements(_i3.ConditionalElementVisitor? visitor) => super.noSuchMethod( Invocation.method( #visitAncestorElements, [visitor], ), returnValueForMissingStub: null, ); @override void visitChildElements(_i3.ElementVisitor? visitor) => super.noSuchMethod( Invocation.method( #visitChildElements, [visitor], ), returnValueForMissingStub: null, ); @override void dispatchNotification(_i3.Notification? notification) => super.noSuchMethod( Invocation.method( #dispatchNotification, [notification], ), returnValueForMissingStub: null, ); @override _i3.DiagnosticsNode describeElement( String? name, { _i4.DiagnosticsTreeStyle? style = _i4.DiagnosticsTreeStyle.errorProperty, }) => (super.noSuchMethod( Invocation.method( #describeElement, [name], {#style: style}, ), returnValue: _FakeDiagnosticsNode_5( this, Invocation.method( #describeElement, [name], {#style: style}, ), ), ) as _i3.DiagnosticsNode); @override _i3.DiagnosticsNode describeWidget( String? name, { _i4.DiagnosticsTreeStyle? style = _i4.DiagnosticsTreeStyle.errorProperty, }) => (super.noSuchMethod( Invocation.method( #describeWidget, [name], {#style: style}, ), returnValue: _FakeDiagnosticsNode_5( this, Invocation.method( #describeWidget, [name], {#style: style}, ), ), ) as _i3.DiagnosticsNode); @override List<_i3.DiagnosticsNode> describeMissingAncestor( {required Type? expectedAncestorType}) => (super.noSuchMethod( Invocation.method( #describeMissingAncestor, [], {#expectedAncestorType: expectedAncestorType}, ), returnValue: <_i3.DiagnosticsNode>[], ) as List<_i3.DiagnosticsNode>); @override _i3.DiagnosticsNode describeOwnershipChain(String? name) => (super.noSuchMethod( Invocation.method( #describeOwnershipChain, [name], ), returnValue: _FakeDiagnosticsNode_5( this, Invocation.method( #describeOwnershipChain, [name], ), ), ) as _i3.DiagnosticsNode); } /// A class which mocks [Utils]. /// /// See the documentation for Mockito's code generation for more information. class MockUtils extends _i1.Mock implements _i8.Utils { MockUtils() { _i1.throwOnMissingStub(this); } @override double radians(double? degrees) => (super.noSuchMethod( Invocation.method( #radians, [degrees], ), returnValue: 0.0, ) as double); @override double degrees(double? radians) => (super.noSuchMethod( Invocation.method( #degrees, [radians], ), returnValue: 0.0, ) as double); @override double translateRotatedPosition( double? size, double? degree, ) => (super.noSuchMethod( Invocation.method( #translateRotatedPosition, [ size, degree, ], ), returnValue: 0.0, ) as double); @override _i2.Offset calculateRotationOffset( _i2.Size? size, double? degree, ) => (super.noSuchMethod( Invocation.method( #calculateRotationOffset, [ size, degree, ], ), returnValue: _FakeOffset_6( this, Invocation.method( #calculateRotationOffset, [ size, degree, ], ), ), ) as _i2.Offset); @override _i3.BorderRadius? normalizeBorderRadius( _i3.BorderRadius? borderRadius, double? width, ) => (super.noSuchMethod(Invocation.method( #normalizeBorderRadius, [ borderRadius, width, ], )) as _i3.BorderRadius?); @override _i3.BorderSide normalizeBorderSide( _i3.BorderSide? borderSide, double? width, ) => (super.noSuchMethod( Invocation.method( #normalizeBorderSide, [ borderSide, width, ], ), returnValue: _FakeBorderSide_7( this, Invocation.method( #normalizeBorderSide, [ borderSide, width, ], ), ), ) as _i3.BorderSide); @override double getEfficientInterval( double? axisViewSize, double? diffInAxis, { double? pixelPerInterval = 40.0, }) => (super.noSuchMethod( Invocation.method( #getEfficientInterval, [ axisViewSize, diffInAxis, ], {#pixelPerInterval: pixelPerInterval}, ), returnValue: 0.0, ) as double); @override double roundInterval(double? input) => (super.noSuchMethod( Invocation.method( #roundInterval, [input], ), returnValue: 0.0, ) as double); @override int getFractionDigits(double? value) => (super.noSuchMethod( Invocation.method( #getFractionDigits, [value], ), returnValue: 0, ) as int); @override String formatNumber( double? axisMin, double? axisMax, double? axisValue, ) => (super.noSuchMethod( Invocation.method( #formatNumber, [ axisMin, axisMax, axisValue, ], ), returnValue: _i9.dummyValue( this, Invocation.method( #formatNumber, [ axisMin, axisMax, axisValue, ], ), ), ) as String); @override _i3.TextStyle getThemeAwareTextStyle( _i3.BuildContext? context, _i3.TextStyle? providedStyle, ) => (super.noSuchMethod( Invocation.method( #getThemeAwareTextStyle, [ context, providedStyle, ], ), returnValue: _FakeTextStyle_8( this, Invocation.method( #getThemeAwareTextStyle, [ context, providedStyle, ], ), ), ) as _i3.TextStyle); @override double getBestInitialIntervalValue( double? min, double? max, double? interval, { double? baseline = 0.0, }) => (super.noSuchMethod( Invocation.method( #getBestInitialIntervalValue, [ min, max, interval, ], {#baseline: baseline}, ), returnValue: 0.0, ) as double); @override double convertRadiusToSigma(double? radius) => (super.noSuchMethod( Invocation.method( #convertRadiusToSigma, [radius], ), returnValue: 0.0, ) as double); } ================================================ FILE: test/chart/scatter_chart/scatter_chart_renderer_test.dart ================================================ import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart'; import 'package:fl_chart/src/chart/scatter_chart/scatter_chart_painter.dart'; import 'package:fl_chart/src/chart/scatter_chart/scatter_chart_renderer.dart'; import 'package:fl_chart/src/utils/canvas_wrapper.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import '../data_pool.dart'; import 'scatter_chart_renderer_test.mocks.dart'; @GenerateMocks([Canvas, PaintingContext, BuildContext, ScatterChartPainter]) void main() { group('ScatterChartRenderer', () { final data = ScatterChartData( scatterSpots: [MockData.scatterSpot1, MockData.scatterSpot2], ); final targetData = ScatterChartData( scatterSpots: [MockData.scatterSpot3], scatterTouchData: ScatterTouchData(enabled: false), ); const textScaler = TextScaler.linear(4); final mockBuildContext = MockBuildContext(); final renderScatterChart = RenderScatterChart( mockBuildContext, data, targetData, textScaler, null, canBeScaled: false, ); final mockPainter = MockScatterChartPainter(); final mockPaintingContext = MockPaintingContext(); final mockCanvas = MockCanvas(); const mockSize = Size(44, 44); when(mockPaintingContext.canvas).thenAnswer((realInvocation) => mockCanvas); renderScatterChart ..mockTestSize = mockSize ..painter = mockPainter; test('test 1 correct data set', () { expect(renderScatterChart.data == data, true); expect(renderScatterChart.data == targetData, false); expect(renderScatterChart.targetData == targetData, true); expect(renderScatterChart.textScaler == textScaler, true); expect(renderScatterChart.paintHolder.data == data, true); expect(renderScatterChart.paintHolder.targetData == targetData, true); expect(renderScatterChart.paintHolder.textScaler == textScaler, true); expect(renderScatterChart.hitTestSelf(Offset.zero), false); }); test('test 2 check paint function', () { renderScatterChart.paint(mockPaintingContext, const Offset(10, 10)); verify(mockCanvas.save()).called(1); verify(mockCanvas.translate(10, 10)).called(1); final result = verify(mockPainter.paint(any, captureAny, captureAny)); expect(result.callCount, 1); final canvasWrapper = result.captured[0] as CanvasWrapper; expect(canvasWrapper.size, const Size(44, 44)); expect(canvasWrapper.canvas, mockCanvas); final paintHolder = result.captured[1] as PaintHolder; expect(paintHolder.data, data); expect(paintHolder.targetData, targetData); expect(paintHolder.textScaler, textScaler); verify(mockCanvas.restore()).called(1); }); test('test 3 check getResponseAtLocation function', () { final results = >[]; when(mockPainter.handleTouch(captureAny, captureAny, captureAny)) .thenAnswer((inv) { results.add({ 'local_position': inv.positionalArguments[0] as Offset, 'size': inv.positionalArguments[1] as Size, 'paint_holder': inv.positionalArguments[2] as PaintHolder, }); return MockData.scatterTouchedSpot; }); when(mockPainter.getChartCoordinateFromPixel(any, any, any)) .thenAnswer((_) => const Offset(10, 10)); final touchResponse = renderScatterChart.getResponseAtLocation(MockData.offset1); expect(touchResponse.touchedSpot, MockData.scatterTouchedSpot); expect(touchResponse.touchChartCoordinate, const Offset(10, 10)); expect(results[0]['local_position'] as Offset, MockData.offset1); expect(results[0]['size'] as Size, mockSize); final paintHolder = results[0]['paint_holder'] as PaintHolder; expect(paintHolder.data, data); expect(paintHolder.targetData, targetData); expect(paintHolder.textScaler, textScaler); }); test('test 4 check setters', () { renderScatterChart ..data = targetData ..targetData = data ..textScaler = const TextScaler.linear(22); expect(renderScatterChart.data, targetData); expect(renderScatterChart.targetData, data); expect(renderScatterChart.textScaler, const TextScaler.linear(22)); }); test('passes chart virtual rect to paint holder', () { final rect1 = Offset.zero & const Size(100, 100); final renderScatterChart = RenderScatterChart( mockBuildContext, data, targetData, textScaler, null, canBeScaled: false, ); expect(renderScatterChart.chartVirtualRect, isNull); expect(renderScatterChart.paintHolder.chartVirtualRect, isNull); renderScatterChart.chartVirtualRect = rect1; expect(renderScatterChart.chartVirtualRect, rect1); expect(renderScatterChart.paintHolder.chartVirtualRect, rect1); }); test('uses canBeScaled', () { final renderScatterChart = RenderScatterChart( mockBuildContext, data, targetData, textScaler, null, canBeScaled: false, ); expect(renderScatterChart.canBeScaled, false); renderScatterChart.canBeScaled = true; expect(renderScatterChart.canBeScaled, true); }); }); } ================================================ FILE: test/chart/scatter_chart/scatter_chart_renderer_test.mocks.dart ================================================ // Mocks generated by Mockito 5.4.6 from annotations // in fl_chart/test/chart/scatter_chart/scatter_chart_renderer_test.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:typed_data' as _i7; import 'dart:ui' as _i2; import 'package:fl_chart/fl_chart.dart' as _i13; import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart' as _i12; import 'package:fl_chart/src/chart/scatter_chart/scatter_chart_painter.dart' as _i10; import 'package:fl_chart/src/utils/canvas_wrapper.dart' as _i11; import 'package:flutter/foundation.dart' as _i5; import 'package:flutter/gestures.dart' as _i8; import 'package:flutter/material.dart' as _i6; import 'package:flutter/rendering.dart' as _i3; import 'package:flutter/src/rendering/layer.dart' as _i4; import 'package:flutter/src/widgets/notification_listener.dart' as _i9; import 'package:mockito/mockito.dart' as _i1; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values // ignore_for_file: avoid_setters_without_getters // ignore_for_file: comment_references // ignore_for_file: deprecated_member_use // ignore_for_file: deprecated_member_use_from_same_package // ignore_for_file: implementation_imports // ignore_for_file: invalid_use_of_visible_for_testing_member // ignore_for_file: must_be_immutable // ignore_for_file: prefer_const_constructors // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class // ignore_for_file: invalid_use_of_internal_member class _FakeRect_0 extends _i1.SmartFake implements _i2.Rect { _FakeRect_0( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeCanvas_1 extends _i1.SmartFake implements _i2.Canvas { _FakeCanvas_1( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakePaintingContext_2 extends _i1.SmartFake implements _i3.PaintingContext { _FakePaintingContext_2( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeColorFilterLayer_3 extends _i1.SmartFake implements _i4.ColorFilterLayer { _FakeColorFilterLayer_3( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => super.toString(); } class _FakeOpacityLayer_4 extends _i1.SmartFake implements _i4.OpacityLayer { _FakeOpacityLayer_4( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => super.toString(); } class _FakeWidget_5 extends _i1.SmartFake implements _i6.Widget { _FakeWidget_5( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => super.toString(); } class _FakeInheritedWidget_6 extends _i1.SmartFake implements _i6.InheritedWidget { _FakeInheritedWidget_6( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => super.toString(); } class _FakeDiagnosticsNode_7 extends _i1.SmartFake implements _i5.DiagnosticsNode { _FakeDiagnosticsNode_7( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({ _i5.TextTreeConfiguration? parentConfiguration, _i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info, }) => super.toString(); } class _FakeOffset_8 extends _i1.SmartFake implements _i2.Offset { _FakeOffset_8( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } /// A class which mocks [Canvas]. /// /// See the documentation for Mockito's code generation for more information. class MockCanvas extends _i1.Mock implements _i2.Canvas { MockCanvas() { _i1.throwOnMissingStub(this); } @override void save() => super.noSuchMethod( Invocation.method( #save, [], ), returnValueForMissingStub: null, ); @override void saveLayer( _i2.Rect? bounds, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #saveLayer, [ bounds, paint, ], ), returnValueForMissingStub: null, ); @override void restore() => super.noSuchMethod( Invocation.method( #restore, [], ), returnValueForMissingStub: null, ); @override void restoreToCount(int? count) => super.noSuchMethod( Invocation.method( #restoreToCount, [count], ), returnValueForMissingStub: null, ); @override int getSaveCount() => (super.noSuchMethod( Invocation.method( #getSaveCount, [], ), returnValue: 0, ) as int); @override void translate( double? dx, double? dy, ) => super.noSuchMethod( Invocation.method( #translate, [ dx, dy, ], ), returnValueForMissingStub: null, ); @override void scale( double? sx, [ double? sy, ]) => super.noSuchMethod( Invocation.method( #scale, [ sx, sy, ], ), returnValueForMissingStub: null, ); @override void rotate(double? radians) => super.noSuchMethod( Invocation.method( #rotate, [radians], ), returnValueForMissingStub: null, ); @override void skew( double? sx, double? sy, ) => super.noSuchMethod( Invocation.method( #skew, [ sx, sy, ], ), returnValueForMissingStub: null, ); @override void transform(_i7.Float64List? matrix4) => super.noSuchMethod( Invocation.method( #transform, [matrix4], ), returnValueForMissingStub: null, ); @override _i7.Float64List getTransform() => (super.noSuchMethod( Invocation.method( #getTransform, [], ), returnValue: _i7.Float64List(0), ) as _i7.Float64List); @override void clipRect( _i2.Rect? rect, { _i2.ClipOp? clipOp = _i2.ClipOp.intersect, bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipRect, [rect], { #clipOp: clipOp, #doAntiAlias: doAntiAlias, }, ), returnValueForMissingStub: null, ); @override void clipRRect( _i2.RRect? rrect, { bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipRRect, [rrect], {#doAntiAlias: doAntiAlias}, ), returnValueForMissingStub: null, ); @override void clipRSuperellipse( _i2.RSuperellipse? rsuperellipse, { bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipRSuperellipse, [rsuperellipse], {#doAntiAlias: doAntiAlias}, ), returnValueForMissingStub: null, ); @override void clipPath( _i2.Path? path, { bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipPath, [path], {#doAntiAlias: doAntiAlias}, ), returnValueForMissingStub: null, ); @override _i2.Rect getLocalClipBounds() => (super.noSuchMethod( Invocation.method( #getLocalClipBounds, [], ), returnValue: _FakeRect_0( this, Invocation.method( #getLocalClipBounds, [], ), ), ) as _i2.Rect); @override _i2.Rect getDestinationClipBounds() => (super.noSuchMethod( Invocation.method( #getDestinationClipBounds, [], ), returnValue: _FakeRect_0( this, Invocation.method( #getDestinationClipBounds, [], ), ), ) as _i2.Rect); @override void drawColor( _i2.Color? color, _i2.BlendMode? blendMode, ) => super.noSuchMethod( Invocation.method( #drawColor, [ color, blendMode, ], ), returnValueForMissingStub: null, ); @override void drawLine( _i2.Offset? p1, _i2.Offset? p2, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawLine, [ p1, p2, paint, ], ), returnValueForMissingStub: null, ); @override void drawPaint(_i2.Paint? paint) => super.noSuchMethod( Invocation.method( #drawPaint, [paint], ), returnValueForMissingStub: null, ); @override void drawRect( _i2.Rect? rect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRect, [ rect, paint, ], ), returnValueForMissingStub: null, ); @override void drawRRect( _i2.RRect? rrect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRRect, [ rrect, paint, ], ), returnValueForMissingStub: null, ); @override void drawDRRect( _i2.RRect? outer, _i2.RRect? inner, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawDRRect, [ outer, inner, paint, ], ), returnValueForMissingStub: null, ); @override void drawRSuperellipse( _i2.RSuperellipse? rsuperellipse, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRSuperellipse, [ rsuperellipse, paint, ], ), returnValueForMissingStub: null, ); @override void drawOval( _i2.Rect? rect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawOval, [ rect, paint, ], ), returnValueForMissingStub: null, ); @override void drawCircle( _i2.Offset? c, double? radius, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawCircle, [ c, radius, paint, ], ), returnValueForMissingStub: null, ); @override void drawArc( _i2.Rect? rect, double? startAngle, double? sweepAngle, bool? useCenter, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawArc, [ rect, startAngle, sweepAngle, useCenter, paint, ], ), returnValueForMissingStub: null, ); @override void drawPath( _i2.Path? path, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawPath, [ path, paint, ], ), returnValueForMissingStub: null, ); @override void drawImage( _i2.Image? image, _i2.Offset? offset, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawImage, [ image, offset, paint, ], ), returnValueForMissingStub: null, ); @override void drawImageRect( _i2.Image? image, _i2.Rect? src, _i2.Rect? dst, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawImageRect, [ image, src, dst, paint, ], ), returnValueForMissingStub: null, ); @override void drawImageNine( _i2.Image? image, _i2.Rect? center, _i2.Rect? dst, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawImageNine, [ image, center, dst, paint, ], ), returnValueForMissingStub: null, ); @override void drawPicture(_i2.Picture? picture) => super.noSuchMethod( Invocation.method( #drawPicture, [picture], ), returnValueForMissingStub: null, ); @override void drawParagraph( _i2.Paragraph? paragraph, _i2.Offset? offset, ) => super.noSuchMethod( Invocation.method( #drawParagraph, [ paragraph, offset, ], ), returnValueForMissingStub: null, ); @override void drawPoints( _i2.PointMode? pointMode, List<_i2.Offset>? points, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawPoints, [ pointMode, points, paint, ], ), returnValueForMissingStub: null, ); @override void drawRawPoints( _i2.PointMode? pointMode, _i7.Float32List? points, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRawPoints, [ pointMode, points, paint, ], ), returnValueForMissingStub: null, ); @override void drawVertices( _i2.Vertices? vertices, _i2.BlendMode? blendMode, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawVertices, [ vertices, blendMode, paint, ], ), returnValueForMissingStub: null, ); @override void drawAtlas( _i2.Image? atlas, List<_i2.RSTransform>? transforms, List<_i2.Rect>? rects, List<_i2.Color>? colors, _i2.BlendMode? blendMode, _i2.Rect? cullRect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawAtlas, [ atlas, transforms, rects, colors, blendMode, cullRect, paint, ], ), returnValueForMissingStub: null, ); @override void drawRawAtlas( _i2.Image? atlas, _i7.Float32List? rstTransforms, _i7.Float32List? rects, _i7.Int32List? colors, _i2.BlendMode? blendMode, _i2.Rect? cullRect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRawAtlas, [ atlas, rstTransforms, rects, colors, blendMode, cullRect, paint, ], ), returnValueForMissingStub: null, ); @override void drawShadow( _i2.Path? path, _i2.Color? color, double? elevation, bool? transparentOccluder, ) => super.noSuchMethod( Invocation.method( #drawShadow, [ path, color, elevation, transparentOccluder, ], ), returnValueForMissingStub: null, ); } /// A class which mocks [PaintingContext]. /// /// See the documentation for Mockito's code generation for more information. class MockPaintingContext extends _i1.Mock implements _i3.PaintingContext { MockPaintingContext() { _i1.throwOnMissingStub(this); } @override _i2.Rect get estimatedBounds => (super.noSuchMethod( Invocation.getter(#estimatedBounds), returnValue: _FakeRect_0( this, Invocation.getter(#estimatedBounds), ), ) as _i2.Rect); @override _i2.Canvas get canvas => (super.noSuchMethod( Invocation.getter(#canvas), returnValue: _FakeCanvas_1( this, Invocation.getter(#canvas), ), ) as _i2.Canvas); @override void paintChild( _i3.RenderObject? child, _i2.Offset? offset, ) => super.noSuchMethod( Invocation.method( #paintChild, [ child, offset, ], ), returnValueForMissingStub: null, ); @override void appendLayer(_i4.Layer? layer) => super.noSuchMethod( Invocation.method( #appendLayer, [layer], ), returnValueForMissingStub: null, ); @override _i2.VoidCallback addCompositionCallback(_i4.CompositionCallback? callback) => (super.noSuchMethod( Invocation.method( #addCompositionCallback, [callback], ), returnValue: () {}, ) as _i2.VoidCallback); @override void stopRecordingIfNeeded() => super.noSuchMethod( Invocation.method( #stopRecordingIfNeeded, [], ), returnValueForMissingStub: null, ); @override void setIsComplexHint() => super.noSuchMethod( Invocation.method( #setIsComplexHint, [], ), returnValueForMissingStub: null, ); @override void setWillChangeHint() => super.noSuchMethod( Invocation.method( #setWillChangeHint, [], ), returnValueForMissingStub: null, ); @override void addLayer(_i4.Layer? layer) => super.noSuchMethod( Invocation.method( #addLayer, [layer], ), returnValueForMissingStub: null, ); @override void pushLayer( _i4.ContainerLayer? childLayer, _i3.PaintingContextCallback? painter, _i2.Offset? offset, { _i2.Rect? childPaintBounds, }) => super.noSuchMethod( Invocation.method( #pushLayer, [ childLayer, painter, offset, ], {#childPaintBounds: childPaintBounds}, ), returnValueForMissingStub: null, ); @override _i3.PaintingContext createChildContext( _i4.ContainerLayer? childLayer, _i2.Rect? bounds, ) => (super.noSuchMethod( Invocation.method( #createChildContext, [ childLayer, bounds, ], ), returnValue: _FakePaintingContext_2( this, Invocation.method( #createChildContext, [ childLayer, bounds, ], ), ), ) as _i3.PaintingContext); @override _i4.ClipRectLayer? pushClipRect( bool? needsCompositing, _i2.Offset? offset, _i2.Rect? clipRect, _i3.PaintingContextCallback? painter, { _i2.Clip? clipBehavior = _i2.Clip.hardEdge, _i4.ClipRectLayer? oldLayer, }) => (super.noSuchMethod(Invocation.method( #pushClipRect, [ needsCompositing, offset, clipRect, painter, ], { #clipBehavior: clipBehavior, #oldLayer: oldLayer, }, )) as _i4.ClipRectLayer?); @override _i4.ClipRRectLayer? pushClipRRect( bool? needsCompositing, _i2.Offset? offset, _i2.Rect? bounds, _i2.RRect? clipRRect, _i3.PaintingContextCallback? painter, { _i2.Clip? clipBehavior = _i2.Clip.antiAlias, _i4.ClipRRectLayer? oldLayer, }) => (super.noSuchMethod(Invocation.method( #pushClipRRect, [ needsCompositing, offset, bounds, clipRRect, painter, ], { #clipBehavior: clipBehavior, #oldLayer: oldLayer, }, )) as _i4.ClipRRectLayer?); @override _i4.ClipRSuperellipseLayer? pushClipRSuperellipse( bool? needsCompositing, _i2.Offset? offset, _i2.Rect? bounds, _i2.RSuperellipse? clipRSuperellipse, _i3.PaintingContextCallback? painter, { _i2.Clip? clipBehavior = _i2.Clip.antiAlias, _i4.ClipRSuperellipseLayer? oldLayer, }) => (super.noSuchMethod(Invocation.method( #pushClipRSuperellipse, [ needsCompositing, offset, bounds, clipRSuperellipse, painter, ], { #clipBehavior: clipBehavior, #oldLayer: oldLayer, }, )) as _i4.ClipRSuperellipseLayer?); @override _i4.ClipPathLayer? pushClipPath( bool? needsCompositing, _i2.Offset? offset, _i2.Rect? bounds, _i2.Path? clipPath, _i3.PaintingContextCallback? painter, { _i2.Clip? clipBehavior = _i2.Clip.antiAlias, _i4.ClipPathLayer? oldLayer, }) => (super.noSuchMethod(Invocation.method( #pushClipPath, [ needsCompositing, offset, bounds, clipPath, painter, ], { #clipBehavior: clipBehavior, #oldLayer: oldLayer, }, )) as _i4.ClipPathLayer?); @override _i4.ColorFilterLayer pushColorFilter( _i2.Offset? offset, _i2.ColorFilter? colorFilter, _i3.PaintingContextCallback? painter, { _i4.ColorFilterLayer? oldLayer, }) => (super.noSuchMethod( Invocation.method( #pushColorFilter, [ offset, colorFilter, painter, ], {#oldLayer: oldLayer}, ), returnValue: _FakeColorFilterLayer_3( this, Invocation.method( #pushColorFilter, [ offset, colorFilter, painter, ], {#oldLayer: oldLayer}, ), ), ) as _i4.ColorFilterLayer); @override _i4.TransformLayer? pushTransform( bool? needsCompositing, _i2.Offset? offset, _i8.Matrix4? transform, _i3.PaintingContextCallback? painter, { _i4.TransformLayer? oldLayer, }) => (super.noSuchMethod(Invocation.method( #pushTransform, [ needsCompositing, offset, transform, painter, ], {#oldLayer: oldLayer}, )) as _i4.TransformLayer?); @override _i4.OpacityLayer pushOpacity( _i2.Offset? offset, int? alpha, _i3.PaintingContextCallback? painter, { _i4.OpacityLayer? oldLayer, }) => (super.noSuchMethod( Invocation.method( #pushOpacity, [ offset, alpha, painter, ], {#oldLayer: oldLayer}, ), returnValue: _FakeOpacityLayer_4( this, Invocation.method( #pushOpacity, [ offset, alpha, painter, ], {#oldLayer: oldLayer}, ), ), ) as _i4.OpacityLayer); @override void clipPathAndPaint( _i2.Path? path, _i2.Clip? clipBehavior, _i2.Rect? bounds, _i2.VoidCallback? painter, ) => super.noSuchMethod( Invocation.method( #clipPathAndPaint, [ path, clipBehavior, bounds, painter, ], ), returnValueForMissingStub: null, ); @override void clipRRectAndPaint( _i2.RRect? rrect, _i2.Clip? clipBehavior, _i2.Rect? bounds, _i2.VoidCallback? painter, ) => super.noSuchMethod( Invocation.method( #clipRRectAndPaint, [ rrect, clipBehavior, bounds, painter, ], ), returnValueForMissingStub: null, ); @override void clipRSuperellipseAndPaint( _i2.RSuperellipse? rse, _i2.Clip? clipBehavior, _i2.Rect? bounds, _i2.VoidCallback? painter, ) => super.noSuchMethod( Invocation.method( #clipRSuperellipseAndPaint, [ rse, clipBehavior, bounds, painter, ], ), returnValueForMissingStub: null, ); @override void clipRectAndPaint( _i2.Rect? rect, _i2.Clip? clipBehavior, _i2.Rect? bounds, _i2.VoidCallback? painter, ) => super.noSuchMethod( Invocation.method( #clipRectAndPaint, [ rect, clipBehavior, bounds, painter, ], ), returnValueForMissingStub: null, ); } /// A class which mocks [BuildContext]. /// /// See the documentation for Mockito's code generation for more information. class MockBuildContext extends _i1.Mock implements _i6.BuildContext { MockBuildContext() { _i1.throwOnMissingStub(this); } @override _i6.Widget get widget => (super.noSuchMethod( Invocation.getter(#widget), returnValue: _FakeWidget_5( this, Invocation.getter(#widget), ), ) as _i6.Widget); @override bool get mounted => (super.noSuchMethod( Invocation.getter(#mounted), returnValue: false, ) as bool); @override bool get debugDoingBuild => (super.noSuchMethod( Invocation.getter(#debugDoingBuild), returnValue: false, ) as bool); @override _i6.InheritedWidget dependOnInheritedElement( _i6.InheritedElement? ancestor, { Object? aspect, }) => (super.noSuchMethod( Invocation.method( #dependOnInheritedElement, [ancestor], {#aspect: aspect}, ), returnValue: _FakeInheritedWidget_6( this, Invocation.method( #dependOnInheritedElement, [ancestor], {#aspect: aspect}, ), ), ) as _i6.InheritedWidget); @override void visitAncestorElements(_i6.ConditionalElementVisitor? visitor) => super.noSuchMethod( Invocation.method( #visitAncestorElements, [visitor], ), returnValueForMissingStub: null, ); @override void visitChildElements(_i6.ElementVisitor? visitor) => super.noSuchMethod( Invocation.method( #visitChildElements, [visitor], ), returnValueForMissingStub: null, ); @override void dispatchNotification(_i9.Notification? notification) => super.noSuchMethod( Invocation.method( #dispatchNotification, [notification], ), returnValueForMissingStub: null, ); @override _i5.DiagnosticsNode describeElement( String? name, { _i5.DiagnosticsTreeStyle? style = _i5.DiagnosticsTreeStyle.errorProperty, }) => (super.noSuchMethod( Invocation.method( #describeElement, [name], {#style: style}, ), returnValue: _FakeDiagnosticsNode_7( this, Invocation.method( #describeElement, [name], {#style: style}, ), ), ) as _i5.DiagnosticsNode); @override _i5.DiagnosticsNode describeWidget( String? name, { _i5.DiagnosticsTreeStyle? style = _i5.DiagnosticsTreeStyle.errorProperty, }) => (super.noSuchMethod( Invocation.method( #describeWidget, [name], {#style: style}, ), returnValue: _FakeDiagnosticsNode_7( this, Invocation.method( #describeWidget, [name], {#style: style}, ), ), ) as _i5.DiagnosticsNode); @override List<_i5.DiagnosticsNode> describeMissingAncestor( {required Type? expectedAncestorType}) => (super.noSuchMethod( Invocation.method( #describeMissingAncestor, [], {#expectedAncestorType: expectedAncestorType}, ), returnValue: <_i5.DiagnosticsNode>[], ) as List<_i5.DiagnosticsNode>); @override _i5.DiagnosticsNode describeOwnershipChain(String? name) => (super.noSuchMethod( Invocation.method( #describeOwnershipChain, [name], ), returnValue: _FakeDiagnosticsNode_7( this, Invocation.method( #describeOwnershipChain, [name], ), ), ) as _i5.DiagnosticsNode); } /// A class which mocks [ScatterChartPainter]. /// /// See the documentation for Mockito's code generation for more information. class MockScatterChartPainter extends _i1.Mock implements _i10.ScatterChartPainter { MockScatterChartPainter() { _i1.throwOnMissingStub(this); } @override void paint( _i6.BuildContext? context, _i11.CanvasWrapper? canvasWrapper, _i12.PaintHolder<_i13.ScatterChartData>? holder, ) => super.noSuchMethod( Invocation.method( #paint, [ context, canvasWrapper, holder, ], ), returnValueForMissingStub: null, ); @override void drawSpots( _i6.BuildContext? context, _i11.CanvasWrapper? canvasWrapper, _i12.PaintHolder<_i13.ScatterChartData>? holder, ) => super.noSuchMethod( Invocation.method( #drawSpots, [ context, canvasWrapper, holder, ], ), returnValueForMissingStub: null, ); @override void drawScatterErrorBars( _i11.CanvasWrapper? canvasWrapper, _i12.PaintHolder<_i13.ScatterChartData>? holder, ) => super.noSuchMethod( Invocation.method( #drawScatterErrorBars, [ canvasWrapper, holder, ], ), returnValueForMissingStub: null, ); @override void drawTouchTooltips( _i6.BuildContext? context, _i11.CanvasWrapper? canvasWrapper, _i12.PaintHolder<_i13.ScatterChartData>? holder, ) => super.noSuchMethod( Invocation.method( #drawTouchTooltips, [ context, canvasWrapper, holder, ], ), returnValueForMissingStub: null, ); @override void drawTouchTooltip( _i6.BuildContext? context, _i11.CanvasWrapper? canvasWrapper, _i13.ScatterTouchTooltipData? tooltipData, _i13.ScatterSpot? showOnSpot, _i12.PaintHolder<_i13.ScatterChartData>? holder, ) => super.noSuchMethod( Invocation.method( #drawTouchTooltip, [ context, canvasWrapper, tooltipData, showOnSpot, holder, ], ), returnValueForMissingStub: null, ); @override _i13.ScatterTouchedSpot? handleTouch( _i2.Offset? localPosition, _i2.Size? viewSize, _i12.PaintHolder<_i13.ScatterChartData>? holder, ) => (super.noSuchMethod(Invocation.method( #handleTouch, [ localPosition, viewSize, holder, ], )) as _i13.ScatterTouchedSpot?); @override void drawGrid( _i11.CanvasWrapper? canvasWrapper, _i12.PaintHolder<_i13.ScatterChartData>? holder, ) => super.noSuchMethod( Invocation.method( #drawGrid, [ canvasWrapper, holder, ], ), returnValueForMissingStub: null, ); @override void drawBackground( _i11.CanvasWrapper? canvasWrapper, _i12.PaintHolder<_i13.ScatterChartData>? holder, ) => super.noSuchMethod( Invocation.method( #drawBackground, [ canvasWrapper, holder, ], ), returnValueForMissingStub: null, ); @override void drawRangeAnnotation( _i11.CanvasWrapper? canvasWrapper, _i12.PaintHolder<_i13.ScatterChartData>? holder, ) => super.noSuchMethod( Invocation.method( #drawRangeAnnotation, [ canvasWrapper, holder, ], ), returnValueForMissingStub: null, ); @override void drawExtraLines( _i6.BuildContext? context, _i11.CanvasWrapper? canvasWrapper, _i12.PaintHolder<_i13.ScatterChartData>? holder, ) => super.noSuchMethod( Invocation.method( #drawExtraLines, [ context, canvasWrapper, holder, ], ), returnValueForMissingStub: null, ); @override void drawHorizontalLines( _i6.BuildContext? context, _i11.CanvasWrapper? canvasWrapper, _i12.PaintHolder<_i13.ScatterChartData>? holder, _i2.Size? viewSize, ) => super.noSuchMethod( Invocation.method( #drawHorizontalLines, [ context, canvasWrapper, holder, viewSize, ], ), returnValueForMissingStub: null, ); @override void drawVerticalLines( _i6.BuildContext? context, _i11.CanvasWrapper? canvasWrapper, _i12.PaintHolder<_i13.ScatterChartData>? holder, _i2.Size? viewSize, ) => super.noSuchMethod( Invocation.method( #drawVerticalLines, [ context, canvasWrapper, holder, viewSize, ], ), returnValueForMissingStub: null, ); @override double getPixelX( double? spotX, _i2.Size? viewSize, _i12.PaintHolder<_i13.ScatterChartData>? holder, ) => (super.noSuchMethod( Invocation.method( #getPixelX, [ spotX, viewSize, holder, ], ), returnValue: 0.0, ) as double); @override double getPixelY( double? spotY, _i2.Size? viewSize, _i12.PaintHolder<_i13.ScatterChartData>? holder, ) => (super.noSuchMethod( Invocation.method( #getPixelY, [ spotY, viewSize, holder, ], ), returnValue: 0.0, ) as double); @override double getXForPixel( double? pixelX, _i2.Size? viewSize, _i12.PaintHolder<_i13.ScatterChartData>? holder, ) => (super.noSuchMethod( Invocation.method( #getXForPixel, [ pixelX, viewSize, holder, ], ), returnValue: 0.0, ) as double); @override double getYForPixel( double? pixelY, _i2.Size? viewSize, _i12.PaintHolder<_i13.ScatterChartData>? holder, ) => (super.noSuchMethod( Invocation.method( #getYForPixel, [ pixelY, viewSize, holder, ], ), returnValue: 0.0, ) as double); @override _i2.Offset getChartCoordinateFromPixel( _i2.Offset? pixelOffset, _i2.Size? viewSize, _i12.PaintHolder<_i13.ScatterChartData>? holder, ) => (super.noSuchMethod( Invocation.method( #getChartCoordinateFromPixel, [ pixelOffset, viewSize, holder, ], ), returnValue: _FakeOffset_8( this, Invocation.method( #getChartCoordinateFromPixel, [ pixelOffset, viewSize, holder, ], ), ), ) as _i2.Offset); @override double getTooltipLeft( double? dx, double? tooltipWidth, _i13.FLHorizontalAlignment? tooltipHorizontalAlignment, double? tooltipHorizontalOffset, ) => (super.noSuchMethod( Invocation.method( #getTooltipLeft, [ dx, tooltipWidth, tooltipHorizontalAlignment, tooltipHorizontalOffset, ], ), returnValue: 0.0, ) as double); } ================================================ FILE: test/chart/scatter_chart/scatter_chart_test.dart ================================================ import 'package:fl_chart/src/chart/base/axis_chart/axis_chart_scaffold_widget.dart'; import 'package:fl_chart/src/chart/base/axis_chart/scale_axis.dart'; import 'package:fl_chart/src/chart/base/axis_chart/transformation_config.dart'; import 'package:fl_chart/src/chart/scatter_chart/scatter_chart.dart'; import 'package:fl_chart/src/chart/scatter_chart/scatter_chart_data.dart'; import 'package:fl_chart/src/chart/scatter_chart/scatter_chart_renderer.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { Widget createTestWidget({ required ScatterChart chart, }) { return MaterialApp( home: chart, ); } group('ScatterChart', () { testWidgets('has correct default values', (tester) async { await tester.pumpWidget( createTestWidget( chart: ScatterChart( ScatterChartData(), ), ), ); final scatterChart = tester.widget( find.byType(ScatterChart), ); expect( scatterChart.transformationConfig, const FlTransformationConfig(), ); }); testWidgets('passes interaction parameters to AxisChartScaffoldWidget', (tester) async { await tester.pumpWidget( createTestWidget( chart: ScatterChart( ScatterChartData(), ), ), ); final axisChartScaffoldWidget = tester.widget( find.byType(AxisChartScaffoldWidget), ); expect( axisChartScaffoldWidget.transformationConfig, const FlTransformationConfig(), ); await tester.pumpAndSettle(); final transformationConfig = FlTransformationConfig( scaleAxis: FlScaleAxis.free, trackpadScrollCausesScale: true, maxScale: 10, minScale: 1.5, transformationController: TransformationController(), ); await tester.pumpWidget( createTestWidget( chart: ScatterChart( ScatterChartData(), transformationConfig: transformationConfig, ), ), ); final axisChartScaffoldWidget1 = tester.widget( find.byType(AxisChartScaffoldWidget), ); expect( axisChartScaffoldWidget1.transformationConfig, transformationConfig, ); }); for (final scaleAxis in FlScaleAxis.scalingEnabledAxis) { testWidgets('passes canBeScaled true for $scaleAxis', (tester) async { await tester.pumpWidget( createTestWidget( chart: ScatterChart( ScatterChartData(), transformationConfig: FlTransformationConfig( scaleAxis: scaleAxis, ), ), ), ); final scatterChartLeaf = tester.widget( find.byType(ScatterChartLeaf), ); expect(scatterChartLeaf.canBeScaled, true); }); } testWidgets('passes canBeScaled false for FlScaleAxis.none', (tester) async { await tester.pumpWidget( createTestWidget( chart: ScatterChart( ScatterChartData(), // This is for test // ignore: avoid_redundant_argument_values transformationConfig: const FlTransformationConfig( // This is for test // ignore: avoid_redundant_argument_values scaleAxis: FlScaleAxis.none, ), ), ), ); final scatterChartLeaf = tester.widget( find.byType(ScatterChartLeaf), ); expect(scatterChartLeaf.canBeScaled, false); }); group('touch gesture', () { testWidgets('does not scale with FlScaleAxis.none', (tester) async { await tester.pumpWidget( createTestWidget( chart: ScatterChart( ScatterChartData(), ), ), ); final scatterChartCenterOffset = tester.getCenter(find.byType(ScatterChartLeaf)); final scaleStart1 = scatterChartCenterOffset; final scaleStart2 = scatterChartCenterOffset; final scaleEnd1 = scatterChartCenterOffset + const Offset(100, 100); final scaleEnd2 = scatterChartCenterOffset - const Offset(100, 100); final gesture1 = await tester.startGesture(scaleStart1); final gesture2 = await tester.startGesture(scaleStart2); await tester.pump(); await gesture1.moveTo(scaleEnd1); await gesture2.moveTo(scaleEnd2); await tester.pump(); await gesture1.up(); await gesture2.up(); await tester.pumpAndSettle(); final scatterChartLeaf = tester.widget( find.byType(ScatterChartLeaf), ); expect(scatterChartLeaf.chartVirtualRect, isNull); }); testWidgets('scales freely with FlScaleAxis.free', (tester) async { await tester.pumpWidget( createTestWidget( chart: ScatterChart( ScatterChartData(), transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.free, ), ), ), ); final scatterChartCenterOffset = tester.getCenter(find.byType(ScatterChartLeaf)); final scaleStart1 = scatterChartCenterOffset; final scaleStart2 = scatterChartCenterOffset; final scaleEnd1 = scatterChartCenterOffset + const Offset(100, 100); final scaleEnd2 = scatterChartCenterOffset - const Offset(100, 100); final gesture1 = await tester.startGesture(scaleStart1); final gesture2 = await tester.startGesture(scaleStart2); await tester.pump(); await gesture1.moveTo(scaleEnd1); await gesture2.moveTo(scaleEnd2); await tester.pump(); await gesture1.up(); await gesture2.up(); await tester.pumpAndSettle(); final scatterChartLeaf = tester.widget( find.byType(ScatterChartLeaf), ); final renderBox = tester.renderObject( find.byType(ScatterChartLeaf), ); final chartVirtualRect = scatterChartLeaf.chartVirtualRect!; expect(chartVirtualRect.size, greaterThan(renderBox.size)); expect(chartVirtualRect.left, isNegative); expect(chartVirtualRect.top, isNegative); }); testWidgets('scales horizontally with FlScaleAxis.horizontal', (tester) async { await tester.pumpWidget( createTestWidget( chart: ScatterChart( ScatterChartData(), transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.horizontal, ), ), ), ); final chartCenterOffset = tester.getCenter(find.byType(ScatterChartLeaf)); final scaleStart1 = chartCenterOffset; final scaleStart2 = chartCenterOffset; final scaleEnd1 = chartCenterOffset + const Offset(100, 100); final scaleEnd2 = chartCenterOffset - const Offset(100, 100); final gesture1 = await tester.startGesture(scaleStart1); final gesture2 = await tester.startGesture(scaleStart2); await tester.pump(); await gesture1.moveTo(scaleEnd1); await gesture2.moveTo(scaleEnd2); await tester.pump(); await gesture1.up(); await gesture2.up(); await tester.pumpAndSettle(); final scatterChartLeaf = tester.widget( find.byType(ScatterChartLeaf), ); final renderBox = tester.renderObject( find.byType(ScatterChartLeaf), ); final chartVirtualRect = scatterChartLeaf.chartVirtualRect!; expect(chartVirtualRect.size.height, renderBox.size.height); expect(chartVirtualRect.size.width, greaterThan(renderBox.size.width)); expect(chartVirtualRect.left, isNegative); expect(chartVirtualRect.top, 0); }); testWidgets('scales vertically with FlScaleAxis.vertical', (tester) async { await tester.pumpWidget( createTestWidget( chart: ScatterChart( ScatterChartData(), transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.vertical, ), ), ), ); final chartCenterOffset = tester.getCenter(find.byType(ScatterChartLeaf)); final scaleStart1 = chartCenterOffset; final scaleStart2 = chartCenterOffset; final scaleEnd1 = chartCenterOffset + const Offset(100, 100); final scaleEnd2 = chartCenterOffset - const Offset(100, 100); final gesture1 = await tester.startGesture(scaleStart1); final gesture2 = await tester.startGesture(scaleStart2); await tester.pump(); await gesture1.moveTo(scaleEnd1); await gesture2.moveTo(scaleEnd2); await tester.pump(); await gesture1.up(); await gesture2.up(); await tester.pumpAndSettle(); final scatterChartLeaf = tester.widget( find.byType(ScatterChartLeaf), ); final renderBox = tester.renderObject( find.byType(ScatterChartLeaf), ); final chartVirtualRect = scatterChartLeaf.chartVirtualRect!; expect( chartVirtualRect.size.height, greaterThan(renderBox.size.height), ); expect(chartVirtualRect.size.width, renderBox.size.width); expect(chartVirtualRect.left, 0); expect(chartVirtualRect.top, isNegative); }); group('pans', () { testWidgets('only horizontally with FlScaleAxis.horizontal', (tester) async { await tester.pumpWidget( createTestWidget( chart: ScatterChart( ScatterChartData(), transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.horizontal, ), ), ), ); final chartCenterOffset = tester.getCenter(find.byType(ScatterChartLeaf)); final scaleStart1 = chartCenterOffset; final scaleStart2 = chartCenterOffset; final scaleEnd1 = chartCenterOffset + const Offset(100, 100); final scaleEnd2 = chartCenterOffset - const Offset(100, 100); final gesture1 = await tester.startGesture(scaleStart1); final gesture2 = await tester.startGesture(scaleStart2); await gesture1.moveTo(scaleEnd1); await gesture2.moveTo(scaleEnd2); await gesture1.up(); await gesture2.up(); await tester.pumpAndSettle(); final scatterChartLeafBeforePan = tester.widget( find.byType(ScatterChartLeaf), ); final chartVirtualRectBeforePan = scatterChartLeafBeforePan.chartVirtualRect!; expect(chartVirtualRectBeforePan.top, 0); const panOffset = Offset(100, 100); await tester.dragFrom(chartCenterOffset, panOffset); await tester.pumpAndSettle(); final scatterChartLeafAfterPan = tester.widget( find.byType(ScatterChartLeaf), ); final chartVirtualRectAfterPan = scatterChartLeafAfterPan.chartVirtualRect!; expect(chartVirtualRectBeforePan.size, chartVirtualRectAfterPan.size); expect( chartVirtualRectAfterPan.left, greaterThan(chartVirtualRectBeforePan.left), ); expect(chartVirtualRectAfterPan.top, 0); }); testWidgets('only vertically with FlScaleAxis.vertical', (tester) async { await tester.pumpWidget( createTestWidget( chart: ScatterChart( ScatterChartData(), transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.vertical, ), ), ), ); final chartCenterOffset = tester.getCenter(find.byType(ScatterChartLeaf)); final scaleStart1 = chartCenterOffset; final scaleStart2 = chartCenterOffset; final scaleEnd1 = chartCenterOffset + const Offset(100, 100); final scaleEnd2 = chartCenterOffset - const Offset(100, 100); final gesture1 = await tester.startGesture(scaleStart1); final gesture2 = await tester.startGesture(scaleStart2); await gesture1.moveTo(scaleEnd1); await gesture2.moveTo(scaleEnd2); await gesture1.up(); await gesture2.up(); await tester.pumpAndSettle(); final scatterChartLeafBeforePan = tester.widget( find.byType(ScatterChartLeaf), ); final chartVirtualRectBeforePan = scatterChartLeafBeforePan.chartVirtualRect!; expect(chartVirtualRectBeforePan.left, 0); const panOffset = Offset(100, 100); await tester.dragFrom(chartCenterOffset, panOffset); await tester.pumpAndSettle(); final scatterChartLeafAfterPan = tester.widget( find.byType(ScatterChartLeaf), ); final chartVirtualRectAfterPan = scatterChartLeafAfterPan.chartVirtualRect!; expect(chartVirtualRectAfterPan.left, 0); expect( chartVirtualRectAfterPan.top, greaterThan(chartVirtualRectBeforePan.top), ); }); testWidgets('freely with FlScaleAxis.free', (tester) async { await tester.pumpWidget( createTestWidget( chart: ScatterChart( ScatterChartData(), transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.free, ), ), ), ); final chartCenterOffset = tester.getCenter(find.byType(ScatterChartLeaf)); final scaleStart1 = chartCenterOffset; final scaleStart2 = chartCenterOffset; final scaleEnd1 = chartCenterOffset + const Offset(100, 100); final scaleEnd2 = chartCenterOffset - const Offset(100, 100); final gesture1 = await tester.startGesture(scaleStart1); final gesture2 = await tester.startGesture(scaleStart2); await gesture1.moveTo(scaleEnd1); await gesture2.moveTo(scaleEnd2); await gesture1.up(); await gesture2.up(); await tester.pumpAndSettle(); final scatterChartLeafBeforePan = tester.widget( find.byType(ScatterChartLeaf), ); final chartVirtualRectBeforePan = scatterChartLeafBeforePan.chartVirtualRect!; expect(chartVirtualRectBeforePan.top, isNegative); expect(chartVirtualRectBeforePan.left, isNegative); const panOffset = Offset(100, 100); await tester.dragFrom(chartCenterOffset, panOffset); await tester.pumpAndSettle(); final scatterChartLeafAfterPan = tester.widget( find.byType(ScatterChartLeaf), ); final chartVirtualRectAfterPan = scatterChartLeafAfterPan.chartVirtualRect!; expect( chartVirtualRectAfterPan.left, greaterThan(chartVirtualRectBeforePan.left), ); expect( chartVirtualRectAfterPan.top, greaterThan(chartVirtualRectBeforePan.top), ); }); }); }); group('trackpad scroll', () { group('pans', () { testWidgets('only horizontally with FlScaleAxis.horizontal', (tester) async { await tester.pumpWidget( createTestWidget( chart: ScatterChart( ScatterChartData(), transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.horizontal, ), ), ), ); final chartCenterOffset = tester.getCenter(find.byType(ScatterChartLeaf)); final scaleStart1 = chartCenterOffset; final scaleStart2 = chartCenterOffset; final scaleEnd1 = chartCenterOffset + const Offset(100, 100); final scaleEnd2 = chartCenterOffset - const Offset(100, 100); final gesture1 = await tester.startGesture(scaleStart1); final gesture2 = await tester.startGesture(scaleStart2); await tester.pump(); await gesture1.moveTo(scaleEnd1); await gesture2.moveTo(scaleEnd2); await tester.pump(); await gesture1.up(); await gesture2.up(); await tester.pumpAndSettle(); final scatterChartLeafBeforePan = tester.widget( find.byType(ScatterChartLeaf), ); final chartVirtualRectBeforePan = scatterChartLeafBeforePan.chartVirtualRect!; expect(chartVirtualRectBeforePan.top, 0); final pointer = TestPointer(1, PointerDeviceKind.trackpad); const leftAndUp = Offset(-100, -100); await tester.sendEventToBinding(pointer.hover(chartCenterOffset)); await tester.pump(); await tester.sendEventToBinding(pointer.scroll(leftAndUp)); await tester.pump(); final scatterChartLeafAfterPan = tester.widget( find.byType(ScatterChartLeaf), ); final chartVirtualRectAfterPan = scatterChartLeafAfterPan.chartVirtualRect!; expect( chartVirtualRectAfterPan.left, greaterThan(chartVirtualRectBeforePan.left), ); expect(chartVirtualRectAfterPan.top, 0); }); testWidgets('vertically with FlScaleAxis.vertical', (tester) async { await tester.pumpWidget( createTestWidget( chart: ScatterChart( ScatterChartData(), transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.vertical, ), ), ), ); final chartCenterOffset = tester.getCenter(find.byType(ScatterChartLeaf)); final scaleStart1 = chartCenterOffset; final scaleStart2 = chartCenterOffset; final scaleEnd1 = chartCenterOffset + const Offset(100, 100); final scaleEnd2 = chartCenterOffset - const Offset(100, 100); final gesture1 = await tester.startGesture(scaleStart1); final gesture2 = await tester.startGesture(scaleStart2); await tester.pump(); await gesture1.moveTo(scaleEnd1); await gesture2.moveTo(scaleEnd2); await tester.pump(); await gesture1.up(); await gesture2.up(); await tester.pumpAndSettle(); final scatterChartLeafBeforePan = tester.widget( find.byType(ScatterChartLeaf), ); final chartVirtualRectBeforePan = scatterChartLeafBeforePan.chartVirtualRect!; expect(chartVirtualRectBeforePan.left, 0); final pointer = TestPointer(1, PointerDeviceKind.trackpad); const leftAndUp = Offset(-100, -100); await tester.sendEventToBinding(pointer.hover(chartCenterOffset)); await tester.pump(); await tester.sendEventToBinding(pointer.scroll(leftAndUp)); await tester.pump(); final scatterChartLeafAfterPan = tester.widget( find.byType(ScatterChartLeaf), ); final chartVirtualRectAfterPan = scatterChartLeafAfterPan.chartVirtualRect!; expect(chartVirtualRectAfterPan.left, 0); expect( chartVirtualRectAfterPan.top, greaterThan(chartVirtualRectBeforePan.top), ); }); testWidgets('freely with FlScaleAxis.free', (tester) async { await tester.pumpWidget( createTestWidget( chart: ScatterChart( ScatterChartData(), transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.free, ), ), ), ); final chartCenterOffset = tester.getCenter(find.byType(ScatterChartLeaf)); final scaleStart1 = chartCenterOffset; final scaleStart2 = chartCenterOffset; final scaleEnd1 = chartCenterOffset + const Offset(100, 100); final scaleEnd2 = chartCenterOffset - const Offset(100, 100); final gesture1 = await tester.startGesture(scaleStart1); final gesture2 = await tester.startGesture(scaleStart2); await tester.pump(); await gesture1.moveTo(scaleEnd1); await gesture2.moveTo(scaleEnd2); await tester.pump(); await gesture1.up(); await gesture2.up(); await tester.pumpAndSettle(); final scatterChartLeafBeforePan = tester.widget( find.byType(ScatterChartLeaf), ); final chartVirtualRectBeforePan = scatterChartLeafBeforePan.chartVirtualRect!; final pointer = TestPointer(1, PointerDeviceKind.trackpad); const leftAndUp = Offset(-100, -100); await tester.sendEventToBinding(pointer.hover(chartCenterOffset)); await tester.pump(); await tester.sendEventToBinding(pointer.scroll(leftAndUp)); await tester.pump(); final scatterChartLeafAfterPan = tester.widget( find.byType(ScatterChartLeaf), ); final chartVirtualRectAfterPan = scatterChartLeafAfterPan.chartVirtualRect!; expect( chartVirtualRectAfterPan.left, greaterThan(chartVirtualRectBeforePan.left), ); expect( chartVirtualRectAfterPan.top, greaterThan(chartVirtualRectBeforePan.top), ); }); }); testWidgets( 'does not scale with FlScaleAxis.none when ' 'trackpadScrollCausesScale is true', (tester) async { await tester.pumpWidget( createTestWidget( chart: ScatterChart( ScatterChartData(), transformationConfig: const FlTransformationConfig( // This is for test // ignore: avoid_redundant_argument_values scaleAxis: FlScaleAxis.none, trackpadScrollCausesScale: true, ), ), ), ); final pointer = TestPointer(1, PointerDeviceKind.trackpad); final chartCenterOffset = tester.getCenter(find.byType(ScatterChartLeaf)); const scrollAmount = Offset(0, -100); await tester.sendEventToBinding(pointer.hover(chartCenterOffset)); await tester.pump(); await tester.sendEventToBinding(pointer.scroll(scrollAmount)); await tester.pump(); final scatterChartLeaf = tester.widget( find.byType(ScatterChartLeaf), ); expect(scatterChartLeaf.chartVirtualRect, null); }, ); for (final scaleAxis in FlScaleAxis.scalingEnabledAxis) { testWidgets( 'does not scale when trackpadScrollCausesScale is false ' 'for $scaleAxis', (tester) async { await tester.pumpWidget( createTestWidget( chart: ScatterChart( ScatterChartData(), transformationConfig: FlTransformationConfig( scaleAxis: scaleAxis, // This is for test // ignore: avoid_redundant_argument_values trackpadScrollCausesScale: false, ), ), ), ); final pointer = TestPointer(1, PointerDeviceKind.trackpad); final chartCenterOffset = tester.getCenter( find.byType(ScatterChartLeaf), ); const scrollAmount = Offset(0, -100); await tester.sendEventToBinding(pointer.hover(chartCenterOffset)); await tester.pump(); await tester.sendEventToBinding(pointer.scroll(scrollAmount)); await tester.pump(); final scatterChartLeaf = tester.widget( find.byType(ScatterChartLeaf), ); expect(scatterChartLeaf.chartVirtualRect, null); }, ); } testWidgets('scales horizontally with FlScaleAxis.horizontal', (tester) async { await tester.pumpWidget( createTestWidget( chart: ScatterChart( ScatterChartData(), transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.horizontal, trackpadScrollCausesScale: true, ), ), ), ); final pointer = TestPointer(1, PointerDeviceKind.trackpad); final chartCenterOffset = tester.getCenter(find.byType(ScatterChartLeaf)); const scrollAmount = Offset(0, -100); await tester.sendEventToBinding(pointer.hover(chartCenterOffset)); await tester.pump(); await tester.sendEventToBinding(pointer.scroll(scrollAmount)); await tester.pump(); final scatterChartLeaf = tester.widget( find.byType(ScatterChartLeaf), ); final renderBox = tester.renderObject( find.byType(ScatterChartLeaf), ); final chartVirtualRect = scatterChartLeaf.chartVirtualRect!; expect(chartVirtualRect.size.height, renderBox.size.height); expect(chartVirtualRect.size.width, greaterThan(renderBox.size.width)); expect(chartVirtualRect.left, isNegative); expect(chartVirtualRect.top, 0); }); testWidgets('scales vertically with FlScaleAxis.vertical', (tester) async { await tester.pumpWidget( createTestWidget( chart: ScatterChart( ScatterChartData(), transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.vertical, trackpadScrollCausesScale: true, ), ), ), ); final pointer = TestPointer(1, PointerDeviceKind.trackpad); final chartCenterOffset = tester.getCenter(find.byType(ScatterChartLeaf)); const scrollAmount = Offset(0, -100); await tester.sendEventToBinding(pointer.hover(chartCenterOffset)); await tester.pump(); await tester.sendEventToBinding(pointer.scroll(scrollAmount)); await tester.pump(); final scatterChartLeaf = tester.widget( find.byType(ScatterChartLeaf), ); final renderBox = tester.renderObject( find.byType(ScatterChartLeaf), ); final chartVirtualRect = scatterChartLeaf.chartVirtualRect!; expect( chartVirtualRect.size.height, greaterThan(renderBox.size.height), ); expect(chartVirtualRect.size.width, renderBox.size.width); expect(chartVirtualRect.left, 0); expect(chartVirtualRect.top, isNegative); }); testWidgets('scales freely with FlScaleAxis.free', (tester) async { await tester.pumpWidget( createTestWidget( chart: ScatterChart( ScatterChartData(), transformationConfig: const FlTransformationConfig( scaleAxis: FlScaleAxis.free, trackpadScrollCausesScale: true, ), ), ), ); final pointer = TestPointer(1, PointerDeviceKind.trackpad); final chartCenterOffset = tester.getCenter(find.byType(ScatterChartLeaf)); const scrollAmount = Offset(0, -100); await tester.sendEventToBinding(pointer.hover(chartCenterOffset)); await tester.pump(); await tester.sendEventToBinding(pointer.scroll(scrollAmount)); await tester.pump(); final scatterChartLeaf = tester.widget(find.byType(ScatterChartLeaf)); final renderBox = tester.renderObject( find.byType(ScatterChartLeaf), ); final chartVirtualRect = scatterChartLeaf.chartVirtualRect!; expect(chartVirtualRect.size, greaterThan(renderBox.size)); expect(chartVirtualRect.left, isNegative); expect(chartVirtualRect.top, isNegative); }); }); }); } ================================================ FILE: test/extensions/bar_chart_data_extensions_test.dart ================================================ import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/extensions/bar_chart_data_extension.dart'; import 'package:flutter_test/flutter_test.dart'; import '../chart/data_pool.dart'; void main() { group('BarChartDataExtension.calculateGroupsX', () { test('calculates correct positions for basic alignments', () { expect( MockData.barChartData1 .copyWith(alignment: BarChartAlignment.start) .calculateGroupsX(100), [9.0, 43.0, 77.0], ); expect( MockData.barChartData1 .copyWith(alignment: BarChartAlignment.end) .calculateGroupsX(100), [23.0, 57.0, 91.0], ); expect( MockData.barChartData1 .copyWith(alignment: BarChartAlignment.center) .calculateGroupsX(100), [16.0, 50.0, 84.0], ); expect( MockData.barChartData1 .copyWith(alignment: BarChartAlignment.spaceBetween) .calculateGroupsX(100), [9.0, 50.0, 91.0], ); expect( MockData.barChartData1 .copyWith(alignment: BarChartAlignment.spaceAround) .calculateGroupsX(100), [16.666666666666668, 50.0, 83.33333333333334], ); expect( MockData.barChartData1 .copyWith(alignment: BarChartAlignment.spaceEvenly) .calculateGroupsX(100), [20.5, 50.0, 79.5], ); }); for (final alignment in [ BarChartAlignment.start, BarChartAlignment.end, BarChartAlignment.center, ]) { test( 'spaces evenly when a groupX exceeds view width for $alignment', () { expect( MockData.barChartData1 .copyWith(alignment: alignment) .calculateGroupsX(60), [10.5, 30.0, 49.5], ); }, ); } test('Throws Assertion error when barGroups is empty', () { expect( () => MockData.barChartData1 .copyWith(barGroups: []).calculateGroupsX(100), throwsAssertionError, ); }); }); } ================================================ FILE: test/extensions/border_extension_test.dart ================================================ import 'package:fl_chart/src/extensions/border_extension.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { group('Border isVisible()', () { test('test 1', () { final border = Border( left: BorderSide( color: Colors.red.withValues(alpha: 0.00001), width: 10, ), ); expect(border.isVisible(), true); }); test('test 2', () { final border = Border.all(width: 0); expect(border.isVisible(), false); }); test('test 3', () { final border = Border.all( color: Colors.red.withValues(alpha: 0), width: 10, ); expect(border.isVisible(), false); }); }); } ================================================ FILE: test/extensions/color_extensions_test.dart ================================================ import 'dart:ui'; import 'package:fl_chart/src/extensions/color_extension.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { test('test darken', () { expect( const Color(0x11111111).darken(), isSameColorAs(const Color(0x110a0a0a)), ); expect( const Color(0x11111111).darken(100), isSameColorAs(const Color(0x11000000)), ); }); } ================================================ FILE: test/extensions/edge_insets_extension_test.dart ================================================ import 'package:fl_chart/src/extensions/edge_insets_extension.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { test('test onlyTopBottom', () { const input = EdgeInsets.symmetric(horizontal: 10, vertical: 20); expect( input.onlyTopBottom, const EdgeInsets.symmetric(vertical: 20), ); expect( input.onlyLeftRight, const EdgeInsets.symmetric(horizontal: 10), ); }); } ================================================ FILE: test/extensions/fl_border_data_extension_test.dart ================================================ import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/extensions/fl_border_data_extension.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { test('test allSidesPadding', () { expect( FlBorderData( show: false, border: Border.all( color: Colors.red, width: 10, ), ).allSidesPadding, EdgeInsets.zero, ); expect( FlBorderData( show: true, border: Border( left: const BorderSide( color: Colors.transparent, ), top: BorderSide( width: 10, color: Colors.red.withValues(alpha: 0.5), ), bottom: const BorderSide( width: 4, color: Colors.red, ), ), ).allSidesPadding, const EdgeInsets.fromLTRB(1, 10, 0, 4), ); }); } ================================================ FILE: test/extensions/fl_titles_data_extension_test.dart ================================================ import 'package:fl_chart/src/extensions/fl_titles_data_extension.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import '../chart/data_pool.dart'; void main() { test('test allSidesPadding', () { expect( MockData.flTitlesData1.copyWith(show: false).allSidesPadding, EdgeInsets.zero, ); expect( MockData.flTitlesData1.allSidesPadding, const EdgeInsets.fromLTRB(27, 16, 16, 16), ); }); } ================================================ FILE: test/extensions/gradient_extension_test.dart ================================================ import 'package:fl_chart/src/extensions/gradient_extension.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { group('GradientExtension.getSafeColorStops', () { group('returns linearly calculated stops', () { test('when no stops are provided', () { expect( const _TestGradient( colors: [Colors.red, Colors.blue], ).getSafeColorStops(), [0, 1], ); expect( const _TestGradient( colors: [Colors.red, Colors.blue, Colors.green], ).getSafeColorStops(), [0, 0.5, 1], ); }); test('when less stops than colors are provided', () { expect( const _TestGradient( colors: [Colors.red, Colors.blue, Colors.green], stops: [0.2, 0.8], ).getSafeColorStops(), [0, 0.5, 1], ); }); test('when more stops than colors are provided', () { expect( const _TestGradient( colors: [Colors.red, Colors.blue], stops: [0.2, 0.8, 0.9], ).getSafeColorStops(), [0, 1], ); }); }); test('returns stops when same length as colors', () { expect( const _TestGradient( colors: [Colors.red, Colors.blue], stops: [0.1, 0.8], ).getSafeColorStops(), [0.1, 0.8], ); }); group('throws ArgumentError', () { group('when colors is empty', () { test('without stops', () { expect( () => const _TestGradient(colors: []).getSafeColorStops(), throwsArgumentError, ); }); test('with stops', () { expect( () => const _TestGradient( colors: [], stops: [0.1, 0.8], ).getSafeColorStops(), throwsArgumentError, ); }); }); group('when colors length is 1', () { test('without stops', () { expect( () => const _TestGradient( colors: [Colors.red], ).getSafeColorStops(), throwsArgumentError, ); }); test('with stops', () { expect( () => const _TestGradient( colors: [Colors.red], stops: [0.1, 0.8], ).getSafeColorStops(), throwsArgumentError, ); }); }); }); }); } class _TestGradient extends Gradient { const _TestGradient({required super.colors, super.stops}); @override Shader createShader(Rect rect, {TextDirection? textDirection}) => throw UnimplementedError(); @override Gradient scale(double t) => throw UnimplementedError(); @override Gradient withOpacity(double opacity) => throw UnimplementedError(); } ================================================ FILE: test/extensions/paint_extension_test.dart ================================================ import 'package:fl_chart/src/extensions/paint_extension.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import '../chart/data_pool.dart'; void main() { test('test transparentIfWidthIsZero', () { final paint = Paint() ..color = MockData.color0 ..strokeWidth = 4 ..transparentIfWidthIsZero(); expect(paint.strokeWidth, 4); expect(MockData.color0, paint.color); paint ..strokeWidth = 0.5 ..transparentIfWidthIsZero(); expect(paint.strokeWidth, 0.5); expect(MockData.color0, paint.color); paint ..strokeWidth = 0.0 ..transparentIfWidthIsZero(); expect(paint.strokeWidth, 0.0); expect(MockData.color0.withValues(alpha: 0), paint.color); }); test('test setColorOrGradient', () { final paint = Paint() ..color = MockData.color0 ..setColorOrGradient(null, MockData.gradient1, MockData.rect1); expect(paint.shader, isNotNull); paint.setColorOrGradient(MockData.color0, null, MockData.rect1); expect(paint.color, MockData.color0); expect(paint.shader, isNull); }); test('test setColorOrGradientForLine', () { final paint = Paint() ..color = MockData.color0 ..setColorOrGradientForLine( null, MockData.gradient1, from: MockData.rect1.topLeft, to: MockData.rect1.bottomRight, ); expect(paint.shader, isNotNull); paint.setColorOrGradient(MockData.color0, null, MockData.rect1); expect(paint.color, MockData.color0); expect(paint.shader, isNull); }); } ================================================ FILE: test/extensions/path_extension_test.dart ================================================ import 'dart:ui'; import 'package:fl_chart/src/extensions/path_extension.dart'; import 'package:fl_chart/src/utils/path_drawing/dash_path.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import '../helper_methods.dart'; void main() { test('test transparentIfWidthIsZero', () { final path1 = Path() ..moveTo(0, 0) ..lineTo(10, 0); expect( path1.toDashedPath(null), path1, ); final path2 = dashPath(path1, dashArray: CircularIntervalList([10.0, 5.0])); expect(HelperMethods.equalsPaths(path1.toDashedPath([10, 5]), path2), true); }); } ================================================ FILE: test/extensions/rrect_extension_test.dart ================================================ import 'dart:ui'; import 'package:fl_chart/src/extensions/rrect_extension.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import '../chart/data_pool.dart'; void main() { test('test getRect', () { expect( MockData.rRect1.getRect(), const Rect.fromLTRB(1, 1, 1, 1), ); }); } ================================================ FILE: test/extensions/side_titles_extension_test.dart ================================================ import 'package:fl_chart/src/chart/base/axis_chart/axis_chart_data.dart'; import 'package:fl_chart/src/extensions/side_titles_extension.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { test('test totalReservedSize', () { expect( const AxisTitles( axisNameSize: 12, sideTitles: SideTitles( showTitles: true, reservedSize: 20, ), ).totalReservedSize, 20, ); expect( const AxisTitles( axisNameWidget: Text('asdf'), axisNameSize: 12, sideTitles: SideTitles( reservedSize: 20, ), ).totalReservedSize, 12, ); expect( const AxisTitles( axisNameWidget: Text('asdf'), axisNameSize: 12, sideTitles: SideTitles( showTitles: true, reservedSize: 20, ), ).totalReservedSize, 32, ); }); } ================================================ FILE: test/extensions/size_extension_test.dart ================================================ import 'dart:ui'; import 'package:fl_chart/src/extensions/size_extension.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { test('test rotateByQuarterTurns extension.', () { expect(const Size(100, 200).rotateByQuarterTurns(0), const Size(100, 200)); expect(const Size(100, 200).rotateByQuarterTurns(1), const Size(200, 100)); expect(const Size(100, 200).rotateByQuarterTurns(2), const Size(100, 200)); expect(const Size(100, 200).rotateByQuarterTurns(3), const Size(200, 100)); expect(const Size(100, 200).rotateByQuarterTurns(4), const Size(100, 200)); expect(const Size(100, 200).rotateByQuarterTurns(5), const Size(200, 100)); expect(const Size(100, 200).rotateByQuarterTurns(6), const Size(100, 200)); expect( () => const Size(100, 200).rotateByQuarterTurns(-1), throwsArgumentError, ); expect( () => const Size(100, 200).rotateByQuarterTurns(-3), throwsArgumentError, ); }); } ================================================ FILE: test/extensions/text_align_extension_test.dart ================================================ import 'package:fl_chart/src/extensions/text_align_extension.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { test('test getFinalHorizontalAlignment extension.', () { const textAlignLeft = TextAlign.left; const textAlignRight = TextAlign.right; expect( textAlignLeft.getFinalHorizontalAlignment(TextDirection.rtl), HorizontalAlignment.left, ); expect( textAlignLeft.getFinalHorizontalAlignment(TextDirection.ltr), HorizontalAlignment.left, ); expect( textAlignRight.getFinalHorizontalAlignment(TextDirection.rtl), HorizontalAlignment.right, ); expect( textAlignRight.getFinalHorizontalAlignment(TextDirection.ltr), HorizontalAlignment.right, ); const textAlignStart = TextAlign.start; expect( textAlignStart.getFinalHorizontalAlignment(TextDirection.ltr), HorizontalAlignment.left, ); expect( textAlignStart.getFinalHorizontalAlignment(TextDirection.rtl), HorizontalAlignment.right, ); const textAlignEnd = TextAlign.end; expect( textAlignEnd.getFinalHorizontalAlignment(TextDirection.rtl), HorizontalAlignment.left, ); expect( textAlignEnd.getFinalHorizontalAlignment(TextDirection.ltr), HorizontalAlignment.right, ); const textAlignCenter = TextAlign.center; expect( textAlignCenter.getFinalHorizontalAlignment(TextDirection.rtl), HorizontalAlignment.center, ); expect( textAlignCenter.getFinalHorizontalAlignment(TextDirection.ltr), HorizontalAlignment.center, ); }); } ================================================ FILE: test/helper_methods.dart ================================================ import 'package:flutter/cupertino.dart'; import 'package:flutter_test/flutter_test.dart'; import 'chart/data_pool.dart'; void main() { test('test equalsPaths', () { expect( HelperMethods.equalsPaths(MockData.path1, MockData.path1Duplicate), true, ); expect(HelperMethods.equalsPaths(MockData.path1, MockData.path2), false); }); } class HelperMethods { static bool equalsPaths(Path path1, Path path2) { final metrics1 = path1.computeMetrics().toList(); final metrics2 = path2.computeMetrics().toList(); if (metrics1.length != metrics2.length) { return false; } for (var i = 0; i < metrics1.length; i++) { if (metrics1[i].length != metrics2[i].length) { return false; } if (metrics1[i].isClosed != metrics2[i].isClosed) { return false; } if (metrics1[i].contourIndex != metrics2[i].contourIndex) { return false; } final half = metrics1[i].length / 2; final tangent1 = metrics1[i].getTangentForOffset(half); final tangent2 = metrics2[i].getTangentForOffset(half); if (tangent1!.position != tangent2!.position) { return false; } if (tangent1.angle != tangent2.angle) { return false; } if (tangent1.vector != tangent2.vector) { return false; } } return true; } // This is actually used, I don't know why the linter thinks otherwise // ignore: unreachable_from_main static bool equalsRRects( RRect rrect1, RRect rrect2, { double tolerance = 0.05, }) { if ((rrect1.left - rrect2.left).abs() > tolerance) { return false; } if ((rrect1.top - rrect2.top).abs() > tolerance) { return false; } if ((rrect1.right - rrect2.right).abs() > tolerance) { return false; } if ((rrect1.bottom - rrect2.bottom).abs() > tolerance) { return false; } if (rrect1.blRadius != rrect2.blRadius) { return false; } if (rrect1.brRadius != rrect2.brRadius) { return false; } if (rrect1.trRadius != rrect2.trRadius) { return false; } if (rrect1.tlRadius != rrect2.tlRadius) { return false; } return true; } // This is actually used, I don't know why the linter thinks otherwise // ignore: unreachable_from_main static bool equalsOffsets( Offset offset1, Offset offset2, { double tolerance = 0.05, }) { if ((offset1.dx - offset2.dx).abs() > tolerance) { return false; } if ((offset1.dy - offset2.dy).abs() > tolerance) { return false; } return true; } } ================================================ FILE: test/matchers.dart ================================================ import 'package:fl_chart/src/chart/base/axis_chart/axis_chart_data.dart'; import 'package:fl_chart/src/chart/scatter_chart/scatter_chart_data.dart'; import 'package:flutter_test/flutter_test.dart'; Matcher matchesScatterSpotWithCirclePainter(ScatterSpot spot) { return isA() .having( (spot) => spot.x, 'x', spot.x, ) .having( (spot) => spot.y, 'y', spot.y, ) .having( (spot) => spot.show, 'show', spot.show, ) .having( (spot) => spot.dotPainter, 'dotPainter', isA().having( (painter) => painter.color, 'color', isSameColorAs((spot.dotPainter as FlDotCirclePainter).color), ), ); } Matcher matchesVerticalRangeAnnotation(VerticalRangeAnnotation annotation) { return isA() .having( (annotation) => annotation.x1, 'x1', annotation.x1, ) .having( (annotation) => annotation.x2, 'x2', annotation.x2, ) .having( (annotation) => annotation.color, 'color', isSameColorAs(annotation.color!), ); } Matcher matchesHorizontalRangeAnnotation(HorizontalRangeAnnotation annotation) { return isA() .having( (annotation) => annotation.y1, 'y1', annotation.y1, ) .having( (annotation) => annotation.y2, 'y2', annotation.y2, ) .having( (annotation) => annotation.color, 'color', isSameColorAs(annotation.color!), ); } Matcher matchesVerticalLine(VerticalLine line) { return isA() .having( (line) => line.x, 'x', line.x, ) .having( (line) => line.color, 'color', isSameColorAs(line.color!), ); } Matcher matchesHorizontalLine(HorizontalLine line) { return isA() .having( (line) => line.y, 'y', line.y, ) .having( (line) => line.color, 'color', isSameColorAs(line.color!), ); } ================================================ FILE: test/utils/canvas_wrapper_test.dart ================================================ import 'dart:ui'; import 'package:fl_chart/src/chart/base/axis_chart/axis_chart_data.dart'; import 'package:fl_chart/src/utils/canvas_wrapper.dart'; import 'package:fl_chart/src/utils/utils.dart'; import 'package:flutter/painting.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import '../chart/data_pool.dart'; import 'canvas_wrapper_test.mocks.dart'; @GenerateMocks([Canvas, FlDotPainter, Utils]) void main() { final mockCanvas = MockCanvas(); final canvasWrapper = CanvasWrapper(mockCanvas, MockData.size1); test('test drawRRect', () { canvasWrapper.drawRRect(MockData.rRect1, MockData.paint1); verify(mockCanvas.drawRRect(MockData.rRect1, MockData.paint1)).called(1); }); test('test save', () { canvasWrapper.save(); verify(mockCanvas.save()).called(1); }); test('test restore', () { canvasWrapper.restore(); verify(mockCanvas.restore()).called(1); }); test('test clipRect', () { canvasWrapper.clipRect(MockData.rect1); verify(mockCanvas.clipRect(MockData.rect1)).called(1); }); test('test translate', () { canvasWrapper.translate(11, 232); verify(mockCanvas.translate(11, 232)).called(1); }); test('test rotate', () { canvasWrapper.rotate(12); verify(mockCanvas.rotate(12)).called(1); }); test('test drawPath', () { canvasWrapper.drawPath(MockData.path1, MockData.paint1); verify(mockCanvas.drawPath(MockData.path1, MockData.paint1)).called(1); }); test('test saveLayer', () { canvasWrapper.saveLayer(MockData.rect1, MockData.paint1); verify(mockCanvas.saveLayer(MockData.rect1, MockData.paint1)).called(1); }); test('test drawPicture', () { canvasWrapper.drawPicture(MockData.picture1()); verify(mockCanvas.drawPicture(MockData.picture1())).called(1); }); test('test clipPath', () { canvasWrapper.clipPath(MockData.path1); verify(mockCanvas.clipPath(MockData.path1)).called(1); }); test('test drawRect', () { canvasWrapper.drawRect(MockData.rect1, MockData.paint1); verify(mockCanvas.drawRect(MockData.rect1, MockData.paint1)).called(1); }); test('test drawLine', () { canvasWrapper.drawLine(MockData.offset1, MockData.offset2, MockData.paint1); verify( mockCanvas.drawLine( MockData.offset1, MockData.offset2, MockData.paint1, ), ).called(1); }); test('test drawCircle', () { canvasWrapper.drawCircle(MockData.offset1, 12, MockData.paint1); verify(mockCanvas.drawCircle(MockData.offset1, 12, MockData.paint1)) .called(1); }); test('test drawArc', () { canvasWrapper.drawArc(MockData.rect1, 12, 22, false, MockData.paint1); verify(mockCanvas.drawArc(MockData.rect1, 12, 22, false, MockData.paint1)) .called(1); }); test('test drawDot', () { final painter = MockFlDotPainter(); canvasWrapper.drawDot(painter, MockData.lineBarSpot1, MockData.offset1); verify(painter.draw(mockCanvas, MockData.lineBarSpot1, MockData.offset1)) .called(1); }); test('test drawRotated', () { final utilsMainInstance = Utils(); final mockUtils = MockUtils(); when(mockUtils.radians(any)).thenAnswer((realInvocation) => 12); Utils.changeInstance(mockUtils); var calledCallback = false; void callback() { calledCallback = true; } canvasWrapper.drawRotated( size: const Size(240, 240), rotationOffset: MockData.offset1, drawOffset: MockData.offset2, angle: 12, drawCallback: callback, ); verify(mockCanvas.save()).called(1); verify(mockCanvas.translate(123, 123)).called(1); verify(mockCanvas.rotate(12)).called(1); verify(mockCanvas.translate(-122, -122)).called(1); expect(calledCallback, true); verify(mockCanvas.restore()).called(1); Utils.changeInstance(utilsMainInstance); }); test('test drawText', () { final tp = MockData.textPainter2; canvasWrapper.drawText(tp, MockData.offset1); verify(tp.paint(mockCanvas, MockData.offset1)).called(1); }); test('test drawVerticalText', () { final tp = MockData.textPainter2; final mockUtils = MockUtils(); when(mockUtils.radians(any)).thenAnswer((realInvocation) => 90); Utils.changeInstance(mockUtils); canvasWrapper.drawVerticalText(tp, MockData.offset1); verify(mockCanvas.save()).called(1); verify(mockCanvas.translate(MockData.offset1.dx, MockData.offset1.dy)) .called(1); verify(mockCanvas.rotate(90)).called(1); verify(mockCanvas.translate(-MockData.offset1.dx, -MockData.offset1.dy)) .called(1); verify(tp.paint(mockCanvas, MockData.offset1)).called(1); verify(mockCanvas.restore()).called(1); }); } ================================================ FILE: test/utils/canvas_wrapper_test.mocks.dart ================================================ // Mocks generated by Mockito 5.4.6 from annotations // in fl_chart/test/utils/canvas_wrapper_test.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:typed_data' as _i5; import 'dart:ui' as _i2; import 'package:fl_chart/fl_chart.dart' as _i3; import 'package:fl_chart/src/utils/utils.dart' as _i6; import 'package:flutter/material.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; import 'package:mockito/src/dummies.dart' as _i7; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values // ignore_for_file: avoid_setters_without_getters // ignore_for_file: comment_references // ignore_for_file: deprecated_member_use // ignore_for_file: deprecated_member_use_from_same_package // ignore_for_file: implementation_imports // ignore_for_file: invalid_use_of_visible_for_testing_member // ignore_for_file: must_be_immutable // ignore_for_file: prefer_const_constructors // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class // ignore_for_file: invalid_use_of_internal_member class _FakeRect_0 extends _i1.SmartFake implements _i2.Rect { _FakeRect_0( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeColor_1 extends _i1.SmartFake implements _i2.Color { _FakeColor_1( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeSize_2 extends _i1.SmartFake implements _i2.Size { _FakeSize_2( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeFlDotPainter_3 extends _i1.SmartFake implements _i3.FlDotPainter { _FakeFlDotPainter_3( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeOffset_4 extends _i1.SmartFake implements _i2.Offset { _FakeOffset_4( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeBorderSide_5 extends _i1.SmartFake implements _i4.BorderSide { _FakeBorderSide_5( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i4.DiagnosticLevel? minLevel = _i4.DiagnosticLevel.info}) => super.toString(); } class _FakeTextStyle_6 extends _i1.SmartFake implements _i4.TextStyle { _FakeTextStyle_6( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i4.DiagnosticLevel? minLevel = _i4.DiagnosticLevel.info}) => super.toString(); } /// A class which mocks [Canvas]. /// /// See the documentation for Mockito's code generation for more information. class MockCanvas extends _i1.Mock implements _i2.Canvas { MockCanvas() { _i1.throwOnMissingStub(this); } @override void save() => super.noSuchMethod( Invocation.method( #save, [], ), returnValueForMissingStub: null, ); @override void saveLayer( _i2.Rect? bounds, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #saveLayer, [ bounds, paint, ], ), returnValueForMissingStub: null, ); @override void restore() => super.noSuchMethod( Invocation.method( #restore, [], ), returnValueForMissingStub: null, ); @override void restoreToCount(int? count) => super.noSuchMethod( Invocation.method( #restoreToCount, [count], ), returnValueForMissingStub: null, ); @override int getSaveCount() => (super.noSuchMethod( Invocation.method( #getSaveCount, [], ), returnValue: 0, ) as int); @override void translate( double? dx, double? dy, ) => super.noSuchMethod( Invocation.method( #translate, [ dx, dy, ], ), returnValueForMissingStub: null, ); @override void scale( double? sx, [ double? sy, ]) => super.noSuchMethod( Invocation.method( #scale, [ sx, sy, ], ), returnValueForMissingStub: null, ); @override void rotate(double? radians) => super.noSuchMethod( Invocation.method( #rotate, [radians], ), returnValueForMissingStub: null, ); @override void skew( double? sx, double? sy, ) => super.noSuchMethod( Invocation.method( #skew, [ sx, sy, ], ), returnValueForMissingStub: null, ); @override void transform(_i5.Float64List? matrix4) => super.noSuchMethod( Invocation.method( #transform, [matrix4], ), returnValueForMissingStub: null, ); @override _i5.Float64List getTransform() => (super.noSuchMethod( Invocation.method( #getTransform, [], ), returnValue: _i5.Float64List(0), ) as _i5.Float64List); @override void clipRect( _i2.Rect? rect, { _i2.ClipOp? clipOp = _i2.ClipOp.intersect, bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipRect, [rect], { #clipOp: clipOp, #doAntiAlias: doAntiAlias, }, ), returnValueForMissingStub: null, ); @override void clipRRect( _i2.RRect? rrect, { bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipRRect, [rrect], {#doAntiAlias: doAntiAlias}, ), returnValueForMissingStub: null, ); @override void clipRSuperellipse( _i2.RSuperellipse? rsuperellipse, { bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipRSuperellipse, [rsuperellipse], {#doAntiAlias: doAntiAlias}, ), returnValueForMissingStub: null, ); @override void clipPath( _i2.Path? path, { bool? doAntiAlias = true, }) => super.noSuchMethod( Invocation.method( #clipPath, [path], {#doAntiAlias: doAntiAlias}, ), returnValueForMissingStub: null, ); @override _i2.Rect getLocalClipBounds() => (super.noSuchMethod( Invocation.method( #getLocalClipBounds, [], ), returnValue: _FakeRect_0( this, Invocation.method( #getLocalClipBounds, [], ), ), ) as _i2.Rect); @override _i2.Rect getDestinationClipBounds() => (super.noSuchMethod( Invocation.method( #getDestinationClipBounds, [], ), returnValue: _FakeRect_0( this, Invocation.method( #getDestinationClipBounds, [], ), ), ) as _i2.Rect); @override void drawColor( _i2.Color? color, _i2.BlendMode? blendMode, ) => super.noSuchMethod( Invocation.method( #drawColor, [ color, blendMode, ], ), returnValueForMissingStub: null, ); @override void drawLine( _i2.Offset? p1, _i2.Offset? p2, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawLine, [ p1, p2, paint, ], ), returnValueForMissingStub: null, ); @override void drawPaint(_i2.Paint? paint) => super.noSuchMethod( Invocation.method( #drawPaint, [paint], ), returnValueForMissingStub: null, ); @override void drawRect( _i2.Rect? rect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRect, [ rect, paint, ], ), returnValueForMissingStub: null, ); @override void drawRRect( _i2.RRect? rrect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRRect, [ rrect, paint, ], ), returnValueForMissingStub: null, ); @override void drawDRRect( _i2.RRect? outer, _i2.RRect? inner, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawDRRect, [ outer, inner, paint, ], ), returnValueForMissingStub: null, ); @override void drawRSuperellipse( _i2.RSuperellipse? rsuperellipse, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRSuperellipse, [ rsuperellipse, paint, ], ), returnValueForMissingStub: null, ); @override void drawOval( _i2.Rect? rect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawOval, [ rect, paint, ], ), returnValueForMissingStub: null, ); @override void drawCircle( _i2.Offset? c, double? radius, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawCircle, [ c, radius, paint, ], ), returnValueForMissingStub: null, ); @override void drawArc( _i2.Rect? rect, double? startAngle, double? sweepAngle, bool? useCenter, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawArc, [ rect, startAngle, sweepAngle, useCenter, paint, ], ), returnValueForMissingStub: null, ); @override void drawPath( _i2.Path? path, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawPath, [ path, paint, ], ), returnValueForMissingStub: null, ); @override void drawImage( _i2.Image? image, _i2.Offset? offset, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawImage, [ image, offset, paint, ], ), returnValueForMissingStub: null, ); @override void drawImageRect( _i2.Image? image, _i2.Rect? src, _i2.Rect? dst, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawImageRect, [ image, src, dst, paint, ], ), returnValueForMissingStub: null, ); @override void drawImageNine( _i2.Image? image, _i2.Rect? center, _i2.Rect? dst, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawImageNine, [ image, center, dst, paint, ], ), returnValueForMissingStub: null, ); @override void drawPicture(_i2.Picture? picture) => super.noSuchMethod( Invocation.method( #drawPicture, [picture], ), returnValueForMissingStub: null, ); @override void drawParagraph( _i2.Paragraph? paragraph, _i2.Offset? offset, ) => super.noSuchMethod( Invocation.method( #drawParagraph, [ paragraph, offset, ], ), returnValueForMissingStub: null, ); @override void drawPoints( _i2.PointMode? pointMode, List<_i2.Offset>? points, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawPoints, [ pointMode, points, paint, ], ), returnValueForMissingStub: null, ); @override void drawRawPoints( _i2.PointMode? pointMode, _i5.Float32List? points, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRawPoints, [ pointMode, points, paint, ], ), returnValueForMissingStub: null, ); @override void drawVertices( _i2.Vertices? vertices, _i2.BlendMode? blendMode, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawVertices, [ vertices, blendMode, paint, ], ), returnValueForMissingStub: null, ); @override void drawAtlas( _i2.Image? atlas, List<_i2.RSTransform>? transforms, List<_i2.Rect>? rects, List<_i2.Color>? colors, _i2.BlendMode? blendMode, _i2.Rect? cullRect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawAtlas, [ atlas, transforms, rects, colors, blendMode, cullRect, paint, ], ), returnValueForMissingStub: null, ); @override void drawRawAtlas( _i2.Image? atlas, _i5.Float32List? rstTransforms, _i5.Float32List? rects, _i5.Int32List? colors, _i2.BlendMode? blendMode, _i2.Rect? cullRect, _i2.Paint? paint, ) => super.noSuchMethod( Invocation.method( #drawRawAtlas, [ atlas, rstTransforms, rects, colors, blendMode, cullRect, paint, ], ), returnValueForMissingStub: null, ); @override void drawShadow( _i2.Path? path, _i2.Color? color, double? elevation, bool? transparentOccluder, ) => super.noSuchMethod( Invocation.method( #drawShadow, [ path, color, elevation, transparentOccluder, ], ), returnValueForMissingStub: null, ); } /// A class which mocks [FlDotPainter]. /// /// See the documentation for Mockito's code generation for more information. class MockFlDotPainter extends _i1.Mock implements _i3.FlDotPainter { MockFlDotPainter() { _i1.throwOnMissingStub(this); } @override _i2.Color get mainColor => (super.noSuchMethod( Invocation.getter(#mainColor), returnValue: _FakeColor_1( this, Invocation.getter(#mainColor), ), ) as _i2.Color); @override List get props => (super.noSuchMethod( Invocation.getter(#props), returnValue: [], ) as List); @override void draw( _i2.Canvas? canvas, _i3.FlSpot? spot, _i2.Offset? offsetInCanvas, ) => super.noSuchMethod( Invocation.method( #draw, [ canvas, spot, offsetInCanvas, ], ), returnValueForMissingStub: null, ); @override _i2.Size getSize(_i3.FlSpot? spot) => (super.noSuchMethod( Invocation.method( #getSize, [spot], ), returnValue: _FakeSize_2( this, Invocation.method( #getSize, [spot], ), ), ) as _i2.Size); @override _i3.FlDotPainter lerp( _i3.FlDotPainter? a, _i3.FlDotPainter? b, double? t, ) => (super.noSuchMethod( Invocation.method( #lerp, [ a, b, t, ], ), returnValue: _FakeFlDotPainter_3( this, Invocation.method( #lerp, [ a, b, t, ], ), ), ) as _i3.FlDotPainter); @override bool hitTest( _i3.FlSpot? spot, _i2.Offset? touched, _i2.Offset? center, double? extraThreshold, ) => (super.noSuchMethod( Invocation.method( #hitTest, [ spot, touched, center, extraThreshold, ], ), returnValue: false, ) as bool); } /// A class which mocks [Utils]. /// /// See the documentation for Mockito's code generation for more information. class MockUtils extends _i1.Mock implements _i6.Utils { MockUtils() { _i1.throwOnMissingStub(this); } @override double radians(double? degrees) => (super.noSuchMethod( Invocation.method( #radians, [degrees], ), returnValue: 0.0, ) as double); @override double degrees(double? radians) => (super.noSuchMethod( Invocation.method( #degrees, [radians], ), returnValue: 0.0, ) as double); @override double translateRotatedPosition( double? size, double? degree, ) => (super.noSuchMethod( Invocation.method( #translateRotatedPosition, [ size, degree, ], ), returnValue: 0.0, ) as double); @override _i2.Offset calculateRotationOffset( _i2.Size? size, double? degree, ) => (super.noSuchMethod( Invocation.method( #calculateRotationOffset, [ size, degree, ], ), returnValue: _FakeOffset_4( this, Invocation.method( #calculateRotationOffset, [ size, degree, ], ), ), ) as _i2.Offset); @override _i4.BorderRadius? normalizeBorderRadius( _i4.BorderRadius? borderRadius, double? width, ) => (super.noSuchMethod(Invocation.method( #normalizeBorderRadius, [ borderRadius, width, ], )) as _i4.BorderRadius?); @override _i4.BorderSide normalizeBorderSide( _i4.BorderSide? borderSide, double? width, ) => (super.noSuchMethod( Invocation.method( #normalizeBorderSide, [ borderSide, width, ], ), returnValue: _FakeBorderSide_5( this, Invocation.method( #normalizeBorderSide, [ borderSide, width, ], ), ), ) as _i4.BorderSide); @override double getEfficientInterval( double? axisViewSize, double? diffInAxis, { double? pixelPerInterval = 40.0, }) => (super.noSuchMethod( Invocation.method( #getEfficientInterval, [ axisViewSize, diffInAxis, ], {#pixelPerInterval: pixelPerInterval}, ), returnValue: 0.0, ) as double); @override double roundInterval(double? input) => (super.noSuchMethod( Invocation.method( #roundInterval, [input], ), returnValue: 0.0, ) as double); @override int getFractionDigits(double? value) => (super.noSuchMethod( Invocation.method( #getFractionDigits, [value], ), returnValue: 0, ) as int); @override String formatNumber( double? axisMin, double? axisMax, double? axisValue, ) => (super.noSuchMethod( Invocation.method( #formatNumber, [ axisMin, axisMax, axisValue, ], ), returnValue: _i7.dummyValue( this, Invocation.method( #formatNumber, [ axisMin, axisMax, axisValue, ], ), ), ) as String); @override _i4.TextStyle getThemeAwareTextStyle( _i4.BuildContext? context, _i4.TextStyle? providedStyle, ) => (super.noSuchMethod( Invocation.method( #getThemeAwareTextStyle, [ context, providedStyle, ], ), returnValue: _FakeTextStyle_6( this, Invocation.method( #getThemeAwareTextStyle, [ context, providedStyle, ], ), ), ) as _i4.TextStyle); @override double getBestInitialIntervalValue( double? min, double? max, double? interval, { double? baseline = 0.0, }) => (super.noSuchMethod( Invocation.method( #getBestInitialIntervalValue, [ min, max, interval, ], {#baseline: baseline}, ), returnValue: 0.0, ) as double); @override double convertRadiusToSigma(double? radius) => (super.noSuchMethod( Invocation.method( #convertRadiusToSigma, [radius], ), returnValue: 0.0, ) as double); } ================================================ FILE: test/utils/lerp_test.dart ================================================ import 'dart:ui'; import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/utils/lerp.dart'; import 'package:flutter_test/flutter_test.dart'; import '../chart/data_pool.dart'; import '../matchers.dart'; void main() { const tolerance = 0.001; test('test lerpList', () { final list1 = [1, 1, 2]; final list2 = [1, 2, 3, 5]; expect(lerpList(list1, list2, 0, lerp: lerpDouble), [1.0, 1.0, 2.0, 5.0]); expect(lerpList(list1, list2, 0.5, lerp: lerpDouble), [1.0, 1.5, 2.5, 5.0]); expect(lerpList(list1, list1, 0.5, lerp: lerpDouble), list1); }); test('test lerpColorList', () { const list1 = [ MockData.color1, MockData.color1, MockData.color2, ]; const list2 = [ MockData.color1, MockData.color2, MockData.color3, MockData.color5, ]; expect(lerpColorList(list1, list2, 0), [ isSameColorAs(MockData.color1), isSameColorAs(MockData.color1), isSameColorAs(MockData.color2), isSameColorAs(MockData.color5), ]); expect(lerpColorList(list1, list2, 1), list2); expect(lerpColorList(list1, list2, 0.5), [ isSameColorAs(MockData.color1), isSameColorAs(const Color(0x19191919)), isSameColorAs(const Color(0x2a2a2a2a)), isSameColorAs(MockData.color5), ]); }); test('test lerpColor', () { expect( lerpColor(MockData.color1, MockData.color1, 0.5), isSameColorAs(MockData.color1), ); expect( lerpColor(MockData.color1, MockData.color1, 0), isSameColorAs(MockData.color1), ); expect( lerpColor(MockData.color1, MockData.color1, 1), isSameColorAs(MockData.color1), ); expect( lerpColor(MockData.color1, MockData.color2, 0), isSameColorAs(MockData.color1), ); expect( lerpColor(MockData.color1, MockData.color2, 0.3), isSameColorAs(const Color(0x16161616)), ); expect( lerpColor(MockData.color1, MockData.color2, 1), isSameColorAs(MockData.color2), ); }); test('test lerpDoubleAllowInfinity', () { expect(lerpDoubleAllowInfinity(12, 12, 0), 12); expect(lerpDoubleAllowInfinity(12, 12, 0.2), 12); expect(lerpDoubleAllowInfinity(12, 12, 0.5), 12); expect(lerpDoubleAllowInfinity(12, 12, 1), 12); expect(lerpDoubleAllowInfinity(12, double.infinity, 1), double.infinity); expect(lerpDoubleAllowInfinity(12, double.infinity, 0), double.infinity); expect(lerpDoubleAllowInfinity(12, double.infinity, 0.4), double.infinity); expect(lerpDoubleAllowInfinity(double.infinity, 12, 1), 12); expect(lerpDoubleAllowInfinity(double.infinity, 12, 0), 12); expect(lerpDoubleAllowInfinity(double.infinity, 12, 0.4), 12); expect(lerpDoubleAllowInfinity(0, 10, 0.4), 4); expect(lerpDoubleAllowInfinity(0, 10, 0.2), 2); expect(lerpDoubleAllowInfinity(0, 10, 0.8), 8); }); test('test lerpDoubleList', () { const list1 = [ 0, 0, 0, ]; const list2 = [ 10, 100, 1000, 10000, ]; expect(lerpDoubleList(list1, list2, 0), const [ 0, 0, 0, 10000, ]); expect(lerpDoubleList(list1, list2, 0.5), [ 5, 50, 500, 10000, ]); expect(lerpDoubleList(list1, list2, 1), list2); }); test('test lerpIntList', () { const list1 = [ 0, 0, 0, ]; const list2 = [ 10, 100, 1000, 10000, ]; expect(lerpIntList(list1, list2, 0), const [ 0, 0, 0, 10000, ]); expect(lerpIntList(list1, list2, 0.5), [ 5, 50, 500, 10000, ]); expect(lerpIntList(list1, list2, 1), list2); }); test('test lerpInt', () { expect(lerpInt(0, 10, 1), 10); expect(lerpInt(0, 10, 0.34), 3); expect(lerpInt(0, 10, 0.38), 4); }); test('test lerpNonNullDouble', () { expect(lerpNonNullDouble(0, 10, 1), 10); expect(lerpNonNullDouble(0, 10, 0.34), closeTo(3.4, tolerance)); expect(lerpNonNullDouble(0, 10, 0.38), closeTo(3.8, tolerance)); }); test('test lerpFlSpotList', () { final list1 = [ MockData.flSpot0, MockData.flSpot0, MockData.flSpot0, ]; final list2 = [ MockData.flSpot1, MockData.flSpot2, MockData.flSpot3, MockData.flSpot4, ]; expect(lerpFlSpotList(list1, list2, 0), [ MockData.flSpot0, MockData.flSpot0, MockData.flSpot0, MockData.flSpot4, ]); expect(lerpFlSpotList(list1, list2, 0.5), [ const FlSpot(0.5, 0.5), MockData.flSpot1, const FlSpot(1.5, 1.5), MockData.flSpot4, ]); expect(lerpFlSpotList(list1, list2, 1), list2); }); test('test lerpHorizontalLineList', () { final list1 = [ MockData.horizontalLine0, MockData.horizontalLine0, MockData.horizontalLine0, ]; final list2 = [ MockData.horizontalLine1, MockData.horizontalLine2, MockData.horizontalLine3, MockData.horizontalLine4, ]; expect(lerpHorizontalLineList(list1, list2, 0), [ MockData.horizontalLine0, MockData.horizontalLine0, MockData.horizontalLine0, MockData.horizontalLine4, ]); expect(lerpHorizontalLineList(list1, list2, 0.5), [ matchesHorizontalLine( HorizontalLine(y: 0.5, color: const Color(0x08080808)), ), matchesHorizontalLine(MockData.horizontalLine1), matchesHorizontalLine( HorizontalLine(y: 1.5, color: const Color(0x19191919)), ), matchesHorizontalLine(MockData.horizontalLine4), ]); expect(lerpHorizontalLineList(list1, list2, 1), list2); }); test('test lerpVerticalLineList', () { final list1 = [ MockData.verticalLine0, MockData.verticalLine0, MockData.verticalLine0, ]; final list2 = [ MockData.verticalLine1, MockData.verticalLine2, MockData.verticalLine3, MockData.verticalLine4, ]; expect(lerpVerticalLineList(list1, list2, 0), [ MockData.verticalLine0, MockData.verticalLine0, MockData.verticalLine0, MockData.verticalLine4, ]); expect(lerpVerticalLineList(list1, list2, 0.5), [ matchesVerticalLine( VerticalLine(x: 0.5, color: const Color(0x08080808)), ), matchesVerticalLine(MockData.verticalLine1), matchesVerticalLine( VerticalLine(x: 1.5, color: const Color(0x19191919)), ), matchesVerticalLine(MockData.verticalLine4), ]); expect(lerpVerticalLineList(list1, list2, 1), list2); }); test('test lerpHorizontalRangeAnnotationList', () { final list1 = [ MockData.horizontalRangeAnnotation0, MockData.horizontalRangeAnnotation0, MockData.horizontalRangeAnnotation0, ]; final list2 = [ MockData.horizontalRangeAnnotation1, MockData.horizontalRangeAnnotation2, MockData.horizontalRangeAnnotation3, MockData.horizontalRangeAnnotation4, ]; expect(lerpHorizontalRangeAnnotationList(list1, list2, 0), [ MockData.horizontalRangeAnnotation0, MockData.horizontalRangeAnnotation0, MockData.horizontalRangeAnnotation0, MockData.horizontalRangeAnnotation4, ]); expect(lerpHorizontalRangeAnnotationList(list1, list2, 0.5), [ matchesHorizontalRangeAnnotation( HorizontalRangeAnnotation( y1: 0.5, y2: 1.5, color: const Color(0x08080808), ), ), matchesHorizontalRangeAnnotation(MockData.horizontalRangeAnnotation1), matchesHorizontalRangeAnnotation( HorizontalRangeAnnotation( y1: 1.5, y2: 2.5, color: const Color(0x19191919), ), ), matchesHorizontalRangeAnnotation(MockData.horizontalRangeAnnotation4), ]); expect(lerpHorizontalRangeAnnotationList(list1, list2, 1), list2); }); test('test lerpVerticalRangeAnnotationList', () { final list1 = [ MockData.verticalRangeAnnotation0, MockData.verticalRangeAnnotation0, MockData.verticalRangeAnnotation0, ]; final list2 = [ MockData.verticalRangeAnnotation1, MockData.verticalRangeAnnotation2, MockData.verticalRangeAnnotation3, MockData.verticalRangeAnnotation4, ]; expect(lerpVerticalRangeAnnotationList(list1, list2, 0), [ MockData.verticalRangeAnnotation0, MockData.verticalRangeAnnotation0, MockData.verticalRangeAnnotation0, MockData.verticalRangeAnnotation4, ]); expect(lerpVerticalRangeAnnotationList(list1, list2, 0.5), [ matchesVerticalRangeAnnotation( VerticalRangeAnnotation( x1: 0.5, x2: 1.5, color: const Color(0x08080808), ), ), matchesVerticalRangeAnnotation(MockData.verticalRangeAnnotation1), matchesVerticalRangeAnnotation( VerticalRangeAnnotation( x1: 1.5, x2: 2.5, color: const Color(0x19191919), ), ), matchesVerticalRangeAnnotation(MockData.verticalRangeAnnotation4), ]); expect(lerpVerticalRangeAnnotationList(list1, list2, 1), list2); }); test('test lerpBetweenBarsDataList', () { final list1 = [ MockData.verticalRangeAnnotation0, MockData.verticalRangeAnnotation0, MockData.verticalRangeAnnotation0, ]; final list2 = [ MockData.verticalRangeAnnotation1, MockData.verticalRangeAnnotation2, MockData.verticalRangeAnnotation3, MockData.verticalRangeAnnotation4, ]; expect(lerpVerticalRangeAnnotationList(list1, list2, 0), [ MockData.verticalRangeAnnotation0, MockData.verticalRangeAnnotation0, MockData.verticalRangeAnnotation0, MockData.verticalRangeAnnotation4, ]); expect(lerpVerticalRangeAnnotationList(list1, list2, 0.5), [ matchesVerticalRangeAnnotation( VerticalRangeAnnotation( x1: 0.5, x2: 1.5, color: const Color(0x08080808), ), ), matchesVerticalRangeAnnotation(MockData.verticalRangeAnnotation1), matchesVerticalRangeAnnotation( VerticalRangeAnnotation( x1: 1.5, x2: 2.5, color: const Color(0x19191919), ), ), matchesVerticalRangeAnnotation(MockData.verticalRangeAnnotation4), ]); expect(lerpVerticalRangeAnnotationList(list1, list2, 1), list2); }); test('test lerpRadarEntryList', () { final list1 = [ MockData.radarEntry0, MockData.radarEntry0, MockData.radarEntry0, ]; final list2 = [ MockData.radarEntry1, MockData.radarEntry2, MockData.radarEntry3, MockData.radarEntry4, ]; expect(lerpRadarEntryList(list1, list2, 0), [ MockData.radarEntry0, MockData.radarEntry0, MockData.radarEntry0, MockData.radarEntry4, ]); expect(lerpRadarEntryList(list1, list2, 0.5), [ const RadarEntry(value: 0.5), MockData.radarEntry1, const RadarEntry(value: 1.5), MockData.radarEntry4, ]); expect(lerpRadarEntryList(list1, list2, 1), list2); }); test('test lerpScatterSpotList', () { final list1 = [ MockData.scatterSpot0, MockData.scatterSpot0, MockData.scatterSpot0, ]; final list2 = [ MockData.scatterSpot1, MockData.scatterSpot2, MockData.scatterSpot3, MockData.scatterSpot4, ]; expect(lerpScatterSpotList(list1, list2, 0), [ MockData.scatterSpot0, MockData.scatterSpot0, MockData.scatterSpot0, MockData.scatterSpot4, ]); expect(lerpScatterSpotList(list1, list2, 0.5), [ matchesScatterSpotWithCirclePainter( ScatterSpot( 0.5, 0.5, dotPainter: FlDotCirclePainter(color: const Color(0x08080808)), ), ), matchesScatterSpotWithCirclePainter(MockData.scatterSpot1), matchesScatterSpotWithCirclePainter( ScatterSpot( 1.5, 1.5, dotPainter: FlDotCirclePainter(color: const Color(0x19191919)), ), ), matchesScatterSpotWithCirclePainter(MockData.scatterSpot4), ]); expect(lerpScatterSpotList(list1, list2, 1), list2); }); test('test lerpGradient', () { final colors = [ MockData.color0, MockData.color1, MockData.color2, MockData.color3, ]; expect( lerpGradient(colors, [], 0), isSameColorAs(const Color(0x00000000)), ); expect( lerpGradient(colors, [], 0.2), isSameColorAs(const Color(0x00000000)), ); expect( lerpGradient(colors, [], 0.4), isSameColorAs(const Color(0x0a0a0a0a)), ); expect( lerpGradient(colors, [], 0.6), isSameColorAs(const Color(0x17171717)), ); expect( lerpGradient(colors, [], 0.8), isSameColorAs(const Color(0x25252525)), ); expect( lerpGradient(colors, [], 1), isSameColorAs(const Color(0x33333333)), ); }); test('lerpLineChartBarDataList uses `LineChartBarData.lerp`', () { final list1 = [ MockData.lineChartBarData1, MockData.lineChartBarData1, MockData.lineChartBarData1, ]; final list2 = [ MockData.lineChartBarData2, MockData.lineChartBarData2, MockData.lineChartBarData2, MockData.lineChartBarData2, ]; expect( lerpLineChartBarDataList(list1, list2, 1), lerpList(list1, list2, 1, lerp: LineChartBarData.lerp), ); }); test('lerpBetweenBarsDataList uses `BetweenBarsData.lerp`', () { final list1 = [ betweenBarsData1, betweenBarsData1, betweenBarsData1, ]; final list2 = [ betweenBarsData2, betweenBarsData2, betweenBarsData2, betweenBarsData2, ]; expect( lerpBetweenBarsDataList(list1, list2, 1), lerpList(list1, list2, 1, lerp: BetweenBarsData.lerp), ); }); test('lerpBarChartGroupDataList uses `BarChartGroupData.lerp`', () { final list1 = [ barChartGroupData1, barChartGroupData1, barChartGroupData1, ]; final list2 = [ barChartGroupData2, barChartGroupData2, barChartGroupData2, barChartGroupData2, ]; expect( lerpBarChartGroupDataList(list1, list2, 1), lerpList(list1, list2, 1, lerp: BarChartGroupData.lerp), ); }); test('lerpBarChartRodDataList uses `BarChartRodData.lerp`', () { final list1 = [ barChartRodData1, barChartRodData1, barChartRodData1, ]; final list2 = [ barChartRodData2, barChartRodData2, barChartRodData2, barChartRodData2, ]; expect( lerpBarChartRodDataList(list1, list2, 1), lerpList(list1, list2, 1, lerp: BarChartRodData.lerp), ); }); test('lerpPieChartSectionDataList uses `PieChartSectionData.lerp`', () { final list1 = [ MockData.pieChartSectionData1, MockData.pieChartSectionData1, MockData.pieChartSectionData1, ]; final list2 = [ MockData.pieChartSectionData2, MockData.pieChartSectionData2, MockData.pieChartSectionData2, MockData.pieChartSectionData2, ]; expect( lerpPieChartSectionDataList(list1, list2, 1), lerpList(list1, list2, 1, lerp: PieChartSectionData.lerp), ); }); test('lerpBarChartRodStackList uses `BarChartRodStackItem.lerp`', () { final list1 = [ barChartRodStackItem1, barChartRodStackItem1, barChartRodStackItem1, ]; final list2 = [ barChartRodStackItem2, barChartRodStackItem2, barChartRodStackItem2, barChartRodStackItem2, ]; expect( lerpBarChartRodStackList(list1, list2, 1), lerpList(list1, list2, 1, lerp: BarChartRodStackItem.lerp), ); }); test('lerpRadarDataSetList uses `RadarDataSet.lerp`', () { final list1 = [ MockData.radarDataSet1, MockData.radarDataSet1, MockData.radarDataSet1, ]; final list2 = [ MockData.radarDataSet2, MockData.radarDataSet2, MockData.radarDataSet2, MockData.radarDataSet2, ]; expect( lerpRadarDataSetList(list1, list2, 1), lerpList(list1, list2, 1, lerp: RadarDataSet.lerp), ); }); } ================================================ FILE: test/utils/utils_test.dart ================================================ import 'package:fl_chart/src/utils/lerp.dart'; import 'package:fl_chart/src/utils/utils.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import '../chart/data_pool.dart'; import 'utils_test.mocks.dart'; @GenerateMocks([Utils, BuildContext]) void main() { const tolerance = 0.001; test('changeInstance', () { final Utils mockUtils = MockUtils(); final realUtils = Utils(); expect(Utils(), realUtils); Utils.changeInstance(mockUtils); expect(Utils(), mockUtils); expect(Utils() != realUtils, true); Utils.changeInstance(realUtils); expect(Utils(), realUtils); expect(Utils() != mockUtils, true); }); test('test degrees to radians', () { expect(Utils().radians(57.2958), closeTo(1, tolerance)); expect(Utils().radians(120), closeTo(2.0944, tolerance)); expect(Utils().radians(324), closeTo(5.65487, tolerance)); expect(Utils().radians(180), closeTo(3.1415, tolerance)); }); test('test radians to degree', () { expect(Utils().degrees(1.5), closeTo(85.9437, tolerance)); expect(Utils().degrees(1.8), closeTo(103.132, tolerance)); expect(Utils().degrees(1.2), closeTo(68.7549, tolerance)); }); test('translate rotated position', () { expect(Utils().translateRotatedPosition(100, 90), 25); expect(Utils().translateRotatedPosition(100, 0), 0); }); test('calculateRotationOffset()', () { expect(Utils().calculateRotationOffset(MockData.size1, 90), Offset.zero); expect( Utils().calculateRotationOffset(MockData.size1, 45).dx, closeTo(-2.278, tolerance), ); expect( Utils().calculateRotationOffset(MockData.size1, 45).dy, closeTo(-2.278, tolerance), ); expect( Utils().calculateRotationOffset(MockData.size1, 180).dx, closeTo(0.0, tolerance), ); expect( Utils().calculateRotationOffset(MockData.size1, 180).dy, closeTo(0.0, tolerance), ); expect( Utils().calculateRotationOffset(MockData.size1, 220).dx, closeTo(-2.2485, tolerance), ); expect( Utils().calculateRotationOffset(MockData.size1, 220).dy, closeTo(-2.2485, tolerance), ); expect( Utils().calculateRotationOffset(MockData.size1, 350).dx, closeTo(-0.87150, tolerance), ); expect( Utils().calculateRotationOffset(MockData.size1, 350).dy, closeTo(-0.87150, tolerance), ); }); test('normalizeBorderRadius()', () { const input1 = BorderRadius.only( topLeft: Radius.circular(18), topRight: Radius.circular(18), bottomRight: Radius.circular(18), bottomLeft: Radius.circular(18), ); const output1 = input1; expect(Utils().normalizeBorderRadius(input1, 40), output1); const input2 = BorderRadius.only( topLeft: Radius.circular(24), topRight: Radius.circular(24), bottomRight: Radius.circular(24), bottomLeft: Radius.circular(24), ); const output2 = BorderRadius.only( topLeft: Radius.circular(20), topRight: Radius.circular(20), bottomRight: Radius.circular(20), bottomLeft: Radius.circular(20), ); expect(Utils().normalizeBorderRadius(input2, 40), output2); }); test('normalizeBorderSide()', () { const input1 = BorderSide(width: 4); const output1 = input1; expect(Utils().normalizeBorderSide(input1, 40), output1); const input2 = BorderSide(width: 24); const output2 = BorderSide(width: 20); expect(Utils().normalizeBorderSide(input2, 40), output2); }); test('lerp gradient', () { expect( lerpGradient( [ Colors.red, Colors.green, ], [], 0, ), Colors.red, ); expect( lerpGradient( [ Colors.red, Colors.green, ], [], 1, ), Colors.green, ); }); test('test roundInterval', () { expect(Utils().roundInterval(99), 100); expect(Utils().roundInterval(75), 50); expect(Utils().roundInterval(76), 100); expect(Utils().roundInterval(60), 50); expect(Utils().roundInterval(0.000123), 0.0001); expect(Utils().roundInterval(0.000190), 0.0002); expect(Utils().roundInterval(0.000200), 0.0002); expect(Utils().roundInterval(0.000390000000), 0.0005); expect(Utils().roundInterval(0.000990000000), 0.001); expect(Utils().roundInterval(0.00000990000), 0.00001000); expect(Utils().roundInterval(0.0000009), 0.0000009); expect( Utils().roundInterval(0.000000000000000000990000000), 0.000000000000000000990000000, ); expect(Utils().roundInterval(0.000004901960784313726), 0.000005); }); test('test Utils().getEfficientInterval', () { expect(Utils().getEfficientInterval(472, 340, pixelPerInterval: 10), 5); expect(Utils().getEfficientInterval(820, 10000, pixelPerInterval: 10), 100); expect( Utils().getEfficientInterval(1024, 412345234, pixelPerInterval: 10), 5000000, ); expect( Utils().getEfficientInterval(720, 812394712349, pixelPerInterval: 10), 10000000000, ); expect( Utils().getEfficientInterval(1024, 0.01, pixelPerInterval: 100), 0.001, ); expect( Utils().getEfficientInterval(1024, 0.0005, pixelPerInterval: 10), 0.000005, ); expect(Utils().getEfficientInterval(200, 0.5, pixelPerInterval: 20), 0.05); expect(Utils().getEfficientInterval(200, 1, pixelPerInterval: 20), 0.1); expect(Utils().getEfficientInterval(100, 0.5, pixelPerInterval: 20), 0.1); expect(Utils().getEfficientInterval(10, 10), 10); expect(Utils().getEfficientInterval(10, 0), 1); }); test('test getFractionDigits', () { expect(Utils().getFractionDigits(1), 1); expect(Utils().getFractionDigits(343), 1); expect(Utils().getFractionDigits(22), 1); expect(Utils().getFractionDigits(444444), 1); expect(Utils().getFractionDigits(0.9), 2); expect(Utils().getFractionDigits(0.1), 2); expect(Utils().getFractionDigits(0.01), 3); expect(Utils().getFractionDigits(0.001), 4); expect(Utils().getFractionDigits(0.009), 4); expect(Utils().getFractionDigits(0.008), 4); expect(Utils().getFractionDigits(0.0001), 5); expect(Utils().getFractionDigits(0.0009), 5); expect(Utils().getFractionDigits(0.00001), 6); expect(Utils().getFractionDigits(0.000001), 7); expect(Utils().getFractionDigits(0.0000001), 8); expect(Utils().getFractionDigits(0.00000001), 9); }); test('test formatNumber', () { expect(Utils().formatNumber(0, 10, 0), '0'); expect(Utils().formatNumber(0, 10, -0), '0'); expect(Utils().formatNumber(0, 10, -0.01), '0'); expect(Utils().formatNumber(0, 10, 0.01), '0'); expect(Utils().formatNumber(0, 10, -0.1), '-0.1'); expect(Utils().formatNumber(0, 10, 423), '423'); expect(Utils().formatNumber(0, 10, -423), '-423'); expect(Utils().formatNumber(0, 10, 1000), '1K'); expect(Utils().formatNumber(0, 10, 1234), '1.2K'); expect(Utils().formatNumber(0, 10, 10000), '10K'); expect(Utils().formatNumber(0, 10, 41234), '41.2K'); expect(Utils().formatNumber(0, 10, 82349), '82.3K'); expect(Utils().formatNumber(0, 10, 82350), '82.3K'); expect(Utils().formatNumber(0, 10, 82351), '82.4K'); expect(Utils().formatNumber(0, 10, -82351), '-82.4K'); expect(Utils().formatNumber(0, 10, 100000), '100K'); expect(Utils().formatNumber(0, 10, 101000), '101K'); expect(Utils().formatNumber(0, 10, 2345123), '2.3M'); expect(Utils().formatNumber(0, 10, 2352123), '2.4M'); expect(Utils().formatNumber(0, 10, -2352123), '-2.4M'); expect(Utils().formatNumber(0, 10, 521000000), '521M'); expect(Utils().formatNumber(0, 10, 4324512345), '4.3B'); expect(Utils().formatNumber(0, 10, 4000000000), '4B'); expect(Utils().formatNumber(0, 10, -4000000000), '-4B'); expect(Utils().formatNumber(0, 10, 823147521343), '823.1B'); expect(Utils().formatNumber(0, 10, 8231475213435), '8231.5B'); expect(Utils().formatNumber(0, 10, -8231475213435), '-8231.5B'); }); group('test getThemeAwareTextStyle', () { testWidgets('test 1', (tester) async { const style = TextStyle(color: Colors.brown); await tester.pumpWidget( MaterialApp( home: Scaffold( body: DefaultTextStyle( style: style, child: Container(), ), ), ), ); await tester.pumpAndSettle(); final context = tester.element(find.byType(Container)); expect( Utils().getThemeAwareTextStyle(context, style), style, ); }); testWidgets('test 2', (tester) async { await tester.pumpWidget( MaterialApp( home: Scaffold( body: MediaQuery( data: const MediaQueryData(boldText: true), child: Container(), ), ), ), ); await tester.pumpAndSettle(); final context = tester.element(find.byType(Container)); expect( Utils() .getThemeAwareTextStyle( context, MockData.textStyle1, ) .fontWeight, FontWeight.bold, ); }); }); test('test getInitialIntervalValue()', () { expect(Utils().getBestInitialIntervalValue(-3, 3, 2), -2); expect(Utils().getBestInitialIntervalValue(-3, 3, 1), -3); expect(Utils().getBestInitialIntervalValue(-30, -20, 13), -26); expect(Utils().getBestInitialIntervalValue(0, 13, 8), 0); expect(Utils().getBestInitialIntervalValue(1, 13, 7), 7); expect(Utils().getBestInitialIntervalValue(1, 13, 3), 3); expect(Utils().getBestInitialIntervalValue(-1, 13, 3), 0); expect(Utils().getBestInitialIntervalValue(-2, 13, 3), 0); expect(Utils().getBestInitialIntervalValue(-3, 13, 3), -3); expect(Utils().getBestInitialIntervalValue(-4, 13, 3), -3); expect(Utils().getBestInitialIntervalValue(-5, 13, 3), -3); expect(Utils().getBestInitialIntervalValue(-6, 13, 3), -6); expect(Utils().getBestInitialIntervalValue(-6.5, 13, 3), -6); expect(Utils().getBestInitialIntervalValue(-1, 1, 2), 0); expect(Utils().getBestInitialIntervalValue(-1, 2, 2), 0); expect(Utils().getBestInitialIntervalValue(-2, 0, 2), -2); expect(Utils().getBestInitialIntervalValue(-3, 0, 2), -2); expect(Utils().getBestInitialIntervalValue(-4, 0, 2), -4); expect(Utils().getBestInitialIntervalValue(-0.5, 0.5, 2), 0); expect(Utils().getBestInitialIntervalValue(35, 130, 50), 50); expect(Utils().getBestInitialIntervalValue(49, 130, 50), 50); expect(Utils().getBestInitialIntervalValue(50, 130, 50), 50); expect(Utils().getBestInitialIntervalValue(60, 130, 50), 100); expect(Utils().getBestInitialIntervalValue(110, 130, 50), 110); expect(Utils().getBestInitialIntervalValue(90, 180, 50), 100); expect(Utils().getBestInitialIntervalValue(100, 180, 50), 100); expect(Utils().getBestInitialIntervalValue(110, 180, 50), 150); expect(Utils().getBestInitialIntervalValue(170, 180, 50), 170); expect(Utils().getBestInitialIntervalValue(-120, -10, 50), -100); expect(Utils().getBestInitialIntervalValue(-110, -10, 50), -100); expect(Utils().getBestInitialIntervalValue(-100, -10, 50), -100); expect(Utils().getBestInitialIntervalValue(-90, -10, 50), -50); expect(Utils().getBestInitialIntervalValue(-80, -10, 50), -50); expect(Utils().getBestInitialIntervalValue(-150, -10, 50), -150); expect(Utils().getBestInitialIntervalValue(-10, 10, 2, baseline: -1), -9); expect(Utils().getBestInitialIntervalValue(-10, 10, 2, baseline: -20), -10); expect(Utils().getBestInitialIntervalValue(-10, 10, 15, baseline: -30), 0); expect(Utils().getBestInitialIntervalValue(0, 20, 8, baseline: 28), 4); expect(Utils().getBestInitialIntervalValue(130, 140, 50), 130); expect(Utils().getBestInitialIntervalValue(145, 155, 50), 150); expect( Utils().getBestInitialIntervalValue(-200, -180, 30), -200, ); expect( Utils().getBestInitialIntervalValue(-190, -170, 30), -180, ); expect( Utils().getBestInitialIntervalValue(-2000, 2000, 100, baseline: -10000), -2000, ); expect( Utils().getBestInitialIntervalValue(-120, 120, 33, baseline: -200), -101, ); expect( Utils().getBestInitialIntervalValue(120, 180, 60, baseline: 2000), 140, ); expect(Utils().getBestInitialIntervalValue(-10, 10, 4, baseline: 3), -9); }); test('test convertRadiusToSigma()', () { expect(Utils().convertRadiusToSigma(10), closeTo(6.2735, tolerance)); expect(Utils().convertRadiusToSigma(42), closeTo(24.7487, tolerance)); expect(Utils().convertRadiusToSigma(26), closeTo(15.5111, tolerance)); }); } ================================================ FILE: test/utils/utils_test.mocks.dart ================================================ // Mocks generated by Mockito 5.4.6 from annotations // in fl_chart/test/utils/utils_test.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:ui' as _i2; import 'package:fl_chart/src/utils/utils.dart' as _i5; import 'package:flutter/foundation.dart' as _i4; import 'package:flutter/material.dart' as _i3; import 'package:mockito/mockito.dart' as _i1; import 'package:mockito/src/dummies.dart' as _i6; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values // ignore_for_file: avoid_setters_without_getters // ignore_for_file: comment_references // ignore_for_file: deprecated_member_use // ignore_for_file: deprecated_member_use_from_same_package // ignore_for_file: implementation_imports // ignore_for_file: invalid_use_of_visible_for_testing_member // ignore_for_file: must_be_immutable // ignore_for_file: prefer_const_constructors // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class // ignore_for_file: invalid_use_of_internal_member class _FakeOffset_0 extends _i1.SmartFake implements _i2.Offset { _FakeOffset_0( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeBorderSide_1 extends _i1.SmartFake implements _i3.BorderSide { _FakeBorderSide_1( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info}) => super.toString(); } class _FakeTextStyle_2 extends _i1.SmartFake implements _i3.TextStyle { _FakeTextStyle_2( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info}) => super.toString(); } class _FakeWidget_3 extends _i1.SmartFake implements _i3.Widget { _FakeWidget_3( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info}) => super.toString(); } class _FakeInheritedWidget_4 extends _i1.SmartFake implements _i3.InheritedWidget { _FakeInheritedWidget_4( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info}) => super.toString(); } class _FakeDiagnosticsNode_5 extends _i1.SmartFake implements _i3.DiagnosticsNode { _FakeDiagnosticsNode_5( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({ _i4.TextTreeConfiguration? parentConfiguration, _i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info, }) => super.toString(); } /// A class which mocks [Utils]. /// /// See the documentation for Mockito's code generation for more information. class MockUtils extends _i1.Mock implements _i5.Utils { MockUtils() { _i1.throwOnMissingStub(this); } @override double radians(double? degrees) => (super.noSuchMethod( Invocation.method( #radians, [degrees], ), returnValue: 0.0, ) as double); @override double degrees(double? radians) => (super.noSuchMethod( Invocation.method( #degrees, [radians], ), returnValue: 0.0, ) as double); @override double translateRotatedPosition( double? size, double? degree, ) => (super.noSuchMethod( Invocation.method( #translateRotatedPosition, [ size, degree, ], ), returnValue: 0.0, ) as double); @override _i2.Offset calculateRotationOffset( _i2.Size? size, double? degree, ) => (super.noSuchMethod( Invocation.method( #calculateRotationOffset, [ size, degree, ], ), returnValue: _FakeOffset_0( this, Invocation.method( #calculateRotationOffset, [ size, degree, ], ), ), ) as _i2.Offset); @override _i3.BorderRadius? normalizeBorderRadius( _i3.BorderRadius? borderRadius, double? width, ) => (super.noSuchMethod(Invocation.method( #normalizeBorderRadius, [ borderRadius, width, ], )) as _i3.BorderRadius?); @override _i3.BorderSide normalizeBorderSide( _i3.BorderSide? borderSide, double? width, ) => (super.noSuchMethod( Invocation.method( #normalizeBorderSide, [ borderSide, width, ], ), returnValue: _FakeBorderSide_1( this, Invocation.method( #normalizeBorderSide, [ borderSide, width, ], ), ), ) as _i3.BorderSide); @override double getEfficientInterval( double? axisViewSize, double? diffInAxis, { double? pixelPerInterval = 40.0, }) => (super.noSuchMethod( Invocation.method( #getEfficientInterval, [ axisViewSize, diffInAxis, ], {#pixelPerInterval: pixelPerInterval}, ), returnValue: 0.0, ) as double); @override double roundInterval(double? input) => (super.noSuchMethod( Invocation.method( #roundInterval, [input], ), returnValue: 0.0, ) as double); @override int getFractionDigits(double? value) => (super.noSuchMethod( Invocation.method( #getFractionDigits, [value], ), returnValue: 0, ) as int); @override String formatNumber( double? axisMin, double? axisMax, double? axisValue, ) => (super.noSuchMethod( Invocation.method( #formatNumber, [ axisMin, axisMax, axisValue, ], ), returnValue: _i6.dummyValue( this, Invocation.method( #formatNumber, [ axisMin, axisMax, axisValue, ], ), ), ) as String); @override _i3.TextStyle getThemeAwareTextStyle( _i3.BuildContext? context, _i3.TextStyle? providedStyle, ) => (super.noSuchMethod( Invocation.method( #getThemeAwareTextStyle, [ context, providedStyle, ], ), returnValue: _FakeTextStyle_2( this, Invocation.method( #getThemeAwareTextStyle, [ context, providedStyle, ], ), ), ) as _i3.TextStyle); @override double getBestInitialIntervalValue( double? min, double? max, double? interval, { double? baseline = 0.0, }) => (super.noSuchMethod( Invocation.method( #getBestInitialIntervalValue, [ min, max, interval, ], {#baseline: baseline}, ), returnValue: 0.0, ) as double); @override double convertRadiusToSigma(double? radius) => (super.noSuchMethod( Invocation.method( #convertRadiusToSigma, [radius], ), returnValue: 0.0, ) as double); } /// A class which mocks [BuildContext]. /// /// See the documentation for Mockito's code generation for more information. class MockBuildContext extends _i1.Mock implements _i3.BuildContext { MockBuildContext() { _i1.throwOnMissingStub(this); } @override _i3.Widget get widget => (super.noSuchMethod( Invocation.getter(#widget), returnValue: _FakeWidget_3( this, Invocation.getter(#widget), ), ) as _i3.Widget); @override bool get mounted => (super.noSuchMethod( Invocation.getter(#mounted), returnValue: false, ) as bool); @override bool get debugDoingBuild => (super.noSuchMethod( Invocation.getter(#debugDoingBuild), returnValue: false, ) as bool); @override _i3.InheritedWidget dependOnInheritedElement( _i3.InheritedElement? ancestor, { Object? aspect, }) => (super.noSuchMethod( Invocation.method( #dependOnInheritedElement, [ancestor], {#aspect: aspect}, ), returnValue: _FakeInheritedWidget_4( this, Invocation.method( #dependOnInheritedElement, [ancestor], {#aspect: aspect}, ), ), ) as _i3.InheritedWidget); @override void visitAncestorElements(_i3.ConditionalElementVisitor? visitor) => super.noSuchMethod( Invocation.method( #visitAncestorElements, [visitor], ), returnValueForMissingStub: null, ); @override void visitChildElements(_i3.ElementVisitor? visitor) => super.noSuchMethod( Invocation.method( #visitChildElements, [visitor], ), returnValueForMissingStub: null, ); @override void dispatchNotification(_i3.Notification? notification) => super.noSuchMethod( Invocation.method( #dispatchNotification, [notification], ), returnValueForMissingStub: null, ); @override _i3.DiagnosticsNode describeElement( String? name, { _i4.DiagnosticsTreeStyle? style = _i4.DiagnosticsTreeStyle.errorProperty, }) => (super.noSuchMethod( Invocation.method( #describeElement, [name], {#style: style}, ), returnValue: _FakeDiagnosticsNode_5( this, Invocation.method( #describeElement, [name], {#style: style}, ), ), ) as _i3.DiagnosticsNode); @override _i3.DiagnosticsNode describeWidget( String? name, { _i4.DiagnosticsTreeStyle? style = _i4.DiagnosticsTreeStyle.errorProperty, }) => (super.noSuchMethod( Invocation.method( #describeWidget, [name], {#style: style}, ), returnValue: _FakeDiagnosticsNode_5( this, Invocation.method( #describeWidget, [name], {#style: style}, ), ), ) as _i3.DiagnosticsNode); @override List<_i3.DiagnosticsNode> describeMissingAncestor( {required Type? expectedAncestorType}) => (super.noSuchMethod( Invocation.method( #describeMissingAncestor, [], {#expectedAncestorType: expectedAncestorType}, ), returnValue: <_i3.DiagnosticsNode>[], ) as List<_i3.DiagnosticsNode>); @override _i3.DiagnosticsNode describeOwnershipChain(String? name) => (super.noSuchMethod( Invocation.method( #describeOwnershipChain, [name], ), returnValue: _FakeDiagnosticsNode_5( this, Invocation.method( #describeOwnershipChain, [name], ), ), ) as _i3.DiagnosticsNode); }