[
  {
    "path": ".browserslistrc",
    "content": "defaults\nnot IE 11\nnot IE_Mob 11\nmaintained node versions\n"
  },
  {
    "path": ".codeclimate.yml",
    "content": "version: \"2\"\nplugins:\n  duplication:\n    enabled: true\n    config:\n      languages:\n        - javascript\n  fixme:\n    enabled: true\nchecks:\n  argument-count:\n    config:\n      threshold: 5\n  method-complexity:\n    config:\n      threshold: 7\nexclude_patterns:\n  - \"dist/\"\n  - \"docs/\"\n  - \"scripts/\"\n  - \"test/\"\n  - \"*.js\"\n  - \"*.json\"\n  - \"*.md\"\n  - \".*\"\n"
  },
  {
    "path": ".editorconfig",
    "content": "# https://editorconfig.org\nroot = true\n\n[*]\nindent_style = space\nindent_size = 2\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n\n[*.html]\nindent_style = tab\nindent_size = 4\n"
  },
  {
    "path": ".eslintignore",
    "content": "dist/*\ntest/integration/react-browser/*\n"
  },
  {
    "path": ".eslintrc.yml",
    "content": "extends:\n  - chartjs\n  - plugin:es/restrict-to-es2018\n  - plugin:markdown/recommended\n\nsettings:\n  es:\n    aggressive: true\n\nenv:\n  es6: true\n  browser: true\n  node: true\n\nparserOptions:\n  ecmaVersion: 2022\n  sourceType: module\n  ecmaFeatures:\n    impliedStrict: true\n    modules: true\n\nplugins: ['html', 'es']\n\nrules:\n  class-methods-use-this: \"off\"\n  complexity: [\"warn\", 10]\n  max-statements: [\"warn\", 30]\n  no-empty-function: \"off\"\n  no-use-before-define: [\"error\", { \"functions\": false }]\n  # disable everything, except Rest/Spread Properties in ES2018\n  es/no-import-meta: \"off\"\n  es/no-async-iteration: \"error\"\n  es/no-malformed-template-literals: \"error\"\n  es/no-regexp-lookbehind-assertions: \"error\"\n  es/no-regexp-named-capture-groups: \"error\"\n  es/no-regexp-s-flag: \"error\"\n  es/no-regexp-unicode-property-escapes: \"error\"\n  es/no-dynamic-import: \"off\"\n\noverrides:\n  - files: ['**/*.ts']\n    parser: '@typescript-eslint/parser'\n    plugins:\n      - '@typescript-eslint'\n    extends:\n      - chartjs\n      - plugin:@typescript-eslint/recommended\n\n    rules:\n      complexity: [\"warn\", 10]\n      max-statements: [\"warn\", 30]\n      # Replace stock eslint rules with typescript-eslint equivalents for proper\n      # TypeScript support.\n      indent: \"off\"\n      \"@typescript-eslint/indent\": [\"error\", 2]\n      no-use-before-define: \"off\"\n      '@typescript-eslint/no-use-before-define': \"error\"\n      no-shadow: \"off\"\n      '@typescript-eslint/no-shadow': \"error\"\n      space-before-function-paren: \"off\"\n      '@typescript-eslint/space-before-function-paren': [2, never]\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug.yml",
    "content": "name: Bug Report\ndescription: Something went awry\nlabels: [\"type: bug\"]\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        Need help or support?\n        Please don't open an issue! Head to https://stackoverflow.com/questions/tagged/chart.js.\n  \n  - type: markdown\n    attributes:\n      value: \"Bug reports MUST be submitted with an interactive example: https://codepen.io/leelenaleee/pen/WNyJXEe.\"\n\n  - type: markdown\n    attributes:\n      value: Chart.js versions lower then 4.x are NOT supported anymore, new issues will be disregarded.\n\n  - type: textarea\n    attributes:\n      label: Expected behavior\n      description: Tell us what should happen.\n    validations:\n      required: true\n\n  - type: textarea\n    attributes:\n      label: Current behavior\n      description: Tell us what happens instead of the expected behavior.\n    validations:\n      required: true\n\n  - type: input\n    attributes:\n      label: Reproducible sample\n      description: |\n        Please provide issue reproduction.\n        You can use [this codepen](https://codepen.io/leelenaleee/pen/WNyJXEe) to make a reproducible sample.\n\n        Major framework wrappers for chart.js templates:\n        [vue-chart-3 sandbox (Vue)](https://codesandbox.io/s/vue-chart-3-chart-js-issue-template-bpg7k?file=/src/App.vue)\n        [ng2-charts sandbox (Angular)](https://codesandbox.io/s/ng2charts-chart-js-issue-template-fhezt?file=/src/app/app.component.ts)\n        [react-chartjs-2 sandbox (React)](https://codesandbox.io/p/sandbox/react-chartjs-2-chart-js-issue-template-v4-forked-lqz5tn?file=%2Fsrc%2FApp.tsx)\n\n        For typescript issues you can make use of [this TS Playground](https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAbzgYQBYENZwL5wGZQQhwDkAxhrAHQBWAziQNwCwAUGwG6ZxkwAecALxwAJhDIBXEAFMAdjCoBzaTACiAG2kz5AIQCeASREAKAEQg9aTDFMBKOOjpwAEgBUAsgBlk6WVzoaWnIwLKxcUHAWVljCstIA7iiUMMa8fAA0iGxwOXAwemDSAFyk6sBxJOnZuSLoMOglCNW5ueroAEbS6nQlANqmAErSIqaZpjrqEtKjcKYAml3qEPEzpgDiUNJyqwAKElBgmqsA8lC+yqYAulWsLS219XQqPXC9Tbd3n22d6iUkAMRwCB4OAANQgMGkDBun0+DwarwAjAAmTKIgCcmQAzJkAKyZVFwLHXZp3bCXUnYGG5CBgGDACCyF7vT50MjoTTM0ktPiNbl3fk5KmCuB6PkfWFwEXYfkyiU4NjYWyMIA) to make a reproducible sample.\n\n        If filing a bug against `master`, you may reference the latest code via\n        https://www.chartjs.org/dist/master/chart.umd.min.js (changing the filename to\n        point at the file you need as appropriate). Do not rely on these files for\n        production purposes as they may be removed at any time.\n    validations:\n      required: true\n  \n  - type: textarea\n    attributes:\n      label: Optional extra steps/info to reproduce\n\n  - type: textarea\n    attributes:\n      label: Possible solution\n      description: If you have suggestions on a fix for the bug.\n\n  - type: textarea\n    attributes:\n      label: Context\n      description: |\n        How has this issue affected you? What are you trying to accomplish?\n        Providing context helps us come up with a solution that is most useful in the real world.\n\n  - type: input\n    attributes:\n      label: chart.js version\n      description: Which version of `chart.js` are you using?\n      placeholder: \"v0.0.0\"\n    validations:\n      required: true\n\n  - type: input\n    attributes:\n      label: Browser name and version\n\n  - type: input\n    attributes:\n      label: Link to your project\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\ncontact_links:\n  - name: Support, Help, and Advice\n    url: https://stackoverflow.com/questions/tagged/chart.js\n    about: Need help or support? Head to https://stackoverflow.com/questions/tagged/chart.js\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/docs.yml",
    "content": "name: Documentation\ndescription: Are the docs lacking or missing something?\nlabels: [\"type: documentation\"]\nbody:\n  - type: checkboxes\n    attributes:\n      label: \"Documentation Is:\"\n      options:\n        - label: Missing or needed?\n        - label: Confusing\n        - label: Not sure?\n\n  - type: textarea\n    attributes:\n      label: Please Explain in Detail...\n    validations:\n      required: true\n\n  - type: textarea\n    attributes:\n      label: Your Proposal for Changes\n    validations:\n      required: true\n  \n  - type: input\n    attributes:\n      label: Example\n      description: |\n        Provide a link to a live example demonstrating the issue or feature to be documented:\n        Normal: https://codepen.io/pen?template=BapRepQ\n        TS: [TS Playground](https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAbzgYQBYENZwL5wGZQQhwDkAxhrAHQBWAziQNwCwAUGwG6ZxkwAecALxwAJhDIBXEAFMAdjCoBzaTACiAG2kz5AIQCeASREAKAEQg9aTDFMBKOOjpwAEgBUAsgBlk6WVzoaWnIwLKxcUHAWVljCstIA7iiUMMa8fAA0iGxwOXAwemDSAFyk6sBxJOnZuSLoMOglCNW5ueroAEbS6nQlANqmAErSIqaZpjrqEtKjcKYAml3qEPEzpgDiUNJyqwAKElBgmqsA8lC+yqYAulWsLS219XQqPXC9Tbd3n22d6iUkAMRwCB4OAANQgMGkDBun0+DwarwAjAAmTKIgCcmQAzJkAKyZVFwLHXZp3bCXUnYGG5CBgGDACCyF7vT50MjoTTM0ktPiNbl3fk5KmCuB6PkfWFwEXYfkyiU4NjYWyMIA)\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature.yml",
    "content": "name: Feature Request\ndescription: Suggest an idea\nlabels: [\"type: enhancement\"]\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        Most features should start as plugins outside of Chart.js\n        (https://www.chartjs.org/docs/latest/developers/plugins.html).\n        Please consider whether your changes are useful for all users, or if this is\n        specific to your usecase and a Chart.js plugin would be more appropriate.\n\n        Need help or tech support? Please don't open an issue!\n        Head to https://stackoverflow.com/questions/tagged/chart.js\n\n  - type: textarea\n    attributes:\n      label: Feature Proposal\n      description: |\n        What are you trying to accomplish?\n        Providing context helps us come up with a solution that is most useful in the real world\n    validations:\n      required: true\n\n  - type: textarea\n    attributes:\n      label: Possible Implementation\n      description: Not obligatory, but suggest ideas for how to implement the addition or change\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE.md",
    "content": "<!--\n  Need help or support? Don't open an issue!\n  Head to https://stackoverflow.com/questions/tagged/chart.js\n\n  Ahoy!\n\n  You're seeing this because you felt none of the other options fit the type of\n  issue you'd like to create. Please use this opportunity to tell us about the\n  type of issue you were looking for, so we can try to accommodate similar\n  issues in the future.\n\n  If you're using this template to report an issue covered by an existing issue\n  type, we'll close it as invalid faster than you can spell 'Mississippi'.\n-->\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "<!--\nPlease consider the following before submitting a pull request:\n\nGuidelines for contributing: https://github.com/chartjs/Chart.js/blob/master/docs/developers/contributing.md\n\nExample of changes on an interactive website such as the following:\n- https://jsbin.com/\n- https://jsfiddle.net/\n- https://codepen.io/pen/\n- Premade template: https://codepen.io/pen?template=wvezeOq\n-->\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n"
  },
  {
    "path": ".github/release-drafter.yml",
    "content": "name-template: 'v$RESOLVED_VERSION'\ntag-template: 'v$RESOLVED_VERSION'\ncategories:\n  - title: 'Breaking Changes'\n    labels:\n        - 'breaking change'\n  - title: 'Enhancements'\n    labels:\n      - 'type: enhancement'\n  - title: 'Performance'\n    labels:\n      - 'type: performance'\n  - title: 'Bugs Fixed'\n    labels:\n      - 'type: bug'\n  - title: 'Types'\n    labels:\n      - 'type: types'\n  - title: 'Documentation'\n    labels:\n      - 'type: documentation'\n  - title: 'Development'\n    labels:\n      - 'type: chore'\n      - 'dependencies'\nexclude-labels:\n  - 'type: infrastructure'\nchange-template: '- #$NUMBER $TITLE'\nchange-title-escapes: '\\<*_&`#@'\nversion-resolver:\n  major:\n    labels:\n      - 'breaking change'\n  minor:\n    labels:\n      - 'type: enhancement'\n  patch:\n    labels:\n      - 'type: bug'\n      - 'type: chore'\n      - 'type: types'\n  default: patch\ntemplate: |\n  # Essential Links\n\n  * [npm](https://www.npmjs.com/package/chart.js)\n  * [Migration guide](https://www.chartjs.org/docs/$RESOLVED_VERSION/migration/v4-migration.html)\n  * [Docs](https://www.chartjs.org/docs/$RESOLVED_VERSION/)\n  * [API](https://www.chartjs.org/docs/$RESOLVED_VERSION/api/)\n  * [Samples](https://www.chartjs.org/docs/$RESOLVED_VERSION/samples/information.html)\n\n  $CHANGES\n\n  Thanks to $CONTRIBUTORS\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  push:\n    branches:\n      - master\n      - \"2.9\"\n  pull_request:\n    branches:\n      - master\n      - \"2.9\"\n  workflow_dispatch:\npermissions:\n  contents: read\n\njobs:\n  build:\n    permissions:\n      checks: write  # for coverallsapp/github-action to create new checks\n      contents: read  # for dorny/paths-filter to fetch a list of changed files\n      pull-requests: read  # for dorny/paths-filter to read pull requests\n    runs-on: ${{ matrix.os }}\n\n    outputs:\n      coveralls: ${{ steps.changes.outputs.src }}\n\n    strategy:\n      matrix:\n        os: [ubuntu-latest, windows-latest]\n      fail-fast: false\n\n    steps:\n      - uses: actions/checkout@v6\n      - uses: pnpm/action-setup@v4.2.0\n      - name: Use Node.js\n        uses: actions/setup-node@v6\n        with:\n          node-version: 16\n          cache: pnpm\n      - uses: dorny/paths-filter@v3\n        id: changes\n        with:\n          filters: |\n            docs:\n              - 'docs/**'\n              - 'package.json'\n              - 'tsconfig.json'\n            src:\n              - 'src/**'\n              - 'package.json'\n            test:\n              - 'test/**'\n              - 'karma.conf.js'\n              - 'package.json'\n            types:\n              - 'package.json'\n              - 'tsconfig.json'\n      - name: Install\n        run: pnpm install\n      - name: Lint\n        run: pnpm run lint\n      - name: Build\n        run: pnpm run build\n      - name: Test\n        if: |\n          (steps.changes.outputs.src == 'true' ||\n          steps.changes.outputs.test == 'true') &&\n          runner.os != 'Windows'\n        run: |\n          pnpm run build\n          if [ \"${{ runner.os }}\" == \"macOS\" ]; then\n            pnpm run test-ci --browsers chrome,safari\n          else\n            xvfb-run --auto-servernum pnpm run test-ci\n          fi\n        shell: bash\n      - name: Package\n        if: steps.changes.outputs.docs == 'true'\n        run: |\n          pnpm run docs\n          pnpm pack\n      - name: Coveralls Parallel - Chrome\n        if: |\n          steps.changes.outputs.src == 'true' &&\n          runner.os != 'Windows'\n        uses: coverallsapp/github-action@master\n        with:\n          github-token: ${{ secrets.github_token }}\n          path-to-lcov: './coverage/chrome/lcov.info'\n          flag-name: ${{ matrix.os }}-chrome\n          parallel: true\n      - name: Coveralls Parallel - Firefox\n        if: |\n          steps.changes.outputs.src == 'true' &&\n          runner.os != 'Windows'\n        uses: coverallsapp/github-action@master\n        with:\n          github-token: ${{ secrets.github_token }}\n          path-to-lcov: './coverage/firefox/lcov.info'\n          flag-name: ${{ matrix.os }}-firefox\n          parallel: true\n\n  finish:\n    permissions:\n      checks: write  # for coverallsapp/github-action to create new checks\n    needs: build\n    runs-on: ubuntu-latest\n    steps:\n      - name: Coveralls Finished\n        if: needs.build.outputs.coveralls == 'true'\n        uses: coverallsapp/github-action@master\n        with:\n          github-token: ${{ secrets.github_token }}\n          parallel-finished: true\n"
  },
  {
    "path": ".github/workflows/compressed-size.yml",
    "content": "name: Compressed Size\n\non: [pull_request]\n\npermissions:\n  contents: read\n\njobs:\n  build:\n\n    permissions:\n      checks: write  # for preactjs/compressed-size-action to create and update the checks\n      contents: read  # for actions/checkout to fetch code\n      issues: write  # for preactjs/compressed-size-action to create comments\n      pull-requests: write  # for preactjs/compressed-size-action to write a PR review\n    runs-on: ubuntu-latest\n\n    steps:\n    - uses: actions/checkout@v6\n    - uses: pnpm/action-setup@v4.2.0\n    - uses: preactjs/compressed-size-action@v2\n      with:\n        repo-token: \"${{ secrets.GITHUB_TOKEN }}\"\n"
  },
  {
    "path": ".github/workflows/deploy-docs.yml",
    "content": "# This workflow publishes new documentation to https://chartjs.org/docs/master after every commit\nname: Deploy docs\n\non:\n  push:\n    branches:\n      - master\n\npermissions:\n  contents: read\n\njobs:\n  correct_repository:\n    permissions:\n      contents: none\n    runs-on: ubuntu-latest\n    steps:\n      - name: fail on fork\n        if: github.repository_owner != 'chartjs'\n        run: exit 1\n\n  build:\n    needs: correct_repository\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      - uses: pnpm/action-setup@v4.2.0\n      - name: Use Node.js\n        uses: actions/setup-node@v6\n        with:\n          node-version: 16\n          cache: pnpm\n      - name: Package & Deploy Docs\n        run: |\n          pnpm install\n          pnpm run build\n          ./scripts/docs-config.sh \"master\"\n          pnpm run docs\n          pnpm pack\n          ./scripts/deploy-docs.sh \"master\"\n        env:\n          GITHUB_TOKEN: ${{ secrets.GH_AUTH_TOKEN }}\n          GH_AUTH_EMAIL: ${{ secrets.GH_AUTH_EMAIL }}\n"
  },
  {
    "path": ".github/workflows/release-drafter.yml",
    "content": "name: Release Drafter\n\non:\n  push:\n    branches:\n      - master\n  workflow_dispatch:\n\npermissions:\n  contents: read\n\njobs:\n  correct_repository:\n    permissions:\n      contents: none\n    runs-on: ubuntu-latest\n    steps:\n    - name: fail on fork\n      if: github.repository_owner != 'chartjs'\n      run: exit 1\n\n  update_release_draft:\n    permissions:\n      contents: write  # for release-drafter/release-drafter to create a github release\n      pull-requests: write  # for release-drafter/release-drafter to add label to PR\n    needs: correct_repository\n    runs-on: ubuntu-latest\n    steps:\n      - uses: release-drafter/release-drafter@v6\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Release\n\non:\n  release:\n    types: [published]\n\npermissions:\n  contents: read\n\njobs:\n  setup:\n    permissions:\n      contents: none\n    runs-on: ubuntu-latest\n    outputs:\n      version: ${{ steps.trim.outputs.version }}\n    steps:\n      - id: trim\n        run: echo \"version=${TAG:1}\" >> $GITHUB_OUTPUT\n        env:\n          TAG: ${{ github.event.release.tag_name }}\n\n  release:\n    permissions:\n      contents: write  # for actions/upload-release-asset to upload release asset\n    needs: setup\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      - uses: pnpm/action-setup@v4.2.0\n      - uses: actions/setup-node@v6\n        with:\n          registry-url: https://registry.npmjs.org/\n          node-version: 16\n          cache: pnpm\n      - name: Setup and build\n        run: |\n          pnpm install\n          pnpm install -g json\n          json -I -f package.json -e \"this.version=\\\"$VERSION\\\"\"\n          pnpm run build\n          ./scripts/docs-config.sh \"$VERSION\" release\n          pnpm run docs\n          pnpm pack\n        env:\n          VERSION: ${{ needs.setup.outputs.version }}\n      - name: Publish to NPM\n        run: ./scripts/publish.sh\n        env:\n          NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}\n          VERSION: ${{ needs.setup.outputs.version }}\n      - name: Deploy Docs\n        run: ./scripts/deploy-docs.sh \"$VERSION\" release\n        env:\n          GITHUB_TOKEN: ${{ secrets.GH_AUTH_TOKEN }}\n          GH_AUTH_EMAIL: ${{ secrets.GH_AUTH_EMAIL }}\n          VERSION: ${{ needs.setup.outputs.version }}\n      - name: Upload NPM package file\n        id: upload-npm-package-file\n        uses: actions/upload-release-asset@v1\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          VERSION: ${{ needs.setup.outputs.version }}\n        with:\n          upload_url: ${{ github.event.release.upload_url }}\n          asset_path: ${{ format('chart.js-{0}.tgz', needs.setup.outputs.version) }}\n          asset_name: ${{ format('chart.js-{0}.tgz', needs.setup.outputs.version) }}\n          asset_content_type: application/gzip\n  release-tag:\n    needs: [setup, release]\n    runs-on: ubuntu-latest\n    if: \"!github.event.release.prerelease\"\n    steps:\n      - uses: actions/checkout@v6\n      - uses: pnpm/action-setup@v4.2.0\n      - uses: actions/setup-node@v6\n        with:\n          registry-url: https://registry.npmjs.org/\n          node-version: 16\n          cache: pnpm\n      - name: Setup and build\n        run: |\n          pnpm install\n          pnpm install -g json\n          json -I -f package.json -e \"this.version=\\\"$VERSION\\\"\"\n          pnpm run build\n          ./scripts/docs-config.sh \"$VERSION\"\n          pnpm run docs\n        env:\n          VERSION: ${{ needs.setup.outputs.version }}\n      - name: Deploy Docs\n        run: ./scripts/deploy-docs.sh \"$VERSION\"\n        env:\n          GITHUB_TOKEN: ${{ secrets.GH_AUTH_TOKEN }}\n          GH_AUTH_EMAIL: ${{ secrets.GH_AUTH_EMAIL }}\n          VERSION: ${{ needs.setup.outputs.version }}\n"
  },
  {
    "path": ".gitignore",
    "content": "# Deployment\n/coverage\n/custom\n/dist\n/gh-pages\n\n# Node.js\nnode_modules/\nnpm-debug.log*\n\n# Docs\n.cache-loader\nbuild/\n\n# Generated type docs\ndocs/api\ndocs/.vuepress/dist\n\n# Development\n.DS_Store\n.env.local\n.env.development.local\n.env.test.local\n.env.production.local\n.idea\n.project\n.settings\n.vscode\n.zed\n*.log\n*.swp\n*.stackdump\n\n# Generated\n/test/types/autogen*.ts\n\n# Eslint\n.eslintcache\n"
  },
  {
    "path": ".htmllintrc",
    "content": "{\n\t\"indent-style\": \"tabs\",\n\t\"line-end-style\": false,\n\t\"attr-quote-style\": \"double\",\n\t\"spec-char-escape\": false,\n\t\"attr-bans\": [\n\t\t\"align\",\n\t\t\"background\",\n\t\t\"bgcolor\",\n\t\t\"border\",\n\t\t\"frameborder\",\n\t\t\"longdesc\",\n\t\t\"marginwidth\",\n\t\t\"marginheight\",\n\t\t\"scrolling\"\n\t],\n\t\"tag-bans\": [ \"b\", \"i\" ],\n\t\"id-class-style\": false\n}\n"
  },
  {
    "path": "LICENSE.md",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2014-2024 Chart.js Contributors\n\nPermission 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:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE 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.\n"
  },
  {
    "path": "MAINTAINING.md",
    "content": "# Maintaining\n\n## Release Process\n\nChart.js relies on [Travis CI](https://travis-ci.org/) to automate the library [releases](https://github.com/chartjs/Chart.js/releases).\n\n### Releasing a New Version\n\n1. Update the release version on [GitHub](https://github.com/chartjs/Chart.js/releases/new) for the release drafted by the `release-drafter` tool\n2. Publish the release\n3. follow the build process on [GitHub Actions](https://github.com/chartjs/Chart.js/actions?query=workflow%3A%22Node.js+Package%22)\n\nCreation of this tag triggers a new build:\n\n* `Chart.js.zip` package is generated, containing dist files and examples\n* `dist/*.js`, `types/*.ts`, and `Chart.js.zip` are attached to the GitHub release (downloads)\n* A new npm package is published on [npmjs](https://www.npmjs.com/package/chart.js)\n\nFinally, [cdnjs](https://cdnjs.com/libraries/Chart.js) is automatically updated from the npm release.\n\n### Releasing a patch version\n\nIf there is a need to create a patch version for an older release:\n\n1. Create a branch for the patch version (without the `v` prefix)\n2. Cherry pick the needed commit(s) to that new branch from master\n3. Trigger the release-drafter workflow on that branch from the actions.\n4. Follow the procedure for [Releasing a New Version](#releasing-a-new-version)\n\n### Further Reading\n\n* [GitHub Action releases](https://github.com/chartjs/Chart.js/pull/7891)\n* [dist/* files](https://github.com/chartjs/Chart.js/issues/3033)\n* [cdnjs npm auto update](https://github.com/cdnjs/cdnjs/pull/8401)\n"
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\">\n  <a href=\"https://www.chartjs.org/\" target=\"_blank\">\n    <img src=\"https://www.chartjs.org/media/logo-title.svg\" alt=\"https://www.chartjs.org/\"><br/>\n  </a>\n    Simple yet flexible JavaScript charting for designers & developers\n</p>\n\n<p align=\"center\">\n    <a href=\"https://www.chartjs.org/docs/latest/getting-started/installation.html\"><img src=\"https://img.shields.io/github/release/chartjs/Chart.js.svg?style=flat-square&maxAge=600\" alt=\"Downloads\"></a>\n    <a href=\"https://github.com/chartjs/Chart.js/actions?query=workflow%3ACI+branch%3Amaster\"><img alt=\"GitHub Workflow Status\" src=\"https://img.shields.io/github/actions/workflow/status/chartjs/Chart.js/ci.yml?branch=master&style=flat-square\"></a>\n    <a href=\"https://coveralls.io/github/chartjs/Chart.js?branch=master\"><img src=\"https://img.shields.io/coveralls/chartjs/Chart.js.svg?style=flat-square&maxAge=600\" alt=\"Coverage\"></a>\n    <a href=\"https://github.com/chartjs/awesome\"><img src=\"https://awesome.re/badge-flat2.svg\" alt=\"Awesome\"></a>\n    <a href=\"https://discord.gg/HxEguTK6av\"><img src=\"https://img.shields.io/badge/discord-chartjs-blue?style=flat-square&maxAge=3600\" alt=\"Discord\"></a>\n</p>\n\n## Documentation\n\nAll the links point to the new version 4 of the lib.\n\n* [Introduction](https://www.chartjs.org/docs/latest/)\n* [Getting Started](https://www.chartjs.org/docs/latest/getting-started/index)\n* [General](https://www.chartjs.org/docs/latest/general/data-structures)\n* [Configuration](https://www.chartjs.org/docs/latest/configuration/index)\n* [Charts](https://www.chartjs.org/docs/latest/charts/line)\n* [Axes](https://www.chartjs.org/docs/latest/axes/index)\n* [Developers](https://www.chartjs.org/docs/latest/developers/index)\n* [Popular Extensions](https://github.com/chartjs/awesome)\n* [Samples](https://www.chartjs.org/samples/)\n\nIn case you are looking for an older version of the docs, you will have to specify the specific version in the url like this: [https://www.chartjs.org/docs/2.9.4/](https://www.chartjs.org/docs/2.9.4/)\n\n## Contributing\n\nInstructions on building and testing Chart.js can be found in [the documentation](https://www.chartjs.org/docs/master/developers/contributing.html#building-and-testing). Before submitting an issue or a pull request, please take a moment to look over the [contributing guidelines](https://www.chartjs.org/docs/master/developers/contributing) first. For support, please post questions on [Stack Overflow](https://stackoverflow.com/questions/tagged/chart.js) with the `chart.js` tag.\n\n## License\n\nChart.js is available under the [MIT license](LICENSE.md).\n"
  },
  {
    "path": "auto/auto.cjs",
    "content": "const chartjs = require('../dist/chart.cjs');\nconst {Chart, registerables} = chartjs;\n\nChart.register(...registerables);\n\nmodule.exports = Object.assign(Chart, chartjs);\n"
  },
  {
    "path": "auto/auto.d.ts",
    "content": "import {Chart} from '../dist/types.js';\n\nexport * from '../dist/types.js';\nexport default Chart;\n"
  },
  {
    "path": "auto/auto.js",
    "content": "import {Chart, registerables} from '../dist/chart.js';\n\nChart.register(...registerables);\n\nexport * from '../dist/chart.js';\nexport default Chart;\n"
  },
  {
    "path": "auto/package.json",
    "content": "{\n    \"name\": \"chart.js-auto\",\n    \"private\": true,\n    \"description\": \"Auto registering package. Exists to support bundlers without exports support such as webpack 4.\",\n    \"type\": \"module\",\n    \"main\": \"./auto.cjs\",\n    \"module\": \"./auto.js\",\n    \"exports\": {\n        \"types\": \"./auto.d.ts\",\n        \"import\": \"./auto.js\",\n        \"require\": \"./auto.cjs\"\n    },\n    \"types\": \"./auto.d.ts\"\n}\n"
  },
  {
    "path": "composer.json",
    "content": "{\n    \"name\": \"nnnick/chartjs\",\n    \"type\": \"library\",\n    \"description\": \"Simple HTML5 charts using the canvas element.\",\n    \"keywords\": [\n        \"chart\",\n        \"js\"\n    ],\n    \"homepage\": \"https://www.chartjs.org/\",\n    \"license\": \"MIT\",\n    \"authors\": [\n        {\n            \"name\": \"NICK DOWNIE\",\n            \"email\": \"hello@nickdownie.com\"\n        }\n    ],\n    \"require\": {\n        \"php\": \">=5.3.3\"\n    },\n    \"minimum-stability\": \"stable\",\n    \"extra\": {\n        \"branch-alias\": {\n            \"release/2.0\": \"v2.0-dev\"\n        }\n    }\n}\n"
  },
  {
    "path": "docs/.vuepress/config.ts",
    "content": "import * as path from 'path';\nimport markdownItInclude from 'markdown-it-include';\nimport { DefaultThemeConfig, defineConfig, PluginTuple } from 'vuepress/config';\n\nconst docsVersion = \"VERSION\";\nconst base: `/${string}/` = process.env.NODE_ENV === \"development\" ? '/docs/master/' : `/docs/${docsVersion}/`;\n\nexport default defineConfig({\n  title: 'Chart.js',\n  description: 'Open source HTML5 Charts for your website',\n  theme: 'chartjs',\n  base,\n  dest: path.resolve(__dirname, '../../dist/docs'),\n  head: [\n    ['link', {rel: 'icon', href: '/favicon.ico'}],\n  ],\n  plugins: [\n    'tabs',\n    ['flexsearch'],\n    ['@vuepress/html-redirect', {\n      countdown: 0,\n    }],\n    [\n      '@vuepress/google-analytics',\n      {\n        'ga': 'UA-28909194-3'\n      }\n    ],\n    ['redirect', {\n      redirectors: [\n        // Default sample page when accessing /samples.\n        {base: '/samples', alternative: ['information']},\n      ],\n    }],\n    ['vuepress-plugin-code-copy', true],\n    ['vuepress-plugin-typedoc', {\n        entryPoints: ['../../src/types/index.d.ts'],\n        hideInPageTOC: true,\n        tsconfig: path.resolve(__dirname, '../../tsconfig.json'),\n      },\n    ],\n    ['@simonbrunel/vuepress-plugin-versions', {\n      filters: {\n        suffix: (tag) => tag ? ` (${tag})` : '',\n        title: (v, vars) => {\n          return window.location.href.includes('master') ? 'Development (master)' :\n                 vars.tag === 'latest' ? 'Latest (' + v + ')' :\n                 v + (vars.tag ? ` (${vars.tag})` : '') + ' (outdated)';\n        },\n      },\n      menu: {\n        text: '{{version|title}}',\n        items: [\n          {\n            text: 'Documentation',\n            items: [\n              {\n                text: 'Development (master)',\n                link: '/docs/master/',\n              },\n              {\n                text: 'Latest version',\n                link: '/docs/latest/',\n              },\n              {\n                type: 'versions',\n                text: '{{version}}{{tag|suffix}}',\n                link: '/docs/{{version}}/',\n                exclude: /^[01]\\.|2\\.[0-5]\\./,\n                group: 'minor',\n              }\n            ]\n          },\n          {\n            text: 'Release notes (5 latest)',\n            items: [\n              {\n                type: 'versions',\n                limit: 5,\n                target: '_blank',\n                group: 'patch',\n                link: 'https://github.com/chartjs/Chart.js/releases/tag/v{{version}}'\n              }\n            ]\n          }\n        ]\n      },\n    }],\n  ] as PluginTuple[],\n  chainWebpack(config) {\n    config.merge({\n      resolve: {\n        alias: {\n          'chart.js': path.resolve(__dirname, '../../dist/chart.js'),\n        }\n      }\n    })\n\n    config.module.rule('images').use('url-loader').tap(options => ({\n      ...options,\n      esModule: false\n    }))\n  },\n  markdown: {\n    extendMarkdown: md => {\n      md.use(markdownItInclude, path.resolve(__dirname, '../'));\n    }\n  },\n  themeConfig: {\n    repo: 'chartjs/Chart.js',\n    logo: '/favicon.ico',\n    lastUpdated: 'Last Updated',\n    searchPlaceholder: 'Search...',\n    editLinks: false,\n    docsDir: 'docs',\n    chart: {\n      imports: [\n        ['scripts/register.js'],\n        ['scripts/utils.js', 'Utils'],\n        ['scripts/helpers.js', 'helpers'],\n        ['scripts/components.js', 'components']\n      ]\n    },\n    nav: [\n      {text: 'Home', link: '/'},\n      {text: 'API', link: '/api/'},\n      {text: 'Samples', link: `/samples/`},\n      {\n        text: 'Ecosystem',\n        ariaLabel: 'Community Menu',\n        items: [\n          { text: 'Awesome', link: 'https://github.com/chartjs/awesome' },\n          { text: 'Discord', link: 'https://discord.gg/HxEguTK6av' },\n          { text: 'Stack Overflow', link: 'https://stackoverflow.com/questions/tagged/chart.js' }\n        ]\n      }\n    ],\n    sidebar: {\n      '/api/': 'API',\n      '/samples/': [\n        'information',\n        {\n          title: 'Bar Charts',\n          children: [\n            'bar/border-radius',\n            'bar/floating',\n            'bar/horizontal',\n            'bar/stacked',\n            'bar/stacked-groups',\n            'bar/vertical',\n          ]\n        },\n        {\n          title: 'Line Charts',\n          children: [\n            'line/interpolation',\n            'line/line',\n            'line/multi-axis',\n            'line/point-styling',\n            'line/segments',\n            'line/stepped',\n            'line/styling',\n          ]\n        },\n        {\n          title: 'Other charts',\n          children: [\n            'other-charts/bubble',\n            'other-charts/combo-bar-line',\n            'other-charts/doughnut',\n            'other-charts/multi-series-pie',\n            'other-charts/pie',\n            'other-charts/polar-area',\n            'other-charts/polar-area-center-labels',\n            'other-charts/radar',\n            'other-charts/radar-skip-points',\n            'other-charts/scatter',\n            'other-charts/scatter-multi-axis',\n            'other-charts/stacked-bar-line',\n          ]\n        },\n        {\n          title: 'Area charts',\n          children: [\n            'area/line-boundaries',\n            'area/line-datasets',\n            'area/line-drawtime',\n            'area/line-stacked',\n            'area/radar'\n          ]\n        },\n        {\n          title: 'Scales',\n          children: [\n            'scales/linear-min-max',\n            'scales/linear-min-max-suggested',\n            'scales/linear-step-size',\n            'scales/log',\n            'scales/stacked',\n            'scales/time-line',\n            'scales/time-max-span',\n            'scales/time-combo',\n          ]\n        },\n        {\n          title: 'Scale Options',\n          children: [\n            'scale-options/center',\n            'scale-options/grid',\n            'scale-options/ticks',\n            'scale-options/titles',\n          ]\n        },\n        {\n          title: 'Legend',\n          children: [\n            'legend/events',\n            'legend/html',\n            'legend/point-style',\n            'legend/position',\n            'legend/title',\n          ]\n        },\n        {\n          title: 'Title',\n          children: [\n            'title/alignment',\n          ]\n        },\n        {\n          title: 'Subtitle',\n          children: [\n            'subtitle/basic',\n          ]\n        },        {\n          title: 'Tooltip',\n          children: [\n            'tooltip/content',\n            'tooltip/html',\n            'tooltip/interactions',\n            'tooltip/point-style',\n            'tooltip/position',\n          ]\n        },\n        {\n          title: 'Scriptable Options',\n          children: [\n            'scriptable/bar',\n            'scriptable/bubble',\n            'scriptable/line',\n            'scriptable/pie',\n            'scriptable/polar',\n            'scriptable/radar',\n          ]\n        },\n        {\n          title: 'Animations',\n          children: [\n            'animations/delay',\n            'animations/drop',\n            'animations/loop',\n            'animations/progressive-line',\n            'animations/progressive-line-easing',\n          ]\n        },\n        {\n          title: 'Advanced',\n          children: [\n            'advanced/data-decimation',\n            'advanced/derived-axis-type',\n            'advanced/derived-chart-type',\n            'advanced/linear-gradient',\n            'advanced/programmatic-events',\n            'advanced/progress-bar',\n            'advanced/radial-gradient',\n          ]\n        },\n        {\n          title: 'Plugins',\n          children: [\n            'plugins/chart-area-border',\n            'plugins/doughnut-empty-state',\n            'plugins/quadrants',\n          ]\n        },\n        'utils'\n      ],\n      '/': [\n        '',\n        {\n          title: 'Getting Started',\n          children: [\n            'getting-started/',\n            'getting-started/installation',\n            'getting-started/integration',\n            'getting-started/usage',\n            'getting-started/using-from-node-js',\n          ]\n        },\n        {\n          title: 'General',\n          children: [\n            'general/accessibility',\n            'general/colors',\n            'general/data-structures',\n            'general/fonts',\n            'general/options',\n            'general/padding',\n            'general/performance'\n          ]\n        },\n        {\n          title: 'Configuration',\n          children: [\n            'configuration/',\n            'configuration/animations',\n            'configuration/canvas-background',\n            'configuration/decimation',\n            'configuration/device-pixel-ratio',\n            'configuration/elements',\n            'configuration/interactions',\n            'configuration/layout',\n            'configuration/legend',\n            'configuration/locale',\n            'configuration/responsive',\n            'configuration/subtitle',\n            'configuration/title',\n            'configuration/tooltip',\n          ]\n        },\n        {\n          title: 'Chart Types',\n          children: [\n            'charts/area',\n            'charts/bar',\n            'charts/bubble',\n            'charts/doughnut',\n            'charts/line',\n            'charts/mixed',\n            'charts/polar',\n            'charts/radar',\n            'charts/scatter',\n          ]\n        },\n        {\n          title: 'Axes',\n          children: [\n            'axes/',\n            {\n              title: 'Cartesian',\n              children: [\n                'axes/cartesian/',\n                'axes/cartesian/category',\n                'axes/cartesian/linear',\n                'axes/cartesian/logarithmic',\n                'axes/cartesian/time',\n                'axes/cartesian/timeseries'\n              ],\n            },\n            {\n              title: 'Radial',\n              children: [\n                'axes/radial/',\n                'axes/radial/linear'\n              ],\n            },\n            'axes/labelling',\n            'axes/styling'\n          ]\n        },\n        {\n          title: 'Developers',\n          children: [\n            'developers/',\n            'developers/api',\n            'developers/axes',\n            'developers/charts',\n            'developers/contributing',\n            'developers/plugins',\n            'developers/publishing',\n            ['api/', 'TypeDoc'],\n            'developers/updates',\n          ]\n        },\n        {\n          title: 'Migration',\n          children: [\n            'migration/v4-migration',\n            'migration/v3-migration',\n          ]\n        },\n      ],\n    } as any\n  } as DefaultThemeConfig\n});\n"
  },
  {
    "path": "docs/.vuepress/redirects",
    "content": "/charts/ /charts/line.html\n/general/ /general/data-structures.html\n/samples/ /samples/information.html\n/getting-started/v3-migration/ /migration/v3-migration.html\n"
  },
  {
    "path": "docs/.vuepress/styles/index.styl",
    "content": "@require '~vuepress-plugin-tabs/dist/themes/default.styl'\n\n.theme-default-content\n  &:not(.custom)\n    max-width: unset\n  \n  .chart-view\n    max-width 800px\n\n.sidebar-group.is-sub-group.depth-1\n  > .sidebar-group-items\n    border-left 1px solid rgba($accentColor, 0.25)\n\n  > .sidebar-heading:not(.open)\n    border-left 1px solid rgba($accentColor, 0.25)\n    margin-left: 0\n\n  > .sidebar-heading\n    padding-left calc(1.475rem - 1px)\n    transition border-color .25s\n    padding 0.35rem 1.475rem\n    border-left-width 3px\n    margin-left -1px\n    font-size 1em\n    line-height 1.4\n    opacity 1 !important\n\n    &.active, &.open\n      border-left-color $accentColor\n      color $accentColor\n      font-weight bold\n\n    >.arrow\n      display none\n\n    >.sidebar-group-items\n      padding-left: 0\n\n.sidebar-group.is-sub-group.depth-1:hover .sidebar-heading:not(.open)\n  color $accentColor\n  margin-left -1px\n  border-left 3px solid rgba($accentColor, 0.25)\n  padding-left calc(1.475rem - 1px)\n"
  },
  {
    "path": "docs/axes/_common.md",
    "content": "### Common options to all axes\n\nNamespace: `options.scales[scaleId]`\n\n| Name | Type | Default | Description\n| ---- | ---- | ------- | -----------\n| `type` | `string` | | Type of scale being employed. Custom scales can be created and registered with a string key. This allows changing the type of an axis for a chart.\n| `alignToPixels` | `boolean` | `false` | Align pixel values to device pixels.\n| `backgroundColor` | [`Color`](/general/colors.md) | | Background color of the scale area.\n| `border` | `object` | | Border configuration. [more...](/axes/styling.md#border-configuration)\n| `display` | `boolean`\\|`string` | `true` | Controls the axis global visibility (visible when `true`, hidden when `false`). When `display: 'auto'`, the axis is visible only if at least one associated dataset is visible.\n| `grid` | `object` | | Grid line configuration. [more...](/axes/styling.md#grid-line-configuration)\n| `min` | `number` | | User defined minimum number for the scale, overrides minimum value from data. [more...](/axes/index.md#axis-range-settings)\n| `max` | `number` | | User defined maximum number for the scale, overrides maximum value from data. [more...](/axes/index.md#axis-range-settings)\n| `reverse` | `boolean` | `false` | Reverse the scale.\n| `stacked` | `boolean`\\|`string` | `false` | Should the data be stacked. [more...](/axes/index.md#stacking)\n| `suggestedMax` | `number` | | Adjustment used when calculating the maximum data value. [more...](/axes/index.md#axis-range-settings)\n| `suggestedMin` | `number` | | Adjustment used when calculating the minimum data value. [more...](/axes/index.md#axis-range-settings)\n| `ticks` | `object` | | Tick configuration. [more...](/axes/index.md#tick-configuration)\n| `weight` | `number` | `0` | The weight used to sort the axis. Higher weights are further away from the chart area.\n"
  },
  {
    "path": "docs/axes/_common_ticks.md",
    "content": "### Common tick options to all axes\n\nNamespace: `options.scales[scaleId].ticks`\n\n| Name | Type | Scriptable | Default | Description\n| ---- | ---- | :-------------------------------: | ------- | -----------\n| `backdropColor` | [`Color`](../../general/colors.md) | Yes | `'rgba(255, 255, 255, 0.75)'` | Color of label backdrops.\n| `backdropPadding` | [`Padding`](../../general/padding.md) | | `2` | Padding of label backdrop.\n| `callback` | `function` | | | Returns the string representation of the tick value as it should be displayed on the chart. See [callback](/axes/labelling.md#creating-custom-tick-formats).\n| `display` | `boolean` | | `true` | If true, show tick labels.\n| `color` | [`Color`](/general/colors.md) | Yes | `Chart.defaults.color` | Color of ticks.\n| `font` | `Font` | Yes | `Chart.defaults.font` | See [Fonts](/general/fonts.md)\n| `major` | `object` | | `{}` | [Major ticks configuration](/axes/styling.md#major-tick-configuration).\n| `padding` | `number` | | `3` | Sets the offset of the tick labels from the axis\n| `showLabelBackdrop` | `boolean` | Yes | `true` for radial scale, `false` otherwise | If true, draw a background behind the tick labels.\n| `textStrokeColor` | [`Color`](/general/colors.md) | Yes | `` | The color of the stroke around the text.\n| `textStrokeWidth` | `number` | Yes | `0` | Stroke width around the text.\n| `z` | `number` | | `0` | z-index of tick layer. Useful when ticks are drawn on chart area. Values &lt;= 0 are drawn under datasets, &gt; 0 on top.\n"
  },
  {
    "path": "docs/axes/cartesian/_common.md",
    "content": "### Common options to all cartesian axes\n\nNamespace: `options.scales[scaleId]`\n\n| Name | Type | Default | Description\n| ---- | ---- | ------- | -----------\n| `bounds` | `string` | `'ticks'` | Determines the scale bounds. [more...](./index.md#scale-bounds)\n| `clip` | `boolean` | `true` | If true, clip the dataset drawing against the size of the scale instead of chart area\n| `position` | `string` \\| `object` | | Position of the axis. [more...](./index.md#axis-position)\n| `stack` | `string` | | Stack group. Axes at the same `position` with same `stack` are stacked.\n| `stackWeight` | `number` | 1 | Weight of the scale in stack group. Used to determine the amount of allocated space for the scale within the group.\n| `axis` | `string` | | Which type of axis this is. Possible values are: `'x'`, `'y'`. If not set, this is inferred from the first character of the ID which should be `'x'` or `'y'`.\n| `offset` | `boolean` | `false` | If true, extra space is added to the both edges and the axis is scaled to fit into the chart area. This is set to `true` for a bar chart by default.\n| `title` | `object` | | Scale title configuration. [more...](../labelling.md#scale-title-configuration)\n"
  },
  {
    "path": "docs/axes/cartesian/_common_ticks.md",
    "content": "### Common tick options to all cartesian axes\n\nNamespace: `options.scales[scaleId].ticks`\n\n| Name | Type | Default | Description\n| ---- | ---- | ------- | -----------\n| `align` | `string` | `'center'` | The tick alignment along the axis. Can be `'start'`, `'center'`, `'end'`, or `'inner'`. `inner` alignment means align `start` for first tick and `end` for the last tick of horizontal axis\n| `crossAlign` | `string` | `'near'` | The tick alignment perpendicular to the axis. Can be `'near'`, `'center'`, or `'far'`. See [Tick Alignment](/axes/cartesian/#tick-alignment)\n| `sampleSize` | `number` | `ticks.length` | The number of ticks to examine when deciding how many labels will fit. Setting a smaller value will be faster, but may be less accurate when there is large variability in label length.\n| `autoSkip` | `boolean` | `true` | If true, automatically calculates how many labels can be shown and hides labels accordingly. Labels will be rotated up to `maxRotation` before skipping any. Turn `autoSkip` off to show all labels no matter what.\n| `autoSkipPadding` | `number` | `3` | Padding between the ticks on the horizontal axis when `autoSkip` is enabled.\n| `includeBounds` | `boolean` | `true` | Should the defined `min` and `max` values be presented as ticks even if they are not \"nice\".\n| `labelOffset` | `number` | `0` | Distance in pixels to offset the label from the centre point of the tick (in the x-direction for the x-axis, and the y-direction for the y-axis). *Note: this can cause labels at the edges to be cropped by the edge of the canvas*\n| `maxRotation` | `number` | `50` | Maximum rotation for tick labels when rotating to condense labels. Note: Rotation doesn't occur until necessary. *Note: Only applicable to horizontal scales.*\n| `minRotation` | `number` | `0` | Minimum rotation for tick labels. *Note: Only applicable to horizontal scales.*\n| `mirror` | `boolean` | `false` | Flips tick labels around axis, displaying the labels inside the chart instead of outside. *Note: Only applicable to vertical scales.*\n| `padding` | `number` | `0` | Padding between the tick label and the axis. When set on a vertical axis, this applies in the horizontal (X) direction. When set on a horizontal axis, this applies in the vertical (Y) direction.\n| `maxTicksLimit` | `number` | `11` | Maximum number of ticks and gridlines to show.\n"
  },
  {
    "path": "docs/axes/cartesian/category.md",
    "content": "# Category Axis\n\nIf the global configuration is used, labels are drawn from one of the label arrays included in the chart data. If only `data.labels` is defined, this will be used. If `data.xLabels` is defined and the axis is horizontal, this will be used. Similarly, if `data.yLabels` is defined and the axis is vertical, this property will be used. Using both `xLabels` and `yLabels` together can create a chart that uses strings for both the X and Y axes.\n\nSpecifying any of the settings above defines the x-axis as `type: 'category'` if not defined otherwise. For more fine-grained control of category labels, it is also possible to add `labels` as part of the category axis definition. Doing so does not apply the global defaults.\n\n## Category Axis Definition\n\nGlobally:\n\n```javascript\nlet chart = new Chart(ctx, {\n    type: ...\n    data: {\n        labels: ['January', 'February', 'March', 'April', 'May', 'June'],\n        datasets: ...\n    }\n});\n```\n\nAs part of axis definition:\n\n```javascript\nlet chart = new Chart(ctx, {\n    type: ...\n    data: ...\n    options: {\n        scales: {\n            x: {\n                type: 'category',\n                labels: ['January', 'February', 'March', 'April', 'May', 'June']\n            }\n        }\n    }\n});\n```\n\n## Configuration Options\n\n### Category Axis specific options\n\nNamespace: `options.scales[scaleId]`\n\n| Name | Type | Description\n| ---- | ---- | -----------\n| `min` | `string`\\|`number` | The minimum item to display. [more...](#min-max-configuration)\n| `max` | `string`\\|`number` | The maximum item to display. [more...](#min-max-configuration)\n| `labels` | `string[]`\\|`string[][]` | An array of labels to display. When an individual label is an array of strings, each item is rendered on a new line.\n\n!!!include(axes/cartesian/_common.md)!!!\n\n!!!include(axes/_common.md)!!!\n\n## Tick Configuration\n\n!!!include(axes/cartesian/_common_ticks.md)!!!\n\n!!!include(axes/_common_ticks.md)!!!\n\n## Min Max Configuration\n\nFor both the `min` and `max` properties, the value must be `string` in the `labels` array or `numeric` value as an index of a label in that array. In the example below, the x axis would only display \"March\" through \"June\".\n\n```javascript\nlet chart = new Chart(ctx, {\n    type: 'line',\n    data: {\n        datasets: [{\n            data: [10, 20, 30, 40, 50, 60]\n        }],\n        labels: ['January', 'February', 'March', 'April', 'May', 'June']\n    },\n    options: {\n        scales: {\n            x: {\n                min: 'March'\n            }\n        }\n    }\n});\n```\n\n## Internal data format\n\nInternally category scale uses label indices\n"
  },
  {
    "path": "docs/axes/cartesian/index.md",
    "content": "# Cartesian Axes\n\nAxes that follow a cartesian grid are known as 'Cartesian Axes'. Cartesian axes are used for line, bar, and bubble charts. Five cartesian axes are included in Chart.js by default.\n\n* [linear](./linear.md)\n* [logarithmic](./logarithmic.md)\n* [category](./category.md)\n* [time](./time.md)\n* [timeseries](./timeseries.md)\n\n## Visual Components\n\nA cartesian axis is composed of visual components that can be individually configured. These components are:\n\n* [border](#border)\n* [grid lines](#grid-lines)\n* [tick](#ticks-and-tick-marks)\n* [tick mark](#ticks-and-tick-marks)\n* [title](#title)\n\n### Border\n\nThe axis border is drawn at the edge of the axis, beside the chart area. In the image below, it is drawn in red.\n\n```js chart-editor\n// <block:setup:1>\nconst labels = Utils.months({count: 7});\nconst data = {\n  labels: labels,\n  datasets: [{\n    label: 'My First dataset',\n    backgroundColor: 'rgba(54, 162, 235, 0.5)',\n    borderColor: 'rgb(54, 162, 235)',\n    borderWidth: 1,\n    data: [10, 20, 30, 40, 50, 0, 5],\n  }]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'line',\n  data,\n  options: {\n    scales: {\n      x: {\n        border: {\n          color: 'red'\n        }\n      }\n    }\n  }\n};\n// </block:config>\n\nmodule.exports = {\n  actions: [],\n  config: config,\n};\n```\n\n### Grid lines\n\nThe grid lines for an axis are drawn on the chart area. In the image below, they are red.\n\n```js chart-editor\n// <block:setup:1>\nconst labels = Utils.months({count: 7});\nconst data = {\n  labels: labels,\n  datasets: [{\n    label: 'My First dataset',\n    backgroundColor: 'rgba(54, 162, 235, 0.5)',\n    borderColor: 'rgb(54, 162, 235)',\n    borderWidth: 1,\n    data: [10, 20, 30, 40, 50, 0, 5],\n  }]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'line',\n  data,\n  options: {\n    scales: {\n      x: {\n        grid: {\n          color: 'red',\n          borderColor: 'grey',\n          tickColor: 'grey'\n        }\n      }\n    }\n  }\n};\n// </block:config>\n\nmodule.exports = {\n  actions: [],\n  config: config,\n};\n```\n\n### Ticks and Tick Marks\n\nTicks represent data values on the axis that appear as labels. The tick mark is the extension of the grid line from the axis border to the label.\nIn this example, the tick mark is drawn in red while the tick label is drawn in blue.\n\n```js chart-editor\n// <block:setup:1>\nconst labels = Utils.months({count: 7});\nconst data = {\n  labels: labels,\n  datasets: [{\n    label: 'My First dataset',\n    backgroundColor: 'rgba(54, 162, 235, 0.5)',\n    borderColor: 'rgb(54, 162, 235)',\n    borderWidth: 1,\n    data: [10, 20, 30, 40, 50, 0, 5],\n  }]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'line',\n  data,\n  options: {\n    scales: {\n      x: {\n        grid: {\n          tickColor: 'red'\n        },\n        ticks: {\n          color: 'blue',\n        }\n      }\n    }\n  }\n};\n// </block:config>\n\nmodule.exports = {\n  actions: [],\n  config: config,\n};\n```\n\n### Title\n\nThe title component of the axis is used to label the data. In the example below, it is shown in red.\n\n```js chart-editor\n// <block:setup:1>\nconst labels = Utils.months({count: 7});\nconst data = {\n  labels: labels,\n  datasets: [{\n    label: 'My First dataset',\n    backgroundColor: 'rgba(54, 162, 235, 0.5)',\n    borderColor: 'rgb(54, 162, 235)',\n    borderWidth: 1,\n    data: [10, 20, 30, 40, 50, 0, 5],\n  }]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'line',\n  data,\n  options: {\n    scales: {\n      x: {\n        title: {\n          color: 'red',\n          display: true,\n          text: 'Month'\n        }\n      }\n    }\n  }\n};\n// </block:config>\n\nmodule.exports = {\n  actions: [],\n  config: config,\n};\n```\n\n## Common Configuration\n\n:::tip Note\nThese are only the common options supported by all cartesian axes. Please see the specific axis documentation for all the available options for that axis.\n:::\n\n!!!include(axes/cartesian/_common.md)!!!\n\n!!!include(axes/_common.md)!!!\n\n### Axis Position\n\nAn axis can either be positioned at the edge of the chart, at the center of the chart area, or dynamically with respect to a data value.\n\nTo position the axis at the edge of the chart, set the `position` option to one of: `'top'`, `'left'`, `'bottom'`, `'right'`.\nTo position the axis at the center of the chart area, set the `position` option to `'center'`. In this mode, either the `axis` option must be specified or the axis ID has to start with the letter 'x' or 'y'. This is so chart.js knows what kind of axis (horizontal or vertical) it is.\nTo position the axis with respect to a data value, set the `position` option to an object such as:\n\n```javascript\n{\n    x: -20\n}\n```\n\nThis will position the axis at a value of -20 on the axis with ID \"x\". For cartesian axes, only 1 axis may be specified.\n\n### Scale Bounds\n\nThe `bounds` property controls the scale boundary strategy (bypassed by `min`/`max` options).\n\n* `'data'`: makes sure data are fully visible, labels outside are removed\n* `'ticks'`: makes sure ticks are fully visible, data outside are truncated\n\n### Tick Configuration\n\n:::tip Note\nThese are only the common tick options supported by all cartesian axes. Please see specific axis documentation for all of the available options for that axis.\n:::\n\n!!!include(axes/cartesian/_common_ticks.md)!!!\n\n!!!include(axes/_common_ticks.md)!!!\n\n### Tick Alignment\n\nThe alignment of ticks is primarily controlled using two settings on the tick configuration object: `align` and `crossAlign`. The `align` setting configures how labels align with the tick mark along the axis direction (i.e. horizontal for a horizontal axis and vertical for a vertical axis). The `crossAlign` setting configures how labels align with the tick mark in the perpendicular direction (i.e. vertical for a horizontal axis and horizontal for a vertical axis). In the example below, the `crossAlign` setting is used to left align the labels on the Y axis.\n\n```js chart-editor\n// <block:setup:1>\nconst labels = Utils.months({count: 7});\nconst data = {\n  labels: labels,\n  datasets: [{\n    label: 'My First dataset',\n    backgroundColor: [\n      'rgba(255, 99, 132, 0.2)',\n      'rgba(255, 159, 64, 0.2)',\n      'rgba(255, 205, 86, 0.2)',\n      'rgba(75, 192, 192, 0.2)',\n      'rgba(54, 162, 235, 0.2)',\n      'rgba(153, 102, 255, 0.2)',\n      'rgba(201, 203, 207, 0.2)'\n    ],\n    borderColor: [\n      'rgb(255, 99, 132)',\n      'rgb(255, 159, 64)',\n      'rgb(255, 205, 86)',\n      'rgb(75, 192, 192)',\n      'rgb(54, 162, 235)',\n      'rgb(153, 102, 255)',\n      'rgb(201, 203, 207)'\n    ],\n    borderWidth: 1,\n    data: [65, 59, 80, 81, 56, 55, 40],\n  }]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'bar',\n  data,\n  options: {\n    indexAxis: 'y',\n    scales: {\n      y: {\n        ticks: {\n          crossAlign: 'far',\n        }\n      }\n    }\n  }\n};\n// </block:config>\n\nmodule.exports = {\n  actions: [],\n  config: config,\n};\n```\n\n:::tip Note\nThe `crossAlign` setting is only effective when these preconditions are met:\n\n* tick rotation is `0`\n* axis position is `'top'`, '`left'`, `'bottom'` or `'right'`\n:::\n\n### Axis ID\n\nThe properties `dataset.xAxisID` or `dataset.yAxisID` have to match to `scales` property. This is especially needed if multi-axes charts are used.\n\n```javascript\nconst myChart = new Chart(ctx, {\n    type: 'line',\n    data: {\n        datasets: [{\n            // This dataset appears on the first axis\n            yAxisID: 'first-y-axis'\n        }, {\n            // This dataset appears on the second axis\n            yAxisID: 'second-y-axis'\n        }]\n    },\n    options: {\n        scales: {\n            'first-y-axis': {\n                type: 'linear'\n            },\n            'second-y-axis': {\n                type: 'linear'\n            }\n        }\n    }\n});\n```\n\n## Creating Multiple Axes\n\nWith cartesian axes, it is possible to create multiple X and Y axes. To do so, you can add multiple configuration objects to the `xAxes` and `yAxes` properties. When adding new axes, it is important to ensure that you specify the type of the new axes as default types are **not** used in this case.\n\nIn the example below, we are creating two Y axes. We then use the `yAxisID` property to map the datasets to their correct axes.\n\n```javascript\nconst myChart = new Chart(ctx, {\n    type: 'line',\n    data: {\n        datasets: [{\n            data: [20, 50, 100, 75, 25, 0],\n            label: 'Left dataset',\n\n            // This binds the dataset to the left y axis\n            yAxisID: 'left-y-axis'\n        }, {\n            data: [0.1, 0.5, 1.0, 2.0, 1.5, 0],\n            label: 'Right dataset',\n\n            // This binds the dataset to the right y axis\n            yAxisID: 'right-y-axis'\n        }],\n        labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun']\n    },\n    options: {\n        scales: {\n            'left-y-axis': {\n                type: 'linear',\n                position: 'left'\n            },\n            'right-y-axis': {\n                type: 'linear',\n                position: 'right'\n            }\n        }\n    }\n});\n```\n"
  },
  {
    "path": "docs/axes/cartesian/linear.md",
    "content": "# Linear Axis\n\nThe linear scale is used to chart numerical data. It can be placed on either the x or y-axis. The scatter chart type automatically configures a line chart to use one of these scales for the x-axis. As the name suggests, linear interpolation is used to determine where a value lies on the axis.\n\n## Configuration Options\n\n### Linear Axis specific options\n\nNamespace: `options.scales[scaleId]`\n\n| Name | Type | Description\n| ---- | ---- | -----------\n| `beginAtZero` | `boolean` | if true, scale will include 0 if it is not already included.\n| `grace` | `number`\\|`string` | Percentage (string ending with `%`) or amount (number) for added room in the scale range above and below data. [more...](#grace)\n\n!!!include(axes/cartesian/_common.md)!!!\n\n!!!include(axes/_common.md)!!!\n\n## Tick Configuration\n\n### Linear Axis specific tick options\n\nNamespace: `options.scales[scaleId].ticks`\n\n| Name | Type | Scriptable | Default | Description\n| ---- | ---- | ------- | ------- | -----------\n| `count` | `number` | Yes | `undefined` | The number of ticks to generate. If specified, this overrides the automatic generation.\n| `format` | `object` | Yes | | The [`Intl.NumberFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat) options used by the default label formatter\n| `precision` | `number` | Yes | | if defined and `stepSize` is not specified, the step size will be rounded to this many decimal places.\n| `stepSize` | `number` | Yes | | User-defined fixed step size for the scale. [more...](#step-size)\n\n!!!include(axes/cartesian/_common_ticks.md)!!!\n\n!!!include(axes/_common_ticks.md)!!!\n\n## Step Size\n\nIf set, the scale ticks will be enumerated by multiple of `stepSize`, having one tick per increment. If not set, the ticks are labeled automatically using the nice numbers algorithm.\n\nThis example sets up a chart with a y-axis that creates ticks at `0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5`.\n\n```javascript\nlet options = {\n    scales: {\n        y: {\n            max: 5,\n            min: 0,\n            ticks: {\n                stepSize: 0.5\n            }\n        }\n    }\n};\n```\n\n## Grace\n\nIf the value is a string ending with `%`, it's treated as a percentage. If a number, it's treated as a value.\nThe value is added to the maximum data value and subtracted from the minimum data. This extends the scale range as if the data values were that much greater.\n\n```js chart-editor\n// <block:setup:1>\nconst labels = Utils.months({count: 7});\nconst data = {\n  labels: ['Positive', 'Negative'],\n  datasets: [{\n    data: [100, -50],\n    backgroundColor: 'rgb(255, 99, 132)'\n  }],\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'bar',\n  data,\n  options: {\n    scales: {\n      y: {\n        type: 'linear',\n        grace: '5%'\n      }\n    },\n    plugins: {\n      legend: false\n    }\n  }\n};\n// </block:config>\n\nmodule.exports = {\n  actions: [],\n  config: config,\n};\n```\n\n## Internal data format\n\nInternally, the linear scale uses numeric data.\n"
  },
  {
    "path": "docs/axes/cartesian/logarithmic.md",
    "content": "# Logarithmic Axis\n\nThe logarithmic scale is used to chart numerical data. It can be placed on either the x or y-axis. As the name suggests, logarithmic interpolation is used to determine where a value lies on the axis.\n\n## Configuration Options\n\n!!!include(axes/cartesian/_common.md)!!!\n\n!!!include(axes/_common.md)!!!\n\n## Tick Configuration\n\n### Logarithmic Axis specific options\n\nNamespace: `options.scales[scaleId].ticks`\n\n| Name | Type | Default | Description\n| ---- | ---- | ------- | -----------\n| `format` | `object` | | The [`Intl.NumberFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat) options used by the default label formatter\n\n!!!include(axes/cartesian/_common_ticks.md)!!!\n\n!!!include(axes/_common_ticks.md)!!!\n\n## Internal data format\n\nInternally, the logarithmic scale uses numeric data.\n"
  },
  {
    "path": "docs/axes/cartesian/time.md",
    "content": "# Time Cartesian Axis\n\nThe time scale is used to display times and dates. Data are spread according to the amount of time between data points. When building its ticks, it will automatically calculate the most comfortable unit based on the size of the scale.\n\n## Date Adapters\n\nThe time scale **requires** both a date library and a corresponding adapter to be present. Please choose from the [available adapters](https://github.com/chartjs/awesome#adapters).\n\n## Data Sets\n\n### Input Data\n\nSee [data structures](../../general/data-structures.md).\n\n### Date Formats\n\nWhen providing data for the time scale, Chart.js uses timestamps defined as milliseconds since the epoch (midnight January 1, 1970, UTC) internally. However, Chart.js also supports all of the formats that your chosen date adapter accepts. You should use timestamps if you'd like to set `parsing: false` for better performance.\n\n## Configuration Options\n\n### Time Axis specific options\n\nNamespace: `options.scales[scaleId]`\n\n| Name | Type | Default | Description\n| ---- | ---- | ------- | -----------\n| `min` | `number`\\|`string` | | The minimum item to display. [more...](#min-max-configuration)\n| `max` | `number`\\|`string` | | The maximum item to display. [more...](#min-max-configuration)\n| `suggestedMin` | `number`\\|`string` | | The minimum item to display if there is no datapoint before it. [more...](../index.md#axis-range-settings)\n| `suggestedMax` | `number`\\|`string` | | The maximum item to display if there is no datapoint behind it. [more...](../index.md#axis-range-settings)\n| `adapters.date` | `object` | `{}` | Options for adapter for external date library if that adapter needs or supports options\n| `bounds` | `string` | `'data'` | Determines the scale bounds. [more...](./index.md#scale-bounds)\n| `offsetAfterAutoskip` | `boolean` | `false` | If true, bar chart offsets are computed with auto skipped ticks.\n| `ticks.source` | `string` | `'auto'` | How ticks are generated. [more...](#ticks-source)\n| `time.displayFormats` | `object` | | Sets how different time units are displayed. [more...](#display-formats)\n| `time.isoWeekday` | `boolean`\\|`number` | `false` | If `boolean` and true and the unit is set to 'week', then the first day of the week will be Monday. Otherwise, it will be Sunday. If `number`, the index of the first day of the week (0 - Sunday, 6 - Saturday)\n| `time.parser` | `string`\\|`function` | | Custom parser for dates. [more...](#parser)\n| `time.round` | `string` | `false` | If defined, dates will be rounded to the start of this unit. See [Time Units](#time-units) below for the allowed units.\n| `time.tooltipFormat` | `string` | | The format string to use for the tooltip.\n| `time.unit` | `string` | `false` | If defined, will force the unit to be a certain type. See [Time Units](#time-units) section below for details.\n| `time.minUnit` | `string` | `'millisecond'` | The minimum display format to be used for a time unit.\n\n!!!include(axes/cartesian/_common.md)!!!\n\n!!!include(axes/_common.md)!!!\n\n#### Time Units\n\nThe following time measurements are supported. The names can be passed as strings to the `time.unit` config option to force a certain unit.\n\n* `'millisecond'`\n* `'second'`\n* `'minute'`\n* `'hour'`\n* `'day'`\n* `'week'`\n* `'month'`\n* `'quarter'`\n* `'year'`\n\nFor example, to create a chart with a time scale that always displayed units per month, the following config could be used.\n\n```javascript\nconst chart = new Chart(ctx, {\n    type: 'line',\n    data: data,\n    options: {\n        scales: {\n            x: {\n                type: 'time',\n                time: {\n                    unit: 'month'\n                }\n            }\n        }\n    }\n});\n```\n\n#### Display Formats\n\nYou may specify a map of display formats with a key for each unit:\n\n* `millisecond`\n* `second`\n* `minute`\n* `hour`\n* `day`\n* `week`\n* `month`\n* `quarter`\n* `year`\n\nThe format string used as a value depends on the date adapter you chose to use.\n\nFor example, to set the display format for the `quarter` unit to show the month and year, the following config might be passed to the chart constructor.\n\n```javascript\nconst chart = new Chart(ctx, {\n    type: 'line',\n    data: data,\n    options: {\n        scales: {\n            x: {\n                type: 'time',\n                time: {\n                    displayFormats: {\n                        quarter: 'MMM YYYY'\n                    }\n                }\n            }\n        }\n    }\n});\n```\n\n#### Ticks Source\n\nThe `ticks.source` property controls the ticks generation.\n\n* `'auto'`: generates \"optimal\" ticks based on scale size and time options\n* `'data'`: generates ticks from data (including labels from data `{x|y}` objects)\n* `'labels'`: generates ticks from user given `labels` ONLY\n\n#### Parser\n\nIf this property is defined as a string, it is interpreted as a custom format to be used by the date adapter to parse the date.\n\nIf this is a function, it must return a type that can be handled by your date adapter's `parse` method.\n\n## Min Max Configuration\n\nFor both the `min` and `max` properties, the value must be `string` that is parsable by your date adapter or a number with the amount of milliseconds that have elapsed since UNIX epoch.\nIn the example below the x axis will start at 7 November 2021.\n\n```javascript\nlet chart = new Chart(ctx, {\n    type: 'line',\n    data: {\n        datasets: [{\n            data: [{\n                x: '2021-11-06 23:39:30',\n                y: 50\n            }, {\n                x: '2021-11-07 01:00:28',\n                y: 60\n            }, {\n                x: '2021-11-07 09:00:28',\n                y: 20\n            }]\n        }],\n    },\n    options: {\n        scales: {\n            x: {\n                min: '2021-11-07 00:00:00',\n            }\n        }\n    }\n});\n```\n\n## Changing the scale type from Time scale to Logarithmic/Linear scale.\n\nWhen changing the scale type from Time scale to Logarithmic/Linear scale, you need to add `bounds: 'ticks'` to the scale options. Changing the `bounds` parameter is necessary because its default value is the `'data'` for the Time scale.\n\nInitial config:\n\n```javascript\nconst chart = new Chart(ctx, {\n    type: 'line',\n    data: data,\n    options: {\n        scales: {\n            x: {\n                type: 'time',\n            }\n        }\n    }\n});\n```\n\nScale update:\n\n```javascript\nchart.options.scales.x = {\n    type: 'logarithmic',\n    bounds: 'ticks'\n};\n```\n\n## Internal data format\n\nInternally time scale uses milliseconds since epoch\n"
  },
  {
    "path": "docs/axes/cartesian/timeseries.md",
    "content": "# Time Series Axis\n\nThe time series scale extends from the time scale and supports all the same options. However, for the time series scale, each data point is spread equidistant.\n\n## Example\n\n```javascript\nconst chart = new Chart(ctx, {\n    type: 'line',\n    data: data,\n    options: {\n        scales: {\n            x: {\n                type: 'timeseries',\n            }\n        }\n    }\n});\n```\n\n## More details\n\nPlease see [the time scale documentation](./time.md) for all other details.\n"
  },
  {
    "path": "docs/axes/index.md",
    "content": "# Axes\n\nAxes are an integral part of a chart. They are used to determine how data maps to a pixel value on the chart. In a cartesian chart, there is 1 or more X-axis and 1 or more Y-axis to map points onto the 2-dimensional canvas. These axes are known as ['cartesian axes'](./cartesian/).\n\nIn a radial chart, such as a radar chart or a polar area chart, there is a single axis that maps points in the angular and radial directions. These are known as ['radial axes'](./radial/).\n\nScales in Chart.js >v2.0 are significantly more powerful, but also different from those of v1.0.\n\n* Multiple X & Y axes are supported.\n* A built-in label auto-skip feature detects would-be overlapping ticks and labels and removes every nth label to keep things displayed normally.\n* Scale titles are supported.\n* New scale types can be extended without writing an entirely new chart type.\n\n## Default scales\n\nThe default `scaleId`'s for cartesian charts are `'x'` and `'y'`. For radial charts: `'r'`.\nEach dataset is mapped to a scale for each axis (x, y or r) it requires. The scaleId's that a dataset is mapped to is determined by the `xAxisID`, `yAxisID` or `rAxisID`.\nIf the ID for an axis is not specified, the first scale for that axis is used. If no scale for an axis is found, a new scale is created.\n\nSome examples:\n\nThe following chart will have `'x'` and `'y'` scales:\n\n```js\nlet chart = new Chart(ctx, {\n  type: 'line'\n});\n```\n\nThe following chart will have scales `'x'` and `'myScale'`:\n\n```js\nlet chart = new Chart(ctx, {\n  type: 'bar',\n  data: {\n    datasets: [{\n      data: [1, 2, 3]\n    }]\n  },\n  options: {\n    scales: {\n      myScale: {\n        type: 'logarithmic',\n        position: 'right', // `axis` is determined by the position as `'y'`\n      }\n    }\n  }\n});\n```\n\nThe following chart will have scales `'xAxis'` and `'yAxis'`:\n\n```js\nlet chart = new Chart(ctx, {\n  type: 'bar',\n  data: {\n    datasets: [{\n      yAxisID: 'yAxis'\n    }]\n  },\n  options: {\n    scales: {\n      xAxis: {\n        // The axis for this scale is determined from the first letter of the id as `'x'`\n        // It is recommended to specify `position` and / or `axis` explicitly.\n        type: 'time',\n      }\n    }\n  }\n});\n```\n\nThe following chart will have `'r'` scale:\n\n```js\nlet chart = new Chart(ctx, {\n  type: 'radar'\n});\n```\n\nThe following chart will have `'myScale'` scale:\n\n```js\nlet chart = new Chart(ctx, {\n  type: 'radar',\n  scales: {\n    myScale: {\n      axis: 'r'\n    }\n  }\n});\n```\n\n## Common Configuration\n\n:::tip Note\nThese are only the common options supported by all axes. Please see specific axis documentation for all the available options for that axis.\n:::\n\n!!!include(axes/_common.md)!!!\n\n## Tick Configuration\n\n:::tip Note\nThese are only the common tick options supported by all axes. Please see specific axis documentation for all the available tick options for that axis.\n:::\n\n!!!include(axes/_common_ticks.md)!!!\n\n## Axis Range Settings\n\nGiven the number of axis range settings, it is important to understand how they all interact with each other.\n\nThe `suggestedMax` and `suggestedMin` settings only change the data values that are used to scale the axis. These are useful for extending the range of the axis while maintaining the auto-fit behaviour.\n\n```javascript\nlet minDataValue = Math.min(mostNegativeValue, options.suggestedMin);\nlet maxDataValue = Math.max(mostPositiveValue, options.suggestedMax);\n```\n\nIn this example, the largest positive value is 50, but the data maximum is expanded out to 100. However, because the lowest data value is below the `suggestedMin` setting, it is ignored.\n\n```javascript\nlet chart = new Chart(ctx, {\n    type: 'line',\n    data: {\n        datasets: [{\n            label: 'First dataset',\n            data: [0, 20, 40, 50]\n        }],\n        labels: ['January', 'February', 'March', 'April']\n    },\n    options: {\n        scales: {\n            y: {\n                suggestedMin: 50,\n                suggestedMax: 100\n            }\n        }\n    }\n});\n```\n\nIn contrast to the `suggested*` settings, the `min` and `max` settings set explicit ends to the axes. When these are set, some data points may not be visible.\n\n## Stacking\n\nBy default, data is not stacked. If the `stacked` option of the value scale (y-axis on horizontal chart) is `true`, positive and negative values are stacked separately. Additionally, a `stack` option can be defined per dataset to further divide into stack groups [more...](../general/data-structures/#dataset-configuration).\nFor some charts, you might want to stack positive and negative values together. That can be achieved by specifying `stacked: 'single'`.\n\n## Callbacks\n\nThere are a number of config callbacks that can be used to change parameters in the scale at different points in the update process. The options are supplied at the top level of the axis options.\n\nNamespace: `options.scales[scaleId]`\n\n| Name | Arguments | Description\n| ---- | --------- | -----------\n| `beforeUpdate` | `axis` | Callback called before the update process starts.\n| `beforeSetDimensions` | `axis` | Callback that runs before dimensions are set.\n| `afterSetDimensions` | `axis` | Callback that runs after dimensions are set.\n| `beforeDataLimits` | `axis` | Callback that runs before data limits are determined.\n| `afterDataLimits` | `axis` | Callback that runs after data limits are determined.\n| `beforeBuildTicks` | `axis` | Callback that runs before ticks are created.\n| `afterBuildTicks` | `axis` | Callback that runs after ticks are created. Useful for filtering ticks.\n| `beforeTickToLabelConversion` | `axis` | Callback that runs before ticks are converted into strings.\n| `afterTickToLabelConversion` | `axis` | Callback that runs after ticks are converted into strings.\n| `beforeCalculateLabelRotation` | `axis` | Callback that runs before tick rotation is determined.\n| `afterCalculateLabelRotation` | `axis` | Callback that runs after tick rotation is determined.\n| `beforeFit` | `axis` | Callback that runs before the scale fits to the canvas.\n| `afterFit` | `axis` | Callback that runs after the scale fits to the canvas.\n| `afterUpdate` | `axis` | Callback that runs at the end of the update process.\n\n### Updating Axis Defaults\n\nThe default configuration for a scale can be easily changed. All you need to do is set the new options to `Chart.defaults.scales[type]`.\n\nFor example, to set the minimum value of 0 for all linear scales, you would do the following. Any linear scales created after this time would now have a minimum of 0.\n\n```javascript\nChart.defaults.scales.linear.min = 0;\n```\n\n## Creating New Axes\n\nTo create a new axis, see the [developer docs](../developers/axes.md).\n"
  },
  {
    "path": "docs/axes/labelling.md",
    "content": "# Labeling Axes\n\nWhen creating a chart, you want to tell the viewer what data they are viewing. To do this, you need to label the axis.\n\n## Scale Title Configuration\n\nNamespace: `options.scales[scaleId].title`, it defines options for the scale title. Note that this only applies to cartesian axes.\n\n| Name | Type | Default | Description\n| ---- | ---- | ------- | -----------\n| `display` | `boolean` | `false` | If true, display the axis title.\n| `align` | `string` | `'center'` | Alignment of the axis title. Possible options are `'start'`, `'center'` and `'end'`\n| `text` | `string`\\|`string[]` | `''` | The text for the title. (i.e. \"# of People\" or \"Response Choices\").\n| `color` | [`Color`](../general/colors.md) | `Chart.defaults.color` | Color of label.\n| `strokeColor` | [`Color`](../general/colors.md) |  | Color of text stroke.\n| `strokeWidth` | `number` |  | Size of stroke width, in pixels.\n| `font` | `Font` | `Chart.defaults.font` | See [Fonts](../general/fonts.md)\n| `padding` | [`Padding`](../general/padding.md) | `4` | Padding to apply around scale labels. Only `top`, `bottom` and `y` are implemented.\n\n## Creating Custom Tick Formats\n\nIt is also common to want to change the tick marks to include information about the data type. For example, adding a dollar sign ('$').\nTo do this, you need to override the `ticks.callback` method in the axis configuration.\n\nThe method receives 3 arguments:\n\n* `value` - the tick value in the **internal data format** of the associated scale. For time scale, it is a timestamp.\n* `index` - the tick index in the ticks array.\n* `ticks` - the array containing all of the [tick objects](../api/interfaces/Tick).\n\nThe call to the method is scoped to the scale. `this` inside the method is the scale object.\n\nIf the callback returns `null` or `undefined` the associated grid line will be hidden.\n\n:::tip\nThe [category axis](../axes/cartesian/category), which is the default x-axis for line and bar charts, uses the `index` as internal data format. For accessing the label, use `this.getLabelForValue(value)`. [API: getLabelForValue](../api/classes/Scale.md#getlabelforvalue)\n:::\n\nIn the following example, every label of the Y-axis would be displayed with a dollar sign at the front.\n\n```javascript\nconst chart = new Chart(ctx, {\n    type: 'line',\n    data: data,\n    options: {\n        scales: {\n            y: {\n                ticks: {\n                    // Include a dollar sign in the ticks\n                    callback: function(value, index, ticks) {\n                        return '$' + value;\n                    }\n                }\n            }\n        }\n    }\n});\n```\n\nKeep in mind that overriding `ticks.callback` means that you are responsible for all formatting of the label. Depending on your use case, you may want to call the default formatter and then modify its output. In the example above, that would look like:\n\n```javascript\n                        // call the default formatter, forwarding `this`\n                        return '$' + Chart.Ticks.formatters.numeric.apply(this, [value, index, ticks]);\n```\n\nRelated samples:\n\n* [Tick configuration sample](../samples/scale-options/ticks)\n"
  },
  {
    "path": "docs/axes/radial/index.md",
    "content": "# Radial Axes\n\nRadial axes are used specifically for the radar and polar area chart types. These axes overlay the chart area, rather than being positioned on one of the edges. One radial axis is included by default in Chart.js.\n\n* [radialLinear](./linear.md)\n\n## Visual Components\n\nA radial axis is composed of visual components that can be individually configured. These components are:\n\n* [angle lines](#angle-lines)\n* [grid lines](#grid-lines)\n* [point labels](#point-labels)\n* [ticks](#ticks)\n\n### Angle Lines\n\nThe grid lines for an axis are drawn on the chart area. They stretch out from the center towards the edge of the canvas. In the example below, they are red.\n\n```js chart-editor\n// <block:setup:1>\nconst labels = Utils.months({count: 7});\nconst data = {\n  labels: labels,\n  datasets: [{\n    label: 'My First dataset',\n    backgroundColor: 'rgba(54, 162, 235, 0.5)',\n    borderColor: 'rgb(54, 162, 235)',\n    borderWidth: 1,\n    data: [10, 20, 30, 40, 50, 0, 5],\n  }]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'radar',\n  data,\n  options: {\n    scales: {\n      r: {\n        angleLines: {\n          color: 'red'\n        }\n      }\n    }\n  }\n};\n// </block:config>\n\nmodule.exports = {\n  actions: [],\n  config: config,\n};\n```\n\n### Grid Lines\n\nThe grid lines for an axis are drawn on the chart area. In the example below, they are red.\n\n```js chart-editor\n// <block:setup:1>\nconst labels = Utils.months({count: 7});\nconst data = {\n  labels: labels,\n  datasets: [{\n    label: 'My First dataset',\n    backgroundColor: 'rgba(54, 162, 235, 0.5)',\n    borderColor: 'rgb(54, 162, 235)',\n    borderWidth: 1,\n    data: [10, 20, 30, 40, 50, 0, 5],\n  }]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'radar',\n  data,\n  options: {\n    scales: {\n      r: {\n        grid: {\n          color: 'red'\n        }\n      }\n    }\n  }\n};\n// </block:config>\n\nmodule.exports = {\n  actions: [],\n  config: config,\n};\n```\n\n### Point Labels\n\nThe point labels indicate the value for each angle line. In the example below, they are red.\n\n```js chart-editor\n// <block:setup:1>\nconst labels = Utils.months({count: 7});\nconst data = {\n  labels: labels,\n  datasets: [{\n    label: 'My First dataset',\n    backgroundColor: 'rgba(54, 162, 235, 0.5)',\n    borderColor: 'rgb(54, 162, 235)',\n    borderWidth: 1,\n    data: [10, 20, 30, 40, 50, 0, 5],\n  }]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'radar',\n  data,\n  options: {\n    scales: {\n      r: {\n        pointLabels: {\n          color: 'red'\n        }\n      }\n    }\n  }\n};\n// </block:config>\n\nmodule.exports = {\n  actions: [],\n  config: config,\n};\n```\n\n### Ticks\n\nThe ticks are used to label values based on how far they are from the center of the axis. In the example below, they are red.\n\n```js chart-editor\n// <block:setup:1>\nconst labels = Utils.months({count: 7});\nconst data = {\n  labels: labels,\n  datasets: [{\n    label: 'My First dataset',\n    backgroundColor: 'rgba(54, 162, 235, 0.5)',\n    borderColor: 'rgb(54, 162, 235)',\n    borderWidth: 1,\n    data: [10, 20, 30, 40, 50, 0, 5],\n  }]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'radar',\n  data,\n  options: {\n    scales: {\n      r: {\n        ticks: {\n          color: 'red'\n        }\n      }\n    }\n  }\n};\n// </block:config>\n\nmodule.exports = {\n  actions: [],\n  config: config,\n};\n```\n"
  },
  {
    "path": "docs/axes/radial/linear.md",
    "content": "# Linear Radial Axis\n\nThe linear radial scale is used to chart numerical data. As the name suggests, linear interpolation is used to determine where a value lies in relation to the center of the axis.\n\nThe following additional configuration options are provided by the radial linear scale.\n\n## Configuration Options\n\n### Linear Radial Axis specific options\n\nNamespace: `options.scales[scaleId]`\n\n| Name | Type | Default | Description\n| ---- | ---- | ------- | -----------\n| `animate` | `boolean` | `true` | Whether to animate scaling the chart from the centre\n| `angleLines` | `object` | | Angle line configuration. [more...](#angle-line-options)\n| `beginAtZero` | `boolean` | `false` | If true, scale will include 0 if it is not already included.\n| `pointLabels` | `object` | | Point label configuration. [more...](#point-label-options)\n| `startAngle` | `number` | `0` | Starting angle of the scale. In degrees, 0 is at top.\n\n### Common options for all axes\n\nNamespace: `options.scales[scaleId]`\n\n| Name | Type | Default | Description\n| ---- | ---- | ------- | -----------\n| `type` | `string` | | Type of scale being employed. Custom scales can be created and registered with a string key. This allows changing the type of an axis for a chart.\n| `alignToPixels` | `boolean` | `false` | Align pixel values to device pixels.\n| `backgroundColor` | [`Color`](/general/colors.md) | | Background color of the scale area.\n| `display` | `boolean`\\|`string` | `true` | Controls the axis global visibility (visible when `true`, hidden when `false`). When `display: 'auto'`, the axis is visible only if at least one associated dataset is visible.\n| `grid` | `object` | | Grid line configuration. [more...](#grid-line-configuration)\n| `min` | `number` | | User defined minimum number for the scale, overrides minimum value from data. [more...](/axes/index.md#axis-range-settings)\n| `max` | `number` | | User defined maximum number for the scale, overrides maximum value from data. [more...](/axes/index.md#axis-range-settings)\n| `reverse` | `boolean` | `false` | Reverse the scale.\n| `stacked` | `boolean`\\|`string` | `false` | Should the data be stacked. [more...](/axes/index.md#stacking)\n| `suggestedMax` | `number` | | Adjustment used when calculating the maximum data value. [more...](/axes/index.md#axis-range-settings)\n| `suggestedMin` | `number` | | Adjustment used when calculating the minimum data value. [more...](/axes/index.md#axis-range-settings)\n| `ticks` | `object` | | Tick configuration. [more...](/axes/index.md#tick-configuration)\n| `weight` | `number` | `0` | The weight used to sort the axis. Higher weights are further away from the chart area.\n\n## Tick Configuration\n\n### Linear Radial Axis specific tick options\n\nNamespace: `options.scales[scaleId].ticks`\n\n| Name | Type | Scriptable | Default | Description\n| ---- | ---- | ------- | ------- | -----------\n| `count` | `number` | Yes | `undefined` | The number of ticks to generate. If specified, this overrides the automatic generation.\n| `format` | `object` | Yes | | The [`Intl.NumberFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat) options used by the default label formatter\n| `maxTicksLimit` | `number` | Yes | `11` | Maximum number of ticks and gridlines to show.\n| `precision` | `number` | Yes | | If defined and `stepSize` is not specified, the step size will be rounded to this many decimal places.\n| `stepSize` | `number` | Yes | | User defined fixed step size for the scale. [more...](#step-size)\n\n!!!include(axes/_common_ticks.md)!!!\n\nThe scriptable context is described in [Options](../../general/options.md#tick) section.\n\n## Grid Line Configuration\n\nNamespace: `options.scales[scaleId].grid`, it defines options for the grid lines of the axis.\n\n| Name | Type | Scriptable | Indexable | Default | Description\n| ---- | ---- | :-------------------------------: | :-----------------------------: | ------- | -----------\n| `borderDash` | `number[]` | | | `[]` | Length and spacing of dashes on grid lines. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash).\n| `borderDashOffset` | `number` | Yes | | `0.0` | Offset for line dashes. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineDashOffset).\n| `circular` | `boolean` | | | `false` | If true, gridlines are circular (on radar and polar area charts only).\n| `color` | [`Color`](../general/colors.md)  | Yes | Yes | `Chart.defaults.borderColor` | The color of the grid lines. If specified as an array, the first color applies to the first grid line, the second to the second grid line, and so on.\n| `display` | `boolean` | | | `true` | If false, do not display grid lines for this axis.\n| `lineWidth` | `number` | Yes | Yes | `1` | Stroke width of grid lines.\n\nThe scriptable context is described in [Options](../general/options.md#tick) section.\n\n## Axis Range Settings\n\nGiven the number of axis range settings, it is important to understand how they all interact with each other.\n\nThe `suggestedMax` and `suggestedMin` settings only change the data values that are used to scale the axis. These are useful for extending the range of the axis while maintaining the auto-fit behaviour.\n\n```javascript\nlet minDataValue = Math.min(mostNegativeValue, options.ticks.suggestedMin);\nlet maxDataValue = Math.max(mostPositiveValue, options.ticks.suggestedMax);\n```\n\nIn this example, the largest positive value is 50, but the data maximum is expanded out to 100. However, because the lowest data value is below the `suggestedMin` setting, it is ignored.\n\n```javascript\nlet chart = new Chart(ctx, {\n    type: 'radar',\n    data: {\n        datasets: [{\n            label: 'First dataset',\n            data: [0, 20, 40, 50]\n        }],\n        labels: ['January', 'February', 'March', 'April']\n    },\n    options: {\n        scales: {\n            r: {\n                suggestedMin: 50,\n                suggestedMax: 100\n            }\n        }\n    }\n});\n```\n\nIn contrast to the `suggested*` settings, the `min` and `max` settings set explicit ends to the axes. When these are set, some data points may not be visible.\n\n## Step Size\n\nIf set, the scale ticks will be enumerated by multiple of `stepSize`, having one tick per increment. If not set, the ticks are labeled automatically using the nice numbers algorithm.\n\nThis example sets up a chart with a y axis that creates ticks at `0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5`.\n\n```javascript\nlet options = {\n    scales: {\n        r: {\n            max: 5,\n            min: 0,\n            ticks: {\n                stepSize: 0.5\n            }\n        }\n    }\n};\n```\n\n## Angle Line Options\n\nThe following options are used to configure angled lines that radiate from the center of the chart to the point labels.\nNamespace: `options.scales[scaleId].angleLines`\n\n| Name | Type | Scriptable | Default | Description\n| ---- | ---- | ------- | ------- | -----------\n| `display` | `boolean` | | `true` | If true, angle lines are shown.\n| `color` | [`Color`](../../general/colors.md) | Yes | `Chart.defaults.borderColor` | Color of angled lines.\n| `lineWidth` | `number` | Yes | `1` | Width of angled lines.\n| `borderDash` | `number[]` | Yes<sup>1</sup> | `[]` | Length and spacing of dashes on angled lines. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash).\n| `borderDashOffset` | `number` | Yes | `0.0` | Offset for line dashes. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineDashOffset).\n\n  1. the `borderDash` setting only accepts a static value or a function. Passing an array of arrays is not supported.\n\nThe scriptable context is described in [Options](../../general/options.md#pointLabel) section.\n\n## Point Label Options\n\nThe following options are used to configure the point labels that are shown on the perimeter of the scale.\nNamespace: `options.scales[scaleId].pointLabels`\n\n| Name | Type | Scriptable | Default | Description\n| ---- | ---- | ------- | ------- | -----------\n| `backdropColor` | [`Color`](../../general/colors.md) | `true` | `undefined` | Background color of the point label.\n| `backdropPadding` | [`Padding`](../../general/padding.md) | | `2` | Padding of label backdrop.\n| `borderRadius` | `number`\\|`object` | `true` | `0` | Border radius of the point label\n| `display` | `boolean`\\|`string` | | `true` | If true, point labels are shown.  When `display: 'auto'`, the label is hidden if it overlaps with another label.\n| `callback` | `function` | | | Callback function to transform data labels to point labels. The default implementation simply returns the current string.\n| `color` | [`Color`](../../general/colors.md) | Yes | `Chart.defaults.color` | Color of label.\n| `font` | `Font` | Yes | `Chart.defaults.font` | See [Fonts](../../general/fonts.md)\n| `padding` | `number` | Yes | 5 | Padding between chart and point labels.\n| [`centerPointLabels`](../../samples/other-charts/polar-area-center-labels.md) | `boolean` | | `false` | If true, point labels are centered.\n\nThe scriptable context is described in [Options](../../general/options.md#pointLabel) section.\n\n## Internal data format\n\nInternally, the linear radial scale uses numeric data\n"
  },
  {
    "path": "docs/axes/styling.md",
    "content": "# Styling\n\nThere are a number of options to allow styling an axis. There are settings to control [grid lines](#grid-line-configuration) and [ticks](#tick-configuration).\n\n## Grid Line Configuration\n\nNamespace: `options.scales[scaleId].grid`, it defines options for the grid lines that run perpendicular to the axis.\n\n| Name | Type | Scriptable | Indexable | Default | Description\n| ---- | ---- | :-------------------------------: | :-----------------------------: | ------- | -----------\n| `circular` | `boolean` | | | `false` | If true, gridlines are circular (on radar and polar area charts only).\n| `color` | [`Color`](../general/colors.md)  | Yes | Yes | `Chart.defaults.borderColor` | The color of the grid lines. If specified as an array, the first color applies to the first grid line, the second to the second grid line, and so on.\n| `display` | `boolean` | | | `true` | If false, do not display grid lines for this axis.\n| `drawOnChartArea` | `boolean` | | | `true` | If true, draw lines on the chart area inside the axis lines. This is useful when there are multiple axes and you need to control which grid lines are drawn.\n| `drawTicks` | `boolean` | | | `true` | If true, draw lines beside the ticks in the axis area beside the chart.\n| `lineWidth` | `number` | Yes | Yes | `1` | Stroke width of grid lines.\n| `offset` | `boolean` | | | `false` | If true, grid lines will be shifted to be between labels. This is set to `true` for a bar chart by default.\n| `tickBorderDash` | `number[]` | Yes | Yes | `[]` | Length and spacing of the tick mark line. If not set, defaults to the grid line `borderDash` value.\n| `tickBorderDashOffset` | `number` | Yes | Yes |  | Offset for the line dash of the tick mark. If unset, defaults to the grid line `borderDashOffset` value\n| `tickColor` | [`Color`](../general/colors.md) | Yes | Yes | | Color of the tick line. If unset, defaults to the grid line color.\n| `tickLength` | `number` | | | `8` | Length in pixels that the grid lines will draw into the axis area.\n| `tickWidth` | `number` | Yes | Yes | | Width of the tick mark in pixels. If unset, defaults to the grid line width.\n| `z` | `number` | | | `-1` | z-index of the gridline layer. Values &lt;= 0 are drawn under datasets, &gt; 0 on top.\n\nThe scriptable context is described in [Options](../general/options.md#tick) section.\n\n## Tick Configuration\n\n!!!include(axes/_common_ticks.md)!!!\n\nThe scriptable context is described in [Options](../general/options.md#tick) section.\n\n## Major Tick Configuration\n\nNamespace: `options.scales[scaleId].ticks.major`, it defines options for the major tick marks that are generated by the axis.\n\n| Name | Type | Default | Description\n| ---- | ---- | ------- | -----------\n| `enabled` | `boolean` | `false` | If true, major ticks are generated. A major tick will affect autoskipping and `major` will be defined on ticks in the scriptable options context.\n\n## Border Configuration\n\nNamespace: `options.scales[scaleId].border`, it defines options for the border that run perpendicular to the axis.\n\n| Name | Type | Scriptable | Indexable | Default | Description\n| ---- | ---- | :-------------------------------: | :-----------------------------: | ------- | -----------\n| `display` | `boolean` | | | `true` | If true, draw a border at the edge between the axis and the chart area.\n| `color` | [`Color`](../general/colors.md) | | | `Chart.defaults.borderColor` | The color of the border line.\n| `width` | `number` | | | `1` | The width of the border line.\n| `dash` | `number[]` | Yes | | `[]` | Length and spacing of dashes on grid lines. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash).\n| `dashOffset` | `number` | Yes | | `0.0` | Offset for line dashes. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineDashOffset).\n| `z` | `number` | | | `0` | z-index of the border layer. Values &lt;= 0 are drawn under datasets, &gt; 0 on top.\n"
  },
  {
    "path": "docs/charts/area.md",
    "content": "# Area Chart\n\nBoth [line](./line.md) and [radar](./radar.md) charts support a `fill` option on the dataset object which can be used to create space between two datasets or a dataset and a boundary, i.e. the scale `origin`, `start,` or `end` (see [filling modes](#filling-modes)).\n\n:::tip Note\nThis feature is implemented by the [`filler` plugin](https://github.com/chartjs/Chart.js/blob/master/src/plugins/plugin.filler/index.js).\n:::\n\n## Filling modes\n\n| Mode | Type | Values |\n| :--- | :--- | :--- |\n| Absolute dataset index | `number` | `1`, `2`, `3`, ... |\n| Relative dataset index | `string` | `'-1'`, `'-2'`, `'+1'`, ... |\n| Boundary | `string` | `'start'`, `'end'`, `'origin'` |\n| Disabled <sup>1</sup> | `boolean` | `false` |\n| Stacked value below | `string` | `'stack'` |\n| Axis value | `object` | `{ value: number; }` |\n| Shape (fill inside line) | `string` | `'shape'` |\n\n> <sup>1</sup> for backward compatibility, `fill: true` is equivalent to `fill: 'origin'`<br/>\n\n### Example\n\n```javascript\nnew Chart(ctx, {\n    data: {\n        datasets: [\n            {fill: 'origin'},      // 0: fill to 'origin'\n            {fill: '+2'},          // 1: fill to dataset 3\n            {fill: 1},             // 2: fill to dataset 1\n            {fill: false},         // 3: no fill\n            {fill: '-2'},          // 4: fill to dataset 2\n            {fill: {value: 25}}    // 5: fill to axis value 25\n        ]\n    }\n});\n```\n\nIf you need to support multiple colors when filling from one dataset to another, you may specify an object with the following option :\n\n| Param | Type | Description |\n| :--- | :--- | :--- |\n| `target` | `number`, `string`, `boolean`, `object` | The accepted values are the same as the filling mode values, so you may use absolute and relative dataset indexes and/or boundaries. |\n| `above` | `Color` | If no color is set, the default color will be the background color of the chart. |\n| `below` | `Color` | Same as the above. |\n\n### Example with multiple colors\n\n```javascript\nnew Chart(ctx, {\n    data: {\n        datasets: [\n            {\n              fill: {\n                target: 'origin',\n                above: 'rgb(255, 0, 0)',   // Area will be red above the origin\n                below: 'rgb(0, 0, 255)'    // And blue below the origin\n              }\n            }\n        ]\n    }\n});\n```\n\n## Configuration\n\nNamespace: `options.plugins.filler`\n\n| Option | Type | Default | Description |\n| :--- | :--- | :--- | :--- |\n| `drawTime` | `string` | `beforeDatasetDraw` | Filler draw time. Supported values: `'beforeDraw'`, `'beforeDatasetDraw'`, `'beforeDatasetsDraw'`\n| [`propagate`](#propagate) | `boolean` | `true` | Fill propagation when target is hidden.\n\n### propagate\n\n`propagate` takes a `boolean` value (default: `true`).\n\nIf `true`, the fill area will be recursively extended to the visible target defined by the `fill` value of hidden dataset targets:\n\n#### Example using propagate\n\n```javascript\nnew Chart(ctx, {\n    data: {\n        datasets: [\n            {fill: 'origin'},   // 0: fill to 'origin'\n            {fill: '-1'},       // 1: fill to dataset 0\n            {fill: 1},          // 2: fill to dataset 1\n            {fill: false},      // 3: no fill\n            {fill: '-2'}        // 4: fill to dataset 2\n        ]\n    },\n    options: {\n        plugins: {\n            filler: {\n                propagate: true\n            }\n        }\n    }\n});\n```\n\n`propagate: true`:\n-if dataset 2 is hidden, dataset 4 will fill to dataset 1\n-if dataset 2 and 1 are hidden, dataset 4 will fill to `'origin'`\n\n`propagate: false`:\n-if dataset 2 and/or 4 are hidden, dataset 4 will not be filled\n"
  },
  {
    "path": "docs/charts/bar.md",
    "content": "# Bar Chart\n\nA bar chart provides a way of showing data values represented as vertical bars. It is sometimes used to show trend data, and the comparison of multiple data sets side by side.\n\n```js chart-editor\n// <block:setup:1>\nconst labels = Utils.months({count: 7});\nconst data = {\n  labels: labels,\n  datasets: [{\n    label: 'My First Dataset',\n    data: [65, 59, 80, 81, 56, 55, 40],\n    backgroundColor: [\n      'rgba(255, 99, 132, 0.2)',\n      'rgba(255, 159, 64, 0.2)',\n      'rgba(255, 205, 86, 0.2)',\n      'rgba(75, 192, 192, 0.2)',\n      'rgba(54, 162, 235, 0.2)',\n      'rgba(153, 102, 255, 0.2)',\n      'rgba(201, 203, 207, 0.2)'\n    ],\n    borderColor: [\n      'rgb(255, 99, 132)',\n      'rgb(255, 159, 64)',\n      'rgb(255, 205, 86)',\n      'rgb(75, 192, 192)',\n      'rgb(54, 162, 235)',\n      'rgb(153, 102, 255)',\n      'rgb(201, 203, 207)'\n    ],\n    borderWidth: 1\n  }]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'bar',\n  data: data,\n  options: {\n    scales: {\n      y: {\n        beginAtZero: true\n      }\n    }\n  },\n};\n// </block:config>\n\nmodule.exports = {\n  actions: [],\n  config: config,\n};\n```\n\n## Dataset Properties\n\nNamespaces:\n\n* `data.datasets[index]` - options for this dataset only\n* `options.datasets.bar` - options for all bar datasets\n* `options.elements.bar` - options for all [bar elements](../configuration/elements.md#bar-configuration)\n* `options` - options for the whole chart\n\nThe bar chart allows a number of properties to be specified for each dataset.\nThese are used to set display properties for a specific dataset. For example,\nthe color of the bars is generally set this way.\nOnly the `data` option needs to be specified in the dataset namespace.\n\n| Name | Type | [Scriptable](../general/options.md#scriptable-options) | [Indexable](../general/options.md#indexable-options) | Default\n| ---- | ---- | :----: | :----: | ----\n| [`backgroundColor`](#styling) | [`Color`](../general/colors.md) | Yes | Yes | `'rgba(0, 0, 0, 0.1)'`\n| [`base`](#general) | `number` | Yes | Yes |\n| [`barPercentage`](#barpercentage) | `number` | - | - | `0.9` |\n| [`barThickness`](#barthickness) | `number`\\|`string` | - | - | |\n| [`borderColor`](#styling) | [`Color`](../general/colors.md) | Yes | Yes | `'rgba(0, 0, 0, 0.1)'`\n| [`borderSkipped`](#borderskipped) | `string`\\|`boolean` | Yes | Yes | `'start'`\n| [`borderWidth`](#borderwidth) | `number`\\|`object` | Yes | Yes | `0`\n| [`borderRadius`](#borderradius) | `number`\\|`object` | Yes | Yes | `0`\n| [`categoryPercentage`](#categorypercentage) | `number` | - | - | `0.8` |\n| [`clip`](#general) | `number`\\|`object`\\|`false` | - | - |\n| [`data`](#data-structure) | `object`\\|`object[]`\\| `number[]`\\|`string[]` | - | - | **required**\n| [`grouped`](#general) | `boolean` | - | - | `true` |\n| [`hoverBackgroundColor`](#interactions) | [`Color`](../general/colors.md) | Yes | Yes |\n| [`hoverBorderColor`](#interactions) | [`Color`](../general/colors.md) | Yes | Yes |\n| [`hoverBorderWidth`](#interactions) | `number` | Yes | Yes | `1`\n| [`hoverBorderRadius`](#interactions) | `number` | Yes | Yes | `0`\n| [`indexAxis`](#general) | `string` | - | - | `'x'`\n| [`inflateAmount`](#inflateamount) | `number`\\|`'auto'` | Yes | Yes | `'auto'`\n| [`maxBarThickness`](#maxbarthickness) | `number` | - | - | |\n| [`minBarLength`](#styling) | `number` | - | - | |\n| [`label`](#general) | `string` | - | - | `''`\n| [`order`](#general) | `number` | - | - | `0`\n| [`pointStyle`](../configuration/elements.md#point-styles) | [`pointStyle`](../configuration/elements.md#types) | Yes | - | `'circle'`\n| [`skipNull`](#general) | `boolean` | - | - | |\n| [`stack`](#general) | `string` | - | - | `'bar'` |\n| [`xAxisID`](#general) | `string` | - | - | first x axis\n| [`yAxisID`](#general) | `string` | - | - | first y axis\n\nAll these values, if `undefined`, fallback to the scopes described in [option resolution](../general/options)\n\n### Example dataset configuration\n\n```javascript\ndata: {\n    datasets: [{\n        barPercentage: 0.5,\n        barThickness: 6,\n        maxBarThickness: 8,\n        minBarLength: 2,\n        data: [10, 20, 30, 40, 50, 60, 70]\n    }]\n};\n```\n\n### General\n\n| Name | Description\n| ---- | ----\n| `base` | Base value for the bar in data units along the value axis. If not set, defaults to the value axis base value.\n| `clip` | How to clip relative to chartArea. Positive value allows overflow, negative value clips that many pixels inside chartArea. `0` = clip at chartArea. Clipping can also be configured per side: `clip: {left: 5, top: false, right: -2, bottom: 0}`\n| `grouped` | Should the bars be grouped on index axis. When `true`, all the datasets at same index value will be placed next to each other centering on that index value. When `false`, each bar is placed on its actual index-axis value.\n| `indexAxis` | The base axis of the dataset. `'x'` for vertical bars and `'y'` for horizontal bars.\n| `label` | The label for the dataset which appears in the legend and tooltips.\n| `order` | The drawing order of dataset. Also affects order for stacking, tooltip and legend. [more](mixed.md#drawing-order)\n| `skipNull` | If `true`, null or undefined values will not be used for spacing calculations when determining bar size.\n| `stack` | The ID of the group to which this dataset belongs to (when stacked, each group will be a separate stack). [more](#stacked-bar-chart)\n| `xAxisID` | The ID of the x-axis to plot this dataset on.\n| `yAxisID` | The ID of the y-axis to plot this dataset on.\n\n### Styling\n\nThe style of each bar can be controlled with the following properties:\n\n| Name | Description\n| ---- | ----\n| `backgroundColor` | The bar background color.\n| `borderColor` | The bar border color.\n| [`borderSkipped`](#borderskipped) | The edge to skip when drawing bar.\n| [`borderWidth`](#borderwidth) | The bar border width (in pixels).\n| [`borderRadius`](#borderradius) | The bar border radius (in pixels).\n| `minBarLength` | Set this to ensure that bars have a minimum length in pixels.\n| `pointStyle` | Style of the point for legend. [more...](../configuration/elements.md#point-styles)\n\nAll these values, if `undefined`, fallback to the associated [`elements.bar.*`](../configuration/elements.md#bar-configuration) options.\n\n#### borderSkipped\n\nThis setting is used to avoid drawing the bar stroke at the base of the fill, or disable the border radius.\nIn general, this does not need to be changed except when creating chart types\nthat derive from a bar chart.\n\n:::tip Note\nFor negative bars in a vertical chart, `top` and `bottom` are flipped. Same goes for `left` and `right` in a horizontal chart.\n:::\n\nOptions are:\n\n* `'start'`\n* `'end'`\n* `'middle'` (only valid on stacked bars: the borders between bars are skipped)\n* `'bottom'`\n* `'left'`\n* `'top'`\n* `'right'`\n* `false` (don't skip any borders)\n* `true` (skip all borders)\n\n#### borderWidth\n\nIf this value is a number, it is applied to all sides of the rectangle (left, top, right, bottom), except [`borderSkipped`](#borderskipped). If this value is an object, the `left` property defines the left border width. Similarly, the `right`, `top`, and `bottom` properties can also be specified. Omitted borders and [`borderSkipped`](#borderskipped) are skipped.\n\n#### borderRadius\n\nIf this value is a number, it is applied to all corners of the rectangle (topLeft, topRight, bottomLeft, bottomRight), except corners touching the [`borderSkipped`](#borderskipped). If this value is an object, the `topLeft` property defines the top-left corners border radius. Similarly, the `topRight`, `bottomLeft`, and `bottomRight` properties can also be specified. Omitted corners and those touching the [`borderSkipped`](#borderskipped) are skipped. For example if the `top` border is skipped, the border radius for the corners `topLeft` and `topRight` will be skipped as well.\n\n:::tip Stacked Charts\nWhen the border radius is supplied as a number and the chart is stacked, the radius will only be applied to the bars that are at the edges of the stack or where the bar is floating. The object syntax can be used to override this behavior.\n:::\n\n#### inflateAmount\n\nThis option can be used to inflate the rects that are used to draw the bars. This can be used to hide artifacts between bars when [`barPercentage`](#barpercentage) * [`categoryPercentage`](#categorypercentage) is 1. The default value `'auto'` should work in most cases.\n\n### Interactions\n\nThe interaction with each bar can be controlled with the following properties:\n\n| Name | Description\n| ---- | -----------\n| `hoverBackgroundColor` | The bar background color when hovered.\n| `hoverBorderColor` | The bar border color when hovered.\n| `hoverBorderWidth` | The bar border width when hovered (in pixels).\n| `hoverBorderRadius` | The bar border radius when hovered (in pixels).\n\nAll these values, if `undefined`, fallback to the associated [`elements.bar.*`](../configuration/elements.md#bar-configuration) options.\n\n### barPercentage\n\nPercent (0-1) of the available width each bar should be within the category width. 1.0 will take the whole category width and put the bars right next to each other. [more...](#barpercentage-vs-categorypercentage)\n\n### categoryPercentage\n\nPercent (0-1) of the available width each category should be within the sample width. [more...](#barpercentage-vs-categorypercentage)\n\n### barThickness\n\nIf this value is a number, it is applied to the width of each bar, in pixels. When this is enforced, `barPercentage` and `categoryPercentage` are ignored.\n\nIf set to `'flex'`, the base sample widths are calculated automatically based on the previous and following samples so that they take the full available widths without overlap. Then, bars are sized using `barPercentage` and `categoryPercentage`. There is no gap when the percentage options are 1. This mode generates bars with different widths when data are not evenly spaced.\n\nIf not set (default), the base sample widths are calculated using the smallest interval that prevents bar overlapping, and bars are sized using `barPercentage` and `categoryPercentage`. This mode always generates bars equally sized.\n\n### maxBarThickness\n\nSet this to ensure that bars are not sized thicker than this.\n\n## Scale Configuration\n\nThe bar chart sets unique default values for the following configuration from the associated `scale` options:\n\n| Name | Type | Default | Description\n| ---- | ---- | ------- | -----------\n| `offset` | `boolean` | `true` | If true, extra space is added to both edges and the axis is scaled to fit into the chart area.\n| `grid.offset` | `boolean` | `true` | If true, the bars for a particular data point fall between the grid lines. The grid line will move to the left by one half of the tick interval. If false, the grid line will go right down the middle of the bars. [more...](#offsetgridlines)\n\n### Example scale configuration\n\n```javascript\noptions = {\n    scales: {\n        x: {\n            grid: {\n              offset: true\n            }\n        }\n    }\n};\n```\n\n### Offset Grid Lines\n\nIf true, the bars for a particular data point fall between the grid lines. The grid line will move to the left by one half of the tick interval, which is the space between the grid lines. If false, the grid line will go right down the middle of the bars. This is set to true for a category scale in a bar chart while false for other scales or chart types by default.\n\n## Default Options\n\nIt is common to want to apply a configuration setting to all created bar charts. The global bar chart settings are stored in `Chart.overrides.bar`. Changing the global options only affects charts created after the change. Existing charts are not changed.\n\n## barPercentage vs categoryPercentage\n\nThe following shows the relationship between the bar percentage option and the category percentage option.\n\n```\n// categoryPercentage: 1.0\n// barPercentage: 1.0\nBar:        | 1.0 | 1.0 |\nCategory:   |    1.0    |\nSample:     |===========|\n\n// categoryPercentage: 1.0\n// barPercentage: 0.5\nBar:          |.5|  |.5|\nCategory:  |      1.0     |\nSample:    |==============|\n\n// categoryPercentage: 0.5\n// barPercentage: 1.0\nBar:             |1.0||1.0|\nCategory:        |   .5   |\nSample:     |==================|\n```\n\n## Data Structure\n\nAll the supported [data structures](../general/data-structures.md) can be used with bar charts.\n\n## Stacked Bar Chart\n\nBar charts can be configured into stacked bar charts by changing the settings on the X and Y axes to enable stacking. Stacked bar charts can be used to show how one data series is made up of a number of smaller pieces.\n\n```javascript\nconst stackedBar = new Chart(ctx, {\n    type: 'bar',\n    data: data,\n    options: {\n        scales: {\n            x: {\n                stacked: true\n            },\n            y: {\n                stacked: true\n            }\n        }\n    }\n});\n```\n\n## Horizontal Bar Chart\n\nA horizontal bar chart is a variation on a vertical bar chart. It is sometimes used to show trend data, and the comparison of multiple data sets side by side.\nTo achieve this, you will have to set the `indexAxis` property in the options object to `'y'`.\nThe default for this property is `'x'` and thus will show vertical bars.\n\n```js chart-editor\n// <block:setup:1>\nconst labels = Utils.months({count: 7});\nconst data = {\n  labels: labels,\n  datasets: [{\n    axis: 'y',\n    label: 'My First Dataset',\n    data: [65, 59, 80, 81, 56, 55, 40],\n    fill: false,\n    backgroundColor: [\n      'rgba(255, 99, 132, 0.2)',\n      'rgba(255, 159, 64, 0.2)',\n      'rgba(255, 205, 86, 0.2)',\n      'rgba(75, 192, 192, 0.2)',\n      'rgba(54, 162, 235, 0.2)',\n      'rgba(153, 102, 255, 0.2)',\n      'rgba(201, 203, 207, 0.2)'\n    ],\n    borderColor: [\n      'rgb(255, 99, 132)',\n      'rgb(255, 159, 64)',\n      'rgb(255, 205, 86)',\n      'rgb(75, 192, 192)',\n      'rgb(54, 162, 235)',\n      'rgb(153, 102, 255)',\n      'rgb(201, 203, 207)'\n    ],\n    borderWidth: 1\n  }]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'bar',\n  data,\n  options: {\n    indexAxis: 'y',\n  }\n};\n// </block:config>\n\nmodule.exports = {\n  actions: [],\n  config: config,\n};\n```\n\n### Horizontal Bar Chart config Options\n\nThe configuration options for the horizontal bar chart are the same as for the [bar chart](#scale-configuration). However, any options specified on the x-axis in a bar chart, are applied to the y-axis in a horizontal bar chart.\n\n## Internal data format\n\n`{x, y, _custom}` where `_custom` is an optional object defining stacked bar properties: `{start, end, barStart, barEnd, min, max}`. `start` and `end` are the input values. Those two are repeated in `barStart` (closer to origin), `barEnd` (further from origin), `min` and `max`.\n"
  },
  {
    "path": "docs/charts/bubble.md",
    "content": "# Bubble Chart\n\nA bubble chart is used to display three dimensions of data at the same time. The location of the bubble is determined by the first two dimensions and the corresponding horizontal and vertical axes. The third dimension is represented by the size of the individual bubbles.\n\n```js chart-editor\n// <block:setup:1>\nconst data = {\n  datasets: [{\n    label: 'First Dataset',\n    data: [{\n      x: 20,\n      y: 30,\n      r: 15\n    }, {\n      x: 40,\n      y: 10,\n      r: 10\n    }],\n    backgroundColor: 'rgb(255, 99, 132)'\n  }]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'bubble',\n  data: data,\n  options: {}\n};\n// </block:config>\n\nmodule.exports = {\n  actions: [],\n  config: config,\n};\n```\n\n## Dataset Properties\n\nNamespaces:\n\n* `data.datasets[index]` - options for this dataset only\n* `options.datasets.bubble` - options for all bubble datasets\n* `options.elements.point` - options for all [point elements](../configuration/elements.md#point-configuration)\n* `options` - options for the whole chart\n\nThe bubble chart allows a number of properties to be specified for each dataset. These are used to set display properties for a specific dataset. For example, the colour of the bubbles is generally set this way.\n\n| Name | Type | [Scriptable](../general/options.md#scriptable-options) | [Indexable](../general/options.md#indexable-options) | Default\n| ---- | ---- | :----: | :----: | ----\n| [`backgroundColor`](#styling) | [`Color`](../general/colors.md) | Yes | Yes | `'rgba(0, 0, 0, 0.1)'`\n| [`borderColor`](#styling) | [`Color`](../general/colors.md) | Yes | Yes | `'rgba(0, 0, 0, 0.1)'`\n| [`borderWidth`](#styling) | `number` | Yes | Yes | `3`\n| [`clip`](#general) | `number`\\|`object`\\|`false` | - | - | `undefined`\n| [`data`](#data-structure) | `object[]` | - | - | **required**\n| [`drawActiveElementsOnTop`](#general) | `boolean` | Yes | Yes | `true`\n| [`hoverBackgroundColor`](#interactions) | [`Color`](../general/colors.md) | Yes | Yes | `undefined`\n| [`hoverBorderColor`](#interactions) | [`Color`](../general/colors.md) | Yes | Yes | `undefined`\n| [`hoverBorderWidth`](#interactions) | `number` | Yes | Yes | `1`\n| [`hoverRadius`](#interactions) | `number` | Yes | Yes | `4`\n| [`hitRadius`](#interactions) | `number` | Yes | Yes | `1`\n| [`label`](#general) | `string` | - | - | `undefined`\n| [`order`](#general) | `number` | - | - | `0`\n| [`pointStyle`](#styling) | [`pointStyle`](../configuration/elements.md#types) | Yes | Yes | `'circle'`\n| [`rotation`](#styling) | `number` | Yes | Yes | `0`\n| [`radius`](#styling) | `number` | Yes | Yes | `3`\n\nAll these values, if `undefined`, fallback to the scopes described in [option resolution](../general/options)\n\n### General\n\n| Name | Description\n| ---- | ----\n| `clip` | How to clip relative to chartArea. Positive value allows overflow, negative value clips that many pixels inside chartArea. `0` = clip at chartArea. Clipping can also be configured per side: `clip: {left: 5, top: false, right: -2, bottom: 0}`\n| `drawActiveElementsOnTop` | Draw the active bubbles of a dataset over the other bubbles of the dataset\n| `label` | The label for the dataset which appears in the legend and tooltips.\n| `order` | The drawing order of dataset. Also affects order for tooltip and legend. [more](mixed.md#drawing-order)\n\n### Styling\n\nThe style of each bubble can be controlled with the following properties:\n\n| Name | Description\n| ---- | ----\n| `backgroundColor` | bubble background color.\n| `borderColor` | bubble border color.\n| `borderWidth` | bubble border width (in pixels).\n| `pointStyle` | bubble [shape style](../configuration/elements.md#point-styles).\n| `rotation` | bubble rotation (in degrees).\n| `radius` | bubble radius (in pixels).\n\nAll these values, if `undefined`, fallback to the associated [`elements.point.*`](../configuration/elements.md#point-configuration) options.\n\n### Interactions\n\nThe interaction with each bubble can be controlled with the following properties:\n\n| Name | Description\n| ---- | -----------\n| `hitRadius` | bubble **additional** radius for hit detection (in pixels).\n| `hoverBackgroundColor` | bubble background color when hovered.\n| `hoverBorderColor` | bubble border color when hovered.\n| `hoverBorderWidth` | bubble border width when hovered (in pixels).\n| `hoverRadius` | bubble **additional** radius when hovered (in pixels).\n\nAll these values, if `undefined`, fallback to the associated [`elements.point.*`](../configuration/elements.md#point-configuration) options.\n\n## Default Options\n\nWe can also change the default values for the Bubble chart type. Doing so will give all bubble charts created after this point the new defaults. The default configuration for the bubble chart can be accessed at `Chart.overrides.bubble`.\n\n## Data Structure\n\nBubble chart datasets need to contain a `data` array of points, each point represented by an object containing the following properties:\n\n```javascript\n{\n    // X Value\n    x: number,\n\n    // Y Value\n    y: number,\n\n    // Bubble radius in pixels (not scaled).\n    r: number\n}\n```\n\n**Important:** the radius property, `r` is **not** scaled by the chart, it is the raw radius in pixels of the bubble that is drawn on the canvas.\n\n## Internal data format\n\n`{x, y, _custom}` where `_custom` is the radius.\n"
  },
  {
    "path": "docs/charts/doughnut.md",
    "content": "# Doughnut and Pie Charts\n\nPie and doughnut charts are probably the most commonly used charts. They are divided into segments, the arc of each segment shows the proportional value of each piece of data.\n\nThey are excellent at showing the relational proportions between data.\n\nPie and doughnut charts are effectively the same class in Chart.js, but have one different default value - their `cutout`. This equates to what portion of the inner should be cut out. This defaults to `0` for pie charts, and `'50%'` for doughnuts.\n\nThey are also registered under two aliases in the `Chart` core. Other than their different default value, and different alias, they are exactly the same.\n\n:::: tabs\n\n::: tab Doughnut\n\n```js chart-editor\n// <block:setup:1>\nconst data = {\n  labels: [\n    'Red',\n    'Blue',\n    'Yellow'\n  ],\n  datasets: [{\n    label: 'My First Dataset',\n    data: [300, 50, 100],\n    backgroundColor: [\n      'rgb(255, 99, 132)',\n      'rgb(54, 162, 235)',\n      'rgb(255, 205, 86)'\n    ],\n    hoverOffset: 4\n  }]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'doughnut',\n  data: data,\n};\n// </block:config>\n\nmodule.exports = {\n  actions: [],\n  config: config,\n};\n```\n\n:::\n\n:::tab Pie\n\n```js chart-editor\n// <block:setup:1>\nconst data = {\n  labels: [\n    'Red',\n    'Blue',\n    'Yellow'\n  ],\n  datasets: [{\n    label: 'My First Dataset',\n    data: [300, 50, 100],\n    backgroundColor: [\n      'rgb(255, 99, 132)',\n      'rgb(54, 162, 235)',\n      'rgb(255, 205, 86)'\n    ],\n    hoverOffset: 4\n  }]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'pie',\n  data: data,\n};\n// </block:config>\n\nmodule.exports = {\n  actions: [],\n  config: config,\n};\n```\n\n:::\n\n::::\n\n## Dataset Properties\n\nNamespaces:\n\n* `data.datasets[index]` - options for this dataset only\n* `options.datasets.doughnut` - options for all doughnut datasets\n* `options.datasets.pie` - options for all pie datasets\n* `options.elements.arc` - options for all [arc elements](../configuration/elements.md#arc-configuration)\n* `options` - options for the whole chart\n\nThe doughnut/pie chart allows a number of properties to be specified for each dataset. These are used to set display properties for a specific dataset. For example, the colours of the dataset's arcs are generally set this way.\n\n| Name | Type | [Scriptable](../general/options.md#scriptable-options) | [Indexable](../general/options.md#indexable-options) | Default\n| ---- | ---- | :----: | :----: | ----\n| [`backgroundColor`](#styling) | [`Color`](../general/colors.md) | Yes | Yes | `'rgba(0, 0, 0, 0.1)'`\n| [`borderAlign`](#border-alignment) | `'center'`\\|`'inner'` | Yes | Yes | `'center'`\n| [`borderColor`](#styling) | [`Color`](../general/colors.md) | Yes | Yes | `'#fff'`\n| [`borderDash`](#styling) | `number[]` | Yes | - | `[]`\n| [`borderDashOffset`](#styling) | `number` | Yes | - | `0.0`\n| [`borderJoinStyle`](#styling) | `'round'`\\|`'bevel'`\\|`'miter'` | Yes | Yes | `undefined`\n| [`borderRadius`](#border-radius) | `number`\\|`object` | Yes | Yes | `0`\n| [`borderWidth`](#styling) | `number` | Yes | Yes | `2`\n| [`circumference`](#general) | `number` | - | - | `undefined`\n| [`clip`](#general) | `number`\\|`object`\\|`false` | - | - | `undefined`\n| [`data`](#data-structure) | `number[]` | - | - | **required**\n| [`hoverBackgroundColor`](#interactions) | [`Color`](../general/colors.md) | Yes | Yes | `undefined`\n| [`hoverBorderColor`](#interactions) | [`Color`](../general/colors.md) | Yes | Yes | `undefined`\n| [`hoverBorderDash`](#interactions) | `number[]` | Yes | - | `undefined`\n| [`hoverBorderDashOffset`](#interactions) | `number` | Yes | - | `undefined`\n| [`hoverBorderJoinStyle`](#interactions) | `'round'`\\|`'bevel'`\\|`'miter'` | Yes | Yes | `undefined`\n| [`hoverBorderWidth`](#interactions) | `number` | Yes | Yes | `undefined`\n| [`hoverOffset`](#interactions) | `number` | Yes | Yes | `0`\n| [`offset`](#styling) | `number`\\|`number[]` | Yes | Yes | `0`\n| [`rotation`](#general) | `number` | - | - | `undefined`\n| [`spacing`](#styling) | `number` | - | - | `0`\n| [`weight`](#styling) | `number` | - | - | `1`\n\nAll these values, if `undefined`, fallback to the scopes described in [option resolution](../general/options)\n\n### General\n\n| Name | Description\n| ---- | ----\n| `circumference` | Per-dataset override for the sweep that the arcs cover\n| `clip` | How to clip relative to chartArea. Positive value allows overflow, negative value clips that many pixels inside chartArea. `0` = clip at chartArea. Clipping can also be configured per side: `clip: {left: 5, top: false, right: -2, bottom: 0}`\n| `rotation` | Per-dataset override for the starting angle to draw arcs from\n\n### Styling\n\nThe style of each arc can be controlled with the following properties:\n\n| Name | Description\n| ---- | ----\n| `backgroundColor` | arc background color.\n| `borderColor` | arc border color.\n| `borderDash` | arc border length and spacing of dashes. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash).\n| `borderDashOffset` | arc border offset for line dashes. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineDashOffset).\n| `borderJoinStyle` | arc border join style. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineJoin).\n| `borderWidth` | arc border width (in pixels).\n| `offset` | arc offset (in pixels).\n| `spacing` | Fixed arc offset (in pixels). Similar to `offset` but applies to all arcs.\n| `weight` | The relative thickness of the dataset. Providing a value for weight will cause the pie or doughnut dataset to be drawn with a thickness relative to the sum of all the dataset weight values.\n\nAll these values, if `undefined`, fallback to the associated [`elements.arc.*`](../configuration/elements.md#arc-configuration) options.\n\n### Border Alignment\n\nThe following values are supported for `borderAlign`.\n\n* `'center'` (default)\n* `'inner'`\n\nWhen `'center'` is set, the borders of arcs next to each other will overlap. When `'inner'` is set, it is guaranteed that all borders will not overlap.\n\n### Border Radius\n\nIf this value is a number, it is applied to all corners of the arc (outerStart, outerEnd, innerStart, innerRight). If this value is an object, the `outerStart` property defines the outer-start corner's border radius. Similarly, the `outerEnd`, `innerStart`, and `innerEnd` properties can also be specified.\n\n### Interactions\n\nThe interaction with each arc can be controlled with the following properties:\n\n| Name | Description\n| ---- | -----------\n| `hoverBackgroundColor` | arc background color when hovered.\n| `hoverBorderColor` | arc border color when hovered.\n| `hoverBorderDash` | arc border length and spacing of dashes when hovered. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash).\n| `hoverBorderDashOffset` | arc border offset for line dashes when hovered. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineDashOffset).\n| `hoverBorderJoinStyle` | arc border join style when hovered. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineJoin).\n| `hoverBorderWidth` | arc border width when hovered (in pixels).\n| `hoverOffset` | arc offset when hovered (in pixels).\n\nAll these values, if `undefined`, fallback to the associated [`elements.arc.*`](../configuration/elements.md#arc-configuration) options.\n\n## Config Options\n\nThese are the customisation options specific to Pie & Doughnut charts. These options are looked up on access, and form together with the global chart configuration the options of the chart.\n\n| Name | Type | Default | Description\n| ---- | ---- | ------- | -----------\n| `cutout` | `number`\\|`string` | `50%` - for doughnut, `0` - for pie | The portion of the chart that is cut out of the middle. If `string` and ending with '%', percentage of the chart radius. `number` is considered to be pixels.\n| `radius` | `number`\\|`string` | `100%` | The outer radius of the chart. If `string` and ending with '%', percentage of the maximum radius. `number` is considered to be pixels.\n| `rotation` | `number` | 0 | Starting angle to draw arcs from.\n| `circumference` | `number` | 360 | Sweep to allow arcs to cover.\n| `animation.animateRotate` | `boolean` | `true` | If true, the chart will animate in with a rotation animation. This property is in the `options.animation` object.\n| `animation.animateScale` | `boolean` | `false` | If true, will animate scaling the chart from the center outwards.\n\n## Default Options\n\nWe can also change these default values for each Doughnut type that is created, this object is available at `Chart.overrides.doughnut`. Pie charts also have a clone of these defaults available to change at `Chart.overrides.pie`, with the only difference being `cutout` being set to 0.\n\n## Data Structure\n\nFor a pie chart, datasets need to contain an array of data points. The data points should be a number, Chart.js will total all the numbers and calculate the relative proportion of each.\n\nYou also need to specify an array of labels so that tooltips appear correctly.\n\n```javascript\ndata = {\n    datasets: [{\n        data: [10, 20, 30]\n    }],\n\n    // These labels appear in the legend and in the tooltips when hovering different arcs\n    labels: [\n        'Red',\n        'Yellow',\n        'Blue'\n    ]\n};\n```\n"
  },
  {
    "path": "docs/charts/line.md",
    "content": "# Line Chart\n\nA line chart is a way of plotting data points on a line. Often, it is used to show trend data, or the comparison of two data sets.\n\n```js chart-editor\n// <block:setup:1>\nconst labels = Utils.months({count: 7});\nconst data = {\n  labels: labels,\n  datasets: [{\n    label: 'My First Dataset',\n    data: [65, 59, 80, 81, 56, 55, 40],\n    fill: false,\n    borderColor: 'rgb(75, 192, 192)',\n    tension: 0.1\n  }]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'line',\n  data: data,\n};\n// </block:config>\n\nmodule.exports = {\n  actions: [],\n  config: config,\n};\n```\n\n## Dataset Properties\n\nNamespaces:\n\n* `data.datasets[index]` - options for this dataset only\n* `options.datasets.line` - options for all line datasets\n* `options.elements.line` - options for all [line elements](../configuration/elements.md#line-configuration)\n* `options.elements.point` - options for all [point elements](../configuration/elements.md#point-configuration)\n* `options` - options for the whole chart\n\nThe line chart allows a number of properties to be specified for each dataset. These are used to set display properties for a specific dataset. For example, the colour of a line is generally set this way.\n\n| Name | Type | [Scriptable](../general/options.md#scriptable-options) | [Indexable](../general/options.md#indexable-options) | Default\n| ---- | ---- | :----: | :----: | ----\n| [`backgroundColor`](#line-styling) | [`Color`](../general/colors.md) | Yes | - | `'rgba(0, 0, 0, 0.1)'`\n| [`borderCapStyle`](#line-styling) | `string` | Yes | - | `'butt'`\n| [`borderColor`](#line-styling) | [`Color`](../general/colors.md) | Yes | - | `'rgba(0, 0, 0, 0.1)'`\n| [`borderDash`](#line-styling) | `number[]` | Yes | - | `[]`\n| [`borderDashOffset`](#line-styling) | `number` | Yes | - | `0.0`\n| [`borderJoinStyle`](#line-styling) | `'round'`\\|`'bevel'`\\|`'miter'` | Yes | - | `'miter'`\n| [`borderWidth`](#line-styling) | `number` | Yes | - | `3`\n| [`clip`](#general) | `number`\\|`object`\\|`false` | - | - | `undefined`\n| [`cubicInterpolationMode`](#cubicinterpolationmode) | `string` | Yes | - | `'default'`\n| [`data`](#data-structure) | `object`\\|`object[]`\\| `number[]`\\|`string[]` | - | - | **required**\n| [`drawActiveElementsOnTop`](#general) | `boolean` | Yes | Yes | `true`\n| [`fill`](#line-styling) | `boolean`\\|`string` | Yes | - | `false`\n| [`hoverBackgroundColor`](#line-styling) | [`Color`](../general/colors.md) | Yes | - | `undefined`\n| [`hoverBorderCapStyle`](#line-styling) | `string` | Yes | - | `undefined`\n| [`hoverBorderColor`](#line-styling) | [`Color`](../general/colors.md) | Yes | - | `undefined`\n| [`hoverBorderDash`](#line-styling) | `number[]` | Yes | - | `undefined`\n| [`hoverBorderDashOffset`](#line-styling) | `number` | Yes | - | `undefined`\n| [`hoverBorderJoinStyle`](#line-styling) | `'round'`\\|`'bevel'`\\|`'miter'` | Yes | - | `undefined`\n| [`hoverBorderWidth`](#line-styling) | `number` | Yes | - | `undefined`\n| [`indexAxis`](#general) | `string` | - | - | `'x'`\n| [`label`](#general) | `string` | - | - | `''`\n| [`order`](#general) | `number` | - | - | `0`\n| [`pointBackgroundColor`](#point-styling) | `Color` | Yes | Yes | `'rgba(0, 0, 0, 0.1)'`\n| [`pointBorderColor`](#point-styling) | `Color` | Yes | Yes | `'rgba(0, 0, 0, 0.1)'`\n| [`pointBorderWidth`](#point-styling) | `number` | Yes | Yes | `1`\n| [`pointHitRadius`](#point-styling) | `number` | Yes | Yes | `1`\n| [`pointHoverBackgroundColor`](#interactions) | `Color` | Yes | Yes | `undefined`\n| [`pointHoverBorderColor`](#interactions) | `Color` | Yes | Yes | `undefined`\n| [`pointHoverBorderWidth`](#interactions) | `number` | Yes | Yes | `1`\n| [`pointHoverRadius`](#interactions) | `number` | Yes | Yes | `4`\n| [`pointRadius`](#point-styling) | `number` | Yes | Yes | `3`\n| [`pointRotation`](#point-styling) | `number` | Yes | Yes | `0`\n| [`pointStyle`](#point-styling) | [`pointStyle`](../configuration/elements.md#types) | Yes | Yes | `'circle'`\n| [`segment`](#segment) | `object` | - | - | `undefined`\n| [`showLine`](#line-styling) | `boolean` | - | - | `true`\n| [`spanGaps`](#line-styling) | `boolean`\\|`number` | - | - | `undefined`\n| [`stack`](#general) | `string` | - | - | `'line'` |\n| [`stepped`](#stepped) | `boolean`\\|`string` | - | - | `false`\n| [`tension`](#line-styling) | `number` | - | - | `0`\n| [`xAxisID`](#general) | `string` | - | - | first x axis\n| [`yAxisID`](#general) | `string` | - | - | first y axis\n\nAll these values, if `undefined`, fallback to the scopes described in [option resolution](../general/options)\n\n### General\n\n| Name | Description\n| ---- | ----\n| `clip` | How to clip relative to chartArea. Positive value allows overflow, negative value clips that many pixels inside chartArea. `0` = clip at chartArea. Clipping can also be configured per side: `clip: {left: 5, top: false, right: -2, bottom: 0}`\n| `drawActiveElementsOnTop` | Draw the active points of a dataset over the other points of the dataset\n| `indexAxis` | The base axis of the dataset. `'x'` for horizontal lines and `'y'` for vertical lines.\n| `label` | The label for the dataset which appears in the legend and tooltips.\n| `order` | The drawing order of dataset. Also affects order for stacking, tooltip and legend. [more](mixed.md#drawing-order)\n| `stack` | The ID of the group to which this dataset belongs to (when stacked, each group will be a separate stack). [more](#stacked-area-chart)\n| `xAxisID` | The ID of the x-axis to plot this dataset on.\n| `yAxisID` | The ID of the y-axis to plot this dataset on.\n\n### Point Styling\n\nThe style of each point can be controlled with the following properties:\n\n| Name | Description\n| ---- | ----\n| `pointBackgroundColor` | The fill color for points.\n| `pointBorderColor` | The border color for points.\n| `pointBorderWidth` | The width of the point border in pixels.\n| `pointHitRadius` | The pixel size of the non-displayed point that reacts to mouse events.\n| `pointRadius` | The radius of the point shape. If set to 0, the point is not rendered.\n| `pointRotation` | The rotation of the point in degrees.\n| `pointStyle` | Style of the point. [more...](../configuration/elements.md#point-styles)\n\nAll these values, if `undefined`, fallback first to the dataset options then to the associated [`elements.point.*`](../configuration/elements.md#point-configuration) options.\n\n### Line Styling\n\nThe style of the line can be controlled with the following properties:\n\n| Name | Description\n| ---- | ----\n| `backgroundColor` | The line fill color.\n| `borderCapStyle` | Cap style of the line. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineCap).\n| `borderColor` | The line color.\n| `borderDash` | Length and spacing of dashes. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash).\n| `borderDashOffset` | Offset for line dashes. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineDashOffset).\n| `borderJoinStyle` | Line joint style. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineJoin).\n| `borderWidth` | The line width (in pixels).\n| `fill` | How to fill the area under the line. See [area charts](area.md).\n| `tension` | Bezier curve tension of the line. Set to 0 to draw straightlines. This option is ignored if monotone cubic interpolation is used.\n| `showLine` | If false, the line is not drawn for this dataset.\n| `spanGaps` | If true, lines will be drawn between points with no or null data. If false, points with `null` data will create a break in the line. Can also be a number specifying the maximum gap length to span. The unit of the value depends on the scale used.\n\nIf the value is `undefined`, the values fallback to the associated [`elements.line.*`](../configuration/elements.md#line-configuration) options.\n\n### Interactions\n\nThe interaction with each point can be controlled with the following properties:\n\n| Name | Description\n| ---- | -----------\n| `pointHoverBackgroundColor` | Point background color when hovered.\n| `pointHoverBorderColor` | Point border color when hovered.\n| `pointHoverBorderWidth` | Border width of point when hovered.\n| `pointHoverRadius` | The radius of the point when hovered.\n\n### cubicInterpolationMode\n\nThe following interpolation modes are supported.\n\n* `'default'`\n* `'monotone'`\n\nThe `'default'` algorithm uses a custom weighted cubic interpolation, which produces pleasant curves for all types of datasets.\n\nThe `'monotone'` algorithm is more suited to `y = f(x)` datasets: it preserves monotonicity (or piecewise monotonicity) of the dataset being interpolated, and ensures local extremums (if any) stay at input data points.\n\nIf left untouched (`undefined`), the global `options.elements.line.cubicInterpolationMode` property is used.\n\n### Segment\n\nLine segment styles can be overridden by scriptable options in the `segment` object. Currently, all of the `border*` and `backgroundColor` options are supported. The segment styles are resolved for each section of the line between each point. `undefined` fallbacks to main line styles.\n\n:::tip\nTo be able to style gaps, you need the [`spanGaps`](#line-styling) option enabled.\n:::\n\nContext for the scriptable segment contains the following properties:\n\n* `type`: `'segment'`\n* `p0`: first point element\n* `p1`: second point element\n* `p0DataIndex`: index of first point in the data array\n* `p1DataIndex`: index of second point in the data array\n* `datasetIndex`: dataset index\n\n[Example usage](../samples/line/segments.md)\n\n### Stepped\n\nThe following values are supported for `stepped`.\n\n* `false`: No Step Interpolation (default)\n* `true`: Step-before Interpolation (eq. `'before'`)\n* `'before'`: Step-before Interpolation\n* `'after'`: Step-after Interpolation\n* `'middle'`: Step-middle Interpolation\n\nIf the `stepped` value is set to anything other than false, `tension` will be ignored.\n\n## Default Options\n\nIt is common to want to apply a configuration setting to all created line charts. The global line chart settings are stored in `Chart.overrides.line`. Changing the global options only affects charts created after the change. Existing charts are not changed.\n\nFor example, to configure all line charts with `spanGaps = true` you would do:\n\n```javascript\nChart.overrides.line.spanGaps = true;\n```\n\n## Data Structure\n\nAll the supported [data structures](../general/data-structures.md) can be used with line charts.\n\n## Stacked Area Chart\n\nLine charts can be configured into stacked area charts by changing the settings on the y-axis to enable stacking. Stacked area charts can be used to show how one data trend is made up of a number of smaller pieces.\n\n```javascript\nconst stackedLine = new Chart(ctx, {\n    type: 'line',\n    data: data,\n    options: {\n        scales: {\n            y: {\n                stacked: true\n            }\n        }\n    }\n});\n```\n\n## Vertical Line Chart\n\nA vertical line chart is a variation on the horizontal line chart.\nTo achieve this, you will have to set the `indexAxis` property in the options object to `'y'`.\nThe default for this property is `'x'` and thus will show horizontal lines.\n\n```js chart-editor\n// <block:setup:1>\nconst labels = Utils.months({count: 7});\nconst data = {\n  labels: labels,\n  datasets: [{\n    axis: 'y',\n    label: 'My First Dataset',\n    data: [65, 59, 80, 81, 56, 55, 40],\n    fill: false,\n    backgroundColor: [\n      'rgba(255, 99, 132, 0.2)',\n      'rgba(255, 159, 64, 0.2)',\n      'rgba(255, 205, 86, 0.2)',\n      'rgba(75, 192, 192, 0.2)',\n      'rgba(54, 162, 235, 0.2)',\n      'rgba(153, 102, 255, 0.2)',\n      'rgba(201, 203, 207, 0.2)'\n    ],\n    borderColor: [\n      'rgb(255, 99, 132)',\n      'rgb(255, 159, 64)',\n      'rgb(255, 205, 86)',\n      'rgb(75, 192, 192)',\n      'rgb(54, 162, 235)',\n      'rgb(153, 102, 255)',\n      'rgb(201, 203, 207)'\n    ],\n    borderWidth: 1\n  }]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'line',\n  data: data,\n  options: {\n    indexAxis: 'y',\n    scales: {\n      x: {\n        beginAtZero: true\n      }\n    }\n  }\n};\n// </block:config>\n\nmodule.exports = {\n  actions: [],\n  config: config,\n};\n```\n\n### Config Options\n\nThe configuration options for the vertical line chart are the same as for the [line chart](#configuration-options). However, any options specified on the x-axis in a line chart, are applied to the y-axis in a vertical line chart.\n\n## Internal data format\n\n`{x, y}`\n"
  },
  {
    "path": "docs/charts/mixed.md",
    "content": "# Mixed Chart Types\n\nWith Chart.js, it is possible to create mixed charts that are a combination of two or more different chart types. A common example is a bar chart that also includes a line dataset.\n\nWhen creating a mixed chart, we specify the chart type on each dataset.\n\n```javascript\nconst mixedChart = new Chart(ctx, {\n    data: {\n        datasets: [{\n            type: 'bar',\n            label: 'Bar Dataset',\n            data: [10, 20, 30, 40]\n        }, {\n            type: 'line',\n            label: 'Line Dataset',\n            data: [50, 50, 50, 50],\n        }],\n        labels: ['January', 'February', 'March', 'April']\n    },\n    options: options\n});\n```\n\nAt this point, we have a chart rendering how we'd like. It's important to note that the default options for the charts are only considered at the dataset level and are not merged at the chart level in this case.\n\n```js chart-editor\n// <block:setup:1>\nconst data = {\n  labels: [\n    'January',\n    'February',\n    'March',\n    'April'\n  ],\n  datasets: [{\n    type: 'bar',\n    label: 'Bar Dataset',\n    data: [10, 20, 30, 40],\n    borderColor: 'rgb(255, 99, 132)',\n    backgroundColor: 'rgba(255, 99, 132, 0.2)'\n  }, {\n    type: 'line',\n    label: 'Line Dataset',\n    data: [50, 50, 50, 50],\n    fill: false,\n    borderColor: 'rgb(54, 162, 235)'\n  }]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'scatter',\n  data: data,\n  options: {\n    scales: {\n      y: {\n        beginAtZero: true\n      }\n    }\n  }\n};\n// </block:config>\n\nmodule.exports = {\n  actions: [],\n  config: config,\n};\n```\n\n## Drawing order\n\n By default, datasets are drawn such that the first one is top-most. This can be altered by specifying `order` option to datasets. `order` defaults to `0`. Note that this also affects stacking, legend, and tooltip. So it's essentially the same as reordering the datasets.\n\nThe `order` property behaves like a weight instead of a specific order, so the higher the number, the sooner that dataset is drawn on the canvas and thus other datasets with a lower order number will get drawn over it.\n\n ```javascript\nconst mixedChart = new Chart(ctx, {\n    type: 'bar',\n    data: {\n        datasets: [{\n            label: 'Bar Dataset',\n            data: [10, 20, 30, 40],\n            // this dataset is drawn below\n            order: 2\n        }, {\n            label: 'Line Dataset',\n            data: [10, 10, 10, 10],\n            type: 'line',\n            // this dataset is drawn on top\n            order: 1\n        }],\n        labels: ['January', 'February', 'March', 'April']\n    },\n    options: options\n});\n```\n"
  },
  {
    "path": "docs/charts/polar.md",
    "content": "# Polar Area Chart\n\nPolar area charts are similar to pie charts, but each segment has the same angle - the radius of the segment differs depending on the value.\n\nThis type of chart is often useful when we want to show a comparison data similar to a pie chart, but also show a scale of values for context.\n\n```js chart-editor\n// <block:setup:1>\nconst data = {\n  labels: [\n    'Red',\n    'Green',\n    'Yellow',\n    'Grey',\n    'Blue'\n  ],\n  datasets: [{\n    label: 'My First Dataset',\n    data: [11, 16, 7, 3, 14],\n    backgroundColor: [\n      'rgb(255, 99, 132)',\n      'rgb(75, 192, 192)',\n      'rgb(255, 205, 86)',\n      'rgb(201, 203, 207)',\n      'rgb(54, 162, 235)'\n    ]\n  }]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'polarArea',\n  data: data,\n  options: {}\n};\n// </block:config>\n\nmodule.exports = {\n  actions: [],\n  config: config,\n};\n```\n\n## Dataset Properties\n\nNamespaces:\n\n* `data.datasets[index]` - options for this dataset only\n* `options.datasets.polarArea` - options for all polarArea datasets\n* `options.elements.arc` - options for all [arc elements](../configuration/elements.md#arc-configuration)\n* `options` - options for the whole chart\n\nThe following options can be included in a polar area chart dataset to configure options for that specific dataset.\n\n| Name | Type | [Scriptable](../general/options.md#scriptable-options) | [Indexable](../general/options.md#indexable-options) | Default\n| ---- | ---- | :----: | :----: | ----\n| [`backgroundColor`](#styling) | [`Color`](../general/colors.md) | Yes | Yes | `'rgba(0, 0, 0, 0.1)'`\n| [`borderAlign`](#border-alignment) | `'center'`\\|`'inner'` | Yes | Yes | `'center'`\n| [`borderColor`](#styling) | [`Color`](../general/colors.md) | Yes | Yes | `'#fff'`\n| [`borderDash`](#styling) | `number[]` | Yes | - | `[]`\n| [`borderDashOffset`](#styling) | `number` | Yes | - | `0.0`\n| [`borderJoinStyle`](#styling) | `'round'`\\|`'bevel'`\\|`'miter'` | Yes | Yes | `undefined`\n| [`borderWidth`](#styling) | `number` | Yes | Yes | `2`\n| [`clip`](#general) | `number`\\|`object`\\|`false` | - | - | `undefined`\n| [`data`](#data-structure) | `number[]` | - | - | **required**\n| [`hoverBackgroundColor`](#interactions) | [`Color`](../general/colors.md) | Yes | Yes | `undefined`\n| [`hoverBorderColor`](#interactions) | [`Color`](../general/colors.md) | Yes | Yes | `undefined`\n| [`hoverBorderDash`](#interactions) | `number[]` | Yes | - | `undefined`\n| [`hoverBorderDashOffset`](#interactions) | `number` | Yes | - | `undefined`\n| [`hoverBorderJoinStyle`](#interactions) | `'round'`\\|`'bevel'`\\|`'miter'` | Yes | Yes | `undefined`\n| [`hoverBorderWidth`](#interactions) | `number` | Yes | Yes | `undefined`\n| [`circular`](#styling) | `boolean` | Yes | Yes | `true`\n\nAll these values, if `undefined`, fallback to the scopes described in [option resolution](../general/options)\n\n### General\n\n| Name | Description\n| ---- | ----\n| `clip` | How to clip relative to chartArea. Positive value allows overflow, negative value clips that many pixels inside chartArea. `0` = clip at chartArea. Clipping can also be configured per side: `clip: {left: 5, top: false, right: -2, bottom: 0}`\n\n### Styling\n\nThe style of each arc can be controlled with the following properties:\n\n| Name | Description\n| ---- | ----\n| `backgroundColor` | arc background color.\n| `borderColor` | arc border color.\n| `borderDash` | arc border length and spacing of dashes. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash).\n| `borderDashOffset` | arc border offset for line dashes. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineDashOffset).\n| `borderJoinStyle` | arc border join style. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineJoin).\n| `borderWidth` | arc border width (in pixels).\n| `circular` | By default the Arc is curved. If `circular: false` the Arc will be flat.\n\nAll these values, if `undefined`, fallback to the associated [`elements.arc.*`](../configuration/elements.md#arc-configuration) options.\n\n### Border Alignment\n\nThe following values are supported for `borderAlign`.\n\n* `'center'` (default)\n* `'inner'`\n\nWhen `'center'` is set, the borders of arcs next to each other will overlap. When `'inner'` is set, it is guaranteed that all the borders do not overlap.\n\n### Interactions\n\nThe interaction with each arc can be controlled with the following properties:\n\n| Name | Description\n| ---- | -----------\n| `hoverBackgroundColor` | arc background color when hovered.\n| `hoverBorderColor` | arc border color when hovered.\n| `hoverBorderDash` | arc border length and spacing of dashes when hovered. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash).\n| `hoverBorderDashOffset` | arc border offset for line dashes when hovered. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineDashOffset).\n| `hoverBorderJoinStyle` | arc border join style when hovered. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineJoin).\n| `hoverBorderWidth` | arc border width when hovered (in pixels).\n\nAll these values, if `undefined`, fallback to the associated [`elements.arc.*`](../configuration/elements.md#arc-configuration) options.\n\n## Config Options\n\nThese are the customisation options specific to Polar Area charts. These options are looked up on access, and form together with the [global chart default options](#default-options) the options of the chart.\n\n| Name | Type | Default | Description\n| ---- | ---- | ------- | -----------\n| `animation.animateRotate` | `boolean` | `true` | If true, the chart will animate in with a rotation animation. This property is in the `options.animation` object.\n| `animation.animateScale` | `boolean` | `true` | If true, will animate scaling the chart from the center outwards.\n\nThe polar area chart uses the [radialLinear](../axes/radial/linear.md) scale. Additional configuration is provided via the scale.\n\n## Default Options\n\nWe can also change these default values for each PolarArea type that is created, this object is available at `Chart.overrides.polarArea`. Changing the global options only affects charts created after the change. Existing charts are not changed.\n\nFor example, to configure all new polar area charts with `animateScale = false` you would do:\n\n```javascript\nChart.overrides.polarArea.animation.animateScale = false;\n```\n\n## Data Structure\n\nFor a polar area chart, datasets need to contain an array of data points. The data points should be a number, Chart.js will total all of the numbers and calculate the relative proportion of each.\n\nYou also need to specify an array of labels so that tooltips appear correctly for each slice.\n\n```javascript\ndata = {\n    datasets: [{\n        data: [10, 20, 30]\n    }],\n\n    // These labels appear in the legend and in the tooltips when hovering different arcs\n    labels: [\n        'Red',\n        'Yellow',\n        'Blue'\n    ]\n};\n```\n"
  },
  {
    "path": "docs/charts/radar.md",
    "content": "# Radar Chart\n\nA radar chart is a way of showing multiple data points and the variation between them.\n\nThey are often useful for comparing the points of two or more different data sets.\n\n```js chart-editor\n// <block:setup:1>\nconst data = {\n  labels: [\n    'Eating',\n    'Drinking',\n    'Sleeping',\n    'Designing',\n    'Coding',\n    'Cycling',\n    'Running'\n  ],\n  datasets: [{\n    label: 'My First Dataset',\n    data: [65, 59, 90, 81, 56, 55, 40],\n    fill: true,\n    backgroundColor: 'rgba(255, 99, 132, 0.2)',\n    borderColor: 'rgb(255, 99, 132)',\n    pointBackgroundColor: 'rgb(255, 99, 132)',\n    pointBorderColor: '#fff',\n    pointHoverBackgroundColor: '#fff',\n    pointHoverBorderColor: 'rgb(255, 99, 132)'\n  }, {\n    label: 'My Second Dataset',\n    data: [28, 48, 40, 19, 96, 27, 100],\n    fill: true,\n    backgroundColor: 'rgba(54, 162, 235, 0.2)',\n    borderColor: 'rgb(54, 162, 235)',\n    pointBackgroundColor: 'rgb(54, 162, 235)',\n    pointBorderColor: '#fff',\n    pointHoverBackgroundColor: '#fff',\n    pointHoverBorderColor: 'rgb(54, 162, 235)'\n  }]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'radar',\n  data: data,\n  options: {\n    elements: {\n      line: {\n        borderWidth: 3\n      }\n    }\n  },\n};\n// </block:config>\n\nmodule.exports = {\n  actions: [],\n  config: config,\n};\n```\n\n## Dataset Properties\n\nNamespaces:\n\n* `data.datasets[index]` - options for this dataset only\n* `options.datasets.line` - options for all line datasets\n* `options.elements.line` - options for all [line elements](../configuration/elements.md#line-configuration)\n* `options.elements.point` - options for all [point elements](../configuration/elements.md#point-configuration)\n* `options` - options for the whole chart\n\nThe radar chart allows a number of properties to be specified for each dataset. These are used to set display properties for a specific dataset. For example, the colour of a line is generally set this way.\n\n| Name | Type | [Scriptable](../general/options.md#scriptable-options) | [Indexable](../general/options.md#indexable-options) | Default\n| ---- | ---- | :----: | :----: | ----\n| [`backgroundColor`](#line-styling) | [`Color`](../general/colors.md) | Yes | - | `'rgba(0, 0, 0, 0.1)'`\n| [`borderCapStyle`](#line-styling) | `string` | Yes | - | `'butt'`\n| [`borderColor`](#line-styling) | [`Color`](../general/colors.md) | Yes | - | `'rgba(0, 0, 0, 0.1)'`\n| [`borderDash`](#line-styling) | `number[]` | Yes | - | `[]`\n| [`borderDashOffset`](#line-styling) | `number` | Yes | - | `0.0`\n| [`borderJoinStyle`](#line-styling) | `'round'`\\|`'bevel'`\\|`'miter'` | Yes | - | `'miter'`\n| [`borderWidth`](#line-styling) | `number` | Yes | - | `3`\n| [`hoverBackgroundColor`](#line-styling) | [`Color`](../general/colors.md) | Yes | - | `undefined`\n| [`hoverBorderCapStyle`](#line-styling) | `string` | Yes | - | `undefined`\n| [`hoverBorderColor`](#line-styling) | [`Color`](../general/colors.md) | Yes | - | `undefined`\n| [`hoverBorderDash`](#line-styling) | `number[]` | Yes | - | `undefined`\n| [`hoverBorderDashOffset`](#line-styling) | `number` | Yes | - | `undefined`\n| [`hoverBorderJoinStyle`](#line-styling) | `'round'`\\|`'bevel'`\\|`'miter'` | Yes | - | `undefined`\n| [`hoverBorderWidth`](#line-styling) | `number` | Yes | - | `undefined`\n| [`clip`](#general) | `number`\\|`object`\\|`false` | - | - | `undefined`\n| [`data`](#data-structure) | `number[]` | - | - | **required**\n| [`fill`](#line-styling) | `boolean`\\|`string` | Yes | - | `false`\n| [`label`](#general) | `string` | - | - | `''`\n| [`order`](#general) | `number` | - | - | `0`\n| [`tension`](#line-styling) | `number` | - | - | `0`\n| [`pointBackgroundColor`](#point-styling) | `Color` | Yes | Yes | `'rgba(0, 0, 0, 0.1)'`\n| [`pointBorderColor`](#point-styling) | `Color` | Yes | Yes | `'rgba(0, 0, 0, 0.1)'`\n| [`pointBorderWidth`](#point-styling) | `number` | Yes | Yes | `1`\n| [`pointHitRadius`](#point-styling) | `number` | Yes | Yes | `1`\n| [`pointHoverBackgroundColor`](#interactions) | `Color` | Yes | Yes | `undefined`\n| [`pointHoverBorderColor`](#interactions) | `Color` | Yes | Yes | `undefined`\n| [`pointHoverBorderWidth`](#interactions) | `number` | Yes | Yes | `1`\n| [`pointHoverRadius`](#interactions) | `number` | Yes | Yes | `4`\n| [`pointRadius`](#point-styling) | `number` | Yes | Yes | `3`\n| [`pointRotation`](#point-styling) | `number` | Yes | Yes | `0`\n| [`pointStyle`](#point-styling) | [`pointStyle`](../configuration/elements.md#types) | Yes | Yes | `'circle'`\n| [`spanGaps`](#line-styling) | `boolean` | - | - | `undefined`\n\nAll these values, if `undefined`, fallback to the scopes described in [option resolution](../general/options)\n\n### General\n\n| Name | Description\n| ---- | ----\n| `clip` | How to clip relative to chartArea. Positive value allows overflow, negative value clips that many pixels inside chartArea. `0` = clip at chartArea. Clipping can also be configured per side: `clip: {left: 5, top: false, right: -2, bottom: 0}`\n| `label` | The label for the dataset which appears in the legend and tooltips.\n| `order` | The drawing order of dataset. Also affects order for tooltip and legend. [more](mixed.md#drawing-order)\n\n### Point Styling\n\nThe style of each point can be controlled with the following properties:\n\n| Name | Description\n| ---- | ----\n| `pointBackgroundColor` | The fill color for points.\n| `pointBorderColor` | The border color for points.\n| `pointBorderWidth` | The width of the point border in pixels.\n| `pointHitRadius` | The pixel size of the non-displayed point that reacts to mouse events.\n| `pointRadius` | The radius of the point shape. If set to 0, the point is not rendered.\n| `pointRotation` | The rotation of the point in degrees.\n| `pointStyle` | Style of the point. [more...](../configuration/elements#point-styles)\n\nAll these values, if `undefined`, fallback first to the dataset options then to the associated [`elements.point.*`](../configuration/elements.md#point-configuration) options.\n\n### Line Styling\n\nThe style of the line can be controlled with the following properties:\n\n| Name | Description\n| ---- | ----\n| `backgroundColor` | The line fill color.\n| `borderCapStyle` | Cap style of the line. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineCap).\n| `borderColor` | The line color.\n| `borderDash` | Length and spacing of dashes. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash).\n| `borderDashOffset` | Offset for line dashes. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineDashOffset).\n| `borderJoinStyle` | Line joint style. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineJoin).\n| `borderWidth` | The line width (in pixels).\n| `fill` | How to fill the area under the line. See [area charts](area.md).\n| `tension` | Bezier curve tension of the line. Set to 0 to draw straight lines.\n| `spanGaps` | If true, lines will be drawn between points with no or null data. If false, points with `null` data will create a break in the line.\n\nIf the value is `undefined`, the values fallback to the associated [`elements.line.*`](../configuration/elements.md#line-configuration) options.\n\n### Interactions\n\nThe interaction with each point can be controlled with the following properties:\n\n| Name | Description\n| ---- | -----------\n| `pointHoverBackgroundColor` | Point background color when hovered.\n| `pointHoverBorderColor` | Point border color when hovered.\n| `pointHoverBorderWidth` | Border width of point when hovered.\n| `pointHoverRadius` | The radius of the point when hovered.\n\n## Scale Options\n\nThe radar chart supports only a single scale. The options for this scale are defined in the `scales.r` property, which can be referenced from the [Linear Radial Axis page](../axes/radial/linear).\n\n```javascript\noptions = {\n    scales: {\n        r: {\n            angleLines: {\n                display: false\n            },\n            suggestedMin: 50,\n            suggestedMax: 100\n        }\n    }\n};\n```\n\n## Default Options\n\nIt is common to want to apply a configuration setting to all created radar charts. The global radar chart settings are stored in `Chart.overrides.radar`. Changing the global options only affects charts created after the change. Existing charts are not changed.\n\n## Data Structure\n\nThe `data` property of a dataset for a radar chart is specified as an array of numbers. Each point in the data array corresponds to the label at the same index.\n\n```javascript\ndata: [20, 10]\n```\n\nFor a radar chart, to provide context of what each point means, we include an array of strings that show around each point in the chart.\n\n```javascript\ndata: {\n    labels: ['Running', 'Swimming', 'Eating', 'Cycling'],\n    datasets: [{\n        data: [20, 10, 4, 2]\n    }]\n}\n```\n\n## Internal data format\n\n`{x, y}`\n"
  },
  {
    "path": "docs/charts/scatter.md",
    "content": "# Scatter Chart\n\nScatter charts are based on basic line charts with the x-axis changed to a linear axis. To use a scatter chart, data must be passed as objects containing X and Y properties. The example below creates a scatter chart with 4 points.\n\n```js chart-editor\n// <block:setup:1>\nconst data = {\n  datasets: [{\n    label: 'Scatter Dataset',\n    data: [{\n      x: -10,\n      y: 0\n    }, {\n      x: 0,\n      y: 10\n    }, {\n      x: 10,\n      y: 5\n    }, {\n      x: 0.5,\n      y: 5.5\n    }],\n    backgroundColor: 'rgb(255, 99, 132)'\n  }],\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'scatter',\n  data: data,\n  options: {\n    scales: {\n      x: {\n        type: 'linear',\n        position: 'bottom'\n      }\n    }\n  }\n};\n// </block:config>\n\nmodule.exports = {\n  actions: [],\n  config: config,\n};\n```\n\n## Dataset Properties\n\nNamespaces:\n\n* `data.datasets[index]` - options for this dataset only\n* `options.datasets.scatter` - options for all scatter datasets\n* `options.elements.line` - options for all [line elements](../configuration/elements.md#line-configuration)\n* `options.elements.point` - options for all [point elements](../configuration/elements.md#point-configuration)\n* `options` - options for the whole chart\n\nThe scatter chart supports all the same properties as the [line chart](./line.md#dataset-properties).\nBy default, the scatter chart will override the showLine property of the line chart to `false`.\n\nThe index scale is of the type `linear`. This means, if you are using the labels array, the values have to be numbers or parsable to numbers, the same applies to the object format for the keys.\n\n## Data Structure\n\nUnlike the line chart where data can be supplied in two different formats, the scatter chart only accepts data in a point format.\n\n```javascript\ndata: [{\n        x: 10,\n        y: 20\n    }, {\n        x: 15,\n        y: 10\n    }]\n```\n\n## Internal data format\n\n`{x, y}`\n"
  },
  {
    "path": "docs/configuration/animations.md",
    "content": "# Animations\n\nChart.js animates charts out of the box. A number of options are provided to configure how the animation looks and how long it takes.\n\n:::: tabs\n\n::: tab \"Looping tension [property]\"\n\n```js chart-editor\n// <block:setup:1>\nconst data = {\n  labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],\n  datasets: [{\n    label: 'Looping tension',\n    data: [65, 59, 80, 81, 26, 55, 40],\n    fill: false,\n    borderColor: 'rgb(75, 192, 192)',\n  }]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'line',\n  data: data,\n  options: {\n    animations: {\n      tension: {\n        duration: 1000,\n        easing: 'linear',\n        from: 1,\n        to: 0,\n        loop: true\n      }\n    },\n    scales: {\n      y: { // defining min and max so hiding the dataset does not change scale range\n        min: 0,\n        max: 100\n      }\n    }\n  }\n};\n// </block:config>\n\nmodule.exports = {\n  actions: [],\n  config: config,\n};\n```\n\n:::\n\n::: tab \"Hide and show [mode]\"\n\n```js chart-editor\n// <block:setup:1>\nconst data = {\n  labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],\n  datasets: [{\n    label: 'Try hiding me',\n    data: [65, 59, 80, 81, 26, 55, 40],\n    fill: false,\n    borderColor: 'rgb(75, 192, 192)',\n  }]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'line',\n  data: data,\n  options: {\n    transitions: {\n      show: {\n        animations: {\n          x: {\n            from: 0\n          },\n          y: {\n            from: 0\n          }\n        }\n      },\n      hide: {\n        animations: {\n          x: {\n            to: 0\n          },\n          y: {\n            to: 0\n          }\n        }\n      }\n    }\n  }\n};\n// </block:config>\n\nmodule.exports = {\n  actions: [],\n  config: config,\n};\n```\n\n:::\n\n::::\n\n## Animation configuration\n\nAnimation configuration consists of 3 keys.\n\n| Name | Type | Details\n| ---- | ---- | -------\n| animation | `object` | [animation](#animation)\n| animations | `object` | [animations](#animations)\n| transitions | `object` | [transitions](#transitions)\n\nThese keys can be configured in following paths:\n\n* `` - chart options\n* `datasets[type]` - dataset type options\n* `overrides[type]` - chart type options\n\nThese paths are valid under `defaults` for global configuration and `options` for instance configuration.\n\n## animation\n\nThe default configuration is defined here: <a href=\"https://github.com/chartjs/Chart.js/blob/master/src/core/core.animations.defaults.js\" target=\"_blank\">core.animations.defaults.js</a>\n\nNamespace: `options.animation`\n\n| Name | Type | Default | Description\n| ---- | ---- | ------- | -----------\n| `duration` | `number` | `1000` | The number of milliseconds an animation takes.\n| `easing` | `string` | `'easeOutQuart'` | Easing function to use. [more...](#easing)\n| `delay` | `number` | `undefined` | Delay before starting the animations.\n| `loop` | `boolean` | `undefined` | If set to `true`, the animations loop endlessly.\n\nThese defaults can be overridden in `options.animation` or `dataset.animation` and `tooltip.animation`. These keys are also [Scriptable](../general/options.md#scriptable-options).\n\n## animations\n\nAnimations options configures which element properties are animated and how.\nIn addition to the main [animation configuration](#animation-configuration), the following options are available:\n\nNamespace: `options.animations[animation]`\n\n| Name | Type | Default | Description\n| ---- | ---- | ------- | -----------\n| `properties` | `string[]` | `key` | The property names this configuration applies to. Defaults to the key name of this object.\n| `type` | `string` | `typeof property` | Type of property, determines the interpolator used. Possible values: `'number'`, `'color'` and `'boolean'`. Only really needed for `'color'`, because `typeof` does not get that right.\n| `from`  | `number`\\|`Color`\\|`boolean` | `undefined` | Start value for the animation. Current value is used when `undefined`\n| `to`  | `number`\\|`Color`\\|`boolean` | `undefined` | End value for the animation. Updated value is used when `undefined`\n| `fn` | <code>&lt;T&gt;(from: T, to: T, factor: number) => T;</code> | `undefined` | Optional custom interpolator, instead of using a predefined interpolator from `type` |\n\n### Default animations\n\n| Name | Option | Value\n| ---- | ------ | -----\n| `numbers` | `properties` | `['x', 'y', 'borderWidth', 'radius', 'tension']`\n| `numbers` | `type` | `'number'`\n| `colors` | `properties` | `['color', 'borderColor', 'backgroundColor']`\n| `colors` | `type` | `'color'`\n\n:::tip Note\nThese default animations are overridden by most of the dataset controllers.\n:::\n\n## transitions\n\nThe core transitions are `'active'`, `'hide'`, `'reset'`, `'resize'`, `'show'`.\nA custom transition can be used by passing a custom `mode` to [update](../developers/api.md#updatemode).\nTransition extends the main [animation configuration](#animation-configuration) and [animations configuration](#animations-configuration).\n\n### Default transitions\n\nNamespace: `options.transitions[mode]`\n\n| Mode | Option | Value | Description\n| -----| ------ | ----- | -----\n| `'active'` | animation.duration | 400 | Override default duration to 400ms for hover animations\n| `'resize'` | animation.duration | 0 | Override default duration to 0ms (= no animation) for resize\n| `'show'` | animations.colors | `{ type: 'color', properties: ['borderColor', 'backgroundColor'], from: 'transparent' }` | Colors are faded in from transparent when dataset is shown using legend / [api](../developers/api.md#showdatasetIndex).\n| `'show'` | animations.visible | `{ type: 'boolean', duration: 0 }` | Dataset visibility is immediately changed to true so the color transition from transparent is visible.\n| `'hide'` | animations.colors | `{ type: 'color', properties: ['borderColor', 'backgroundColor'], to: 'transparent' }` | Colors are faded to transparent when dataset id hidden using legend / [api](../developers/api.md#hidedatasetIndex).\n| `'hide'` | animations.visible | `{ type: 'boolean', easing: 'easeInExpo' }` | Visibility is changed to false at a very late phase of animation\n\n## Disabling animation\n\nTo disable an animation configuration, the animation node must be set to `false`, with the exception for animation modes which can be disabled by setting the `duration` to `0`.\n\n```javascript\nchart.options.animation = false; // disables all animations\nchart.options.animations.colors = false; // disables animation defined by the collection of 'colors' properties\nchart.options.animations.x = false; // disables animation defined by the 'x' property\nchart.options.transitions.active.animation.duration = 0; // disables the animation for 'active' mode\n```\n\n## Easing\n\nAvailable options are:\n\n* `'linear'`\n* `'easeInQuad'`\n* `'easeOutQuad'`\n* `'easeInOutQuad'`\n* `'easeInCubic'`\n* `'easeOutCubic'`\n* `'easeInOutCubic'`\n* `'easeInQuart'`\n* `'easeOutQuart'`\n* `'easeInOutQuart'`\n* `'easeInQuint'`\n* `'easeOutQuint'`\n* `'easeInOutQuint'`\n* `'easeInSine'`\n* `'easeOutSine'`\n* `'easeInOutSine'`\n* `'easeInExpo'`\n* `'easeOutExpo'`\n* `'easeInOutExpo'`\n* `'easeInCirc'`\n* `'easeOutCirc'`\n* `'easeInOutCirc'`\n* `'easeInElastic'`\n* `'easeOutElastic'`\n* `'easeInOutElastic'`\n* `'easeInBack'`\n* `'easeOutBack'`\n* `'easeInOutBack'`\n* `'easeInBounce'`\n* `'easeOutBounce'`\n* `'easeInOutBounce'`\n\nSee [Robert Penner's easing equations](http://robertpenner.com/easing/).\n\n## Animation Callbacks\n\nThe animation configuration provides callbacks which are useful for synchronizing an external draw to the chart animation.\nThe callbacks can be set only at main [animation configuration](#animation-configuration).\n\nNamespace: `options.animation`\n\n| Name | Type | Default | Description\n| ---- | ---- | ------- | -----------\n| `onProgress` | `function` | `null` | Callback called on each step of an animation.\n| `onComplete` | `function` | `null` | Callback called when all animations are completed.\n\nThe callback is passed the following object:\n\n```javascript\n{\n  // Chart object\n  chart: Chart,\n\n  // Number of animations still in progress\n  currentStep: number,\n\n  // `true` for the initial animation of the chart\n  initial: boolean,\n\n  // Total number of animations at the start of current animation\n  numSteps: number,\n}\n```\n\nThe following example fills a progress bar during the chart animation.\n\n```javascript\nconst chart = new Chart(ctx, {\n    type: 'line',\n    data: data,\n    options: {\n        animation: {\n            onProgress: function(animation) {\n                progress.value = animation.currentStep / animation.numSteps;\n            }\n        }\n    }\n});\n```\n\nAnother example usage of these callbacks can be found [in this progress bar sample,](../samples/advanced/progress-bar.md) which displays a progress bar showing how far along the animation is.\n"
  },
  {
    "path": "docs/configuration/canvas-background.md",
    "content": "# Canvas background\n\nIn some use cases you would want a background image or color over the whole canvas. There is no built-in support for this, the way you can achieve this is by writing a custom plugin.\n\nIn the two example plugins underneath here you can see how you can draw a color or image to the canvas as background. This way of giving the chart a background is only necessary if you want to export the chart with that specific background.\nFor normal use you can set the background more easily with [CSS](https://www.w3schools.com/cssref/css3_pr_background.asp).\n\n:::: tabs\n\n::: tab Color\n\n```js chart-editor\n// <block:setup:1>\nconst data = {\n  labels: [\n    'Red',\n    'Blue',\n    'Yellow'\n  ],\n  datasets: [{\n    label: 'My First Dataset',\n    data: [300, 50, 100],\n    backgroundColor: [\n      'rgb(255, 99, 132)',\n      'rgb(54, 162, 235)',\n      'rgb(255, 205, 86)'\n    ],\n    hoverOffset: 4\n  }]\n};\n// </block:setup>\n\n// <block:plugin:2>\n// Note: changes to the plugin code is not reflected to the chart, because the plugin is loaded at chart construction time and editor changes only trigger an chart.update().\nconst plugin = {\n  id: 'customCanvasBackgroundColor',\n  beforeDraw: (chart, args, options) => {\n    const {ctx} = chart;\n    ctx.save();\n    ctx.globalCompositeOperation = 'destination-over';\n    ctx.fillStyle = options.color || '#99ffff';\n    ctx.fillRect(0, 0, chart.width, chart.height);\n    ctx.restore();\n  }\n};\n// </block:plugin>\n\n// <block:config:0>\nconst config = {\n  type: 'doughnut',\n  data: data,\n  options: {\n    plugins: {\n      customCanvasBackgroundColor: {\n        color: 'lightGreen',\n      }\n    }\n  },\n  plugins: [plugin],\n};\n// </block:config>\n\nmodule.exports = {\n  actions: [],\n  config: config,\n};\n```\n\n:::\n\n::: tab Image\n\n```js chart-editor\n// <block:setup:1>\nconst data = {\n  labels: [\n    'Red',\n    'Blue',\n    'Yellow'\n  ],\n  datasets: [{\n    label: 'My First Dataset',\n    data: [300, 50, 100],\n    backgroundColor: [\n      'rgb(255, 99, 132)',\n      'rgb(54, 162, 235)',\n      'rgb(255, 205, 86)'\n    ],\n    hoverOffset: 4\n  }]\n};\n// </block:setup>\n\n// <block:plugin:2>\n// Note: changes to the plugin code is not reflected to the chart, because the plugin is loaded at chart construction time and editor changes only trigger an chart.update().\nconst image = new Image();\nimage.src = 'https://www.chartjs.org/img/chartjs-logo.svg';\n\nconst plugin = {\n  id: 'customCanvasBackgroundImage',\n  beforeDraw: (chart) => {\n    if (image.complete) {\n      const ctx = chart.ctx;\n      const {top, left, width, height} = chart.chartArea;\n      const x = left + width / 2 - image.width / 2;\n      const y = top + height / 2 - image.height / 2;\n      ctx.drawImage(image, x, y);\n    } else {\n      image.onload = () => chart.draw();\n    }\n  }\n};\n// </block:plugin>\n\n// <block:config:0>\nconst config = {\n  type: 'doughnut',\n  data: data,\n  plugins: [plugin],\n};\n// </block:config>\n\nmodule.exports = {\n  actions: [],\n  config: config,\n};\n```\n\n:::\n\n::::\n"
  },
  {
    "path": "docs/configuration/decimation.md",
    "content": "# Data Decimation\n\nThe decimation plugin can be used with line charts to automatically decimate data at the start of the chart lifecycle. Before enabling this plugin, review the [requirements](#requirements) to ensure that it will work with the chart you want to create.\n\n## Configuration Options\n\nNamespace: `options.plugins.decimation`, the global options for the plugin are defined in `Chart.defaults.plugins.decimation`.\n\n| Name | Type | Default | Description\n| ---- | ---- | ------- | -----------\n| `enabled` | `boolean` | `false` | Is decimation enabled?\n| `algorithm` | `string` | `'min-max'` | Decimation algorithm to use. See the [more...](#decimation-algorithms)\n| `samples` | `number` | | If the `'lttb'` algorithm is used, this is the number of samples in the output dataset. Defaults to the canvas width to pick 1 sample per pixel.\n| `threshold` | `number` | | If the number of samples in the current axis range is above this value, the decimation will be triggered. Defaults to 4 times the canvas width.<br />The number of point after decimation can be higher than the `threshold` value.\n\n## Decimation Algorithms\n\nDecimation algorithm to use for data. Options are:\n\n* `'lttb'`\n* `'min-max'`\n\n### Largest Triangle Three Bucket (LTTB) Decimation\n\n[LTTB](https://github.com/sveinn-steinarsson/flot-downsample) decimation reduces the number of data points significantly. This is most useful for showing trends in data using only a few data points.\n\n### Min/Max Decimation\n\n[Min/max](https://digital.ni.com/public.nsf/allkb/F694FFEEA0ACF282862576020075F784) decimation will preserve peaks in your data but could require up to 4 points for each pixel. This type of decimation would work well for a very noisy signal where you need to see data peaks.\n\n## Requirements\n\nTo use the decimation plugin, the following requirements must be met:\n\n1. The dataset must have an [`indexAxis`](../charts/line.md#general) of `'x'`\n2. The dataset must be a line\n3. The X axis for the dataset must be either a [`'linear'`](../axes/cartesian/linear.md) or [`'time'`](../axes/cartesian/time.md) type axis\n4. Data must not need parsing, i.e. [`parsing`](../general/data-structures.md#dataset-configuration) must be `false`\n5. The dataset object must be mutable. The plugin stores the original data as `dataset._data` and then defines a new `data` property on the dataset.\n6. There must be more points on the chart than the threshold value. Take a look at the Configuration Options for more information.\n\n## Related Samples\n\n* [Data Decimation Sample](../samples/advanced/data-decimation)\n"
  },
  {
    "path": "docs/configuration/device-pixel-ratio.md",
    "content": "# Device Pixel Ratio\n\nBy default, the chart's canvas will use a 1:1 pixel ratio, unless the physical display has a higher pixel ratio (e.g. Retina displays).\n\nFor applications where a chart will be converted to a bitmap, or printed to a higher DPI medium, it can be desirable to render the chart at a higher resolution than the default.\n\nSetting `devicePixelRatio` to a value other than 1 will force the canvas size to be scaled by that amount, relative to the container size. There should be no visible difference on screen; the difference will only be visible when the image is zoomed or printed.\n\n## Configuration Options\n\nNamespace: `options`\n\n| Name | Type | Default | Description\n| ---- | ---- | ------- | -----------\n| `devicePixelRatio` | `number` | `window.devicePixelRatio` | Override the window's default devicePixelRatio.\n"
  },
  {
    "path": "docs/configuration/elements.md",
    "content": "# Elements\n\nWhile chart types provide settings to configure the styling of each dataset, you sometimes want to style **all datasets the same way**. A common example would be to stroke all the bars in a bar chart with the same colour but change the fill per dataset. Options can be configured for four different types of elements: **[arc](#arc-configuration)**, **[lines](#line-configuration)**, **[points](#point-configuration)**, and **[bars](#bar-configuration)**. When set, these options apply to all objects of that type unless specifically overridden by the configuration attached to a dataset.\n\n## Global Configuration\n\nThe element options can be specified per chart or globally. The global options for elements are defined in `Chart.defaults.elements`. For example, to set the border width of all bar charts globally, you would do:\n\n```javascript\nChart.defaults.elements.bar.borderWidth = 2;\n```\n\n## Point Configuration\n\nPoint elements are used to represent the points in a line, radar or bubble chart.\n\nNamespace: `options.elements.point`, global point options: `Chart.defaults.elements.point`.\n\n| Name | Type | Default | Description\n| ---- | ---- | ------- | -----------\n| `radius` | `number` | `3` | Point radius.\n| [`pointStyle`](#point-styles) | [`pointStyle`](#types) | `'circle'` | Point style.\n| `rotation` | `number` | `0` | Point rotation (in degrees).\n| `backgroundColor` | [`Color`](../general/colors.md) | `Chart.defaults.backgroundColor` | Point fill color.\n| `borderWidth` | `number` | `1` | Point stroke width.\n| `borderColor` | [`Color`](../general/colors.md) | `'Chart.defaults.borderColor` | Point stroke color.\n| `hitRadius` | `number` | `1` | Extra radius added to point radius for hit detection.\n| `hoverRadius` | `number` | `4` | Point radius when hovered.\n| `hoverBorderWidth` | `number` | `1` | Stroke width when hovered.\n\n### Point Styles\n\n#### Types\n\nThe `pointStyle` argument accepts the following type of inputs: `string`, `Image` and `HTMLCanvasElement`\n\n#### Info\nWhen a string is provided, the following values are supported:\n\n- `'circle'`\n- `'cross'`\n- `'crossRot'`\n- `'dash'`\n- `'line'`\n- `'rect'`\n- `'rectRounded'`\n- `'rectRot'`\n- `'star'`\n- `'triangle'`\n- `false`\n\nIf the value is an image or a canvas element, that image or canvas element is drawn on the canvas using [drawImage](https://developer.mozilla.org/en/docs/Web/API/CanvasRenderingContext2D/drawImage).\n\n## Line Configuration\n\nLine elements are used to represent the line in a line chart.\n\nNamespace: `options.elements.line`, global line options: `Chart.defaults.elements.line`.\n\n| Name | Type | Default | Description\n| ---- | ---- | ------- | -----------\n| `tension` | `number` | `0` | Bézier curve tension (`0` for no Bézier curves).\n| `backgroundColor` | [`Color`](/general/colors.md) | `Chart.defaults.backgroundColor` | Line fill color.\n| `borderWidth` | `number` | `3` | Line stroke width.\n| `borderColor` | [`Color`](/general/colors.md) | `Chart.defaults.borderColor` | Line stroke color.\n| `borderCapStyle` | `string` | `'butt'` | Line cap style. See [MDN](https://developer.mozilla.org/en/docs/Web/API/CanvasRenderingContext2D/lineCap).\n| `borderDash` | `number[]` | `[]` | Line dash. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash).\n| `borderDashOffset` | `number` | `0.0` | Line dash offset. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineDashOffset).\n| `borderJoinStyle` | `'round'`\\|`'bevel'`\\|`'miter'` | `'miter'` | Line join style. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineJoin).\n| `capBezierPoints` | `boolean` | `true` | `true` to keep Bézier control inside the chart, `false` for no restriction.\n| `cubicInterpolationMode` | `string` | `'default'` |  Interpolation mode to apply. [See more...](/charts/line.md#cubicinterpolationmode)\n| `fill` | `boolean`\\|`string` | `false` | How to fill the area under the line. See [area charts](/charts/area.md#filling-modes).\n| `stepped` | `boolean` | `false` | `true` to show the line as a stepped line (`tension` will be ignored).\n\n## Bar Configuration\n\nBar elements are used to represent the bars in a bar chart.\n\nNamespace: `options.elements.bar`, global bar options: `Chart.defaults.elements.bar`.\n\n| Name | Type | Default | Description\n| ---- | ---- | ------- | -----------\n| `backgroundColor` | [`Color`](/general/colors.md) | `Chart.defaults.backgroundColor` | Bar fill color.\n| `borderWidth` | `number` | `0` | Bar stroke width.\n| `borderColor` | [`Color`](/general/colors.md) | `Chart.defaults.borderColor` | Bar stroke color.\n| `borderSkipped` | `string` | `'start'` | Skipped (excluded) border: `'start'`, `'end'`, `'middle'`, `'bottom'`, `'left'`, `'top'`, `'right'` or `false`.\n| `borderRadius` | `number`\\|`object` | `0` | The bar border radius (in pixels).\n| `inflateAmount` | `number`\\|`'auto'` | `'auto'` | The amount of pixels to inflate the bar rectangle(s) when drawing.\n| [`pointStyle`](#point-styles) | `string`\\|`Image`\\|`HTMLCanvasElement` | `'circle'` | Style of the point for legend.\n\n## Arc Configuration\n\nArcs are used in the polar area, doughnut and pie charts.\n\nNamespace: `options.elements.arc`, global arc options: `Chart.defaults.elements.arc`.\n\n| Name | Type | Default | Description\n| ---- | ---- | ------- | -----------\n| `angle` - for polar only | `number` | `circumference / (arc count)` | Arc angle to cover.\n| `backgroundColor` | [`Color`](/general/colors.md) | `Chart.defaults.backgroundColor` | Arc fill color.\n| `borderAlign` | `'center'`\\|`'inner'` | `'center'` | Arc stroke alignment.\n| `borderColor` | [`Color`](/general/colors.md) | `'#fff'` | Arc stroke color.\n| `borderDash` | `number[]` | `[]` | Arc line dash. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash).\n| `borderDashOffset` | `number` | `0.0` | Arc line dash offset. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineDashOffset).\n| `borderJoinStyle` | `'round'`\\|`'bevel'`\\|`'miter'` | `'bevel'`\\|`'round'` | Line join style. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineJoin). The default is `'round'` when `borderAlign` is `'inner'`\n| `borderWidth`| `number` | `2` | Arc stroke width.\n| `circular` | `boolean` | `true` | By default the Arc is curved. If `circular: false` the Arc will be flat\n"
  },
  {
    "path": "docs/configuration/index.md",
    "content": "# Configuration\n\nThe configuration is used to change how the chart behaves. There are properties to control styling, fonts, the legend, etc.\n\n## Configuration object structure\n\nThe top level structure of Chart.js configuration:\n\n```javascript\nconst config = {\n  type: 'line',\n  data: {},\n  options: {},\n  plugins: []\n}\n```\n\n### type\n\nChart type determines the main type of the chart.\n\n**note** A dataset can override the `type`, this is how mixed charts are constructed.\n\n### data\n\nSee [Data Structures](../general/data-structures.md) for details.\n\n### options\n\nMajority of the documentation talks about these options.\n\n### plugins\n\nInline plugins can be included in this array. It is an alternative way of adding plugins for single chart (vs registering the plugin globally).\nMore about plugins in the [developers section](../developers/plugins.md).\n\n## Global Configuration\n\nThis concept was introduced in Chart.js 1.0 to keep configuration [DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself), and allow for changing options globally across chart types, avoiding the need to specify options for each instance, or the default for a particular chart type.\n\nChart.js merges the `options` object passed to the chart with the global configuration using chart type defaults and scales defaults appropriately. This way you can be as specific as you would like in your individual chart configuration, while still changing the defaults for all chart types where applicable. The global general options are defined in `Chart.defaults`. The defaults for each chart type are discussed in the documentation for that chart type.\n\nThe following example would set the interaction mode to 'nearest' for all charts where this was not overridden by the chart type defaults or the options passed to the constructor on creation.\n\n```javascript\nChart.defaults.interaction.mode = 'nearest';\n\n// Interaction mode is set to nearest because it was not overridden here\nconst chartInteractionModeNearest = new Chart(ctx, {\n    type: 'line',\n    data: data\n});\n\n// This chart would have the interaction mode that was passed in\nconst chartDifferentInteractionMode = new Chart(ctx, {\n    type: 'line',\n    data: data,\n    options: {\n        interaction: {\n            // Overrides the global setting\n            mode: 'index'\n        }\n    }\n});\n```\n\n## Dataset Configuration\n\nOptions may be configured directly on the dataset. The dataset options can be changed at multiple different levels. See [options](../general/options.md#dataset-level-options) for details on how the options are resolved.\n\nThe following example would set the `showLine` option to 'false' for all line datasets except for those overridden by options passed to the dataset on creation.\n\n```javascript\n// Do not show lines for all datasets by default\nChart.defaults.datasets.line.showLine = false;\n\n// This chart would show a line only for the third dataset\nconst chart = new Chart(ctx, {\n    type: 'line',\n    data: {\n        datasets: [{\n            data: [0, 0],\n        }, {\n            data: [0, 1]\n        }, {\n            data: [1, 0],\n            showLine: true // overrides the `line` dataset default\n        }, {\n            type: 'scatter', // 'line' dataset default does not affect this dataset since it's a 'scatter'\n            data: [1, 1]\n        }]\n    }\n});\n```\n"
  },
  {
    "path": "docs/configuration/interactions.md",
    "content": "# Interactions\n\nNamespace: `options.interaction`, the global interaction configuration is at `Chart.defaults.interaction`. To configure which events trigger chart interactions, see [events](#events).\n\n| Name | Type | Default | Description\n| ---- | ---- | ------- | -----------\n| `mode` | `string` | `'nearest'` | Sets which elements appear in the interaction. See [Interaction Modes](#modes) for details.\n| `intersect` | `boolean` | `true` | if true, the interaction mode only applies when the mouse position intersects an item on the chart.\n| `axis` | `string` | `'x'` | Can be set to `'x'`, `'y'`, `'xy'` or `'r'` to define which directions are used in calculating distances. Defaults to `'x'` for `'index'` mode and `'xy'` in `dataset` and `'nearest'` modes.\n| `includeInvisible` | `boolean` | `false` | if true, the invisible points that are outside of the chart area will also be included when evaluating interactions.\n\nBy default, these options apply to both the hover and tooltip interactions. The same options can be set in the `options.hover` namespace, in which case they will only affect the hover interaction. Similarly, the options can be set in the `options.plugins.tooltip` namespace to independently configure the tooltip interactions.\n\n## Events\n\nThe following properties define how the chart interacts with events.\nNamespace: `options`\n\n| Name | Type | Default | Description\n| ---- | ---- | ------- | -----------\n| `events` | `string[]` | `['mousemove', 'mouseout', 'click', 'touchstart', 'touchmove']` | The `events` option defines the browser events that the chart should listen to for. Each of these events trigger hover and are passed to plugins. [more...](#event-option)\n| `onHover` | `function` | `null` | Called when any of the events fire over chartArea. Passed the event, an array of active elements (bars, points, etc), and the chart.\n| `onClick` | `function` | `null` | Called if the event is of type `'mouseup'`, `'click'` or '`'contextmenu'` over chartArea. Passed the event, an array of active elements, and the chart.\n\n### Event Option\n\nFor example, to have the chart only respond to click events, you could do:\n\n```javascript\nconst chart = new Chart(ctx, {\n  type: 'line',\n  data: data,\n  options: {\n    // This chart will not respond to mousemove, etc\n    events: ['click']\n  }\n});\n```\n\nEvents for each plugin can be further limited by defining (allowed) events array in plugin options:\n\n```javascript\nconst chart = new Chart(ctx, {\n  type: 'line',\n  data: data,\n  options: {\n    // All of these (default) events trigger a hover and are passed to all plugins,\n    // unless limited at plugin options\n    events: ['mousemove', 'mouseout', 'click', 'touchstart', 'touchmove'],\n    plugins: {\n      tooltip: {\n        // Tooltip will only receive click events\n        events: ['click']\n      }\n    }\n  }\n});\n```\n\nEvents that do not fire over chartArea, like `mouseout`, can be captured using a simple plugin:\n\n```javascript\nconst chart = new Chart(ctx, {\n  type: 'line',\n  data: data,\n  options: {\n    // these are the default events:\n    // events: ['mousemove', 'mouseout', 'click', 'touchstart', 'touchmove'],\n  },\n  plugins: [{\n    id: 'myEventCatcher',\n    beforeEvent(chart, args, pluginOptions) {\n      const event = args.event;\n      if (event.type === 'mouseout') {\n        // process the event\n      }\n    }\n  }]\n});\n```\n\nFor more information about plugins, see [Plugins](../developers/plugins.md)\n\n### Converting Events to Data Values\n\nA common occurrence is taking an event, such as a click, and finding the data coordinates on the chart where the event occurred. Chart.js provides helpers that make this a straightforward process.\n\n```javascript\nconst chart = new Chart(ctx, {\n    type: 'line',\n    data: data,\n    options: {\n        onClick: (e) => {\n            const canvasPosition = Chart.helpers.getRelativePosition(e, chart);\n\n            // Substitute the appropriate scale IDs\n            const dataX = chart.scales.x.getValueForPixel(canvasPosition.x);\n            const dataY = chart.scales.y.getValueForPixel(canvasPosition.y);\n        }\n    }\n});\n```\n\nWhen using a bundler, the helper functions have to be imported separately, for a full explanation of this please head over to the [integration](../getting-started/integration.md#helper-functions) page\n\n## Modes\n\nWhen configuring the interaction with the graph via `interaction`, `hover` or `tooltips`, a number of different modes are available.\n\n`options.hover` and `options.plugins.tooltip` extend from `options.interaction`. So if `mode`, `intersect` or any other common settings are configured only in `options.interaction`, both hover and tooltips obey that.\n\nThe modes are detailed below and how they behave in conjunction with the `intersect` setting.\n\nSee how different modes work with the tooltip in [tooltip interactions sample](../samples/tooltip/interactions.md )\n\n### point\n\nFinds all of the items that intersect the point.\n\n```javascript\nconst chart = new Chart(ctx, {\n    type: 'line',\n    data: data,\n    options: {\n        interaction: {\n            mode: 'point'\n        }\n    }\n});\n```\n\n### nearest\n\nGets the items that are at the nearest distance to the point. The nearest item is determined based on the distance to the center of the chart item (point, bar). You can use the `axis` setting to define which coordinates are considered in distance calculation. If `intersect` is true, this is only triggered when the mouse position intersects an item in the graph. This is very useful for combo charts where points are hidden behind bars.\n\n```javascript\nconst chart = new Chart(ctx, {\n    type: 'line',\n    data: data,\n    options: {\n        interaction: {\n            mode: 'nearest'\n        }\n    }\n});\n```\n\n### index\n\nFinds item at the same index. If the `intersect` setting is true, the first intersecting item is used to determine the index in the data. If `intersect` false the nearest item, in the x direction, is used to determine the index.\n\n```javascript\nconst chart = new Chart(ctx, {\n    type: 'line',\n    data: data,\n    options: {\n        interaction: {\n            mode: 'index'\n        }\n    }\n});\n```\n\nTo use index mode in a chart like the horizontal bar chart, where we search along the y direction, you can use the `axis` setting introduced in v2.7.0. By setting this value to `'y'` on the y direction is used.\n\n```javascript\nconst chart = new Chart(ctx, {\n    type: 'bar',\n    data: data,\n    options: {\n        interaction: {\n            mode: 'index',\n            axis: 'y'\n        }\n    }\n});\n```\n\n### dataset\n\nFinds items in the same dataset. If the `intersect` setting is true, the first intersecting item is used to determine the index in the data. If `intersect` false the nearest item is used to determine the index.\n\n```javascript\nconst chart = new Chart(ctx, {\n    type: 'line',\n    data: data,\n    options: {\n        interaction: {\n            mode: 'dataset'\n        }\n    }\n});\n```\n\n### x\n\nReturns all items that would intersect based on the `X` coordinate of the position only. Would be useful for a vertical cursor implementation. Note that this only applies to cartesian charts.\n\n```javascript\nconst chart = new Chart(ctx, {\n    type: 'line',\n    data: data,\n    options: {\n        interaction: {\n            mode: 'x'\n        }\n    }\n});\n```\n\n### y\n\nReturns all items that would intersect based on the `Y` coordinate of the position. This would be useful for a horizontal cursor implementation. Note that this only applies to cartesian charts.\n\n```javascript\nconst chart = new Chart(ctx, {\n    type: 'line',\n    data: data,\n    options: {\n        interaction: {\n            mode: 'y'\n        }\n    }\n});\n```\n\n## Custom Interaction Modes\n\nNew modes can be defined by adding functions to the `Chart.Interaction.modes` map.  You can use the `Chart.Interaction.evaluateInteractionItems` function to help implement these.\n\nExample:\n\n```javascript\nimport { Interaction } from 'chart.js';\nimport { getRelativePosition } from 'chart.js/helpers';\n\n/**\n * Custom interaction mode\n * @function Interaction.modes.myCustomMode\n * @param {Chart} chart - the chart we are returning items from\n * @param {Event} e - the event we are find things at\n * @param {InteractionOptions} options - options to use\n * @param {boolean} [useFinalPosition] - use final element position (animation target)\n * @return {InteractionItem[]} - items that are found\n */\nInteraction.modes.myCustomMode = function(chart, e, options, useFinalPosition) {\n  const position = getRelativePosition(e, chart);\n\n  const items = [];\n  Interaction.evaluateInteractionItems(chart, 'x', position, (element, datasetIndex, index) => {\n    if (element.inXRange(position.x, useFinalPosition) && myCustomLogic(element)) {\n      items.push({element, datasetIndex, index});\n    }\n  });\n  return items;\n};\n\n// Then, to use it...\nnew Chart.js(ctx, {\n    type: 'line',\n    data: data,\n    options: {\n        interaction: {\n            mode: 'myCustomMode'\n        }\n    }\n})\n```\n\nIf you're using TypeScript, you'll also need to register the new mode:\n\n```typescript\ndeclare module 'chart.js' {\n  interface InteractionModeMap {\n    myCustomMode: InteractionModeFunction;\n  }\n}\n```\n"
  },
  {
    "path": "docs/configuration/layout.md",
    "content": "# Layout\n\nNamespace: `options.layout`, the global options for the chart layout is defined in `Chart.defaults.layout`.\n\n| Name | Type | Default | [Scriptable](../general/options.md#scriptable-options) | Description\n| ---- | ---- | ------- | :----: | -----------\n| `autoPadding` | `boolean` | `true` | No | Apply automatic padding so visible elements are completely drawn.\n| `padding` | [`Padding`](../general/padding.md) | `0` | Yes | The padding to add inside the chart.\n"
  },
  {
    "path": "docs/configuration/legend.md",
    "content": "# Legend\n\nThe chart legend displays data about the datasets that are appearing on the chart.\n\n## Configuration options\n\nNamespace: `options.plugins.legend`, the global options for the chart legend is defined in `Chart.defaults.plugins.legend`.\n\n:::warning\nThe doughnut, pie, and polar area charts override the legend defaults. To change the overrides for those chart types, the options are defined in `Chart.overrides[type].plugins.legend`.\n:::\n\n| Name | Type | Default | Description\n| ---- | ---- | ------- | -----------\n| `display` | `boolean` | `true` | Is the legend shown?\n| `position` | `string` | `'top'` | Position of the legend. [more...](#position)\n| `align` | `string` | `'center'` | Alignment of the legend. [more...](#align)\n| `maxHeight` | `number` | | Maximum height of the legend, in pixels\n| `maxWidth` | `number` | | Maximum width of the legend, in pixels\n| `fullSize` | `boolean` | `true` | Marks that this box should take the full width/height of the canvas (moving other boxes). This is unlikely to need to be changed in day-to-day use.\n| `onClick` | `function` | | A callback that is called when a click event is registered on a label item. Arguments: `[event, legendItem, legend]`.\n| `onHover` | `function` | | A callback that is called when a 'mousemove' event is registered on top of a label item. Arguments: `[event, legendItem, legend]`.\n| `onLeave` | `function` | | A callback that is called when a 'mousemove' event is registered outside of a previously hovered label item. Arguments: `[event, legendItem, legend]`.\n| `reverse` | `boolean` | `false` | Legend will show datasets in reverse order.\n| `labels` | `object` | | See the [Legend Label Configuration](#legend-label-configuration) section below.\n| `rtl` | `boolean` | | `true` for rendering the legends from right to left.\n| `textDirection` | `string` | canvas' default | This will force the text direction `'rtl'` or `'ltr'` on the canvas for rendering the legend, regardless of the css specified on the canvas\n| `title` | `object` | | See the [Legend Title Configuration](#legend-title-configuration) section below.\n\n:::tip Note\nIf you need more visual customizations, please use an [HTML legend](../samples/legend/html.md).\n:::\n\n## Position\n\nPosition of the legend. Options are:\n\n* `'top'`\n* `'left'`\n* `'bottom'`\n* `'right'`\n* `'chartArea'`\n\nWhen using the `'chartArea'` option the legend position is at the moment not configurable, it will always be on the left side of the chart in the middle.\n\n## Align\n\nAlignment of the legend. Options are:\n\n* `'start'`\n* `'center'`\n* `'end'`\n\nDefaults to `'center'` for unrecognized values.\n\n## Legend Label Configuration\n\nNamespace: `options.plugins.legend.labels`\n\n| Name | Type | Default | Description\n| ---- | ---- | ------- | -----------\n| `boxWidth` | `number` | `40` | Width of coloured box.\n| `boxHeight` | `number` | `font.size` | Height of the coloured box.\n| `color` | [`Color`](../general/colors.md) | `Chart.defaults.color` | Color of label and the strikethrough.\n| `font` | `Font` | `Chart.defaults.font` | See [Fonts](../general/fonts.md)\n| `padding` | `number` | `10` | Padding between rows of colored boxes.\n| `generateLabels` | `function` | | Generates legend items for each thing in the legend. Default implementation returns the text + styling for the color box. See [Legend Item](#legend-item-interface) for details.\n| `filter` | `function` | `null` | Filters legend items out of the legend. Receives 2 parameters, a [Legend Item](#legend-item-interface) and the chart data.\n| `sort` | `function` | `null` | Sorts legend items. Type is : `sort(a: LegendItem, b: LegendItem, data: ChartData): number;`. Receives 3 parameters, two [Legend Items](#legend-item-interface) and the chart data. The return value of the function is a number that indicates the order of the two legend item parameters. The ordering matches the [return value](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#description) of `Array.prototype.sort()`\n| [`pointStyle`](elements.md#point-styles) | [`pointStyle`](elements.md#types) | `'circle'` | If specified, this style of point is used for the legend. Only used if `usePointStyle` is true.\n| `textAlign` | `string` | `'center'` | Horizontal alignment of the label text. Options are: `'left'`, `'right'` or `'center'`.\n| `usePointStyle` | `boolean` | `false` | Label style will match corresponding point style (size is based on pointStyleWidth or the minimum value between boxWidth and font.size).\n| `pointStyleWidth` | `number` | `null` | If `usePointStyle` is true, the width of the point style used for the legend.\n| `useBorderRadius` | `boolean` | `false` | Label borderRadius will match corresponding borderRadius.\n| `borderRadius` | `number` | `undefined` | Override the borderRadius to use.\n\n## Legend Title Configuration\n\nNamespace: `options.plugins.legend.title`\n\n| Name | Type | Default | Description\n| ---- | ---- | ------- | -----------\n| `color` | [`Color`](../general/colors.md) | `Chart.defaults.color` | Color of text.\n| `display` | `boolean` | `false` | Is the legend title displayed.\n| `font` | `Font` | `Chart.defaults.font` | See [Fonts](../general/fonts.md)\n| `padding` | [`Padding`](../general/padding.md) | `0` | Padding around the title.\n| `text` | `string` | | The string title.\n\n## Legend Item Interface\n\nItems passed to the legend `onClick` function are the ones returned from `labels.generateLabels`. These items must implement the following interface.\n\n```javascript\n{\n    // Label that will be displayed\n    text: string,\n\n    // Border radius of the legend item.\n    // Introduced in 3.1.0\n    borderRadius?: number | BorderRadius,\n\n    // Index of the associated dataset\n    datasetIndex: number,\n\n    // Fill style of the legend box\n    fillStyle: Color,\n\n    // Text color\n    fontColor: Color,\n\n    // If true, this item represents a hidden dataset. Label will be rendered with a strike-through effect\n    hidden: boolean,\n\n    // For box border. See https://developer.mozilla.org/en/docs/Web/API/CanvasRenderingContext2D/lineCap\n    lineCap: string,\n\n    // For box border. See https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash\n    lineDash: number[],\n\n    // For box border. See https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineDashOffset\n    lineDashOffset: number,\n\n    // For box border. See https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineJoin\n    lineJoin: string,\n\n    // Width of box border\n    lineWidth: number,\n\n    // Stroke style of the legend box\n    strokeStyle: Color,\n\n    // Point style of the legend box (only used if usePointStyle is true)\n    pointStyle: string | Image | HTMLCanvasElement,\n\n    // Rotation of the point in degrees (only used if usePointStyle is true)\n    rotation: number\n}\n```\n\n## Example\n\nThe following example will create a chart with the legend enabled and turn all the text red in color.\n\n```javascript\nconst chart = new Chart(ctx, {\n    type: 'bar',\n    data: data,\n    options: {\n        plugins: {\n            legend: {\n                display: true,\n                labels: {\n                    color: 'rgb(255, 99, 132)'\n                }\n            }\n        }\n    }\n});\n```\n\n## Custom On Click Actions\n\nIt can be common to want to trigger different behaviour when clicking an item in the legend. This can be easily achieved using a callback in the config object.\n\nThe default legend click handler is:\n\n```javascript\nfunction(e, legendItem, legend) {\n    const index = legendItem.datasetIndex;\n    const ci = legend.chart;\n    if (ci.isDatasetVisible(index)) {\n        ci.hide(index);\n        legendItem.hidden = true;\n    } else {\n        ci.show(index);\n        legendItem.hidden = false;\n    }\n}\n```\n\nLet's say we wanted instead to link the display of the first two datasets. We could change the click handler accordingly.\n\n```javascript\nconst defaultLegendClickHandler = Chart.defaults.plugins.legend.onClick;\nconst pieDoughnutLegendClickHandler = Chart.controllers.doughnut.overrides.plugins.legend.onClick;\nconst newLegendClickHandler = function (e, legendItem, legend) {\n    const index = legendItem.datasetIndex;\n    const type = legend.chart.config.type;\n\n    if (index > 1) {\n        // Do the original logic\n        if (type === 'pie' || type === 'doughnut') {\n            pieDoughnutLegendClickHandler(e, legendItem, legend)\n        } else {\n            defaultLegendClickHandler(e, legendItem, legend);\n        }\n\n    } else {\n        let ci = legend.chart;\n        [\n            ci.getDatasetMeta(0),\n            ci.getDatasetMeta(1)\n        ].forEach(function(meta) {\n            meta.hidden = meta.hidden === null ? !ci.data.datasets[index].hidden : null;\n        });\n        ci.update();\n    }\n};\n\nconst chart = new Chart(ctx, {\n    type: 'line',\n    data: data,\n    options: {\n        plugins: {\n            legend: {\n                onClick: newLegendClickHandler\n            }\n        }\n    }\n});\n```\n\nNow when you click the legend in this chart, the visibility of the first two datasets will be linked together.\n"
  },
  {
    "path": "docs/configuration/locale.md",
    "content": "# Locale\n\nFor applications where the numbers of ticks on scales must be formatted accordingly with a language sensitive number formatting, you can enable this kind of formatting by setting the `locale` option.\n\nThe locale is a string that is a [Unicode BCP 47 locale identifier](https://www.unicode.org/reports/tr35/tr35.html#BCP_47_Conformance).\n\nA Unicode BCP 47 locale identifier consists of\n\n  1. a language code,\n  2. (optionally) a script code,\n  3. (optionally) a region (or country) code,\n  4. (optionally) one or more variant codes, and\n  5. (optionally) one or more extension sequences,\n\nwith all present components separated by hyphens.\n\nBy default, the chart is using the default locale of the platform which is running on.\n\n## Configuration Options\n\nNamespace: `options`\n\n| Name | Type | Default | Description\n| ---- | ---- | ------- | -----------\n| `locale` | `string` | `undefined` | a string with a BCP 47 language tag, leveraging on [INTL NumberFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat).\n"
  },
  {
    "path": "docs/configuration/responsive.md",
    "content": "# Responsive Charts\n\nWhen it comes to changing the chart size based on the window size, a major limitation is that the canvas *render* size (`canvas.width` and `.height`) can **not** be expressed with relative values, contrary to the *display* size (`canvas.style.width` and `.height`). Furthermore, these sizes are independent of each other and thus the canvas *render* size does not adjust automatically based on the *display* size, making the rendering inaccurate.\n\nThe following examples **do not work**:\n\n- `<canvas height=\"40vh\" width=\"80vw\">`: **invalid** values, the canvas doesn't resize ([example](https://codepen.io/chartjs/pen/oWLZaR))\n- `<canvas style=\"height:40vh; width:80vw\">`: **invalid** behavior, the canvas is resized but becomes blurry ([example](https://codepen.io/chartjs/pen/WjxpmO))\n- `<canvas style=\"margin: 0 auto;\">`: **invalid** behavior, the canvas continually shrinks. Chart.js needs a dedicated container for each canvas and this styling should be applied there.\n\nChart.js provides a [few options](#configuration-options) to enable responsiveness and control the resize behavior of charts by detecting when the canvas *display* size changes and update the *render* size accordingly.\n\n## Configuration Options\n\nNamespace: `options`\n\n| Name | Type | Default | Description\n| ---- | ---- | ------- | -----------\n| `responsive` | `boolean` | `true` | Resizes the chart canvas when its container does ([important note...](#important-note)).\n| `maintainAspectRatio` | `boolean` | `true` | Maintain the original canvas aspect ratio `(width / height)` when resizing.\n| `aspectRatio` | `number` | `1`\\|`2` | Canvas aspect ratio (i.e. `width / height`, a value of 1 representing a square canvas). Note that this option is ignored if the height is explicitly defined either as attribute or via the style. The default value varies by chart type; Radial charts (doughnut, pie, polarArea, radar) default to `1` and others default to `2`.\n| `onResize` | `function` | `null` | Called when a resize occurs. Gets passed two arguments: the chart instance and the new size.\n| `resizeDelay` | `number` | `0` | Delay the resize update by the given amount of milliseconds. This can ease the resize process by debouncing the update of the elements.\n\n## Important Note\n\nDetecting when the canvas size changes can not be done directly from the `canvas` element. Chart.js uses its parent container to update the canvas *render* and *display* sizes. However, this method requires the container to be **relatively positioned** and **dedicated to the chart canvas only**. Responsiveness can then be achieved by setting relative values for the container size ([example](https://codepen.io/chartjs/pen/YVWZbz)):\n\n```html\n<div class=\"chart-container\" style=\"position: relative; height:40vh; width:80vw\">\n    <canvas id=\"chart\"></canvas>\n</div>\n```\n\nThe chart can also be programmatically resized by modifying the container size:\n\n```javascript\nchart.canvas.parentNode.style.height = '128px';\nchart.canvas.parentNode.style.width = '128px';\n```\n\nNote that in order for the above code to correctly resize the chart height, the [`maintainAspectRatio`](#configuration-options) option must also be set to `false`.\n\n## Flexbox / Grid Layout\n\nTo prevent overflow issues when using flexbox / grid layout, you must set the flex / grid child element to have a `min-width` of `0`.\nSee [issue 4156](https://github.com/chartjs/Chart.js/issues/4156#issuecomment-295180128) for more details.\n\n```html\n<div class=\"grid-container\" style=\"display: grid\">\n  <div class=\"chart-container\" style=\"min-width: 0\">\n    <canvas id=\"chart\"></canvas>\n  </div>\n</div>\n```\n\n## Printing Resizable Charts\n\nCSS media queries allow changing styles when printing a page. The CSS applied from these media queries may cause charts to need to resize. However, the resize won't happen automatically. To support resizing charts when printing, you need to hook the [onbeforeprint](https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onbeforeprint) event and manually trigger resizing of each chart.\n\n```javascript\nfunction beforePrintHandler () {\n    for (let id in Chart.instances) {\n        Chart.instances[id].resize();\n    }\n}\n```\n\nYou may also find that, due to complexities in when the browser lays out the document for printing and when resize events are fired, Chart.js is unable to properly resize for the print layout. To work around this, you can pass an explicit size to `.resize()` then use an [onafterprint](https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onafterprint) event to restore the automatic size when done.\n\n```javascript\nwindow.addEventListener('beforeprint', () => {\n  myChart.resize(600, 600);\n});\nwindow.addEventListener('afterprint', () => {\n  myChart.resize();\n});\n```"
  },
  {
    "path": "docs/configuration/subtitle.md",
    "content": "# Subtitle\n\nSubtitle is a second title placed under the main title, by default. It has exactly the same configuration options with the main [title](./title.md).\n\n## Subtitle Configuration\n\nNamespace: `options.plugins.subtitle`. The global defaults for subtitle are configured in `Chart.defaults.plugins.subtitle`.\n\nExactly the same configuration options with [title](./title.md) are available for subtitle, the namespaces only differ.\n\n## Example Usage\n\nThe example below would enable a title of 'Custom Chart Subtitle' on the chart that is created.\n\n```javascript\nconst chart = new Chart(ctx, {\n    type: 'line',\n    data: data,\n    options: {\n        plugins: {\n            subtitle: {\n                display: true,\n                text: 'Custom Chart Subtitle'\n            }\n        }\n    }\n});\n```\n"
  },
  {
    "path": "docs/configuration/title.md",
    "content": "# Title\n\nThe chart title defines text to draw at the top of the chart.\n\n## Title Configuration\n\nNamespace: `options.plugins.title`, the global options for the chart title is defined in `Chart.defaults.plugins.title`.\n\n| Name | Type | Default | [Scriptable](../general/options.md#scriptable-options) | Description\n| ---- | ---- | ------- | :----: | -----------\n| `align` | `string` | `'center'` | Yes | Alignment of the title. [more...](#align)\n| `color` | [`Color`](../general/colors.md) | `Chart.defaults.color` | Yes | Color of text.\n| `display` | `boolean` | `false` | Yes | Is the title shown?\n| `fullSize` | `boolean` | `true` | Yes | Marks that this box should take the full width/height of the canvas. If `false`, the box is sized and placed above/beside the chart area.\n| `position` | `string` | `'top'` | Yes | Position of title. [more...](#position)\n| `font` | `Font` | `{weight: 'bold'}` | Yes | See [Fonts](../general/fonts.md)\n| `padding` | [`Padding`](../general/padding.md) | `10` | Yes | Padding to apply around the title. Only `top` and `bottom` are implemented.\n| `text` | `string`\\|`string[]` | `''` | Yes | Title text to display. If specified as an array, text is rendered on multiple lines.\n\n:::tip Note\nIf you need more visual customizations, you can implement the title with HTML and CSS.\n:::\n\n### Position\n\nPossible title position values are:\n\n* `'top'`\n* `'left'`\n* `'bottom'`\n* `'right'`\n\n## Align\n\nAlignment of the title. Options are:\n\n* `'start'`\n* `'center'`\n* `'end'`\n\n## Example Usage\n\nThe example below would enable a title of 'Custom Chart Title' on the chart that is created.\n\n```javascript\nconst chart = new Chart(ctx, {\n    type: 'line',\n    data: data,\n    options: {\n        plugins: {\n            title: {\n                display: true,\n                text: 'Custom Chart Title'\n            }\n        }\n    }\n});\n```\n\nThis example shows how to specify separate top and bottom title text padding:\n\n```javascript\nconst chart = new Chart(ctx, {\n    type: 'line',\n    data: data,\n    options: {\n        plugins: {\n            title: {\n                display: true,\n                text: 'Custom Chart Title',\n                padding: {\n                    top: 10,\n                    bottom: 30\n                }\n            }\n        }\n    }\n});\n```\n"
  },
  {
    "path": "docs/configuration/tooltip.md",
    "content": "# Tooltip\n\n## Tooltip Configuration\n\nNamespace: `options.plugins.tooltip`, the global options for the chart tooltips is defined in `Chart.defaults.plugins.tooltip`.\n\n:::warning\nThe `titleFont`, `bodyFont` and `footerFont` options default to the `Chart.defaults.font` options. To change the overrides for those options, you will need to pass a function that returns a font object. See section about [overriding default fonts](#default-font-overrides) for extra information below.\n:::\n\n| Name | Type | Default | Description\n| ---- | ---- | ------- | -----------\n| `enabled` | `boolean` | `true` | Are on-canvas tooltips enabled?\n| `external` | `function` | `null` | See [external tooltip](#external-custom-tooltips) section.\n| `mode` | `string` | `interaction.mode` | Sets which elements appear in the tooltip. [more...](interactions.md#modes).\n| `intersect` | `boolean` | `interaction.intersect` | If true, the tooltip mode applies only when the mouse position intersects with an element. If false, the mode will be applied at all times.\n| `position` | `string` | `'average'` | The mode for positioning the tooltip. [more...](#position-modes)\n| `callbacks` | `object` | | See the [callbacks section](#tooltip-callbacks).\n| `itemSort` | `function` | | Sort tooltip items. [more...](#sort-callback)\n| `filter` | `function` | | Filter tooltip items. [more...](#filter-callback)\n| `backgroundColor` | [`Color`](../general/colors.md) | `'rgba(0, 0, 0, 0.8)'` | Background color of the tooltip.\n| `titleColor` | [`Color`](../general/colors.md) | `'#fff'` | Color of title text.\n| `titleFont` | `Font` | `{weight: 'bold'}` | See [Fonts](../general/fonts.md).\n| `titleAlign` | `string` | `'left'` | Horizontal alignment of the title text lines. [more...](#text-alignment)\n| `titleSpacing` | `number` | `2` | Spacing to add to top and bottom of each title line.\n| `titleMarginBottom` | `number` | `6` | Margin to add on bottom of title section.\n| `bodyColor` | [`Color`](../general/colors.md) | `'#fff'` | Color of body text.\n| `bodyFont` | `Font` | `{}` | See [Fonts](../general/fonts.md).\n| `bodyAlign` | `string` | `'left'` | Horizontal alignment of the body text lines. [more...](#text-alignment)\n| `bodySpacing` | `number` | `2` | Spacing to add to top and bottom of each tooltip item.\n| `footerColor` | [`Color`](../general/colors.md) | `'#fff'` | Color of footer text.\n| `footerFont` | `Font` | `{weight: 'bold'}` | See [Fonts](../general/fonts.md).\n| `footerAlign` | `string` | `'left'` | Horizontal alignment of the footer text lines. [more...](#text-alignment)\n| `footerSpacing` | `number` | `2` | Spacing to add to top and bottom of each footer line.\n| `footerMarginTop` | `number` | `6` | Margin to add before drawing the footer.\n| `padding` | [`Padding`](../general/padding.md) | `6` | Padding inside the tooltip.\n| `caretPadding` | `number` | `2` | Extra distance to move the end of the tooltip arrow away from the tooltip point.\n| `caretSize` | `number` | `5` | Size, in px, of the tooltip arrow.\n| `cornerRadius` | `number`\\|`object` | `6` | Radius of tooltip corner curves.\n| `multiKeyBackground` | [`Color`](../general/colors.md) | `'#fff'` | Color to draw behind the colored boxes when multiple items are in the tooltip.\n| `displayColors` | `boolean` | `true` | If true, color boxes are shown in the tooltip.\n| `boxWidth` | `number` | `bodyFont.size` | Width of the color box if displayColors is true.\n| `boxHeight` | `number` | `bodyFont.size` | Height of the color box if displayColors is true.\n| `boxPadding` | `number` | `1` | Padding between the color box and the text.\n| `usePointStyle` | `boolean` | `false` | Use the corresponding point style (from dataset options) instead of color boxes, ex: star, triangle etc. (size is based on the minimum value between boxWidth and boxHeight).\n| `borderColor` | [`Color`](../general/colors.md) | `'rgba(0, 0, 0, 0)'` | Color of the border.\n| `borderWidth` | `number` | `0` | Size of the border.\n| `rtl` | `boolean` | | `true` for rendering the tooltip from right to left.\n| `textDirection` | `string` | canvas' default | This will force the text direction `'rtl'` or `'ltr'` on the canvas for rendering the tooltips, regardless of the css specified on the canvas\n| `xAlign` | `string` | `undefined` | Position of the tooltip caret in the X direction. [more](#tooltip-alignment)\n| `yAlign` | `string` | `undefined` | Position of the tooltip caret in the Y direction. [more](#tooltip-alignment)\n\n:::tip Note\nIf you need more visual customizations, please use an [HTML tooltip](../samples/tooltip/html.md).\n:::\n\n### Position Modes\n\nPossible modes are:\n\n* `'average'`\n* `'nearest'`\n\n`'average'` mode will place the tooltip at the average position of the items displayed in the tooltip. `'nearest'` will place the tooltip at the position of the element closest to the event position.\n\nYou can also define [custom position modes](#custom-position-modes).\n\n### Tooltip Alignment\n\nThe `xAlign` and `yAlign` options define the position of the tooltip caret. If these parameters are unset, the optimal caret position is determined.\n\nThe following values for the `xAlign` setting are supported.\n\n* `'left'`\n* `'center'`\n* `'right'`\n\nThe following values for the `yAlign` setting are supported.\n\n* `'top'`\n* `'center'`\n* `'bottom'`\n\n### Text Alignment\n\nThe `titleAlign`, `bodyAlign` and `footerAlign` options define the horizontal position of the text lines with respect to the tooltip box. The following values are supported.\n\n* `'left'` (default)\n* `'right'`\n* `'center'`\n\nThese options are only applied to text lines. Color boxes are always aligned to the left edge.\n\n### Sort Callback\n\nAllows sorting of [tooltip items](#tooltip-item-context). Must implement at minimum a function that can be passed to [Array.prototype.sort](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort). This function can also accept a third parameter that is the data object passed to the chart.\n\n### Filter Callback\n\nAllows filtering of [tooltip items](#tooltip-item-context). Must implement at minimum a function that can be passed to [Array.prototype.filter](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/filter). This function can also accept a fourth parameter that is the data object passed to the chart.\n\n## Tooltip Callbacks\n\nNamespace: `options.plugins.tooltip.callbacks`, the tooltip has the following callbacks for providing text. For all functions, `this` will be the tooltip object created from the `Tooltip` constructor. If the callback returns `undefined`, then the default callback will be used. To remove things from the tooltip callback should return an empty string.\n\nNamespace: `data.datasets[].tooltip.callbacks`, items marked with `Yes` in the column `Dataset override` can be overridden per dataset.\n\nA [tooltip item context](#tooltip-item-context) is generated for each item that appears in the tooltip. This is the primary model that the callback methods interact with. For functions that return text, arrays of strings are treated as multiple lines of text.\n\n| Name | Arguments | Return Type | Dataset override | Description\n| ---- | --------- | ----------- | ---------------- | -----------\n| `beforeTitle` | `TooltipItem[]` | `string | string[] | undefined` | | Returns the text to render before the title.\n| `title` | `TooltipItem[]` | `string | string[] | undefined` | | Returns text to render as the title of the tooltip.\n| `afterTitle` | `TooltipItem[]` | `string | string[] | undefined` | | Returns text to render after the title.\n| `beforeBody` | `TooltipItem[]` | `string | string[] | undefined` | | Returns text to render before the body section.\n| `beforeLabel` | `TooltipItem` | `string | string[] | undefined` | Yes | Returns text to render before an individual label. This will be called for each item in the tooltip.\n| `label` | `TooltipItem` | `string | string[] | undefined` | Yes | Returns text to render for an individual item in the tooltip. [more...](#label-callback)\n| `labelColor` | `TooltipItem` | `object | undefined` | Yes | Returns the colors to render for the tooltip item. [more...](#label-color-callback)\n| `labelTextColor` | `TooltipItem` | `Color | undefined` | Yes | Returns the colors for the text of the label for the tooltip item.\n| `labelPointStyle` | `TooltipItem` | `object | undefined` | Yes | Returns the point style to use instead of color boxes if usePointStyle is true (object with values `pointStyle` and `rotation`). Default implementation uses the point style from the dataset points. [more...](#label-point-style-callback)\n| `afterLabel` | `TooltipItem` | `string | string[] | undefined` | Yes | Returns text to render after an individual label.\n| `afterBody` | `TooltipItem[]` | `string | string[] | undefined` | | Returns text to render after the body section.\n| `beforeFooter` | `TooltipItem[]` | `string | string[] | undefined` | | Returns text to render before the footer section.\n| `footer` | `TooltipItem[]` | `string | string[] | undefined` | | Returns text to render as the footer of the tooltip.\n| `afterFooter` | `TooltipItem[]` | `string | string[] | undefined` | | Text to render after the footer section.\n\n### Label Callback\n\nThe `label` callback can change the text that displays for a given data point. A common example to show a unit. The example below puts a `'$'` before every row.\n\n```javascript\nconst chart = new Chart(ctx, {\n    type: 'line',\n    data: data,\n    options: {\n        plugins: {\n            tooltip: {\n                callbacks: {\n                    label: function(context) {\n                        let label = context.dataset.label || '';\n\n                        if (label) {\n                            label += ': ';\n                        }\n                        if (context.parsed.y !== null) {\n                            label += new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(context.parsed.y);\n                        }\n                        return label;\n                    }\n                }\n            }\n        }\n    }\n});\n```\n\n### Label Color Callback\n\nFor example, to return a red box with a blue dashed border that has a border radius for each item in the tooltip you could do:\n\n```javascript\nconst chart = new Chart(ctx, {\n    type: 'line',\n    data: data,\n    options: {\n        plugins: {\n            tooltip: {\n                callbacks: {\n                    labelColor: function(context) {\n                        return {\n                            borderColor: 'rgb(0, 0, 255)',\n                            backgroundColor: 'rgb(255, 0, 0)',\n                            borderWidth: 2,\n                            borderDash: [2, 2],\n                            borderRadius: 2,\n                        };\n                    },\n                    labelTextColor: function(context) {\n                        return '#543453';\n                    }\n                }\n            }\n        }\n    }\n});\n```\n\n### Label Point Style Callback\n\nFor example, to draw triangles instead of the regular color box for each item in the tooltip, you could do:\n\n```javascript\nconst chart = new Chart(ctx, {\n    type: 'line',\n    data: data,\n    options: {\n        plugins: {\n            tooltip: {\n                usePointStyle: true,\n                callbacks: {\n                    labelPointStyle: function(context) {\n                        return {\n                            pointStyle: 'triangle',\n                            rotation: 0\n                        };\n                    }\n                }\n            }\n        }\n    }\n});\n```\n\n### Tooltip Item Context\n\nThe tooltip items passed to the tooltip callbacks implement the following interface.\n\n```javascript\n{\n    // The chart the tooltip is being shown on\n    chart: Chart\n\n    // Label for the tooltip\n    label: string,\n\n    // Parsed data values for the given `dataIndex` and `datasetIndex`\n    parsed: object,\n\n    // Raw data values for the given `dataIndex` and `datasetIndex`\n    raw: object,\n\n    // Formatted value for the tooltip\n    formattedValue: string,\n\n    // The dataset the item comes from\n    dataset: object\n\n    // Index of the dataset the item comes from\n    datasetIndex: number,\n\n    // Index of this data item in the dataset\n    dataIndex: number,\n\n    // The chart element (point, arc, bar, etc.) for this tooltip item\n    element: Element,\n}\n```\n\n## External (Custom) Tooltips\n\nExternal tooltips allow you to hook into the tooltip rendering process so that you can render the tooltip in your own custom way. Generally this is used to create an HTML tooltip instead of an on-canvas tooltip. The `external` option takes a function which is passed a context parameter containing the `chart` and `tooltip`. You can enable external tooltips in the global or chart configuration like so:\n\n```javascript\nconst myPieChart = new Chart(ctx, {\n    type: 'pie',\n    data: data,\n    options: {\n        plugins: {\n            tooltip: {\n                // Disable the on-canvas tooltip\n                enabled: false,\n\n                external: function(context) {\n                    // Tooltip Element\n                    let tooltipEl = document.getElementById('chartjs-tooltip');\n\n                    // Create element on first render\n                    if (!tooltipEl) {\n                        tooltipEl = document.createElement('div');\n                        tooltipEl.id = 'chartjs-tooltip';\n                        tooltipEl.innerHTML = '<table></table>';\n                        document.body.appendChild(tooltipEl);\n                    }\n\n                    // Hide if no tooltip\n                    const tooltipModel = context.tooltip;\n                    if (tooltipModel.opacity === 0) {\n                        tooltipEl.style.opacity = 0;\n                        return;\n                    }\n\n                    // Set caret Position\n                    tooltipEl.classList.remove('above', 'below', 'no-transform');\n                    if (tooltipModel.yAlign) {\n                        tooltipEl.classList.add(tooltipModel.yAlign);\n                    } else {\n                        tooltipEl.classList.add('no-transform');\n                    }\n\n                    function getBody(bodyItem) {\n                        return bodyItem.lines;\n                    }\n\n                    // Set Text\n                    if (tooltipModel.body) {\n                        const titleLines = tooltipModel.title || [];\n                        const bodyLines = tooltipModel.body.map(getBody);\n\n                        let innerHtml = '<thead>';\n\n                        titleLines.forEach(function(title) {\n                            innerHtml += '<tr><th>' + title + '</th></tr>';\n                        });\n                        innerHtml += '</thead><tbody>';\n\n                        bodyLines.forEach(function(body, i) {\n                            const colors = tooltipModel.labelColors[i];\n                            let style = 'background:' + colors.backgroundColor;\n                            style += '; border-color:' + colors.borderColor;\n                            style += '; border-width: 2px';\n                            const span = '<span style=\"' + style + '\">' + body + '</span>';\n                            innerHtml += '<tr><td>' + span + '</td></tr>';\n                        });\n                        innerHtml += '</tbody>';\n\n                        let tableRoot = tooltipEl.querySelector('table');\n                        tableRoot.innerHTML = innerHtml;\n                    }\n\n                    const position = context.chart.canvas.getBoundingClientRect();\n                    const bodyFont = Chart.helpers.toFont(tooltipModel.options.bodyFont);\n\n                    // Display, position, and set styles for font\n                    tooltipEl.style.opacity = 1;\n                    tooltipEl.style.position = 'absolute';\n                    tooltipEl.style.left = position.left + window.pageXOffset + tooltipModel.caretX + 'px';\n                    tooltipEl.style.top = position.top + window.pageYOffset + tooltipModel.caretY + 'px';\n                    tooltipEl.style.font = bodyFont.string;\n                    tooltipEl.style.padding = tooltipModel.padding + 'px ' + tooltipModel.padding + 'px';\n                    tooltipEl.style.pointerEvents = 'none';\n                }\n            }\n        }\n    }\n});\n```\n\nSee [samples](/samples/tooltip/html.md) for examples on how to get started with external tooltips.\n\n## Tooltip Model\n\nThe tooltip model contains parameters that can be used to render the tooltip.\n\n```javascript\n{\n    chart: Chart,\n\n    // The items that we are rendering in the tooltip. See Tooltip Item Interface section\n    dataPoints: TooltipItem[],\n\n    // Positioning\n    xAlign: string,\n    yAlign: string,\n\n    // X and Y properties are the top left of the tooltip\n    x: number,\n    y: number,\n    width: number,\n    height: number,\n    // Where the tooltip points to\n    caretX: number,\n    caretY: number,\n\n    // Body\n    // The body lines that need to be rendered\n    // Each object contains 3 parameters\n    // before: string[] // lines of text before the line with the color square\n    // lines: string[], // lines of text to render as the main item with color square\n    // after: string[], // lines of text to render after the main lines\n    body: object[],\n    // lines of text that appear after the title but before the body\n    beforeBody: string[],\n    // line of text that appear after the body and before the footer\n    afterBody: string[],\n\n    // Title\n    // lines of text that form the title\n    title: string[],\n\n    // Footer\n    // lines of text that form the footer\n    footer: string[],\n\n    // style to render for each item in body[]. This is the style of the squares in the tooltip\n    labelColors: TooltipLabelStyle[],\n    labelTextColors: Color[],\n    labelPointStyles: { pointStyle: PointStyle; rotation: number }[],\n\n    // 0 opacity is a hidden tooltip\n    opacity: number,\n\n    // tooltip options\n    options: Object\n}\n```\n\n## Custom Position Modes\n\nNew modes can be defined by adding functions to the `Chart.Tooltip.positioners` map.\n\nExample:\n\n```javascript\nimport { Tooltip } from 'chart.js';\n\n/**\n * Custom positioner\n * @function Tooltip.positioners.myCustomPositioner\n * @param elements {Chart.Element[]} the tooltip elements\n * @param eventPosition {Point} the position of the event in canvas coordinates\n * @returns {TooltipPosition} the tooltip position\n */\nTooltip.positioners.myCustomPositioner = function(elements, eventPosition) {\n    // A reference to the tooltip model\n    const tooltip = this;\n\n    /* ... */\n\n    return {\n        x: 0,\n        y: 0\n        // You may also include xAlign and yAlign to override those tooltip options.\n    };\n};\n\n// Then, to use it...\nnew Chart(ctx, {\n    data,\n    options: {\n        plugins: {\n            tooltip: {\n                position: 'myCustomPositioner'\n            }\n        }\n    }\n})\n```\n\nSee [samples](/samples/tooltip/position.md) for a more detailed example.\n\nIf you're using TypeScript, you'll also need to register the new mode:\n\n```typescript\ndeclare module 'chart.js' {\n  interface TooltipPositionerMap {\n    myCustomPositioner: TooltipPositionerFunction<ChartType>;\n  }\n}\n```\n\n## Default font overrides\n\nBy default, the `titleFont`, `bodyFont` and `footerFont` listen to the `Chart.defaults.font` options for setting its values.\nOverriding these normally by accessing the object won't work because it is backed by a get function that looks to the default `font` namespace.\nSo you will need to override this get function with your own function that returns the desired config.\n\nExample:\n\n```javascript\nChart.defaults.plugins.tooltip.titleFont = () => ({ size: 20, lineHeight: 1.2, weight: 800 });\n```"
  },
  {
    "path": "docs/developers/api.md",
    "content": "# API\n\nFor each chart, there are a set of global prototype methods on the shared chart type which you may find useful. These are available on all charts created with Chart.js, but for the examples, let's use a line chart we've made.\n\n```javascript\n// For example:\nconst myLineChart = new Chart(ctx, config);\n```\n\n## .destroy()\n\nUse this to destroy any chart instances that are created. This will clean up any references stored to the chart object within Chart.js, along with any associated event listeners attached by Chart.js.\nThis must be called before the canvas is reused for a new chart.\n\n```javascript\n// Destroys a specific chart instance\nmyLineChart.destroy();\n```\n\n## .update(mode?)\n\nTriggers an update of the chart. This can be safely called after updating the data object. This will update all scales, legends, and then re-render the chart.\n\n```javascript\nmyLineChart.data.datasets[0].data[2] = 50; // Would update the first dataset's value of 'March' to be 50\nmyLineChart.update(); // Calling update now animates the position of March from 90 to 50.\n```\nA `mode` can be provided to indicate transition configuration should be used. This can be either:\n\n- **string value**: Core calls this method using any of `'active'`, `'hide'`, `'reset'`, `'resize'`, `'show'` or `undefined`. `'none'` is also supported for skipping animations for single update. Please see [animations](../configuration/animations.md) docs for more details.\n\n- **function**: that receives a context object `{ datasetIndex: number }` and returns a mode string, allowing different modes per dataset.\n\nExamples:\n```javascript\n// Using string mode\nmyChart.update('active');\n\n// Using function mode for dataset-specific animations\nmyChart.update(ctx => ctx.datasetIndex === 0 ? 'active' : 'none');\n```\n\nSee [Updating Charts](updates.md) for more details.\n\n## .reset()\n\nReset the chart to its state before the initial animation. A new animation can then be triggered using `update`.\n\n```javascript\nmyLineChart.reset();\n```\n\n## .render()\n\nTriggers a redraw of all chart elements. Note, this does not update elements for new data. Use `.update()` in that case.\n\n## .stop()\n\nUse this to stop any current animation. This will pause the chart during any current animation frame. Call `.render()` to re-animate.\n\n```javascript\n// Stops the charts animation loop at its current frame\nmyLineChart.stop();\n// => returns 'this' for chainability\n```\n\n## .resize(width?, height?)\n\nUse this to manually resize the canvas element. This is run each time the canvas container is resized, but you can call this method manually if you change the size of the canvas nodes container element.\n\nYou can call `.resize()` with no parameters to have the chart take the size of its container element, or you can pass explicit dimensions (e.g., for [printing](../configuration/responsive.md#printing-resizable-charts)).\n\n```javascript\n// Resizes & redraws to fill its container element\nmyLineChart.resize();\n// => returns 'this' for chainability\n\n// With an explicit size:\nmyLineChart.resize(width, height);\n```\n\n## .clear()\n\nWill clear the chart canvas. Used extensively internally between animation frames, but you might find it useful.\n\n```javascript\n// Will clear the canvas that myLineChart is drawn on\nmyLineChart.clear();\n// => returns 'this' for chainability\n```\n\n## .toBase64Image(type?, quality?)\n\nThis returns a base 64 encoded string of the chart in its current state.\n\n```javascript\nmyLineChart.toBase64Image();\n// => returns png data url of the image on the canvas\n\nmyLineChart.toBase64Image('image/jpeg', 1)\n// => returns a jpeg data url in the highest quality of the canvas\n```\n\n## .getElementsAtEventForMode(e, mode, options, useFinalPosition)\n\nCalling `getElementsAtEventForMode(e, mode, options, useFinalPosition)` on your Chart instance passing an event and a mode will return the elements that are found. The `options` and `useFinalPosition` arguments are passed through to the handlers.\n\nTo get an item that was clicked on, `getElementsAtEventForMode` can be used.\n\n```javascript\nfunction clickHandler(evt) {\n    const points = myChart.getElementsAtEventForMode(evt, 'nearest', { intersect: true }, true);\n\n    if (points.length) {\n        const firstPoint = points[0];\n        const label = myChart.data.labels[firstPoint.index];\n        const value = myChart.data.datasets[firstPoint.datasetIndex].data[firstPoint.index];\n    }\n}\n```\n\n## .getSortedVisibleDatasetMetas()\n\nReturns an array of all the dataset meta's in the order that they are drawn on the canvas that are not hidden.\n\n```javascript\nconst visibleMetas = chart.getSortedVisibleDatasetMetas();\n```\n\n## .getDatasetMeta(index)\n\nLooks for the dataset that matches the current index and returns that metadata. This returned data has all of the metadata that is used to construct the chart.\n\nThe `data` property of the metadata will contain information about each point, bar, etc. depending on the chart type.\n\nExtensive examples of usage are available in the [Chart.js tests](https://github.com/chartjs/Chart.js/tree/master/test).\n\n```javascript\nconst meta = myChart.getDatasetMeta(0);\nconst x = meta.data[0].x;\n```\n\n## getVisibleDatasetCount\n\nReturns the number of datasets that are currently not hidden.\n\n```javascript\nconst numberOfVisibleDatasets = chart.getVisibleDatasetCount();\n```\n## isDatasetVisible(datasetIndex)\n\nReturns a boolean if a dataset at the given index is currently visible.\n\nThe visibility is determined by first checking the hidden property in the dataset metadata (set via [`setDatasetVisibility()`](#setdatasetvisibility-datasetindex-visibility) and accessible through [`getDatasetMeta()`](#getdatasetmeta-index)). If this is not set, the hidden property of the dataset object itself (`chart.data.datasets[n].hidden`) is returned.\n\n```javascript\nchart.isDatasetVisible(1);\n```\n\n## setDatasetVisibility(datasetIndex, visibility)\n\nSets the visibility for a given dataset. This can be used to build a chart legend in HTML. During click on one of the HTML items, you can call `setDatasetVisibility` to change the appropriate dataset.\n\n```javascript\nchart.setDatasetVisibility(1, false); // hides dataset at index 1\nchart.update(); // chart now renders with dataset hidden\n```\n\n## toggleDataVisibility(index)\n\nToggles the visibility of an item in all datasets. A dataset needs to explicitly support this feature for it to have an effect. From internal chart types, doughnut / pie, polar area, and bar use this.\n\n```javascript\nchart.toggleDataVisibility(2); // toggles the item in all datasets, at index 2\nchart.update(); // chart now renders with item hidden\n```\n\n## getDataVisibility(index)\n\nReturns the stored visibility state of a data index for all datasets. Set by [toggleDataVisibility](#toggledatavisibility-index). A dataset controller should use this method to determine if an item should not be visible.\n\n```javascript\nconst visible = chart.getDataVisibility(2);\n```\n\n## hide(datasetIndex, dataIndex?)\n\nIf dataIndex is not specified, sets the visibility for the given dataset to false. Updates the chart and animates the dataset with `'hide'` mode. This animation can be configured under the `hide` key in animation options. Please see [animations](../configuration/animations.md) docs for more details.\n\nIf dataIndex is specified, sets the hidden flag of that element to true and updates the chart.\n\n```javascript\nchart.hide(1); // hides dataset at index 1 and does 'hide' animation.\nchart.hide(0, 2); // hides the data element at index 2 of the first dataset.\n```\n\n## show(datasetIndex, dataIndex?)\n\nIf dataIndex is not specified, sets the visibility for the given dataset to true. Updates the chart and animates the dataset with `'show'` mode. This animation can be configured under the `show` key in animation options. Please see [animations](../configuration/animations.md) docs for more details.\n\nIf dataIndex is specified, sets the hidden flag of that element to false and updates the chart.\n\n```javascript\nchart.show(1); // shows dataset at index 1 and does 'show' animation.\nchart.show(0, 2); // shows the data element at index 2 of the first dataset.\n```\n\n## setActiveElements(activeElements)\n\nSets the active (hovered) elements for the chart. See the \"Programmatic Events\" sample file to see this in action.\n\n```javascript\nchart.setActiveElements([\n    {datasetIndex: 0, index: 1},\n]);\n```\n\n## isPluginEnabled(pluginId)\n\nReturns a boolean if a plugin with the given ID has been registered to the chart instance.\n\n```javascript\nchart.isPluginEnabled('filler');\n```\n\n## Static: getChart(key)\n\nFinds the chart instance from the given key. If the key is a `string`, it is interpreted as the ID of the Canvas node for the Chart. The key can also be a `CanvasRenderingContext2D` or an `HTMLDOMElement`. This will return `undefined` if no Chart is found. To be found, the chart must have previously been created.\n\n```javascript\nconst chart = Chart.getChart(\"canvas-id\");\n```\n\n## Static: register(chartComponentLike)\n\nUsed to register plugins, axis types or chart types globally to all your charts.\n\n```javascript\nimport { Chart, Tooltip, LinearScale, PointElement, BubbleController } from 'chart.js';\n\nChart.register(Tooltip, LinearScale, PointElement, BubbleController);\n```\n\n## Static: unregister(chartComponentLike)\n\nUsed to unregister plugins, axis types or chart types globally from all your charts.\n\n```javascript\nimport { Chart, Tooltip, LinearScale, PointElement, BubbleController } from 'chart.js';\n\nChart.unregister(Tooltip, LinearScale, PointElement, BubbleController);\n```\n"
  },
  {
    "path": "docs/developers/axes.md",
    "content": "# New Axes\n\nAxes in Chart.js can be individually extended. Axes should always derive from `Chart.Scale` but this is not a mandatory requirement.\n\n```javascript\nclass MyScale extends Chart.Scale {\n    /* extensions ... */\n}\nMyScale.id = 'myScale';\nMyScale.defaults = defaultConfigObject;\n\n// MyScale is now derived from Chart.Scale\n```\n\nOnce you have created your scale class, you need to register it with the global chart object so that it can be used.\n\n```javascript\nChart.register(MyScale);\n\n// If the new scale is not extending Chart.Scale, the prototype can not be used to detect what\n// you are trying to register - so you need to be explicit:\n\n// Chart.registry.addScales(MyScale);\n```\n\nTo use the new scale, simply pass in the string key to the config when creating a chart.\n\n```javascript\nconst lineChart = new Chart(ctx, {\n    data: data,\n    type: 'line',\n    options: {\n        scales: {\n            y: {\n                type: 'myScale' // this is the same id that was set on the scale\n            }\n        }\n    }\n});\n```\n\n## Scale Properties\n\nScale instances are given the following properties during the fitting process.\n\n```javascript\n{\n    left: number, // left edge of the scale bounding box\n    right: number, // right edge of the bounding box\n    top: number,\n    bottom: number,\n    width: number, // the same as right - left\n    height: number, // the same as bottom - top\n\n    // Margin on each side. Like css, this is outside the bounding box.\n    margins: {\n        left: number,\n        right: number,\n        top: number,\n        bottom: number\n    },\n\n    // Amount of padding on the inside of the bounding box (like CSS)\n    paddingLeft: number,\n    paddingRight: number,\n    paddingTop: number,\n    paddingBottom: number\n}\n```\n\n## Scale Interface\n\nTo work with Chart.js, custom scale types must implement the following interface.\n\n```javascript\n{\n    // Determines the data limits. Should set this.min and this.max to be the data max/min\n    determineDataLimits: function() {},\n\n    // Generate tick marks. this.chart is the chart instance. The data object can be accessed as this.chart.data\n    // buildTicks() should create a ticks array on the axis instance, if you intend to use any of the implementations from the base class\n    buildTicks: function() {},\n\n    // Get the label to show for the given value\n    getLabelForValue: function(value) {},\n\n    // Get the pixel (x coordinate for horizontal axis, y coordinate for vertical axis) for a given value\n    // @param index: index into the ticks array\n    getPixelForTick: function(index) {},\n\n    // Get the pixel (x coordinate for horizontal axis, y coordinate for vertical axis) for a given value\n    // @param value : the value to get the pixel for\n    // @param [index] : index into the data array of the value\n    getPixelForValue: function(value, index) {},\n\n    // Get the value for a given pixel (x coordinate for horizontal axis, y coordinate for vertical axis)\n    // @param pixel : pixel value\n    getValueForPixel: function(pixel) {}\n}\n```\n\nOptionally, the following methods may also be overwritten, but an implementation is already provided by the `Chart.Scale` base class.\n\n```javascript\n{\n    // Adds labels to objects in the ticks array. The default implementation simply calls this.options.ticks.callback(numericalTick, index, ticks);\n    generateTickLabels: function() {},\n\n    // Determine how much the labels will rotate by. The default implementation will only rotate labels if the scale is horizontal.\n    calculateLabelRotation: function() {},\n\n    // Fits the scale into the canvas.\n    // this.maxWidth and this.maxHeight will tell you the maximum dimensions the scale instance can be. Scales should endeavour to be as efficient as possible with canvas space.\n    // this.margins is the amount of space you have on either side of your scale that you may expand in to. This is used already for calculating the best label rotation\n    // You must set this.minSize to be the size of your scale. It must be an object containing 2 properties: width and height.\n    // You must set this.width to be the width and this.height to be the height of the scale\n    fit: function() {},\n\n    // Draws the scale onto the canvas. this.(left|right|top|bottom) will have been populated to tell you the area on the canvas to draw in\n    // @param chartArea : an object containing four properties: left, right, top, bottom. This is the rectangle that lines, bars, etc will be drawn in. It may be used, for example, to draw grid lines.\n    draw: function(chartArea) {}\n}\n```\n\nThe Core.Scale base class also has some utility functions that you may find useful.\n\n```javascript\n{\n    // Returns true if the scale instance is horizontal\n    isHorizontal: function() {},\n\n    // Returns the scale tick objects ({label, major})\n    getTicks: function() {}\n}\n```\n"
  },
  {
    "path": "docs/developers/charts.md",
    "content": "# New Charts\n\nChart.js 2.0 introduced the concept of controllers for each dataset. Like scales, new controllers can be written as needed.\n\n```javascript\nclass MyType extends Chart.DatasetController {\n\n}\n\nChart.register(MyType);\n\n// Now we can create a new instance of our chart, using the Chart.js API\nnew Chart(ctx, {\n    // this is the string the constructor was registered at, ie Chart.controllers.MyType\n    type: 'MyType',\n    data: data,\n    options: options\n});\n```\n\n## Dataset Controller Interface\n\nDataset controllers must implement the following interface.\n\n```javascript\n{\n    // Defaults for charts of this type\n    defaults: {\n        // If set to `false` or `null`, no dataset level element is created.\n        // If set to a string, this is the type of element to create for the dataset.\n        // For example, a line create needs to create a line element so this is the string 'line'\n        datasetElementType: string | null | false,\n\n        // If set to `false` or `null`, no elements are created for each data value.\n        // If set to a string, this is the type of element to create for each data value.\n        // For example, a line create needs to create a point element so this is the string 'point'\n        dataElementType: string | null | false,\n    }\n\n    // ID of the controller\n    id: string;\n\n    // Update the elements in response to new data\n    // @param mode : update mode, core calls this method using any of `'active'`, `'hide'`, `'reset'`, `'resize'`, `'show'` or `undefined`\n    update: function(mode) {}\n}\n```\n\nThe following methods may optionally be overridden by derived dataset controllers.\n\n```javascript\n{\n    // Draw the representation of the dataset. The base implementation works in most cases, and an example of a derived version\n    // can be found in the line controller\n    draw: function() {},\n\n    // Initializes the controller\n    initialize: function() {},\n\n    // Ensures that the dataset represented by this controller is linked to a scale. Overridden to helpers.noop in the polar area and doughnut controllers as these\n    // chart types using a single scale\n    linkScales: function() {},\n\n    // Parse the data into the controller meta data. The default implementation will work for cartesian parsing, but an example of an overridden\n    // version can be found in the doughnut controller\n    parse: function(start, count) {},\n}\n```\n\n## Extending Existing Chart Types\n\nExtending or replacing an existing controller type is easy. Simply replace the constructor for one of the built-in types with your own.\n\nThe built-in controller types are:\n\n* `BarController`\n* `BubbleController`\n* `DoughnutController`\n* `LineController`\n* `PieController`\n* `PolarAreaController`\n* `RadarController`\n* `ScatterController`\n\nThese controllers are also available in the UMD package, directly under `Chart`. Eg: `Chart.BarController`.\n\nFor example, to derive a new chart type that extends from a bubble chart, you would do the following.\n\n```javascript\nimport {BubbleController} from 'chart.js';\nclass Custom extends BubbleController {\n    draw() {\n        // Call bubble controller method to draw all the points\n        super.draw(arguments);\n\n        // Now we can do some custom drawing for this dataset. Here we'll draw a red box around the first point in each dataset\n        const meta = this.getMeta();\n        const pt0 = meta.data[0];\n\n        const {x, y} = pt0.getProps(['x', 'y']);\n        const {radius} = pt0.options;\n\n        const ctx = this.chart.ctx;\n        ctx.save();\n        ctx.strokeStyle = 'red';\n        ctx.lineWidth = 1;\n        ctx.strokeRect(x - radius, y - radius, 2 * radius, 2 * radius);\n        ctx.restore();\n    }\n};\nCustom.id = 'derivedBubble';\nCustom.defaults = BubbleController.defaults;\n\n// Stores the controller so that the chart initialization routine can look it up\nChart.register(Custom);\n\n// Now we can create and use our new chart type\nnew Chart(ctx, {\n    type: 'derivedBubble',\n    data: data,\n    options: options\n});\n```\n\n## TypeScript Typings\n\nIf you want your new chart type to be statically typed, you must provide a `.d.ts` TypeScript declaration file. Chart.js provides a way to augment built-in types with user-defined ones, by using the concept of \"declaration merging\".\n\nWhen adding a new chart type, `ChartTypeRegistry` must contain the declarations for the new type, either by extending an existing entry in `ChartTypeRegistry` or by creating a new one.\n\nFor example, to provide typings for a new chart type that extends from a bubble chart, you would add a `.d.ts` containing:\n\n```typescript\nimport { ChartTypeRegistry } from 'chart.js';\n\ndeclare module 'chart.js' {\n    interface ChartTypeRegistry {\n        derivedBubble: ChartTypeRegistry['bubble']\n    }\n}\n```\n"
  },
  {
    "path": "docs/developers/contributing.md",
    "content": "# Contributing\n\nNew contributions to the library are welcome, but we ask that you please follow these guidelines:\n\n- Before opening a PR for major additions or changes, please discuss the expected API and/or implementation by [filing an issue](https://github.com/chartjs/Chart.js/issues) or asking about it in the [Chart.js Discord](https://discord.gg/HxEguTK6av) #dev channel. This will save you development time by getting feedback upfront and make reviews faster by giving the maintainers more context and details.\n- Consider whether your changes are useful for all users, or if creating a Chart.js [plugin](plugins.md) would be more appropriate.\n- Check that your code will pass tests and `eslint` code standards. `pnpm test` will run both the linter and tests for you.\n- Add unit tests and document new functionality (in the `test/` and `docs/` directories respectively).\n- Avoid breaking changes unless there is an upcoming major release, which is infrequent. We encourage people to write plugins for the most new advanced features, and care a lot about backward compatibility.\n- We strongly prefer new methods to be added as private whenever possible. A method can be made private either by making a top-level `function` outside of a class or by prefixing it with `_` and adding `@private` JSDoc if inside a class. Public APIs take considerable time to review and become locked once implemented as we have limited ability to change them without breaking backward compatibility. Private APIs allow the flexibility to address unforeseen cases.\n\n## Joining the project\n\nActive committers and contributors are invited to introduce themselves and request commit access to this project. We have a very active Discord community that you can join [here](https://discord.gg/HxEguTK6av). If you think you can help, we'd love to have you!\n\n## Building and Testing\n\nFirstly, we need to ensure development dependencies are installed. With node and pnpm installed, after cloning the Chart.js repo to a local directory, and navigating to that directory in the command line, we can run the following:\n\n```bash\n> pnpm install\n```\n\nThis will install the local development dependencies for Chart.js.\n\nThe following commands are now available from the repository root:\n\n```bash\n> pnpm run build             // build dist files in ./dist\n> pnpm run autobuild         // build and watch for source changes\n> pnpm run dev               // run tests and watch for source and test changes\n> pnpm run lint              // perform code linting (ESLint, tsc)\n> pnpm test                  // perform code linting and run unit tests with coverage\n```\n\n`pnpm run dev` and `pnpm test` can be appended with a string that is used to match the spec filenames. For example: `pnpm run dev plugins` will start karma in watch mode for `test/specs/**/*plugin*.js`.\n\n### Documentation\n\nWe use [Vuepress](https://vuepress.vuejs.org/) to manage the docs which are contained as Markdown files in the docs directory. You can run the doc server locally using these commands:\n\n```bash\n> pnpm run docs:dev\n```\n\n### Image-Based Tests\n\nSome display-related functionality is difficult to test via typical Jasmine units. For this reason, we introduced image-based tests ([#3988](https://github.com/chartjs/Chart.js/pull/3988) and [#5777](https://github.com/chartjs/Chart.js/pull/5777)) to assert that a chart is drawn pixel-for-pixel matching an expected image.\n\nGenerated charts in image-based tests should be **as minimal as possible** and focus only on the tested feature to prevent failure if another feature breaks (e.g. disable the title and legend when testing scales).\n\nYou can create a new image-based test by following the steps below:\n\n- Create a JS file ([example](https://github.com/chartjs/Chart.js/blob/f7b671006a86201808402c3b6fe2054fe834fd4a/test/fixtures/controller.bubble/radius-scriptable.js)) or JSON file ([example](https://github.com/chartjs/Chart.js/blob/4b421a50bfa17f73ac7aa8db7d077e674dbc148d/test/fixtures/plugin.filler/fill-line-dataset.json)) that defines chart config and generation options.\n- Add this file in `test/fixtures/{spec.name}/{feature-name}.json`.\n- Add a [describe line](https://github.com/chartjs/Chart.js/blob/4b421a50bfa17f73ac7aa8db7d077e674dbc148d/test/specs/plugin.filler.tests.js#L10) to the beginning of `test/specs/{spec.name}.tests.js` if it doesn't exist yet.\n- Run `pnpm run dev`.\n- Click the *\"Debug\"* button (top/right): a test should fail with the associated canvas visible.\n- Right-click on the chart and *\"Save image as...\"* `test/fixtures/{spec.name}/{feature-name}.png` making sure not to activate the tooltip or any hover functionality\n- Refresh the browser page (`CTRL+R`): test should now pass\n- Verify test relevancy by changing the feature values *slightly* in the JSON file.\n\nTests should pass in both browsers. In general, we've hidden all text in image tests since it's quite difficult to get them to pass between different browsers. As a result, it is recommended to hide all scales in image-based tests. It is also recommended to disable animations. If tests still do not pass, adjust [`tolerance` and/or `threshold`](https://github.com/chartjs/Chart.js/blob/1ca0ffb5d5b6c2072176fd36fa85a58c483aa434/test/jasmine.matchers.js) at the beginning of the JSON file keeping them **as low as possible**.\n\nWhen a test fails, the expected and actual images are shown. If you'd like to see the images even when the tests pass, set `\"debug\": true` in the JSON file.\n\n## Bugs and Issues\n\nPlease report these on the GitHub page - at <a href=\"https://github.com/chartjs/Chart.js\" target=\"_blank\">github.com/chartjs/Chart.js</a>. Please do not use issues for support requests. For help using Chart.js, please take a look at the [`chart.js`](https://stackoverflow.com/questions/tagged/chart.js) tag on Stack Overflow.\n\nWell-structured, detailed bug reports are hugely valuable for the project.\n\nGuidelines for reporting bugs:\n\n- Check the issue search to see if it has already been reported\n- Isolate the problem to a simple test case\n- Please include a demonstration of the bug on a website such as [JS Bin](https://jsbin.com/), [JS Fiddle](https://jsfiddle.net/), or [Codepen](https://codepen.io/pen/). ([Template](https://codepen.io/pen?template=wvezeOq)). If filing a bug against `master`, you may reference the latest code via <https://www.chartjs.org/dist/master/chart.umd.min.js> (changing the filename to point at the file you need as appropriate). Do not rely on these files for production purposes as they may be removed at any time.\n\nPlease provide any additional details associated with the bug, if it's browser or screen density specific, or only happens with a certain configuration or data.\n"
  },
  {
    "path": "docs/developers/index.md",
    "content": "# Developers\n\nDeveloper features allow extending and enhancing Chart.js in many different ways.\n\n## Latest resources\n\nThe latest documentation and samples, including unreleased features, are available at:\n\n- <https://www.chartjs.org/docs/master/>\n- <https://www.chartjs.org/samples/master/>\n\n## Development releases\n\nLatest builds are available for testing at:\n\n- <https://www.chartjs.org/dist/master/chart.js>\n- <https://www.chartjs.org/dist/master/chart.umd.min.js>\n\n:::warning Warning\n\nDevelopment builds **must not** be used for production purposes or as replacement for a CDN. See [available CDNs](../getting-started/installation.md#cdn).\n\n:::\n\n## Browser support\n\nAll modern and up-to-date browsers are supported, including, but not limited to:\n\n* Chrome\n* Edge\n* Firefox\n* Safari\n\nAs of version 3, we have dropped Internet Explorer 11 support.\n\nBrowser support for the canvas element is available in all modern & major mobile browsers. [CanIUse](https://caniuse.com/#feat=canvas)\n\nRun `npx browserslist` at the root of the [codebase](https://github.com/chartjs/Chart.js) to get a list of supported browsers.\n\nThanks to [BrowserStack](https://browserstack.com) for allowing our team to test on thousands of browsers.\n\n## Previous versions\n\nTo migrate from version 2 to version 3, please see [the v3 migration guide](../getting-started/v3-migration).\n\nVersion 3 has a largely different API than earlier versions.\n\nMost earlier version options have current equivalents or are the same.\n\nPlease note - documentation for previous versions is available online or in the GitHub repo.\n\n- [2.9.4 Documentation](https://www.chartjs.org/docs/2.9.4/)\n- [1.x Documentation](https://github.com/chartjs/Chart.js/tree/v1.1.1/docs)\n"
  },
  {
    "path": "docs/developers/plugin_flowcharts.drawio",
    "content": "<mxfile host=\"app.diagrams.net\" modified=\"2022-08-02T22:40:51.791Z\" agent=\"5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36\" etag=\"r-mRQn2uuU6jSS0wK3iE\" version=\"20.2.2\" type=\"device\" pages=\"6\"><diagram id=\"GnJziOrIlQhF6Q0E3uKP\" name=\"Chart render\">7V3dl5o4FP9rfGyPSQjiY50Z23M6O/sx7enOY5So7CBxEUftX79BkhEICiqSYLcvhUsC8d77y/2E6aC7+eZzSBaz35hL/Q7supsOuu9ACLrI4f/FlG1CwVYvIUxDzxWD9oRn7yeVMwV15bl0mRkYMeZH3iJLHLMgoOMoQyNhyNbZYRPmZ5+6IFOqEJ7HxFepPzw3miVUB3f39C/Um87kk0FXXJkTOVgQljPisnWKhB466C5kLEqO5ps76sfMk3xJ5g0PXH1fWEiDqMqEydz/ffvkTt3hcPBvb7x+GX65/4DE2qKt/MHU5b9fnLIwmrEpC4j/sKcOQrYKXBrftcvP9mMeGVtwIuDEf2gUbYUwySpinDSL5r64Sjde9Hc8/SMWZy+pK/cbcefdyVaeBFG4TU2KT1/S1/bTdmdynsolwbglW4VjeoQ1UttIOKXRkXEwGRfzLfUAIYPPlM0pXw8fEFKfRN5bVq+IUM/p+7i9BPmBEOIJAhX3fSP+SjxpQKdewEl/US61UJW373MsxXJdz7yIPi/IjitrDues1MhykQBs4m1i6R9m7RsNI7o5ygxxFTkCHGJ3kFhZp6AmSLMUyiStdu7h/+FQoualcLCMggNU4PDEIm8SL2BEJyykB1CxFyooR8bE8/075rNwNxe5mDquxenLKGSvNHXFgSNk2zVBx85CBxRg552WBo91LfA4Cq9fuOXWCCiQgtMeXGWAysBpj67aAWVVBFTPKEBJv8z8LfL6kukbJRlLgd8dCcZ8Ysz4obrDzdh8tFqW725XMfOa7XxP4dXzq7c47CSdZA5qYBiGOYZp39wBvH3c9yviHgCjgN8v83HuQ7K+AQ8H6QeBusdqdnG6nXNcnNNihpzwJ86YjsdFwh852MIXBt1Vo26AKmJQKNOH7keBk8qg3N3rUxiSbWrAgnlBtEw96o+YkFLablZpLSuXlSkZD5ySCRY8PoEfJGvOTZc/gE0mS87ZPBTeOXZBrKXmHp7Y7VuNyiorBWeI2QCquIx1GC3dDiOwW6zbGnKnqOo2js3CRO/WjXkDMq09LD7LEMO8Ye3n6yMlE6RlrmqIc+OzdrguGwuqJ9keyYj6uRS+700DfjzmwudRLhrEO7I3Jv4ncWHuuW6i03Tp/SSj3f1itRHc5jfHgw6+L1Sko4hS9v73kp14SiddFTvkweEMx2X16jRFatIlkphJiWsYknksMb7qOfECL5iaZmyB42i2tpbebbgZtxFX3E4t2ywTiQs8IZFuIBO+rdxMRYUbBN0JB9ia+vz5QJAMLY+foFFAkOtOASFJtHUfyZaGy53ecLWJx9k+/2GDEUeFPY2PfiZUMo/1fndtdwOpbxozz3kQYO1JN9iaovwFGIBVMWBYeR0eNgYi90wiwp2r5Y3koG3tJgG1pgDbhnwzrFrrhWZ5YUhN3pkdB0q1rSUOdDC2M7AUdzI3EERaHbnzGmEa6yyrikFklgOI1GrggZywIRCUyYg6IGjbMJuMMR6CsEUNMrbuDAx02uJlXLDv2FVtv1kteFCtRZ0XeO4oRkadoKhD7Gp+9urP4Os2+k6fwqkz+fp6D75j+J66NR8Bil/c3f27DBs9FRvFbDLLJkO1sy+bnbyROBTAAgvRcCBqtwUg58NAepzlrqlZdWy57tK8TEvhkO+P7dm60WBBnWgwO8BDlUtdZjXWIrXUZUzEgK0sAmQXoLaIARn3+o/Z7TSowMO6LOuR6pMAjgh2r9tjg/M9NgAd75nJT+iVNLti5+h4fb2uVmviY4M1u/6c+nlanO9/Ac6JLdi5Cddp/bKK3vMo1EIz0o0SIrWkGy0Eshl/83u/kBqKymJox6Dur7wjAbp93d1fVlv21vP3SOnplrvEVd9zaeglU7XuKHKPbqLbqi43nEq0c/os2341hoataWBp3lewqjaVI7Ny8Jba2JvLM7Y6v5IHEdDfCmmpVQ/Zacr9ifnCpxFVcWbWx210V/VQvy07UZM9QJXTU6btQWoDquE9QDKCqSMi6GGZcZIRgUkdCIW1sl+lolhcKFRBVjiuarardowdW/XhOsq35GOELbXzSlnR6umuuxcVcH9l3MCKuNHWn1qMG71dyg1k/hVZD07yRKrK+rKPXzT7rQuA+1o/dlGXnS74MoIxjtVRtNXTXN2X6mVgZ+cx2JhYplXercW4uRiwWFda825tbTtzDVa4aohY/858kbDL3tW9Me+1VwCuZr3X1pSFrwGSgn7qY1ppCEYOdlOL78xfBoxrqLmtPUgDekt0LXXvZa90a/17dCP+fZFZLFRoQ9x7+eZSLd/QgdIDNNehB61pLb9KprRqykdbt+zRZadQ9U1aUG6pdjdwY8tqWkiGrheS8dP9H3hJ0LH/Mzno4T8=</diagram><diagram id=\"qz6xZkkN5_TWD1aOfnxG\" name=\"Chart Init\">7Zlbd6IwEMc/jY/u4Rarj/XWdtf2dLVb6754ogRJRUJD8PbpN0AQEEqt22r19EkymYTwn/klA5bUxmx5RaFj3hIdWSVF0pcltVlSFFlSq/zHt6xCC9AuQsOEYl04xYYeXqNopLB6WEduypERYjHspI1jYttozFI2SClZpN0MYqXv6sAJyhh6Y2hlrX2sMzO0VoEU268RnpjRnWVJ9Mxg5CwMrgl1skiY1FZJbVBCWHg1WzaQ5YsX6fJIbyvPjy910NJbDz+f5tPhqFYOJ2u/Z8jmESiy2d5T15V+D9eHvzpTZIyacDKd33ejqV22ivRCOpdPNAllJpkQG1qt2FqnxLN15M8q8Vbs0yHE4UaZG58RYyuRC9BjhJtMNrNEL1pi9uQP/wFEa5DoaS7FzEFjFTVsRleJQX5zkOyLhwWtaNyO0kU6EI+OhRALYtZ6tG4icN+/braf3VpnLSSWGKQTxAp0FX6+mIk0FIG5QmSG+CK5A0UWZHiezlUoUn6y8YvDyi9EZPOjXLTqObQ8caeGCSnzqaMIMh7LTBZYFgfUj/bCxAz1HBjIsuB7RDqW0HVCag289HPinYLPEWVoWSiR6FWr4inEPhRRuUhALUxmgufI9j+aTpWhWbu7af/BHdIe12WFyWpZORVy9iegKLOTBOTqczQCiladIOCOMGz4C8C2y6A/w1Y442DJb2NgYMtqEIvQYKyqA1TVNW53GSVTlOipKiO1UvlMTippTuQcUDa2JCnaB5Dirgee8XteGbVbtNcd9EC565TVY5IiJziJqfkqZ0xuripZwnJ1VY5FWO5qtAxhA1725UW+A0e8xkyfIhae2Px6zBVEnJS6n++YF3GXomOGdT1MDOTiNRwF8/naOwTbLHgWUC+BZm40irIyA9amEhU3SRV7ecCVpR+VFHFCiZ1lFzPf+0+ScCGG4fL4b8dls4D9Q1XJ2QxP5TQ7MUbBjozGuQT2yZ5LSuEq4SCoyCaXuI+mpA8JDWy9O7zPn1+EK/jMVC6KQyKVu8h1iO0G0rWzh7pJZiPPfftAP1QZq4LD1bG5GXrxTf5r5Beduh9G/mFO5ywnm/o3OFHRuZS/2hZfmnrs8rd2KoB9MChgR1CqXwoU8Doo0OC16Y2N2bmwsv2qCORjs1LNqO/rjaE/0HN0yLL71Nf/UgVqhzviKw9Dp+24y9Z1o1+TXjztL+18f+R9fefK1WvXj7xHewEvWnXezjVCBqHonLeuDTyH2Lpyv3eeDGT7w1L0nTcJS1F6Hh6WolXnweKy4K+RM+VE+jxOeDP+MzR8wY//UlZb/wA=</diagram><diagram id=\"6Hx3QUBt-kPyE_5nGTOz\" name=\"Chart update\">7V1de6I4FP41XrqPIYD2sq2dzu52OrPTbWc6d1GiMgPExVi1v34DJvKRCDgVidaryoGEcM55k/OVtAWv/eVtiKaTT8TBXsvoOMsW7LcMA3Rgj/2JKCtOsc3umjIOXYfTEsKD+4pFU06duw6eZR6khHjUnWaJQxIEeEgzNBSGZJF9bES87FunaIwlwsMQeTL1m+vQyZraszoJ/SN2xxPxZtDhd3wkHuaE2QQ5ZJEiwZsWvA4Joetf/vIaexH3BF+e/hn5n6f/9nHnqW+5l9R+as/b684+7NJk8wkhDuhvdz1//bX8e/X9H9SfI2vY7X9ZPF61Tf5pdCX4hR3GPn5JQjohYxIg7yahXoVkHjg46rXDrpJn7giZMiJgxJ+Y0hXXBTSnhJEm1Pf4Xbx06feo+R8Wv3pO3ekvec/xxUpcBDRcpRpFl8/pe0mz+Eq0q8g6zuIZmYdDzoj//po/0sHHr5O7Ob15fqIfe9jlLO5QFI4x74+O/Vn38da9/wFfbs0uvvpx3xbPRcxMqSEXzC0mPmaDZA+E2EPUfcnqKuIqP948l4iV/eCSVUu5aNQvyJvzN13hsRsw0uPUQRTLSuB5DJ+RsBcTl+KHKYq5smBzRFaUaDZdg3bkLiOV2JHfLzikeFnIIX4X9vhH8HmoDQVOFylUc9IkBWhBewtTldCxztDZBh0lvxTQUT5nNAWdolGnoHNPqDuKBjDAIxLiLQhKJA3KUTRyPe+aeCSM20LHwj3HZPQZDckvnLrTMwbQtuuEmZ2FGTBklG1oaZiZdcEMSAJ4ZuZEg9ADKeAlMCyDXgZ4CQ63QC+nEKPeEA+HKoUY9CzTqgGsRkWwQq3A2j1PybVI2dZKyoY0I1yjYMgaRlL8IM/EE+IP5rPyWfhQpovRtOUCJQY+/HKnjDLfw1pWFxctI8fFxlemi2OZbfY8a9gVZ42eVrOGLc8aIY61vRN1bHT62MPxJcMAmrEvi2MSbMFlE0s4i0MPYTQwJ5KfaBRin7zE17yVbJpogZ68XQeroseuza4DEqc0hY8mi3WvIuxAY7GHomFvd6BuPOwzDs1OzJGysoAzgQJw5kEdqXOsbzfAgaoRC6CXFwTkmEV/y5ImDL4OXoOQ/WJjQNFadhzrmNmruI5ZtcHqvZqBQuvL4WHpBQ/Z/cmtSHdoReb0ZFaiHGQsu2nHCTQap/m9+F2zK5FVFWp6RWqAyvhTyv4ODbCXSy157jhgv4eMh5iB5SpSeXeIvEt+w3cdZ60aeOa+okHcX8T9KXGZPRl1bl21rL5SHoWKKYFrk57mb2mlM8Aq0LWZwsCelQEe76ky63nnX6KvST1CRqN4Kc/JZjOGN4jLksR1XJE1GzYcWTPOTu2OM1vVYJKhl1cL5HBSLB72qiCKBk29CAvMoCDLKFvViZPtdBKRhih4QZqGicwcoLqNB1kNeEbUbjmdynl2QytEGbLXujHL0YhZAKdlleeR1lMkhQ6MNLtJpB2hVS4AVI40vRxgQ1i55SUVepjlQjP3Y5ZbupvhxnuNKFUHlF6pRTHuFKAuA9dnbyZBZPzhINLP43CheoqA6mFdqJNPUzRe3FU5nKSZiSi8+9NVjX1PqJUFrdmEKgeivuJ1/irAi5a6MGPmE8JmrMgD53Pv23yFQ1n+F43H46FxLLjaNz6q1lRAoBc+SosqeML3xIsqQMdUYOegVRWwUXPlCL1mAaVyzOlVVQHlNUnrXJZQzL04zTawzCzyWpo70ULNjjaXBVRbbw5bJn402yn2PUVVrWyBeqXbYWllCzcLTssqyBvUQLXB4sBWwXsN4cGq+Vyol8cJ5XyuAEmyH0BLb/Iir/xVKyJrU37zvbqTsKo7KVivi/IXuJNx6vU0l408cmDjgRjzRDY7N+pgCunsz8FM/KALUTy8m+NzGYZolXqAO3WyX8TfZJs51RQmzoeqDUBJAysfSck1YD/Wg67TcbPdm+nXTz9fP49+DL7Z9v3ysTvciFn7xUNjdVcyVq81Rwxb3qQyi8fgIzdwg7GWrroF8vBUhCHrctWVsj0XyW0FTREWSjFjNIWZolGXmWknFvSX3HvlBpa63HulIBqtlDuMg1MEh1LYNFbvVjTqrbA5LbhIJzjZqhxZXW6NUgDnip66K3pUSbUjWNDklNqfgUtd5MUjYPIPZc3R/mRCoNo0UZc1qDx8stH42y4Rg99HQdGhm6WHczZWzKEcjQwCbaqxi7RrL3nlLjBhBjyidy0Sy8rPNyVxbSkDONT6pnWE7k04bawApGjUacHHtYhTb74+S5dJKNroN6NRnYGWgQvpEF2gcKUOulQdzylQe8bA3tcgdZzXzJ2atzH8RRdrEPJWiTB3jVhbuQB0Sfg5P6yi4PO+Jm45t7JxAxlkw5PdWdg+ZFpLyXk5vdhwVmuXNXPPuFeUFimf62q19r2Dcpaq0io6JXzPs/muk7Bxof0kLFeofRHmU3KU5igkvu6mlJFLoLYPuG9OvbydN0fthu3uUWEb6I9teVt5ysBi+nQi9hWAWdbWeEQKu0z+vdBaTMl/aYI3/wM=</diagram><diagram id=\"_1jcRxh_d02XbuYfSdjN\" name=\"Chart Events\">3Vpbc6IwFP41PtrhLvvYqrU72227a2d2uy87ESKkjYQJ8frrN4EgUBC1VdHtgyUnF5LvfN/JIdDSu5PFgILQ/05ciFua4i5aeq+laaqi2/yfsCwTi2l0EoNHkSsbZYYhWsG0p7ROkQujQkNGCGYoLBodEgTQYQUboJTMi83GBBfvGgIPlgxDB+Cy9RdymZ9YbVPJ7HcQeX56Z1WRNROQNpaGyAcumedMer+ldykhLLmaLLoQC/BSXB6JYdz9fMPfQwSfn17bg6+PT+1ksNt9uqyXQGHAPjz0b3IbrOw/9w+vU2fV9WZ/ekRtp0tjyxQv6HL4ZJFQ5hOPBAD3M+sNJdPAhWJUhZeyNveEhNyocuMrZGwpuQCmjHCTzyZY1sIFYr9F9ytTll5yNb2FHDkuLNNCwOgy10kUX/J1Wbe4lPbbEToJcUSm1IE1eKUMBtSDrK6dkTQUaOZ4KD0zgGQC+Sx5AwoxYGhWJCuQnPfW7dZdnwjiC9EUqU8jJadUp5qW0yGSmcpeGTv4RW4amSnmzB78kROeATyVS+jPYDzBn9CBfF1umWAYc+0LIs19xOAwBDHicx5+ijQBUZgEhDFaCLrt6csZpAwuasGXtbpdBDHFcJ6LF9Lk50KFrWz2VgHnGlCH5jPpPz7YULn5+mOo+9po4LXNSxHlocVllNVVCZB2aHHt6q7aaedE8EAYGosZjOCYUCg18c6pmcvU7WIYI4y7BBMa99VdE9quwe0Ro+QN5mpsbaRb1jHVYm0IOTm5rG15vRjH0ks5CL3wjKNBDak5BWV62raxFba1bJc7+MZWJ6mt0tObkt639orCO+S+0M7fv4NQvR4MrPXqzj5Snj592dXLlbgqTXm5btY5fXdB4PCOwou35bjqk8loGm2PqafLJxpOKPQSgMM3FMZJGpcC/dzOdCwUzXepbVVadrR9plIVnUuJNgeOGtaOUcM6q6hhbc7KwJhB+l8nZUZFyDmtWLT/Xyx1W+fWRKoxsdTNOieWOxC43Cl8hp/XyalYrze+RagXk5EemPbmjnvEl7OivVmi/TNFnidSIj6D882NjE6R+Fbj4d5ukvcfe94+1ZNYXa60VS/mWelFLT+KbTpquQcjiN8d6GLkBfza4RByZek3gvDIAfhaVkyQ6ybMgBFagVE8ngA/FOfX8WLMm5bZq3RHHS1Lylq/b5I3aeVf6VQprq1cWXbHLKhOjvTRI/y0CRmPI/jZ0/lqd13M652G1HVeu5Fafk5/IGcsrpReB1FXR9PVy1JX+QGz64PAu5BDqXUKcYJDqTrt5eDrkQCW6X72rwstu2Ekq19A8atrbnzCUw8FQpJA/KLA5UGBiQc7IIwxY/nFSCyQC4WhwBN11IuuHEln8cWG+KHTsns4ZmzzoUggPLpPbKrycXFDKp2tKPEfr3FB5K8T82M5n9+v4Hw9zQnymbdS4f0v+3ufF7OvS5KolX2jo/f/AQ==</diagram><diagram id=\"IEXjfTlYIfaVPTfT2BSW\" name=\"Scale Update\">7VxbU9s4FP41eWTH8t2P5dZOh7I7S7stTx0RK4mKbWUcBRJ+/UqO7NhSCAr4CmEGiI9v8tH3nYvOcUbWWbz6nML57BsJUTQyjXA1ss5HpgkMy2f/uGQtJK7tbSTTFIdCthXc4CeUnyqkSxyiReVASkhE8bwqHJMkQWNakcE0JY/VwyYkqt51DqdIEdyMYaRKf+KQzjZS3zG28i8IT2f5nYEh9sQwP1gIFjMYkseSyLoYWWcpIXTzKV6doYhrL9dLSN2/Z+6P35Of/vLh6ov1359fwcnmYpeHnFI8QooS+upLf326vP7qBEsv+W1cGPG3ZOo/nbimeDa6zhWGQqY/sUlSOiNTksDoYis9TckyCRG/rMG2tsdcETJnQsCEfxClawEGuKSEiWY0jsRetML0Fz/9L0ds3Zb2nK/ElbONtdjQ1IHQ1YIs0zHa8+A5FGE6RXSfgsSBXCslQAkVf0YkRjRdswNSFEGKH6qogwK80+K47QSxD2KODpgvcd0HGC3FnU7RFCdM9GMeQorU2YwixjQ+a48zTNHNHGZqeWRsr84JXMw39JvgFZ/bAxX+gFKKVns1JPZavuCTsCgnpisEjyV+CtGsRM1cVrtSXXsoJGBTka5LJ/HN2/K+7WnZVkPkcbXZ49TNHnHqPwSzJykwZZsSpoApgWUzVHGahJdiHG+AkErMf9EC8SEuMl/E0bWLn1sYgZc52hglXVl9OyhZqLTMSbsxTjqKQm8ydYY4RskCk2QxFF0aXevSMxRVHe3bXvvm6Nq3oBv7JrvCzRM1Zt28TqNEUILPFkz9BlCgCSCvX+GlZylW95rsnPsreMcyxWoEGeFpwj6PmQ5RygTcaGLm/T6JHTEOww000AI/wbvselz7cw7c7Fmc05FzvnM+9gJTMc9FPinuMiqnbLvM9gkDDHC9CstEWPha7uaHkMkkiwTq56U/mOxtaLzMDWxPeJmPu8TLc0ghk0Q4xow6DO9wPOOzeqmGRTMS3y0XHYZEcsZnd5zwec5QeFMz/j3dxM2ze4V/T02vrgnFEz6COzQhKeJ0uBJceEtaMMFRdEYikmbnWqGD/NBm8gVNyT0q7fHNO8t1G2SNLbFmB2laTiO8j8oaW5c1br9YY6teA7HQLMYJX5AIqx6kj8m0zIEe5NLBRyWBq0sCv18kcJ93HXDC2PD+PUf3q3k+UHT7QVjja7KmZwlHPu4Say5Wc5iEfAQwmaIMTQxMvOzLGcF0weYCJ1N+5Qn7kyDEJ28QbqUo8XZHkMGUoGomSD4TLxPE6hVB8nE/n5GcLnEUfsfj+3fjV+TShtV5NOa7H5U2li5taq/Avm3C1AXmjCdMVANVWgN+9wGVPxTg92Tl19ct6QFQe0lGr6bnuRJYGm5Z8NUKezUxev8OzO487gvUMOIWqfo+Vlv3cbug7MvesK16vVcFmg8kADVcsA/Mo3toCEJB7ZURzZYP2T08A6EXLxS03BoXWEcsHhaq6Bapu4Ji2wjKFVLuBYTxPOuqpP3NGmQn4HRewQsGU8HrCRUD3cpfUZhqm4vAkLskmjbnalHxjCScFYKN/B/h42G/UdYlxgGXZkvDfaSpnBS4nScFwDi6zAPDt5x+GkTtyGkCq2WvWejkmfJ/zs2UUPacJBkIO/3u2ekpej3m7IczVt+16nbVbFt5LfYjucnNZn97eUE+xKPRbwBC/epJKUZeMiHZ1LCnvsfzIq3pZTOvlNV4u7IaY4dBLoQNqHMwTVm9oY5uN1f/qKP2c13iiL9wUiwG8O8NgOt+xjN2IC3rgc7jGXCMZ+pglG6nFwAtvRTtuPJSgWyBm85AwNEuN4Yi02wHRUoea3pt57FqL2Kx/IsS/oJfT197yl8zyk19oJr6YIelD5qz9OprNJkmN22bvdal/DLMjmWAVl8hK6z4u1kHlSOToPMmp+JuR/+h6z+AdptTW/5DiUKAbN9eW8hu3xPlb4Qf8aiLxxxlGnisPc/UQ5FpyParcRSZiue4xDT3Ej0tZsvr8MDovJpdQKakyPxb0Iwx4RHiIL8QDYAdawI1BTdsc/uFhRtEb7/30br4Hw==</diagram><diagram id=\"c8UZMIniomCoghozd-Fr\" name=\"Chart destroy\">7ZjbctowEIafhst2fMI4lw2QdtrS6QzTNrlU8NpWI1seWQa7T18JSz5gIKQFEjK5wvtLK0u7+7EaD+xxXHxkKI1m1AcysAy/GNiTgWWZhu2JH6mUlTJ0RpUQMuyrSY0wx39Aeyo1xz5knYmcUsJx2hUXNElgwTsaYoyuutMCSrpvTVEIPWG+QKSv/sI+jyrVs0aN/glwGOk3m+5VNRIjPVmdJIuQT1ctyZ4O7DGjlFdPcTEGIoOn41L53ewYrTfGIOGHOMROdmM8/PzxxboNZt7sw+frRfhOrZLxUh8YfHF+ZVLGIxrSBJFpo14zmic+yFUNYTVzvlKaCtEU4m/gvFTJRDmnQop4TNQoFJjfSvf3Q2XdtUYmhVp5bZTaSDgrW07SvGuPNW5rS/tV55OH2hk2HQOaswXsiZWlyg+xEPieeU6dXEEF0BjEfoQfA4I4Xnb3gVR5hvW8JoPiQSXxCQlVm1wikqs3XUOIE0kgZJzRsp9wQgRMMrGrCHOYp2gdhZXguZs2lKUVYQEuZPpVbJfAOBT7o9uPhnKwPUWH+nvQsKxarCkpamGmtaOHz37j4WAe/rfOlet3isWb64pwrM2K2Mh1xZ/y2kh3vY1/rwCnB9A3ynEgT3YPAWUw2cFRUwbm4ywFmJAxJZStfW1/CJ7vCF0u/QCtEc+6t133SLC53dCadp8209qCm3Mq3IaXgtsRsTH1NeaxPuKehC93owgcw+ouUR3gZHy5Pb5qoMQlLk4JcHjZPcoxredtUqNLoeYFNCnvQNhMY3sNnOfW5u1uOijgwF5Pz7GMPjzn7TlXb/Qc3quMQ/HZUQXnwUdvcxs/GRcpegXcjJ6bG31v2RbkPMFJxpFc5OIjbTuni7Qwmw8+1Y2q+WxmT/8C</diagram></mxfile>"
  },
  {
    "path": "docs/developers/plugins.md",
    "content": "# Plugins\n\nPlugins are the most efficient way to customize or change the default behavior of a chart. They have been introduced at [version 2.1.0](https://github.com/chartjs/Chart.js/releases/tag/2.1.0) (global plugins only) and extended at [version 2.5.0](https://github.com/chartjs/Chart.js/releases/tag/v2.5.0) (per chart plugins and options).\n\n## Using plugins\n\nPlugins can be shared between chart instances:\n\n```javascript\nconst plugin = { /* plugin implementation */ };\n\n// chart1 and chart2 use \"plugin\"\nconst chart1 = new Chart(ctx, {\n    plugins: [plugin]\n});\n\nconst chart2 = new Chart(ctx, {\n    plugins: [plugin]\n});\n\n// chart3 doesn't use \"plugin\"\nconst chart3 = new Chart(ctx, {});\n```\n\nPlugins can also be defined directly in the chart `plugins` config (a.k.a. *inline plugins*):\n\n:::warning\n*inline* plugins are not registered. Some plugins require registering, i.e. can't be used *inline*.\n:::\n\n```javascript\nconst chart = new Chart(ctx, {\n    plugins: [{\n        beforeInit: function(chart, args, options) {\n            //..\n        }\n    }]\n});\n```\n\nHowever, this approach is not ideal when the customization needs to apply to many charts.\n\n## Global plugins\n\nPlugins can be registered globally to be applied on all charts (a.k.a. *global plugins*):\n\n```javascript\nChart.register({\n    // plugin implementation\n});\n```\n\n:::warning\n*inline* plugins can't be registered globally.\n:::\n\n## Configuration\n\n### Plugin ID\n\nPlugins must define a unique id in order to be configurable.\n\nThis id should follow the [npm package name convention](https://docs.npmjs.com/files/package.json#name):\n\n- can't start with a dot or an underscore\n- can't contain any non-URL-safe characters\n- can't contain uppercase letters\n- should be something short, but also reasonably descriptive\n\nIf a plugin is intended to be released publicly, you may want to check the [registry](https://www.npmjs.com/search?q=chartjs-plugin-) to see if there's something by that name already. Note that in this case, the package name should be prefixed by `chartjs-plugin-` to appear in Chart.js plugin registry.\n\n### Plugin options\n\nPlugin options are located under the `options.plugins` config and are scoped by the plugin ID: `options.plugins.{plugin-id}`.\n\n```javascript\nconst chart = new Chart(ctx, {\n    options: {\n        foo: { ... },           // chart 'foo' option\n        plugins: {\n            p1: {\n                foo: { ... },   // p1 plugin 'foo' option\n                bar: { ... }\n            },\n            p2: {\n                foo: { ... },   // p2 plugin 'foo' option\n                bla: { ... }\n            }\n        }\n    }\n});\n```\n\n#### Disable plugins\n\nTo disable a global plugin for a specific chart instance, the plugin options must be set to `false`:\n\n```javascript\nChart.register({\n    id: 'p1',\n    // ...\n});\n\nconst chart = new Chart(ctx, {\n    options: {\n        plugins: {\n            p1: false   // disable plugin 'p1' for this instance\n        }\n    }\n});\n```\n\nTo disable all plugins for a specific chart instance, set `options.plugins` to `false`:\n\n```javascript\nconst chart = new Chart(ctx, {\n    options: {\n        plugins: false // all plugins are disabled for this instance\n    }\n});\n```\n\n#### Plugin defaults\n\nYou can set default values for your plugin options in the `defaults` entry of your plugin object. In the example below the canvas will always have a lightgreen backgroundColor unless the user overrides this option in `options.plugins.custom_canvas_background_color.color`.\n\n```javascript\nconst plugin = {\n    id: 'custom_canvas_background_color',\n    beforeDraw: (chart, args, options) => {\n        const {ctx} = chart;\n        ctx.save();\n        ctx.globalCompositeOperation = 'destination-over';\n        ctx.fillStyle = options.color;\n        ctx.fillRect(0, 0, chart.width, chart.height);\n        ctx.restore();\n    },\n    defaults: {\n        color: 'lightGreen'\n    }\n}\n```\n\n## Plugin Core API\n\nRead more about the [existing plugin extension hooks](../api/interfaces/Plugin).\n\n### Chart Initialization\n\nPlugins are notified during the initialization process. These hooks can be used to set up data needed for the plugin to operate.\n\n![Chart.js init flowchart](./init_flowchart.png)\n\n### Chart Update\n\nPlugins are notified throughout the update process.\n\n![Chart.js update flowchart](./update_flowchart.png)\n\n### Scale Update\n\nPlugins are notified throughout the scale update process.\n\n![Chart.js scale update flowchart](./scale_flowchart.png)\n\n### Rendering\n\nPlugins can interact with the chart throughout the render process. The rendering process is documented in the flowchart below. Each of the green processes is a plugin notification. The red lines indicate how cancelling part of the render process can occur when a plugin returns `false` from a hook. Not all hooks are cancelable, however, in general most `before*` hooks can be cancelled.\n\n![Chart.js render pipeline flowchart](./render_flowchart.png)\n\n### Event Handling\n\nPlugins can interact with the chart during the event handling process. The event handling flow is documented in the flowchart below. Each of the green processes is a plugin notification. If a plugin makes changes that require a re-render, the plugin can set `args.changed` to `true` to indicate that a render is needed. The built-in tooltip plugin uses this method to indicate when the tooltip has changed.\n\n![Chart.js event handling flowchart](./event_flowchart.png)\n\n### Chart destroy\n\nPlugins are notified during the destroy process. These hooks can be used to destroy things that the plugin made and used during its life.\nThe `destroy` hook has been deprecated since Chart.js version 3.7.0, use the `afterDestroy` hook instead.\n\n![Chart.js destroy flowchart](./destroy_flowchart.png)\n\n## TypeScript Typings\n\nIf you want your plugin to be statically typed, you must provide a `.d.ts` TypeScript declaration file. Chart.js provides a way to augment built-in types with user-defined ones, by using the concept of \"declaration merging\".\n\nWhen adding a plugin, `PluginOptionsByType` must contain the declarations for the plugin.\n\nFor example, to provide typings for the [`canvas backgroundColor plugin`](../configuration/canvas-background.md), you would add a `.d.ts` containing:\n\n```ts\nimport {ChartType, Plugin} from 'chart.js';\n\ndeclare module 'chart.js' {\n  interface PluginOptionsByType<TType extends ChartType> {\n    customCanvasBackgroundColor?: {\n      color?: string\n    }\n  }\n}\n```\n"
  },
  {
    "path": "docs/developers/publishing.md",
    "content": "# Publishing an extension\n\nIf you are planning on publishing an extension for Chart.js, here are some pointers.\n\n## Awesome\n\nYou'd probably want your extension to be listed in the [awesome](https://github.com/chartjs/awesome).\n\nNote the minimum extension age requirement of 30 days.\n\n## ESM\n\nIf you are utilizing ESM, you probably still want to publish a UMD bundle of your extension. Because Chart.js v3 is tree shakeable, the interface is a bit different.\nUMD package's global `Chart` includes everything, while ESM package exports all the things separately.\nFortunately, most of the exports can be mapped automatically by the bundlers.\n\nBut not the helpers.\n\nIn UMD, helpers are available through `Chart.helpers`. In ESM, they are imported from `chart.js/helpers`.\n\nFor example `import {isNullOrUndef} from 'chart.js/helpers'` is available at `Chart.helpers.isNullOrUndef` for UMD.\n\n### Rollup\n\n`output.globals` can be used to convert the helpers.\n\n```js\nmodule.exports = {\n  // ...\n  output: {\n    globals: {\n      'chart.js': 'Chart',\n      'chart.js/helpers': 'Chart.helpers'\n    }\n  }\n};\n```\n"
  },
  {
    "path": "docs/developers/updates.md",
    "content": "# Updating Charts\n\nIt's pretty common to want to update charts after they've been created. When the chart data or options are changed, Chart.js will animate to the new data values and options.\n\n## Adding or Removing Data\n\nAdding and removing data is supported by changing the data array. To add data, just add data into the data array as seen in this example, to remove it you can pop it again.\n\n```javascript\nfunction addData(chart, label, newData) {\n    chart.data.labels.push(label);\n    chart.data.datasets.forEach((dataset) => {\n        dataset.data.push(newData);\n    });\n    chart.update();\n}\n\nfunction removeData(chart) {\n    chart.data.labels.pop();\n    chart.data.datasets.forEach((dataset) => {\n        dataset.data.pop();\n    });\n    chart.update();\n}\n```\n\n## Updating Options\n\nTo update the options, mutating the `options` property in place or passing in a new options object are supported.\n\n- If the options are mutated in place, other option properties would be preserved, including those calculated by Chart.js.\n- If created as a new object, it would be like creating a new chart with the options - old options would be discarded.\n\n```javascript\nfunction updateConfigByMutating(chart) {\n    chart.options.plugins.title.text = 'new title';\n    chart.update();\n}\n\nfunction updateConfigAsNewObject(chart) {\n    chart.options = {\n        responsive: true,\n        plugins: {\n            title: {\n                display: true,\n                text: 'Chart.js'\n            }\n        },\n        scales: {\n            x: {\n                display: true\n            },\n            y: {\n                display: true\n            }\n        }\n    };\n    chart.update();\n}\n```\n\nScales can be updated separately without changing other options.\nTo update the scales, pass in an object containing all the customization including those unchanged ones.\n\nVariables referencing any one from `chart.scales` would be lost after updating scales with a new `id` or the changed `type`.\n\n```javascript\nfunction updateScales(chart) {\n    let xScale = chart.scales.x;\n    let yScale = chart.scales.y;\n    chart.options.scales = {\n        newId: {\n            display: true\n        },\n        y: {\n            display: true,\n            type: 'logarithmic'\n        }\n    };\n    chart.update();\n    // need to update the reference\n    xScale = chart.scales.newId;\n    yScale = chart.scales.y;\n}\n```\n\nYou can update a specific scale by its id as well.\n\n```javascript\nfunction updateScale(chart) {\n    chart.options.scales.y = {\n        type: 'logarithmic'\n    };\n    chart.update();\n}\n```\n\nCode sample for updating options can be found in [line-datasets.html](https://www.chartjs.org/docs/latest/samples/area/line-datasets.html).\n\n## Preventing Animations\n\nSometimes when a chart updates, you may not want an animation. To achieve this you can call `update` with `'none'` as mode.\n\n```javascript\nmyChart.update('none');\n```\n"
  },
  {
    "path": "docs/general/accessibility.md",
    "content": "# Accessibility\n\nChart.js charts are rendered on user provided `canvas` elements. Thus, it is up to the user to create the `canvas` element in a way that is accessible. The `canvas` element has support in all browsers and will render on screen but the `canvas` content will not be accessible to screen readers.\n\nWith `canvas`, the accessibility has to be added with ARIA attributes on the `canvas` element or added using internal fallback content placed within the opening and closing canvas tags.\n\nThis [website](http://pauljadam.com/demos/canvas.html) has a more detailed explanation of `canvas` accessibility as well as in depth examples.\n\n## Examples\n\nThese are some examples of **accessible** `canvas` elements.\n\nBy setting the `role` and `aria-label`, this `canvas` now has an accessible name.\n\n```html\n<canvas id=\"goodCanvas1\" width=\"400\" height=\"100\" aria-label=\"Hello ARIA World\" role=\"img\"></canvas>\n```\n\nThis `canvas` element has a text alternative via fallback content.\n\n```html\n<canvas id=\"okCanvas2\" width=\"400\" height=\"100\">\n    <p>Hello Fallback World</p>\n</canvas>\n```\n\nThese are some bad examples of **inaccessible** `canvas` elements.\n\nThis `canvas` element does not have an accessible name or role.\n\n```html\n<canvas id=\"badCanvas1\" width=\"400\" height=\"100\"></canvas>\n```\n\nThis `canvas` element has inaccessible fallback content.\n\n```html\n<canvas id=\"badCanvas2\" width=\"400\" height=\"100\">Your browser does not support the canvas element.</canvas>\n```\n"
  },
  {
    "path": "docs/general/colors.md",
    "content": "# Colors\n\nCharts support three color options:\n* for geometric elements, you can change *background* and *border* colors;\n* for textual elements, you can change the *font* color.\n\nAlso, you can change the whole [canvas background](../configuration/canvas-background.md).\n\n## Default colors\n\nIf a color is not specified, a global default color from `Chart.defaults` is used:\n\n| Name | Type | Description | Default value\n| ---- | ---- | ----------- | -------------\n| `backgroundColor` | [`Color`](../api/#color) | Background color | `rgba(0, 0, 0, 0.1)`\n| `borderColor` | [`Color`](../api/#color) | Border color | `rgba(0, 0, 0, 0.1)`\n| `color` | [`Color`](../api/#color) | Font color | `#666`\n\nYou can reset default colors by updating these properties of `Chart.defaults`:\n\n```javascript\nChart.defaults.backgroundColor = '#9BD0F5';\nChart.defaults.borderColor = '#36A2EB';\nChart.defaults.color = '#000';\n```\n\n### Per-dataset color settings\n\nIf your chart has multiple datasets, using default colors would make individual datasets indistinguishable. In that case, you can set `backgroundColor` and `borderColor` for each dataset:\n\n```javascript\nconst data = {\n  labels: ['A', 'B', 'C'],\n  datasets: [\n    {\n      label: 'Dataset 1',\n      data: [1, 2, 3],\n      borderColor: '#36A2EB',\n      backgroundColor: '#9BD0F5',\n    },\n    {\n      label: 'Dataset 2',\n      data: [2, 3, 4],\n      borderColor: '#FF6384',\n      backgroundColor: '#FFB1C1',\n    }\n  ]\n};\n```\n\nHowever, setting colors for each dataset might require additional work that you'd rather not do. In that case, consider using the following plugins with pre-defined or generated palettes.\n\n### Default color palette\n\nIf you don't have any preference for colors, you can use the built-in `Colors` plugin. It will cycle through a palette of seven Chart.js brand colors:\n\n<div style=\"max-width: 500px;\">\n\n![Colors plugin palette](./colors-plugin-palette.png)\n\n</div>\n\nAll you need is to import and register the plugin:\n\n```javascript\nimport { Colors } from 'chart.js';\n\nChart.register(Colors);\n```\n\n:::tip Note\n\nIf you are using the UMD version of Chart.js, this plugin will be enabled by default. You can disable it by setting the `enabled` option to `false`:\n\n```js\nconst options = {\n  plugins: {\n    colors: {\n      enabled: false\n    }\n  }\n};\n```\n\n:::\n\n### Dynamic datasets at runtime\n\nBy default, the colors plugin only works when you initialize the chart without any colors for the border or background specified.\nIf you want to force the colors plugin to always color your datasets, for example, when using dynamic datasets at runtime you will need to set the `forceOverride` option to true:\n\n```js\nconst options = {\n  plugins: {\n    colors: {\n      forceOverride: true\n    }\n  }\n};\n```\n\n\n### Advanced color palettes\n\nSee the [awesome list](https://github.com/chartjs/awesome#plugins) for plugins that would give you more flexibility defining color palettes.\n\n## Color formats\n\nYou can specify the color as a string in either of the following notations:\n\n| Notation | Example | Example with transparency\n| -------- | ------- | -------------------------\n| [Hexadecimal](https://developer.mozilla.org/en-US/docs/Web/CSS/hex-color) | `#36A2EB` | `#36A2EB80`\n| [RGB](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/rgb) or [RGBA](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/rgba) | `rgb(54, 162, 235)` | `rgba(54, 162, 235, 0.5)`\n| [HSL](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/hsl) or [HSLA](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/hsla) | `hsl(204, 82%, 57%)` | `hsla(204, 82%, 57%, 0.5)`\n\nAlternatively, you can pass a [CanvasPattern](https://developer.mozilla.org/en-US/docs/Web/API/CanvasPattern) or [CanvasGradient](https://developer.mozilla.org/en/docs/Web/API/CanvasGradient) object instead of a string color to achieve some interesting effects.\n\n## Patterns and Gradients\n\nFor example, you can fill a dataset with a pattern from an image.\n\n```javascript\nconst img = new Image();\nimg.src = 'https://example.com/my_image.png';\nimg.onload = () => {\n  const ctx = document.getElementById('canvas').getContext('2d');\n  const fillPattern = ctx.createPattern(img, 'repeat');\n\n  const chart = new Chart(ctx, {\n    data: {\n      labels: ['Item 1', 'Item 2', 'Item 3'],\n      datasets: [{\n        data: [10, 20, 30],\n        backgroundColor: fillPattern\n      }]\n    }\n  });\n};\n```\nPattern fills can help viewers with vision deficiencies (e.g., color-blindness or partial sight) [more easily understand your data](http://betweentwobrackets.com/data-graphics-and-colour-vision/).\n\nYou can use the [Patternomaly](https://github.com/ashiguruma/patternomaly) library to generate patterns to fill datasets:\n\n```javascript\nconst chartData = {\n  datasets: [{\n    data: [45, 25, 20, 10],\n    backgroundColor: [\n      pattern.draw('square', '#ff6384'),\n      pattern.draw('circle', '#36a2eb'),\n      pattern.draw('diamond', '#cc65fe'),\n      pattern.draw('triangle', '#ffce56')\n    ]\n  }],\n  labels: ['Red', 'Blue', 'Purple', 'Yellow']\n};\n```\n"
  },
  {
    "path": "docs/general/data-structures.md",
    "content": "# Data structures\n\nThe `data` property of a dataset can be passed in various formats. By default, that `data` is parsed using the associated chart type and scales.\n\nIf the `labels` property of the main `data` property is used, it has to contain the same amount of elements as the dataset with the most values. These labels are used to label the index axis (default `x` axis). The values for the labels have to be provided in an array.\nThe provided labels can be of the type string or number to be rendered correctly. If you want multiline labels, you can provide an array with each line as one entry in the array.\n\n## Primitive[]\n\n```javascript\nconst cfg = {\n  type: 'bar',\n  data: {\n    datasets: [{\n      data: [20, 10],\n    }],\n    labels: ['a', 'b']\n  }\n}\n```\n\nWhen `data` is an array of numbers, values from the `labels` array at the same index are used for the index axis (`x` for vertical, `y` for horizontal charts).\n\n## Array[]\n\n```javascript\nconst cfg = {\n  type: 'line',\n  data: {\n    datasets: [{\n      data: [[10, 20], [15, null], [20, 10]]\n    }]\n  }\n}\n```\n\nWhen `data` is an array of arrays (or what TypeScript would call tuples), the first element of each tuple is the index  (`x` for vertical, `y` for horizontal charts) and the second element is the value (`y` by default).\n\n## Object[]\n\n```javascript\nconst cfg = {\n  type: 'line',\n  data: {\n    datasets: [{\n      data: [{x: 10, y: 20}, {x: 15, y: null}, {x: 20, y: 10}]\n    }]\n  }\n}\n```\n\n```javascript\nconst cfg = {\n  type: 'line',\n  data: {\n    datasets: [{\n      data: [{x: '2016-12-25', y: 20}, {x: '2016-12-26', y: 10}]\n    }]\n  }\n}\n```\n\n```javascript\nconst cfg = {\n  type: 'bar',\n  data: {\n    datasets: [{\n      data: [{x: 'Sales', y: 20}, {x: 'Revenue', y: 10}]\n    }]\n  }\n}\n```\n\nThis is also the internal format used for parsed data. In this mode, parsing can be disabled by specifying `parsing: false` at chart options or dataset. If parsing is disabled, data must be sorted and in the formats the associated chart type and scales use internally.\n\nThe values provided must be parsable by the associated scales or in the internal format of the associated scales. For example, the `category` scale uses integers as an internal format, where each integer represents an index in the labels array; but, if parsing is enabled, it can also parse string labels.\n\n`null` can be used for skipped values.\n\n## Object[] using custom properties\n\n```javascript\nconst cfg = {\n  type: 'bar',\n  data: {\n    datasets: [{\n      data: [{id: 'Sales', nested: {value: 1500}}, {id: 'Purchases', nested: {value: 500}}]\n    }]\n  },\n  options: {\n    parsing: {\n      xAxisKey: 'id',\n      yAxisKey: 'nested.value'\n    }\n  }\n}\n```\n\nWhen using the pie/doughnut, radar or polarArea chart type, the `parsing` object should have a `key` item that points to the value to look at. In this example, the doughnut chart will show two items with values 1500 and 500.\n\n```javascript\nconst cfg = {\n  type: 'doughnut',\n  data: {\n    datasets: [{\n      data: [{id: 'Sales', nested: {value: 1500}}, {id: 'Purchases', nested: {value: 500}}]\n    }]\n  },\n  options: {\n    parsing: {\n      key: 'nested.value'\n    }\n  }\n}\n```\n\nIf the key contains a dot, it needs to be escaped with a double slash:\n\n```javascript\nconst cfg = {\n  type: 'line',\n  data: {\n    datasets: [{\n      data: [{'data.key': 'one', 'data.value': 20}, {'data.key': 'two', 'data.value': 30}]\n    }]\n  },\n  options: {\n    parsing: {\n      xAxisKey: 'data\\\\.key',\n      yAxisKey: 'data\\\\.value'\n    }\n  }\n}\n```\n\n:::warning\nWhen using object notation in a radar chart, you still need a `labels` array with labels for the chart to show correctly.\n:::\n\n## Object\n\n```javascript\nconst cfg = {\n  type: 'line',\n  data: {\n    datasets: [{\n      data: {\n        January: 10,\n        February: 20\n      }\n    }]\n  }\n}\n```\n\nIn this mode, the property name is used for the `index` scale and value for the `value` scale. For vertical charts, the index scale is `x` and value scale is `y`.\n\n## Dataset Configuration\n\n| Name | Type | Description\n| ---- | ---- | -----------\n| `label` | `string` | The label for the dataset which appears in the legend and tooltips.\n| `clip` | `number`\\|`object` | How to clip relative to chartArea. Positive value allows overflow, negative value clips that many pixels inside chartArea. 0 = clip at chartArea. Clipping can also be configured per side: clip: {left: 5, top: false, right: -2, bottom: 0}\n| `order` | `number` | The drawing order of dataset. Also affects order for stacking, tooltip and legend.\n| `stack` | `string` | The ID of the group to which this dataset belongs to (when stacked, each group will be a separate stack). Defaults to dataset `type`.\n| `parsing` | `boolean`\\|`object` | How to parse the dataset. The parsing can be disabled by specifying parsing: false at chart options or dataset. If parsing is disabled, data must be sorted and in the formats the associated chart type and scales use internally.\n| `hidden`  | `boolean` | Configure the visibility of the dataset. Using `hidden: true` will hide the dataset from being rendered in the Chart.\n\n### parsing\n\n```javascript\nconst data = [{x: 'Jan', net: 100, cogs: 50, gm: 50}, {x: 'Feb', net: 120, cogs: 55, gm: 75}];\nconst cfg = {\n  type: 'bar',\n  data: {\n    labels: ['Jan', 'Feb'],\n    datasets: [{\n      label: 'Net sales',\n      data: data,\n      parsing: {\n        yAxisKey: 'net'\n      }\n    }, {\n      label: 'Cost of goods sold',\n      data: data,\n      parsing: {\n        yAxisKey: 'cogs'\n      }\n    }, {\n      label: 'Gross margin',\n      data: data,\n      parsing: {\n        yAxisKey: 'gm'\n      }\n    }]\n  },\n};\n```\n\n## TypeScript\n\nWhen using TypeScript, if you want to use a data structure that is not the default data structure, you will need to pass it to the type interface when instantiating the data variable.\n\n```ts\nimport {ChartData} from 'chart.js';\n\nconst datasets: ChartData <'bar', {key: string, value: number} []> = {\n  datasets: [{\n    data: [{key: 'Sales', value: 20}, {key: 'Revenue', value: 10}],\n    parsing: {\n      xAxisKey: 'key',\n      yAxisKey: 'value'\n    }\n  }],\n};\n```\n"
  },
  {
    "path": "docs/general/fonts.md",
    "content": "# Fonts\n\nThere are special global settings that can change all the fonts on the chart. These options are in `Chart.defaults.font`. The global font settings only apply when more specific options are not included in the config.\n\nFor example, in this chart, the text will have a font size of 16px except for the labels in the legend.\n\n```javascript\nChart.defaults.font.size = 16;\nlet chart = new Chart(ctx, {\n    type: 'line',\n    data: data,\n    options: {\n        plugins: {\n            legend: {\n                labels: {\n                    // This more specific font property overrides the global property\n                    font: {\n                        size: 14\n                    }\n                }\n            }\n        }\n    }\n});\n```\n\n| Name | Type | Default | Description\n| ---- | ---- | ------- | -----------\n| `family` | `string` | `\"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif\"` | Default font family for all text, follows CSS font-family options.\n| `size` | `number` | `12` | Default font size (in px) for text. Does not apply to radialLinear scale point labels.\n| `style` | `string` | `'normal'` | Default font style. Does not apply to tooltip title or footer. Does not apply to chart title. Follows CSS font-style options (i.e. normal, italic, oblique, initial, inherit).\n| `weight` | `normal` \\| `bold` \\| `lighter` \\| `bolder` \\| `number` | `undefined` | Default font weight (boldness). (see [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight)).\n| `lineHeight` | `number`\\|`string` | `1.2` | Height of an individual line of text (see [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/line-height)).\n\n## Missing Fonts\n\nIf a font is specified for a chart that does exist on the system, the browser will not apply the font when it is set. If you notice odd fonts appearing in your charts, check that the font you are applying exists on your system. See [issue 3318](https://github.com/chartjs/Chart.js/issues/3318) for more details.\n\n## Loading Fonts\n\nIf a font is not cached and needs to be loaded, charts that use the font will need to be updated once the font is loaded. This can be accomplished using the [Font Loading APIs](https://developer.mozilla.org/en-US/docs/Web/API/CSS_Font_Loading_API). See [issue 8020](https://github.com/chartjs/Chart.js/issues/8020) for more details.\n"
  },
  {
    "path": "docs/general/options.md",
    "content": "# Options\n\n## Option resolution\n\nOptions are resolved from top to bottom, using a context dependent route.\n\n### Chart level options\n\n* options\n* overrides[`config.type`]\n* defaults\n\n### Dataset level options\n\n`dataset.type` defaults to `config.type`, if not specified.\n\n* dataset\n* options.datasets[`dataset.type`]\n* options\n* overrides[`config.type`].datasets[`dataset.type`]\n* defaults.datasets[`dataset.type`]\n* defaults\n\n### Dataset animation options\n\n* dataset.animation\n* options.datasets[`dataset.type`].animation\n* options.animation\n* overrides[`config.type`].datasets[`dataset.type`].animation\n* defaults.datasets[`dataset.type`].animation\n* defaults.animation\n\n### Dataset element level options\n\nEach scope is looked up with `elementType` prefix in the option name first, then without the prefix. For example, `radius` for `point` element is looked up using `pointRadius` and if that does not hit, then `radius`.\n\n* dataset\n* options.datasets[`dataset.type`]\n* options.datasets[`dataset.type`].elements[`elementType`]\n* options.elements[`elementType`]\n* options\n* overrides[`config.type`].datasets[`dataset.type`]\n* overrides[`config.type`].datasets[`dataset.type`].elements[`elementType`]\n* defaults.datasets[`dataset.type`]\n* defaults.datasets[`dataset.type`].elements[`elementType`]\n* defaults.elements[`elementType`]\n* defaults\n\n### Scale options\n\n* options.scales\n* overrides[`config.type`].scales\n* defaults.scales\n* defaults.scale\n\n### Plugin options\n\nA plugin can provide `additionalOptionScopes` array of paths to additionally look for its options in. For root scope, use empty string: `''`. Most core plugins also take options from root scope.\n\n* options.plugins[`plugin.id`]\n* (options.[`...plugin.additionalOptionScopes`])\n* overrides[`config.type`].plugins[`plugin.id`]\n* defaults.plugins[`plugin.id`]\n* (defaults.[`...plugin.additionalOptionScopes`])\n\n## Scriptable Options\n\nScriptable options also accept a function which is called for each of the underlying data values and that takes the unique argument `context` representing contextual information (see [option context](options.md#option-context)).\nA resolver is passed as second parameter, that can be used to access other options in the same context.\n\n:::tip Note\n\nThe `context` argument should be validated in the scriptable function, because the function can be invoked in different contexts. The `type` field is a good candidate for this validation.\n\n:::\n\nExample:\n\n```javascript\ncolor: function(context) {\n    const index = context.dataIndex;\n    const value = context.dataset.data[index];\n    return value < 0 ? 'red' :  // draw negative values in red\n        index % 2 ? 'blue' :    // else, alternate values in blue and green\n        'green';\n},\nborderColor: function(context, options) {\n    const color = options.color; // resolve the value of another scriptable option: 'red', 'blue' or 'green'\n    return Chart.helpers.color(color).lighten(0.2);\n}\n```\n\n## Indexable Options\n\nIndexable options also accept an array in which each item corresponds to the element at the same index. Note that if there are less items than data, the items are looped over. In many cases, using a [function](#scriptable-options) is more appropriate if supported.\n\nExample:\n\n```javascript\ncolor: [\n    'red',    // color for data at index 0\n    'blue',   // color for data at index 1\n    'green',  // color for data at index 2\n    'black',  // color for data at index 3\n    //...\n]\n```\n\n## Option Context\n\nThe option context is used to give contextual information when resolving options and currently only applies to [scriptable options](#scriptable-options).\nThe object is preserved, so it can be used to store and pass information between calls.\n\nThere are multiple levels of context objects:\n\n* `chart`\n  * `dataset`\n    * `data`\n  * `scale`\n    * `tick`\n    * `pointLabel` (only used in the radial linear scale)\n  * `tooltip`\n\nEach level inherits its parent(s) and any contextual information stored in the parent is available through the child.\n\nThe context object contains the following properties:\n\n### chart\n\n* `chart`: the associated chart\n* `type`: `'chart'`\n\n### dataset\n\nIn addition to [chart](#chart)\n\n* `active`: true if an element is active (hovered)\n* `dataset`: dataset at index `datasetIndex`\n* `datasetIndex`: index of the current dataset\n* `index`: same as `datasetIndex`\n* `mode`: the update mode\n* `type`: `'dataset'`\n\n### data\n\nIn addition to [dataset](#dataset)\n\n* `active`: true if an element is active (hovered)\n* `dataIndex`: index of the current data\n* `parsed`: the parsed data values for the given `dataIndex` and `datasetIndex`\n* `raw`: the raw data values for the given `dataIndex` and `datasetIndex`\n* `element`: the element (point, arc, bar, etc.) for this data\n* `index`: same as `dataIndex`\n* `type`: `'data'`\n\n### scale\n\nIn addition to [chart](#chart)\n\n* `scale`: the associated scale\n* `type`: `'scale'`\n\n### tick\n\nIn addition to [scale](#scale)\n\n* `tick`: the associated tick object\n* `index`: tick index\n* `type`: `'tick'`\n\n### pointLabel\n\nIn addition to [scale](#scale)\n\n* `label`: the associated label value\n* `index`: label index\n* `type`: `'pointLabel'`\n\n### tooltip\n\nIn addition to [chart](#chart)\n\n* `tooltip`: the tooltip object\n* `tooltipItems`: the items the tooltip is displaying\n"
  },
  {
    "path": "docs/general/padding.md",
    "content": "# Padding\n\nPadding values in Chart options can be supplied in a couple of different formats.\n\n## Number\n\nIf this value is a number, it is applied to all sides (left, top, right, bottom).\n\nFor example, defining a 20px padding to all sides of the chart:\n\n```javascript\nlet chart = new Chart(ctx, {\n    type: 'line',\n    data: data,\n    options: {\n        layout: {\n            padding: 20\n        }\n    }\n});\n```\n\n## {top, left, bottom, right} object\n\nIf this value is an object, the `left` property defines the left padding. Similarly, the `right`, `top` and `bottom` properties can also be specified.\nOmitted properties default to `0`.\n\nLet's say you wanted to add 50px of padding to the left side of the chart canvas, you would do:\n\n```javascript\nlet chart = new Chart(ctx, {\n    type: 'line',\n    data: data,\n    options: {\n        layout: {\n            padding: {\n                left: 50\n            }\n        }\n    }\n});\n```\n\n## {x, y} object\n\nThis is a shorthand for defining left/right and top/bottom to the same values.\n\nFor example, 10px left / right and 4px top / bottom padding on a Radial Linear Axis [tick backdropPadding](../axes/radial/linear.md#linear-radial-axis-specific-tick-options):\n\n```javascript\nlet chart = new Chart(ctx, {\n    type: 'radar',\n    data: data,\n    options: {\n        scales: {\n          r: {\n            ticks: {\n              backdropPadding: {\n                  x: 10,\n                  y: 4\n              }\n            }\n        }\n    }\n});\n```\n"
  },
  {
    "path": "docs/general/performance.md",
    "content": "# Performance\n\nChart.js charts are rendered on `canvas` elements, which makes rendering quite fast. For large datasets or performance sensitive applications, you may wish to consider the tips below.\n\n## Data structure and format\n\n### Parsing\n\nProvide prepared data in the internal format accepted by the dataset and scales, and set `parsing: false`. See [Data structures](data-structures.md) for more information.\n\n### Data normalization\n\nChart.js is fastest if you provide data with indices that are unique, sorted, and consistent across datasets and provide the `normalized: true` option to let Chart.js know that you have done so. Even without this option, it can sometimes still be faster to provide sorted data.\n\n### Decimation\n\nDecimating your data will achieve the best results. When there is a lot of data to display on the graph, it doesn't make sense to show tens of thousands of data points on a graph that is only a few hundred pixels wide.\n\nThe [decimation plugin](../configuration/decimation.md) can be used with line charts to decimate data before the chart is rendered. This will provide the best performance since it will reduce the memory needed to render the chart.\n\nLine charts are able to do [automatic data decimation during draw](#automatic-data-decimation-during-draw), when certain conditions are met. You should still consider decimating data yourself before passing it in for maximum performance since the automatic decimation occurs late in the chart life cycle.\n\n## Tick Calculation\n\n### Rotation\n\n[Specify a rotation value](../axes/cartesian/index.md#tick-configuration) by setting `minRotation` and `maxRotation` to the same value, which avoids the chart from having to automatically determine a value to use.\n\n### Sampling\n\nSet the [`ticks.sampleSize`](../axes/cartesian/index.md#tick-configuration) option. This will determine how large your labels are by looking at only a subset of them in order to render axes more quickly. This works best if there is not a large variance in the size of your labels.\n\n## Disable Animations\n\nIf your charts have long render times, it is a good idea to disable animations. Doing so will mean that the chart needs to only be rendered once during an update instead of multiple times. This will have the effect of reducing CPU usage and improving general page performance.\nLine charts use Path2D caching when animations are disabled and Path2D is available.\n\nTo disable animations\n\n```javascript\nnew Chart(ctx, {\n    type: 'line',\n    data: data,\n    options: {\n        animation: false\n    }\n});\n```\n\n## Specify `min` and `max` for scales\n\nIf you specify the `min` and `max`, the scale does not have to compute the range from the data.\n\n```javascript\nnew Chart(ctx, {\n    type: 'line',\n    data: data,\n    options: {\n        scales: {\n            x: {\n                type: 'time',\n                min: new Date('2019-01-01').valueOf(),\n                max: new Date('2019-12-31').valueOf()\n            },\n            y: {\n                type: 'linear',\n                min: 0,\n                max: 100\n            }\n        }\n    }\n});\n```\n\n## Parallel rendering with web workers\n\nAs of 2023, modern browser have the ability to [transfer rendering control of a canvas](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/transferControlToOffscreen) to a web worker. Web workers can use the [OffscreenCanvas API](https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas) to render from a web worker onto canvases in the DOM. Chart.js is a canvas-based library and supports rendering in a web worker - just pass an OffscreenCanvas into the Chart constructor instead of a Canvas element.\n\nBy moving all Chart.js calculations onto a separate thread, the main thread can be freed up for other uses. Some tips and tricks when using Chart.js in a web worker:\n\n* Transferring data between threads can be expensive, so ensure that your config and data objects are as small as possible. Try generating them on the worker side if you can (workers can make HTTP requests!) or passing them to your worker as ArrayBuffers, which can be transferred quickly from one thread to another.\n* You can't transfer functions between threads, so if your config object includes functions you'll have to strip them out before transferring and then add them back later.\n* You can't access the DOM from worker threads, so Chart.js plugins that use the DOM (including any mouse interactions) will likely not work.\n* Ensure that you have a fallback if you support older browsers.\n* Resizing the chart must be done manually. See an example in the worker code below.\n\nExample main thread code:\n\n```javascript\nconst config = {};\nconst canvas = new HTMLCanvasElement();\nconst offscreenCanvas = canvas.transferControlToOffscreen();\n\nconst worker = new Worker('worker.js');\nworker.postMessage({canvas: offscreenCanvas, config}, [offscreenCanvas]);\n```\n\nExample worker code, in `worker.js`:\n\n```javascript\nonmessage = function(event) {\n    const {canvas, config} = event.data;\n    const chart = new Chart(canvas, config);\n\n    // Resizing the chart must be done manually, since OffscreenCanvas does not include event listeners.\n    canvas.width = 100;\n    canvas.height = 100;\n    chart.resize();\n};\n```\n\n## Line Charts\n\n### Leave Bézier curves disabled\n\nIf you are drawing lines on your chart, disabling Bézier curves will improve render times since drawing a straight line is more performant than a Bézier curve. Bézier curves are disabled by default.\n\n### Automatic data decimation during draw\n\nLine element will automatically decimate data, when `tension`, `stepped`, and `borderDash` are left set to their default values (`false`, `0`, and `[]` respectively). This improves rendering speed by skipping drawing of invisible line segments.\n\n### Enable spanGaps\n\nIf you have a lot of data points, it can be more performant to enable `spanGaps`. This disables segmentation of the line, which can be an unneeded step.\n\nTo enable `spanGaps`:\n\n```javascript\nnew Chart(ctx, {\n    type: 'line',\n    data: {\n        datasets: [{\n            spanGaps: true // enable for a single dataset\n        }]\n    },\n    options: {\n        spanGaps: true // enable for all datasets\n    }\n});\n```\n\n### Disable Line Drawing\n\nIf you have a lot of data points, it can be more performant to disable rendering of the line for a dataset and only draw points. Doing this means that there is less to draw on the canvas which will improve render performance.\n\nTo disable lines:\n\n```javascript\nnew Chart(ctx, {\n    type: 'line',\n    data: {\n        datasets: [{\n            showLine: false // disable for a single dataset\n        }]\n    },\n    options: {\n        showLine: false // disable for all datasets\n    }\n});\n```\n\n### Disable Point Drawing\n\nIf you have a lot of data points, it can be more performant to disable rendering of the points for a dataset and only draw lines. Doing this means that there is less to draw on the canvas which will improve render performance.\n\nTo disable point drawing:\n\n```javascript\nnew Chart(ctx, {\n    type: 'line',\n    data: {\n        datasets: [{\n            pointRadius: 0 // disable for a single dataset\n        }]\n    },\n    options: {\n        datasets: {\n            line: {\n                pointRadius: 0 // disable for all `'line'` datasets\n            }\n        },\n        elements: {\n            point: {\n                radius: 0 // default to disabled in all datasets\n            }\n        }\n    }\n});\n```\n\n## When transpiling with Babel, consider using `loose` mode\n\nBabel 7.9 changed the way classes are constructed. It is slow, unless used with `loose` mode.\n[More information](https://github.com/babel/babel/issues/11356)\n"
  },
  {
    "path": "docs/getting-started/index.md",
    "content": "# Getting Started\n\nLet's get started with Chart.js!\n\n* **[Follow a step-by-step guide](./usage) to get up to speed with Chart.js**\n* [Install Chart.js](./installation) from npm or a CDN\n* [Integrate Chart.js](./integration) with bundlers, loaders, and front-end frameworks\n* [Use Chart.js from Node.js](./using-from-node-js)\n\nAlternatively, see the example below or check [samples](../samples).\n\n## Create a Chart\n\nIn this example, we create a bar chart for a single dataset and render it on an HTML page. Add this code snippet to your page:\n\n```html\n<div>\n  <canvas id=\"myChart\"></canvas>\n</div>\n\n<script src=\"https://cdn.jsdelivr.net/npm/chart.js\"></script>\n\n<script>\n  const ctx = document.getElementById('myChart');\n\n  new Chart(ctx, {\n    type: 'bar',\n    data: {\n      labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],\n      datasets: [{\n        label: '# of Votes',\n        data: [12, 19, 3, 5, 2, 3],\n        borderWidth: 1\n      }]\n    },\n    options: {\n      scales: {\n        y: {\n          beginAtZero: true\n        }\n      }\n    }\n  });\n</script>\n```\n\nYou should get a chart like this:\n\n![demo](./preview.png)\n\nLet's break this code down.\n\nFirst, we need to have a canvas in our page. It's recommended to give the chart its own container for [responsiveness](../configuration/responsive.md).\n\n```html\n<div>\n  <canvas id=\"myChart\"></canvas>\n</div>\n```\n\nNow that we have a canvas, we can include Chart.js from a CDN.\n\n```html\n<script src=\"https://cdn.jsdelivr.net/npm/chart.js\"></script>\n```\n\nFinally, we can create a chart. We add a script that acquires the `myChart` canvas element and instantiates `new Chart` with desired configuration: `bar` chart type, labels, data points, and options.\n\n```html\n<script>\n  const ctx = document.getElementById('myChart');\n\n  new Chart(ctx, {\n    type: 'bar',\n    data: {\n      labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],\n      datasets: [{\n        label: '# of Votes',\n        data: [12, 19, 3, 5, 2, 3],\n        borderWidth: 1\n      }]\n    },\n    options: {\n      scales: {\n        y: {\n          beginAtZero: true\n        }\n      }\n    }\n  });\n</script>\n```\n\nYou can see all the ways to use Chart.js in the [step-by-step guide](./usage).\n"
  },
  {
    "path": "docs/getting-started/installation.md",
    "content": "# Installation\n\n## npm\n\n[![npm](https://img.shields.io/npm/v/chart.js.svg?style=flat-square&maxAge=600)](https://npmjs.com/package/chart.js)\n[![npm](https://img.shields.io/npm/dm/chart.js.svg?style=flat-square&maxAge=600)](https://npmjs.com/package/chart.js)\n\n```sh\nnpm install chart.js\n```\n\n## CDN\n\n### CDNJS\n\n[![cdnjs](https://img.shields.io/cdnjs/v/Chart.js.svg?style=flat-square&maxAge=600)](https://cdnjs.com/libraries/Chart.js)\n\nChart.js built files are available on [CDNJS](https://cdnjs.com/):\n\n<https://cdnjs.com/libraries/Chart.js>\n\n### jsDelivr\n\n[![jsdelivr](https://img.shields.io/npm/v/chart.js.svg?label=jsdelivr&style=flat-square&maxAge=600)](https://cdn.jsdelivr.net/npm/chart.js@latest/dist/) [![jsdelivr hits](https://data.jsdelivr.com/v1/package/npm/chart.js/badge)](https://www.jsdelivr.com/package/npm/chart.js)\n\nChart.js built files are also available through [jsDelivr](https://www.jsdelivr.com/):\n\n<https://www.jsdelivr.com/package/npm/chart.js?path=dist>\n\n## GitHub\n\n[![github](https://img.shields.io/github/release/chartjs/Chart.js.svg?style=flat-square&maxAge=600)](https://github.com/chartjs/Chart.js/releases/latest)\n\nYou can download the latest version of [Chart.js on GitHub](https://github.com/chartjs/Chart.js/releases/latest).\n\nIf you download or clone the repository, you must [build](../developers/contributing.md#building-and-testing) Chart.js to generate the dist files. Chart.js no longer comes with prebuilt release versions, so an alternative option to downloading the repo is **strongly** advised.\n"
  },
  {
    "path": "docs/getting-started/integration.md",
    "content": "# Integration\n\nChart.js can be integrated with plain JavaScript or with different module loaders. The examples below show how to load Chart.js in different systems.\n\nIf you're using a front-end framework (e.g., React, Angular, or Vue), please see [available integrations](https://github.com/chartjs/awesome#integrations).\n\n## Script Tag\n\n```html\n<script src=\"path/to/chartjs/dist/chart.umd.min.js\"></script>\n<script>\n    const myChart = new Chart(ctx, {...});\n</script>\n```\n\n## Bundlers (Webpack, Rollup, etc.)\n\nChart.js is tree-shakeable, so it is necessary to import and register the controllers, elements, scales and plugins you are going to use.\n\n### Quick start\n\nIf you don't care about the bundle size, you can use the `auto` package ensuring all features are available:\n\n```javascript\nimport Chart from 'chart.js/auto';\n```\n\n### Bundle optimization\n\nWhen optimizing the bundle, you need to import and register the components that are needed in your application.\n\nThe options are categorized into controllers, elements, plugins, scales. You can pick and choose many of these, e.g. if you are not going to use tooltips, don't import and register the `Tooltip` plugin. But each type of chart has its own bare-minimum requirements (typically the type's controller, element(s) used by that controller and scale(s)):\n\n* Bar chart\n  * `BarController`\n  * `BarElement`\n  * Default scales: `CategoryScale` (x), `LinearScale` (y)\n* Bubble chart\n  * `BubbleController`\n  * `PointElement`\n  * Default scales: `LinearScale` (x/y)\n* Doughnut chart\n  * `DoughnutController`\n  * `ArcElement`\n  * Not using scales\n* Line chart\n  * `LineController`\n  * `LineElement`\n  * `PointElement`\n  * Default scales: `CategoryScale` (x), `LinearScale` (y)\n* Pie chart\n  * `PieController`\n  * `ArcElement`\n  * Not using scales\n* PolarArea chart\n  * `PolarAreaController`\n  * `ArcElement`\n  * Default scale: `RadialLinearScale` (r)\n* Radar chart\n  * `RadarController`\n  * `LineElement`\n  * `PointElement`\n  * Default scale: `RadialLinearScale` (r)\n* Scatter chart\n  * `ScatterController`\n  * `PointElement`\n  * Default scales: `LinearScale` (x/y)\n\nAvailable plugins:\n\n* [`Decimation`](../configuration/decimation.md)\n* `Filler` - used to fill area described by `LineElement`, see [Area charts](../charts/area.md)\n* [`Legend`](../configuration/legend.md)\n* [`SubTitle`](../configuration/subtitle.md)\n* [`Title`](../configuration/title.md)\n* [`Tooltip`](../configuration/tooltip.md)\n\nAvailable scales:\n\n* Cartesian scales (x/y)\n  * [`CategoryScale`](../axes/cartesian/category.md)\n  * [`LinearScale`](../axes/cartesian/linear.md)\n  * [`LogarithmicScale`](../axes/cartesian/logarithmic.md)\n  * [`TimeScale`](../axes/cartesian/time.md)\n  * [`TimeSeriesScale`](../axes/cartesian/timeseries.md)\n\n* Radial scales (r)\n  * [`RadialLinearScale`](../axes/radial/linear.md)\n\n### Helper functions\n\nIf you want to use the helper functions, you will need to import these separately from the helpers package and use them as stand-alone functions.\n\nExample of [Converting Events to Data Values](../configuration/interactions.md#converting-events-to-data-values) using bundlers.\n\n```javascript\nimport Chart from 'chart.js/auto';\nimport { getRelativePosition } from 'chart.js/helpers';\n\nconst chart = new Chart(ctx, {\n  type: 'line',\n  data: data,\n  options: {\n    onClick: (e) => {\n      const canvasPosition = getRelativePosition(e, chart);\n\n      // Substitute the appropriate scale IDs\n      const dataX = chart.scales.x.getValueForPixel(canvasPosition.x);\n      const dataY = chart.scales.y.getValueForPixel(canvasPosition.y);\n    }\n  }\n});\n```\n\n## CommonJS\n\nBecause Chart.js is an ESM library, in CommonJS modules you should use a dynamic `import`:\n\n```javascript\nconst { Chart } = await import('chart.js');\n```\n\n## RequireJS\n\n**Important:** RequireJS can load only [AMD modules](https://requirejs.org/docs/whyamd.html), so be sure to require one of the UMD builds instead (i.e. `dist/chart.umd.min.js`).\n\n```javascript\nrequire(['path/to/chartjs/dist/chart.umd.min.js'], function(Chart){\n    const myChart = new Chart(ctx, {...});\n});\n```\n\n:::tip Note\n\nIn order to use the time scale, you need to make sure [one of the available date adapters](https://github.com/chartjs/awesome#adapters) and corresponding date library are fully loaded **after** requiring Chart.js. For this you can use nested requires:\n\n```javascript\nrequire(['chartjs'], function(Chart) {\n    require(['moment'], function() {\n        require(['chartjs-adapter-moment'], function() {\n            new Chart(ctx, {...});\n        });\n    });\n});\n```\n:::"
  },
  {
    "path": "docs/getting-started/usage.md",
    "content": "# Step-by-step guide\n\nFollow this guide to get familiar with all major concepts of Chart.js: chart types and elements, datasets, customization, plugins, components, and tree-shaking. Don't hesitate to follow the links in the text.\n\nWe'll build a Chart.js data visualization with a couple of charts from scratch:\n\n![result](./usage-8.png)\n\n## Build a new application with Chart.js\n\nIn a new folder, create the `package.json` file with the following contents:\n\n```json\n{\n  \"name\": \"chartjs-example\",\n  \"version\": \"1.0.0\",\n  \"license\": \"MIT\",\n  \"scripts\": {\n    \"dev\": \"parcel src/index.html\",\n    \"build\": \"parcel build src/index.html\"\n  },\n  \"devDependencies\": {\n    \"parcel\": \"^2.6.2\"\n  },\n  \"dependencies\": {\n    \"@cubejs-client/core\": \"^0.31.0\",\n    \"chart.js\": \"^4.0.0\"\n  }\n}\n```\n\nModern front-end applications often use JavaScript module bundlers, so we’ve picked [Parcel](https://parceljs.org) as a nice zero-configuration build tool. We’re also installing Chart.js v4 and a JavaScript client for [Cube](https://cube.dev/?ref=eco-chartjs), an open-source API for data apps we’ll use to fetch real-world data (more on that later).\n\nRun `npm install`, `yarn install`, or `pnpm install` to install the dependencies, then create the `src` folder. Inside that folder, we’ll need a very simple `index.html` file:\n\n```html\n<!doctype html>\n<html lang=\"en\">\n  <head>\n    <title>Chart.js example</title>\n  </head>\n  <body>\n    <!-- <div style=\"width: 500px;\"><canvas id=\"dimensions\"></canvas></div><br/> -->\n    <div style=\"width: 800px;\"><canvas id=\"acquisitions\"></canvas></div>\n\n    <!-- <script type=\"module\" src=\"dimensions.js\"></script> -->\n    <script type=\"module\" src=\"acquisitions.js\"></script>\n  </body>\n</html>\n```\n\nAs you can see, Chart.js requires minimal markup: a `canvas` tag with an `id` by which we’ll reference the chart later. By default, Chart.js charts are [responsive](../configuration/responsive.md) and take the whole enclosing container. So, we set the width of the `div` to control chart width.\n\nLastly, let’s create the `src/acquisitions.js` file with the following contents:\n\n```jsx\nimport Chart from 'chart.js/auto'\n\n(async function() {\n  const data = [\n    { year: 2010, count: 10 },\n    { year: 2011, count: 20 },\n    { year: 2012, count: 15 },\n    { year: 2013, count: 25 },\n    { year: 2014, count: 22 },\n    { year: 2015, count: 30 },\n    { year: 2016, count: 28 },\n  ];\n\n  new Chart(\n    document.getElementById('acquisitions'),\n    {\n      type: 'bar',\n      data: {\n        labels: data.map(row => row.year),\n        datasets: [\n          {\n            label: 'Acquisitions by year',\n            data: data.map(row => row.count)\n          }\n        ]\n      }\n    }\n  );\n})();\n```\n\nLet’s walk through this code:\n\n- We import `Chart`, the main Chart.js class, from the special `chart.js/auto` path. It loads [all available Chart.js components](./integration) (which is very convenient) but disallows tree-shaking. We’ll address that later.\n- We instantiate a new `Chart` instance and provide two arguments: the canvas element where the chart would be rendered and the options object.\n- We just need to provide a chart type (`bar`) and provide `data` which consists of `labels` (often, numeric or textual descriptions of data points) and an array of `datasets` (Chart.js supports multiple datasets for most chart types). Each dataset is designated with a `label` and contains an array of data points.\n- For now, we only have a few entries of dummy data. So, we extract `year` and `count` properties to produce the arrays of `labels` and data points within the only dataset.\n\nTime to run the example with `npm run dev`, `yarn dev`, or `pnpm dev` and navigate to [localhost:1234](http://localhost:1234) in your web browser:\n\n![result](./usage-1.png)\n\nWith just a few lines of code, we’ve got a chart with a lot of features: a [legend](../configuration/legend.md), [grid lines](../samples/scale-options/grid.md), [ticks](../samples/scale-options/ticks.md), and [tooltips](../configuration/tooltip.md) shown on hover. Refresh the web page a few times to see that the chart is also [animated](../configuration/animations.md#animations). Try clicking on the “Acquisitions by year” label to see that you’re also able to toggle datasets visibility (especially useful when you have multiple datasets). \n\n### Simple customizations\n\nLet’s see how Chart.js charts can be customized. First, let’s turn off the animations so the chart appears instantly. Second, let’s hide the legend and tooltips since we have only one dataset and pretty trivial data.\n\nReplace the `new Chart(...);` invocation in `src/acquisitions.js` with the following snippet:\n\n```jsx\n  new Chart(\n    document.getElementById('acquisitions'),\n    {\n      type: 'bar',\n      options: {\n        animation: false,\n        plugins: {\n          legend: {\n            display: false\n          },\n          tooltip: {\n            enabled: false\n          }\n        }\n      },\n      data: {\n        labels: data.map(row => row.year),\n        datasets: [\n          {\n            label: 'Acquisitions by year',\n            data: data.map(row => row.count)\n          }\n        ]\n      }\n    }\n  );\n```\n\nAs you can see, we’ve added the `options` property to the second argument—that’s how you can specify all kinds of customization options for Chart.js. The [animation is disabled](../configuration/animations.md#disabling-animation) with a boolean flag provided via `animation`. Most chart-wide options (e.g., [responsiveness](../configuration/responsive.md) or [device pixel ratio](../configuration/device-pixel-ratio.md)) are configured like this.\n\nThe legend and tooltips are hidden with boolean flags provided under the respective sections in `plugins`. Note that some of Chart.js features are extracted into plugins: self-contained, separate pieces of code. A few of them are available as a part of [Chart.js distribution](https://github.com/chartjs/Chart.js/tree/master/src/plugins), other plugins are maintained independently and can be located in the [awesome list](https://github.com/chartjs/awesome) of plugins, framework integrations, and additional chart types.\n\nYou should be able to see the updated minimalistic chart in your browser.\n\n### Real-world data\n\nWith hardcoded, limited-size, unrealistic data, it’s hard to show the full potential of Chart.js. Let’s quickly connect to a data API to make our example application closer to a production use case.\n\nLet’s create the `src/api.js` file with the following contents:\n\n```jsx\nimport { CubejsApi } from '@cubejs-client/core';\n\nconst apiUrl = 'https://heavy-lansford.gcp-us-central1.cubecloudapp.dev/cubejs-api/v1';\nconst cubeToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjEwMDAwMDAwMDAsImV4cCI6NTAwMDAwMDAwMH0.OHZOpOBVKr-sCwn8sbZ5UFsqI3uCs6e4omT7P6WVMFw';\n\nconst cubeApi = new CubejsApi(cubeToken, { apiUrl });\n\nexport async function getAquisitionsByYear() {\n  const acquisitionsByYearQuery = {\n    dimensions: [\n      'Artworks.yearAcquired',\n    ],\n    measures: [\n      'Artworks.count'\n    ],\n    filters: [ {\n      member: 'Artworks.yearAcquired',\n      operator: 'set'\n    } ],\n    order: {\n      'Artworks.yearAcquired': 'asc'\n    }\n  };\n\n  const resultSet = await cubeApi.load(acquisitionsByYearQuery);\n\n  return resultSet.tablePivot().map(row => ({\n    year: parseInt(row['Artworks.yearAcquired']),\n    count: parseInt(row['Artworks.count'])\n  }));\n}\n\nexport async function getDimensions() {\n  const dimensionsQuery = {\n    dimensions: [\n      'Artworks.widthCm',\n      'Artworks.heightCm'\n    ],\n    measures: [\n      'Artworks.count'\n    ],\n    filters: [\n      {\n        member: 'Artworks.classification',\n        operator: 'equals',\n        values: [ 'Painting' ]\n      },\n      {\n        member: 'Artworks.widthCm',\n        operator: 'set'\n      },\n      {\n        member: 'Artworks.widthCm',\n        operator: 'lt',\n        values: [ '500' ]\n      },\n      {\n        member: 'Artworks.heightCm',\n        operator: 'set'\n      },\n      {\n        member: 'Artworks.heightCm',\n        operator: 'lt',\n        values: [ '500' ]\n      }\n    ]\n  };\n\n  const resultSet = await cubeApi.load(dimensionsQuery);\n\n  return resultSet.tablePivot().map(row => ({\n    width: parseInt(row['Artworks.widthCm']),\n    height: parseInt(row['Artworks.heightCm']),\n    count: parseInt(row['Artworks.count'])\n  }));\n}\n```\n\nLet’s see what’s happening there:\n\n- We `import` the JavaScript client library for [Cube](https://cube.dev/?ref=eco-chartjs), an open-source API for data apps, configure it with the API URL (`apiUrl`) and the authentication token (`cubeToken`), and finally instantiate the client (`cubeApi`).\n- Cube API is hosted in [Cube Cloud](https://cube.dev/cloud/?ref=eco-chartjs) and connected to a database with a [public dataset](https://github.com/MuseumofModernArt/collection) of ~140,000 records representing all of the artworks in the collection of the [Museum of Modern Art](https://www.moma.org) in New York, USA. Certainly, a more real-world dataset than what we’ve got now.\n- We define a couple of asynchronous functions to fetch data from the API: `getAquisitionsByYear` and `getDimensions`. The first one returns the number of artworks by the year of acquisition, the other returns the number of artworks for every width-height pair (we’ll need it for another chart).\n- Let’s take a look at `getAquisitionsByYear`. First, we create a declarative, JSON-based query in the `acquisitionsByYearQuery` variable. As you can see, we specify that for every `yearAcquired` we’d like to get the `count` of artworks; `yearAcquired` has to be set (i.e., not undefined); the result set would be sorted by `yearAcquired` in the ascending order.\n- Second, we fetch the `resultSet` by calling `cubeApi.load` and map it to an array of objects with desired `year` and `count` properties.\n\nNow, let’s deliver the real-world data to our chart. Please apply a couple of changes to `src/acquisitions.js`: add an import and replace the definition of the `data` variable.\n\n```jsx\nimport { getAquisitionsByYear } from './api'\n\n// ...\n\nconst data = await getAquisitionsByYear();\n```\n\nDone! Now, our chart with real-world data looks like this. Looks like something interesting happened in 1964, 1968, and 2008!\n\n![result](./usage-2.png)\n\nWe’re done with the bar chart. Let’s try another Chart.js chart type.\n\n### Further customizations\n\nChart.js supports many common chart types.\n\nFor instance, [Bubble chart](../charts/bubble.md) allows to display three dimensions of data at the same time: locations on `x` and `y` axes represent two dimensions, and the third dimension is represented by the size of the individual bubbles.\n\nTo create the chart, stop the already running application, then go to `src/index.html`, and uncomment the following two lines:\n\n```html\n<div style=\"width: 500px;\"><canvas id=\"dimensions\"></canvas></div><br/>\n\n<script type=\"module\" src=\"dimensions.js\"></script>\n```\n\nThen, create the `src/dimensions.js` file with the following contents:\n\n```jsx\nimport Chart from 'chart.js/auto'\nimport { getDimensions } from './api'\n\n(async function() {\n  const data = await getDimensions();\n\n  new Chart(\n    document.getElementById('dimensions'),\n    {\n      type: 'bubble',\n      data: {\n        labels: data.map(x => x.year),\n        datasets: [\n          {\n            label: 'Dimensions',\n            data: data.map(row => ({\n              x: row.width,\n              y: row.height,\n              r: row.count\n            }))\n          }\n        ]\n      }\n    }\n  );\n})();\n```\n\nProbably, everything is pretty straightforward there: we get data from the API and render a new chart with the `bubble` type, passing three dimensions of data as `x`, `y`, and `r` (radius) properties.\n\nNow, reset caches with `rm -rf .parcel-cache` and start the application again with `npm run dev`, `yarn dev`, or `pnpm dev`. We can review the new chart now:\n\n![result](./usage-3.png)\n\nWell, it doesn’t look pretty.\n\nFirst of all, the chart is not square. Artworks’ width and height are equally important so we’d like to make the chart width equal to its height as well. By default, Chart.js charts have the [aspect ratio](../configuration/responsive.md) of either 1 (for all radial charts, e.g., a doughnut chart) or 2 (for all the rest). Let’s modify the aspect ratio for our chart:\n\n```jsx\n// ...\n\n\tnew Chart(\n    document.getElementById('dimensions'),\n    {\n      type: 'bubble',\n      options: {\n        aspectRatio: 1,\n      },\n\n// ...\n```\n\nLooks much better now:\n\n![result](./usage-4.png)\n\nHowever, it’s still not ideal. The horizontal axis spans from 0 to 500 while the vertical axis spans from 0 to 450. By default, Chart.js automatically adjusts the range (minimum and maximum values) of the axes to the values provided in the dataset, so the chart “fits” your data. Apparently, MoMa collection doesn’t have artworks in the range of 450 to 500 cm in height. Let’s modify the [axes configuration](../axes/) for our chart to account for that:\n\n```jsx\n// ...\n\n  new Chart(\n    document.getElementById('dimensions'),\n    {\n      type: 'bubble',\n      options: {\n        aspectRatio: 1,\n        scales: {\n          x: {\n            max: 500\n          },\n          y: {\n            max: 500\n          }\n        }\n      },\n\n// ...\n```\n\nGreat! Behold the updated chart:\n\n![result](./usage-5.png)\n\nHowever, there’s one more nitpick: what are these numbers? It’s not very obvious that the units are centimetres. Let’s apply a [custom tick format](../axes/labelling.md#creating-custom-tick-formats) to both axes to make things clear. We’ll provide a callback function that would be called to format each tick value. Here’s the updated axes configuration:\n\n```jsx\n// ...\n\n  new Chart(\n    document.getElementById('dimensions'),\n    {\n      type: 'bubble',\n      options: {\n        aspectRatio: 1,\n        scales: {\n          x: {\n            max: 500,\n            ticks: {\n              callback: value => `${value / 100} m`\n            }\n          },\n          y: {\n            max: 500,\n            ticks: {\n              callback: value => `${value / 100} m`\n            }\n          }\n        }\n      },\n\n// ...\n```\n\nPerfect, now we have proper units on both axes:\n\n![result](./usage-6.png)\n\n### Multiple datasets\n\nChart.js plots each dataset independently and allows to apply custom styles to them.\n\nTake a look at the chart: there’s a visible “line” of bubbles with equal `x` and `y` coordinates representing square artworks. It would be cool to put these bubbles in their own dataset and paint them differently. Also, we can separate “taller” artworks from “wider” ones and paint them differently, too. \n\nHere’s how we can do that. Replace the `datasets` with the following code:\n\n```jsx\n// ...\n\n        datasets: [\n          {\n            label: 'width = height',\n            data: data\n              .filter(row => row.width === row.height)\n              .map(row => ({\n                x: row.width,\n                y: row.height,\n                r: row.count\n              }))\n          },\n          {\n            label: 'width > height',\n            data: data\n              .filter(row => row.width > row.height)\n              .map(row => ({\n                x: row.width,\n                y: row.height,\n                r: row.count\n              }))\n          },\n          {\n            label: 'width < height',\n            data: data\n              .filter(row => row.width < row.height)\n              .map(row => ({\n                x: row.width,\n                y: row.height,\n                r: row.count\n              }))\n          }\n        ]\n\n// ..\n```\n\nAs you can see, we define three datasets with different labels. Each dataset gets its own slice of data extracted with `filter`. Now they are visually distinct and, as you already know, you can toggle their visibility independently.\n\n![result](./usage-7.png)\n\nHere we rely on the default color palette. However, keep in mind every chart type supports a lot of [dataset options](../charts/bubble.md#dataset-properties) that you can feel free to customize.\n\n### Plugins\n\nAnother—and very powerful!—way to customize Chart.js charts is to use plugins. You can find some in the [plugin directory](https://github.com/chartjs/awesome#plugins) or create your own, ad-hoc ones. In Chart.js ecosystem, it’s idiomatic and expected to fine tune charts with plugins. For example, you can customize [canvas background](../configuration/canvas-background.md) or [add a border](../samples/plugins/chart-area-border.md) to it with simple ad-hoc plugins. Let’s try the latter.\n\nPlugins have an [extensive API](../developers/plugins.md) but, in a nutshell, a plugin is defined as an object with a `name` and one or more callback functions defined in the extension points. Insert the following snippet before and in place of the `new Chart(...);` invocation in `src/dimensions.js`:\n\n```jsx\n// ...\n\n  const chartAreaBorder = {\n    id: 'chartAreaBorder',\n\n    beforeDraw(chart, args, options) {\n      const { ctx, chartArea: { left, top, width, height } } = chart;\n\n      ctx.save();\n      ctx.strokeStyle = options.borderColor;\n      ctx.lineWidth = options.borderWidth;\n      ctx.setLineDash(options.borderDash || []);\n      ctx.lineDashOffset = options.borderDashOffset;\n      ctx.strokeRect(left, top, width, height);\n      ctx.restore();\n    }\n  };\n\n  new Chart(\n    document.getElementById('dimensions'),\n    {\n      type: 'bubble',\n      plugins: [ chartAreaBorder ],\n      options: {\n        plugins: {\n          chartAreaBorder: {\n            borderColor: 'red',\n            borderWidth: 2,\n            borderDash: [ 5, 5 ],\n            borderDashOffset: 2,\n          }\n        },\n        aspectRatio: 1,\n\n// ...\n```\n\nAs you can see, in this `chartAreaBorder` plugin, we acquire the canvas context, save its current state, apply styles, draw a rectangular shape around the chart area, and restore the canvas state. We’re also passing the plugin in `plugins` so it’s only applied to this particular chart. We also pass the plugin options in `options.plugins.chartAreaBorder`; we could surely hardcode them in the plugin source code but it’s much more reusable this way.\n\nOur bubble chart looks fancier now:\n\n![result](./usage-8.png)\n\n### Tree-shaking\n\nIn production, we strive to ship as little code as possible, so the end users can load our data applications faster and have better experience. For that, we’ll need to apply [tree-shaking](https://cube.dev/blog/how-to-build-tree-shakeable-javascript-libraries/?ref=eco-chartjs) which is fancy term for removing unused code from the JavaScript bundle.\n\nChart.js fully supports tree-shaking with its component design. You can register all Chart.js components at once (which is convenient when you’re prototyping) and get them bundled with your application. Or, you can register only necessary components and get a minimal bundle, much less in size.\n\nLet’s inspect our example application. What’s the bundle size? You can stop the application and run `npm run build`, or `yarn build`, or `pnpm build`. In a few moments, you’ll get something like this:\n\n```bash\n% yarn build\nyarn run v1.22.17\n$ parcel build src/index.html\n✨ Built in 88ms\n\ndist/index.html              381 B   164ms\ndist/index.74a47636.js   265.48 KB   1.25s\ndist/index.ba0c2e17.js       881 B    63ms\n✨ Done in 0.51s.\n```\n\nWe can see that Chart.js and other dependencies were bundled together in a single 265 KB file.\n\nTo reduce the bundle size, we’ll need to apply a couple of changes to `src/acquisitions.js` and `src/dimensions.js`. First, we’ll need to remove the following import statement from both files: `import Chart from 'chart.js/auto'`.\n\nInstead, let’s load only necessary components and “register” them with Chart.js using `Chart.register(...)`. Here’s what we need in `src/acquisitions.js`:\n\n```jsx\nimport {\n  Chart,\n  Colors,\n  BarController,\n  CategoryScale,\n  LinearScale,\n  BarElement,\n  Legend\n} from 'chart.js'\n\nChart.register(\n  Colors,\n  BarController,\n  BarElement,\n  CategoryScale,\n  LinearScale,\n  Legend\n);\n```\n\nAnd here’s the snippet for `src/dimensions.js`:\n\n```jsx\nimport {\n  Chart,\n  Colors,\n  BubbleController,\n  CategoryScale,\n  LinearScale,\n  PointElement,\n  Legend\n} from 'chart.js'\n\nChart.register(\n  Colors,\n  BubbleController,\n  PointElement,\n  CategoryScale,\n  LinearScale,\n  Legend\n);\n```\n\nYou can see that, in addition to the `Chart` class, we’re also loading a controller for the chart type, scales, and other chart elements (e.g., bars or points). You can look all available components up in the [documentation](./integration.md#bundle-optimization).\n\nAlternatively, you can follow Chart.js advice in the console. For example, if you forget to import `BarController` for your bar chart, you’ll see the following message in the browser console:\n\n```\nUnhandled Promise Rejection: Error: \"bar\" is not a registered controller.\n```\n\nRemember to carefully check for imports from `chart.js/auto` when preparing your application for production. It takes only one import like this to effectively disable tree-shaking.\n\nNow, let’s inspect our application once again. Run `yarn build` and you’ll get something like this:\n\n```bash\n% yarn build\nyarn run v1.22.17\n$ parcel build src/index.html\n✨ Built in 88ms\n\ndist/index.html              381 B   176ms\ndist/index.5888047.js    208.66 KB   1.23s\ndist/index.dcb2e865.js       932 B    58ms\n✨ Done in 0.51s.\n```\n\nBy importing and registering only select components, we’ve removed more than 56 KB of unnecessary code. Given that other dependencies take ~50 KB in the bundle, tree-shaking helps remove ~25% of Chart.js code from the bundle for our example application. \n\n## Next steps\n\nNow you’re familiar with all major concepts of Chart.js: chart types and elements, datasets, customization, plugins, components, and tree-shaking.\n\nFeel free to review many [examples of charts](../samples/information.md) in the documentation and check the [awesome list](https://github.com/chartjs/awesome) of Chart.js plugins and additional chart types as well as [framework integrations](https://github.com/chartjs/awesome#integrations) (e.g., React, Vue, Svelte, etc.). Also, don’t hesitate to join [Chart.js Discord](https://discord.gg/HxEguTK6av) and follow [Chart.js on Twitter](https://twitter.com/chartjs).\n\nHave fun and good luck building with Chart.js!"
  },
  {
    "path": "docs/getting-started/using-from-node-js.md",
    "content": "# Using from Node.js\n\nYou can use Chart.js in Node.js for server-side generation of plots with help from an NPM package such as [node-canvas](https://github.com/Automattic/node-canvas) or [skia-canvas](https://skia-canvas.org/).\n\nSample usage:\n\n```js\nimport {CategoryScale, Chart, LinearScale, LineController, LineElement, PointElement} from 'chart.js';\nimport {Canvas} from 'skia-canvas';\nimport fsp from 'node:fs/promises';\n\nChart.register([\n  CategoryScale,\n  LineController,\n  LineElement,\n  LinearScale,\n  PointElement\n]);\n\nconst canvas = new Canvas(400, 300);\nconst chart = new Chart(\n  canvas, // TypeScript needs \"as any\" here\n  {\n    type: 'line',\n    data: {\n      labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],\n      datasets: [{\n        label: '# of Votes',\n        data: [12, 19, 3, 5, 2, 3],\n        borderColor: 'red'\n      }]\n    }\n  }\n);\nconst pngBuffer = await canvas.toBuffer('png', {matte: 'white'});\nawait fsp.writeFile('output.png', pngBuffer);\nchart.destroy();\n```\n"
  },
  {
    "path": "docs/index.md",
    "content": "# Chart.js\n\nWelcome to Chart.js!\n\n* **[Get started with Chart.js](./getting-started/) — best if you're new to Chart.js**\n* Migrate from [Chart.js v3](./migration/v4-migration.md) or [Chart.js v2](./migration/v3-migration.md)\n* Join the community on [Discord](https://discord.gg/HxEguTK6av) and [Twitter](https://twitter.com/chartjs)\n* Post a question tagged with `chart.js` on [Stack Overflow](https://stackoverflow.com/questions/tagged/chart.js)\n* [Contribute to Chart.js](./developers/contributing.md)\n\n## Why Chart.js\n\nAmong [many charting libraries](https://awesome.cube.dev/?tools=charts&ref=eco-chartjs) for JavaScript application developers, Chart.js is currently the most popular one according to [GitHub stars](https://github.com/chartjs/Chart.js) (~60,000) and [npm downloads](https://www.npmjs.com/package/chart.js) (~2,400,000 weekly).\n\nChart.js was created and [announced](https://twitter.com/_nnnick/status/313599208387137536) in 2013 but has come a long way since then. It’s open-source, licensed under the very permissive [MIT license](https://github.com/chartjs/Chart.js/blob/master/LICENSE.md), and maintained by an active community.\n\n### Features\n\nChart.js provides a set of frequently used chart types, plugins, and customization options. In addition to a reasonable set of [built-in chart types](./charts/area.md), you can use additional community-maintained [chart types](https://github.com/chartjs/awesome#charts). On top of that, it’s possible to combine several chart types into a [mixed chart](./charts/mixed.md) (essentially, blending multiple chart types into one on the same canvas).\n\nChart.js is highly customizable with [custom plugins](https://github.com/chartjs/awesome#plugins) to create annotations, zoom, or drag-and-drop functionalities to name a few things.\n\n### Defaults\n\nChart.js comes with a sound default configuration, making it very easy to start with and get an app that is ready for production. Chances are you will get a very appealing chart even if you don’t specify any options at all. For instance, Chart.js has animations turned on by default, so you can instantly bring attention to the story you’re telling with the data.\n\n### Integrations\n\nChart.js comes with built-in TypeScript typings and is compatible with all popular [JavaScript frameworks](https://github.com/chartjs/awesome#javascript) including [React](https://github.com/reactchartjs/react-chartjs-2), [Vue](https://github.com/apertureless/vue-chartjs/), [Svelte](https://github.com/SauravKanchan/svelte-chartjs), and [Angular](https://github.com/valor-software/ng2-charts). You can use Chart.js directly or leverage well-maintained wrapper packages that allow for a more native integration with your frameworks of choice.\n\n### Developer experience\n\nChart.js has very thorough documentation (yes, you're reading it), [API reference](./api/), and [examples](./samples/information.md). Maintainers and community members eagerly engage in conversations on [Discord](https://discord.gg/HxEguTK6av), [GitHub Discussions](https://github.com/chartjs/Chart.js/discussions), and [Stack Overflow](https://stackoverflow.com/questions/tagged/chart.js) where more than 11,000 questions are tagged with `chart.js`.\n\n### Canvas rendering\n\nChart.js renders chart elements on an HTML5 canvas unlike several others, mostly D3.js-based, charting libraries that render as SVG. Canvas rendering makes Chart.js very performant, especially for large datasets and complex visualizations that would otherwise require thousands of SVG nodes in the DOM tree. At the same time, canvas rendering disallows CSS styling, so you will have to use built-in options for that, or create a custom plugin or chart type to render everything to your liking.\n\n### Performance\n\nChart.js is very well suited for large datasets. Such datasets can be efficiently ingested using the internal format, so you can skip data [parsing](./general/performance.md#parsing) and [normalization](./general/performance.md#data-normalization). Alternatively, [data decimation](./configuration/decimation.md) can be configured to sample the dataset and reduce its size before rendering.\n\nIn the end, the canvas rendering that Chart.js uses reduces the toll on your DOM tree in comparison to SVG rendering. Also, tree-shaking support allows you to include minimal parts of Chart.js code in your bundle, reducing bundle size and page load time.\n\n### Community\n\nChart.js is [actively developed](https://github.com/chartjs/Chart.js/pulls?q=is%3Apr+is%3Aclosed) and maintained by the community. With minor [releases](https://github.com/chartjs/Chart.js/releases) on an approximately bi-monthly basis and major releases with breaking changes every couple of years, Chart.js keeps the balance between adding new features and making it a hassle to keep up with them.\n"
  },
  {
    "path": "docs/migration/v3-migration.md",
    "content": "# 3.x Migration Guide\n\nChart.js 3.0 introduces a number of breaking changes. Chart.js 2.0 was released in April 2016. In the years since then, as Chart.js has grown in popularity and feature set, we've learned some lessons about how to better create a charting library. In order to improve performance, offer new features, and improve maintainability, it was necessary to break backwards compatibility, but we aimed to do so only when worth the benefit. Some major highlights of v3 include:\n\n* Large [performance](../general/performance.md) improvements including the ability to skip data parsing and render charts in parallel via webworkers\n* Additional configurability and scriptable options with better defaults\n* Completely rewritten animation system\n* Rewritten filler plugin with numerous bug fixes\n* Documentation migrated from GitBook to Vuepress\n* API documentation generated and verified by TypeDoc\n* No more CSS injection\n* Tons of bug fixes\n* Tree shaking\n\n## End user migration\n\n### Setup and installation\n\n* Distributed files are now in lower case. For example: `dist/chart.js`.\n* Chart.js is no longer providing the `Chart.bundle.js` and `Chart.bundle.min.js`. Please see the [installation](../getting-started/installation.md) and [integration](../getting-started/integration.md) docs for details on the recommended way to setup Chart.js if you were using these builds.\n* `moment` is no longer specified as an npm dependency. If you are using the `time` or `timeseries` scales, you must include one of [the available adapters](https://github.com/chartjs/awesome#adapters) and corresponding date library. You no longer need to exclude moment from your build.\n* The `Chart` constructor will throw an error if the canvas/context provided is already in use\n* Chart.js 3 is tree-shakeable. So if you are using it as an `npm` module in a project and want to make use of this feature, you need to import and register the controllers, elements, scales and plugins you want to use, for a list of all the available items to import see [integration](../getting-started/integration.md#bundlers-webpack-rollup-etc). You will not have to call `register` if importing Chart.js via a `script` tag or from the [`auto`](../getting-started/integration.md#bundlers-webpack-rollup-etc) register path as an `npm` module, in this case you will not get the tree shaking benefits. Here is an example of registering components:\n\n```javascript\nimport { Chart, LineController, LineElement, PointElement, LinearScale, Title } from `chart.js`\n\nChart.register(LineController, LineElement, PointElement, LinearScale, Title);\n\nconst chart = new Chart(ctx, {\n    type: 'line',\n    // data: ...\n    options: {\n        plugins: {\n            title: {\n                display: true,\n                text: 'Chart Title'\n            }\n        },\n        scales: {\n            x: {\n                type: 'linear'\n            },\n            y: {\n                type: 'linear'\n            }\n        }\n    }\n})\n```\n\n### Chart types\n\n* `horizontalBar` chart type was removed. Horizontal bar charts can be configured using the new [`indexAxis`](../charts/bar.md#horizontal-bar-chart) option\n\n### Options\n\nA number of changes were made to the configuration options passed to the `Chart` constructor. Those changes are documented below.\n\n#### Generic changes\n\n* Indexable options are now looping. `backgroundColor: ['red', 'green']` will result in alternating `'red'` / `'green'` if there are more than 2 data points.\n* The input properties of object data can now be freely specified, see [data structures](../general/data-structures.md) for details.\n* Most options are resolved utilizing proxies, instead of merging with defaults. In addition to easily enabling different resolution routes for different contexts, it allows using other resolved options in scriptable options.\n  * Options are by default scriptable and indexable, unless disabled for some reason.\n  * Scriptable options receive a option resolver as second parameter for accessing other options in same context.\n  * Resolution falls to upper scopes, if no match is found earlier. See [options](../general/options.md) for details.\n\n#### Specific changes\n\n* `elements.rectangle` is now `elements.bar`\n* `hover.animationDuration` is now configured in `animation.active.duration`\n* `responsiveAnimationDuration` is now configured in `animation.resize.duration`\n* Polar area `elements.arc.angle` is now configured in degrees instead of radians.\n* Polar area `startAngle` option is now consistent with `Radar`, 0 is at top and value is in degrees. Default is changed from `-½π` to  `0`.\n* Doughnut `rotation` option is now in degrees and 0 is at top. Default is changed from `-½π` to  `0`.\n* Doughnut `circumference` option is now in degrees. Default is changed from `2π` to `360`.\n* Doughnut `cutoutPercentage` was renamed to `cutout`and accepts pixels as number and percent as string ending with `%`.\n* `scale` option was removed in favor of `options.scales.r` (or any other scale id, with `axis: 'r'`)\n* `scales.[x/y]Axes` arrays were removed. Scales are now configured directly to `options.scales` object with the object key being the scale Id.\n* `scales.[x/y]Axes.barPercentage` was moved to dataset option `barPercentage`\n* `scales.[x/y]Axes.barThickness` was moved to dataset option `barThickness`\n* `scales.[x/y]Axes.categoryPercentage` was moved to dataset option `categoryPercentage`\n* `scales.[x/y]Axes.maxBarThickness` was moved to dataset option `maxBarThickness`\n* `scales.[x/y]Axes.minBarLength` was moved to dataset option `minBarLength`\n* `scales.[x/y]Axes.scaleLabel` was renamed to `scales[id].title`\n* `scales.[x/y]Axes.scaleLabel.labelString` was renamed to `scales[id].title.text`\n* `scales.[x/y]Axes.ticks.beginAtZero` was renamed to `scales[id].beginAtZero`\n* `scales.[x/y]Axes.ticks.max` was renamed to `scales[id].max`\n* `scales.[x/y]Axes.ticks.min` was renamed to `scales[id].min`\n* `scales.[x/y]Axes.ticks.reverse` was renamed to `scales[id].reverse`\n* `scales.[x/y]Axes.ticks.suggestedMax` was renamed to `scales[id].suggestedMax`\n* `scales.[x/y]Axes.ticks.suggestedMin` was renamed to `scales[id].suggestedMin`\n* `scales.[x/y]Axes.ticks.unitStepSize` was removed. Use `scales[id].ticks.stepSize`\n* `scales.[x/y]Axes.ticks.userCallback` was renamed to `scales[id].ticks.callback`\n* `scales.[x/y]Axes.time.format` was renamed to `scales[id].time.parser`\n* `scales.[x/y]Axes.time.max` was renamed to `scales[id].max`\n* `scales.[x/y]Axes.time.min` was renamed to `scales[id].min`\n* `scales.[x/y]Axes.zeroLine*` options of axes were removed. Use scriptable scale options instead.\n* The dataset option `steppedLine` was removed. Use `stepped`\n* The chart option `showLines` was renamed to `showLine` to match the dataset option.\n* The chart option `startAngle` was moved to `radial` scale options.\n* To override the platform class used in a chart instance, pass `platform: PlatformClass` in the config object. Note that the class should be passed, not an instance of the class.\n* `aspectRatio` defaults to 1 for doughnut, pie, polarArea, and radar charts\n* `TimeScale` does not read `t` from object data by default anymore. The default property is `x` or `y`, depending on the orientation. See [data structures](../general/data-structures.md) for details on how to change the default.\n* `tooltips` namespace was renamed to `tooltip` to match the plugin name\n* `legend`, `title` and `tooltip` namespaces were moved from `options` to `options.plugins`.\n* `tooltips.custom` was renamed to `plugins.tooltip.external`\n\n#### Defaults\n\n* `global` namespace was removed from `defaults`. So `Chart.defaults.global` is now `Chart.defaults`\n* Dataset controller defaults were relocate to `overrides`. For example `Chart.defaults.line` is now `Chart.overrides.line`\n* `default` prefix was removed from defaults. For example `Chart.defaults.global.defaultColor` is now `Chart.defaults.color`\n* `defaultColor` was split to `color`, `borderColor` and `backgroundColor`\n* `defaultFontColor` was renamed to `color`\n* `defaultFontFamily` was renamed to `font.family`\n* `defaultFontSize` was renamed to `font.size`\n* `defaultFontStyle` was renamed to `font.style`\n* `defaultLineHeight` was renamed to `font.lineHeight`\n* Horizontal Bar default tooltip mode was changed from `'index'` to `'nearest'` to match vertical bar charts\n* `legend`, `title` and `tooltip` namespaces were moved from `Chart.defaults` to `Chart.defaults.plugins`.\n* `elements.line.fill` default changed from `true` to `false`.\n* Line charts no longer override the default `interaction` mode. Default is changed from `'index'` to `'nearest'`.\n\n#### Scales\n\nThe configuration options for scales is the largest change in v3. The `xAxes` and `yAxes` arrays were removed and axis options are individual scales now keyed by scale ID.\n\nThe v2 configuration below is shown with it's new v3 configuration\n\n```javascript\noptions: {\n  scales: {\n    xAxes: [{\n      id: 'x',\n      type: 'time',\n      display: true,\n      title: {\n        display: true,\n        text: 'Date'\n      },\n      ticks: {\n        major: {\n          enabled: true\n        },\n        font: function(context) {\n          if (context.tick && context.tick.major) {\n            return {\n              weight: 'bold',\n              color: '#FF0000'\n            };\n          }\n        }\n      }\n    }],\n    yAxes: [{\n      id: 'y',\n      display: true,\n      title: {\n        display: true,\n        text: 'value'\n      }\n    }]\n  }\n}\n```\n\nAnd now, in v3:\n\n```javascript\noptions: {\n  scales: {\n    x: {\n      type: 'time',\n      display: true,\n      title: {\n        display: true,\n        text: 'Date'\n      },\n      ticks: {\n        major: {\n          enabled: true\n        },\n        color: (context) => context.tick && context.tick.major && '#FF0000',\n        font: function(context) {\n          if (context.tick && context.tick.major) {\n            return {\n              weight: 'bold'\n            };\n          }\n        }\n      }\n    },\n    y: {\n      display: true,\n      title: {\n        display: true,\n        text: 'value'\n      }\n    }\n  }\n}\n```\n\n* The time scale option `distribution: 'series'` was removed and a new scale type `timeseries` was introduced in its place\n* In the time scale, `autoSkip` is now enabled by default for consistency with the other scales\n\n#### Animations\n\nAnimation system was completely rewritten in Chart.js v3. Each property can now be animated separately. Please see [animations](../configuration/animations.md) docs for details.\n\n#### Customizability\n\n* `custom` attribute of elements was removed. Please use scriptable options\n* The `hover` property of scriptable options `context` object was renamed to `active` to align it with the datalabels plugin.\n\n#### Interactions\n\n* To allow DRY configuration, a root options scope for common interaction options was added. `options.hover` and `options.plugins.tooltip` now both extend from `options.interaction`. Defaults are defined at `defaults.interaction` level, so by default hover and tooltip interactions share the same mode etc.\n* `interactions` are now limited to the chart area + allowed overflow\n* `{mode: 'label'}` was replaced with `{mode: 'index'}`\n* `{mode: 'single'}` was replaced with `{mode: 'nearest', intersect: true}`\n* `modes['X-axis']` was replaced with `{mode: 'index', intersect: false}`\n* `options.onClick` is now limited to the chart area\n* `options.onClick` and `options.onHover` now receive the `chart` instance as a 3rd argument\n* `options.onHover` now receives a wrapped `event` as the first parameter. The previous first parameter value is accessible via `event.native`.\n* `options.hover.onHover` was removed, use `options.onHover`.\n\n#### Ticks\n\n* `options.gridLines` was renamed to `options.grid`\n* `options.gridLines.offsetGridLines` was renamed to `options.grid.offset`.\n* `options.gridLines.tickMarkLength` was renamed to `options.grid.tickLength`.\n* `options.ticks.fixedStepSize` is no longer used. Use `options.ticks.stepSize`.\n* `options.ticks.major` and `options.ticks.minor` were replaced with scriptable options for tick fonts.\n* `Chart.Ticks.formatters.linear` was renamed to `Chart.Ticks.formatters.numeric`.\n* `options.ticks.backdropPaddingX` and `options.ticks.backdropPaddingY` were replaced with `options.ticks.backdropPadding` in the radial linear scale.\n\n#### Tooltip\n\n* `xLabel` and `yLabel` were removed. Please use `label` and `formattedValue`\n* The `filter` option will now be passed additional parameters when called and should have the method signature `function(tooltipItem, index, tooltipItems, data)`\n* The `custom` callback now takes a context object that has `tooltip` and `chart` properties\n* All properties of tooltip model related to the tooltip options have been moved to reside within the `options` property.\n* The callbacks no longer are given a `data` parameter. The tooltip item parameter contains the chart and dataset instead\n* The tooltip item's `index` parameter was renamed to `dataIndex` and `value` was renamed to `formattedValue`\n* The `xPadding` and `yPadding` options were merged into a single `padding` object\n\n## Developer migration\n\nWhile the end-user migration for Chart.js 3 is fairly straight-forward, the developer migration can be more complicated. Please reach out for help in the #dev [Discord](https://discord.gg/HxEguTK6av) channel if tips on migrating would be helpful.\n\nSome of the biggest things that have changed:\n\n* There is a completely rewritten and more performant animation system.\n  * `Element._model` and `Element._view` are no longer used and properties are now set directly on the elements. You will have to use the method `getProps` to access these properties inside most methods such as `inXRange`/`inYRange` and `getCenterPoint`. Please take a look at [the Chart.js-provided elements](https://github.com/chartjs/Chart.js/tree/master/src/elements) for examples.\n  * When building the elements in a controller, it's now suggested to call `updateElement` to provide the element properties. There are also methods such as `getSharedOptions` and `includeOptions` that have been added to skip redundant computation. Please take a look at [the Chart.js-provided controllers](https://github.com/chartjs/Chart.js/tree/master/src/controllers) for examples.\n* Scales introduced a new parsing API. This API takes user data and converts it into a more standard format. E.g. it allows users to provide numeric data as a `string` and converts it to a `number` where necessary. Previously this was done on the fly as charts were rendered. Now it's done up front with the ability to skip it for better performance if users provide data in the correct format. If you're using standard data format like `x`/`y` you may not need to do anything. If you're using a custom data format you will have to override some of the parse methods in `core.datasetController.js`. An example can be found in [chartjs-chart-financial](https://github.com/chartjs/chartjs-chart-financial), which uses an `{o, h, l, c}` data format.\n\nA few changes were made to controllers that are more straight-forward, but will affect all controllers:\n\n* Options:\n  * `global` was removed from the defaults namespace as it was unnecessary and sometimes inconsistent\n  * Dataset defaults are now under the chart type options instead of vice-versa. This was not able to be done when introduced in 2.x for backwards compatibility. Fixing it removes the biggest stumbling block that new chart developers encountered\n  * Scale default options need to be updated as described in the end user migration section (e.g. `x` instead of `xAxes` and `y` instead of `yAxes`)\n* `updateElement` was changed to `updateElements` and has a new method signature as described below. This provides performance enhancements such as allowing easier reuse of computations that are common to all elements and reducing the number of function calls\n\n### Removed\n\nThe following properties and methods were removed:\n\n#### Removed from Chart\n\n* `Chart.animationService`\n* `Chart.active`\n* `Chart.borderWidth`\n* `Chart.chart.chart`\n* `Chart.Bar`. New charts are created via `new Chart` and providing the appropriate `type` parameter\n* `Chart.Bubble`. New charts are created via `new Chart` and providing the appropriate `type` parameter\n* `Chart.Chart`\n* `Chart.Controller`\n* `Chart.Doughnut`. New charts are created via `new Chart` and providing the appropriate `type` parameter\n* `Chart.innerRadius` now lives on doughnut, pie, and polarArea controllers\n* `Chart.lastActive`\n* `Chart.Legend` was moved to `Chart.plugins.legend._element` and made private\n* `Chart.Line`. New charts are created via `new Chart` and providing the appropriate `type` parameter\n* `Chart.LinearScaleBase` now must be imported and cannot be accessed off the `Chart` object\n* `Chart.offsetX`\n* `Chart.offsetY`\n* `Chart.outerRadius` now lives on doughnut, pie, and polarArea controllers\n* `Chart.plugins` was replaced with `Chart.registry`. Plugin defaults are now in `Chart.defaults.plugins[id]`.\n* `Chart.plugins.register` was replaced by `Chart.register`.\n* `Chart.PolarArea`. New charts are created via `new Chart` and providing the appropriate `type` parameter\n* `Chart.prototype.generateLegend`\n* `Chart.platform`. It only contained `disableCSSInjection`. CSS is never injected in v3.\n* `Chart.PluginBase`\n* `Chart.Radar`. New charts are created via `new Chart` and providing the appropriate `type` parameter\n* `Chart.radiusLength`\n* `Chart.scaleService` was replaced with `Chart.registry`. Scale defaults are now in `Chart.defaults.scales[type]`.\n* `Chart.Scatter`. New charts are created via `new Chart` and providing the appropriate `type` parameter\n* `Chart.types`\n* `Chart.Title` was moved to `Chart.plugins.title._element` and made private\n* `Chart.Tooltip` is now provided by the tooltip plugin. The positioners can be accessed from `tooltipPlugin.positioners`\n* `ILayoutItem.minSize`\n\n#### Removed from Dataset Controllers\n\n* `BarController.getDatasetMeta().bar`\n* `DatasetController.addElementAndReset`\n* `DatasetController.createMetaData`\n* `DatasetController.createMetaDataset`\n* `DoughnutController.getRingIndex`\n\n#### Removed from Elements\n\n* `Element.getArea`\n* `Element.height`\n* `Element.hidden` was replaced by chart level status, usable with `getDataVisibility(index)` / `toggleDataVisibility(index)`\n* `Element.initialize`\n* `Element.inLabelRange`\n* `Line.calculatePointY`\n\n#### Removed from Helpers\n\n* `helpers.addEvent`\n* `helpers.aliasPixel`\n* `helpers.arrayEquals`\n* `helpers.configMerge`\n* `helpers.findIndex`\n* `helpers.findNextWhere`\n* `helpers.findPreviousWhere`\n* `helpers.extend`. Use `Object.assign` instead\n* `helpers.getValueAtIndexOrDefault`. Use `helpers.resolve` instead.\n* `helpers.indexOf`\n* `helpers.lineTo`\n* `helpers.longestText` was made private\n* `helpers.max`\n* `helpers.measureText` was made private\n* `helpers.min`\n* `helpers.nextItem`\n* `helpers.niceNum`\n* `helpers.numberOfLabelLines`\n* `helpers.previousItem`\n* `helpers.removeEvent`\n* `helpers.roundedRect`\n* `helpers.scaleMerge`\n* `helpers.where`\n\n#### Removed from Layout\n\n* `Layout.defaults`\n\n#### Removed from Scales\n\n* `LinearScaleBase.handleDirectionalChanges`\n* `LogarithmicScale.minNotZero`\n* `Scale.getRightValue`\n* `Scale.longestLabelWidth`\n* `Scale.longestTextCache` is now private\n* `Scale.margins` is now private\n* `Scale.mergeTicksOptions`\n* `Scale.ticksAsNumbers`\n* `Scale.tickValues` is now private\n* `TimeScale.getLabelCapacity` is now private\n* `TimeScale.tickFormatFunction` is now private\n\n#### Removed from Plugins (Legend, Title, and Tooltip)\n\n* `IPlugin.afterScaleUpdate`. Use `afterLayout` instead\n* `Legend.margins` is now private\n* Legend `onClick`, `onHover`, and `onLeave` options now receive the legend as the 3rd argument in addition to implicitly via `this`\n* Legend `onClick`, `onHover`, and `onLeave` options now receive a wrapped `event` as the first parameter. The previous first parameter value is accessible via `event.native`.\n* `Title.margins` is now private\n* The tooltip item's `x` and `y` attributes were replaced by `element`. You can use `element.x` and `element.y` or `element.tooltipPosition()` instead.\n\n#### Removal of Public APIs\n\nThe following public APIs were removed.\n\n* `getElementAtEvent` is replaced with `chart.getElementsAtEventForMode(e, 'nearest', { intersect: true }, false)`\n* `getElementsAtEvent` is replaced with `chart.getElementsAtEventForMode(e, 'index', { intersect: true }, false)`\n* `getElementsAtXAxis` is replaced with `chart.getElementsAtEventForMode(e, 'index', { intersect: false }, false)`\n* `getDatasetAtEvent` is replaced with `chart.getElementsAtEventForMode(e, 'dataset', { intersect: true }, false)`\n\n#### Removal of private APIs\n\nThe following private APIs were removed.\n\n* `Chart._bufferedRender`\n* `Chart._updating`\n* `Chart.data.datasets[datasetIndex]._meta`\n* `DatasetController._getIndexScaleId`\n* `DatasetController._getIndexScale`\n* `DatasetController._getValueScaleId`\n* `DatasetController._getValueScale`\n* `Element._ctx`\n* `Element._model`\n* `Element._view`\n* `LogarithmicScale._valueOffset`\n* `TimeScale.getPixelForOffset`\n* `TimeScale.getLabelWidth`\n* `Tooltip._lastActive`\n\n### Renamed\n\nThe following properties were renamed during v3 development:\n\n* `Chart.Animation.animationObject` was renamed to `Chart.Animation`\n* `Chart.Animation.chartInstance` was renamed to `Chart.Animation.chart`\n* `Chart.canvasHelpers` was merged with `Chart.helpers`\n* `Chart.elements.Arc` was renamed to `Chart.elements.ArcElement`\n* `Chart.elements.Line` was renamed to `Chart.elements.LineElement`\n* `Chart.elements.Point` was renamed to `Chart.elements.PointElement`\n* `Chart.elements.Rectangle` was renamed to `Chart.elements.BarElement`\n* `Chart.layoutService` was renamed to `Chart.layouts`\n* `Chart.pluginService` was renamed to `Chart.plugins`\n* `helpers.callCallback` was renamed to `helpers.callback`\n* `helpers.drawRoundedRectangle` was renamed to `helpers.roundedRect`\n* `helpers.getValueOrDefault` was renamed to `helpers.valueOrDefault`\n* `LayoutItem.fullWidth` was renamed to `LayoutItem.fullSize`\n* `Point.controlPointPreviousX` was renamed to `Point.cp1x`\n* `Point.controlPointPreviousY` was renamed to `Point.cp1y`\n* `Point.controlPointNextX` was renamed to `Point.cp2x`\n* `Point.controlPointNextY` was renamed to `Point.cp2y`\n* `Scale.calculateTickRotation` was renamed to `Scale.calculateLabelRotation`\n* `Tooltip.options.legendColorBackgroupd` was renamed to `Tooltip.options.multiKeyBackground`\n\n#### Renamed private APIs\n\nThe private APIs listed below were renamed:\n\n* `BarController.calculateBarIndexPixels` was renamed to `BarController._calculateBarIndexPixels`\n* `BarController.calculateBarValuePixels` was renamed to `BarController._calculateBarValuePixels`\n* `BarController.getStackCount` was renamed to `BarController._getStackCount`\n* `BarController.getStackIndex` was renamed to `BarController._getStackIndex`\n* `BarController.getRuler` was renamed to `BarController._getRuler`\n* `Chart.destroyDatasetMeta` was renamed to `Chart._destroyDatasetMeta`\n* `Chart.drawDataset` was renamed to `Chart._drawDataset`\n* `Chart.drawDatasets` was renamed to `Chart._drawDatasets`\n* `Chart.eventHandler` was renamed to `Chart._eventHandler`\n* `Chart.handleEvent` was renamed to `Chart._handleEvent`\n* `Chart.initialize` was renamed to `Chart._initialize`\n* `Chart.resetElements` was renamed to `Chart._resetElements`\n* `Chart.unbindEvents` was renamed to `Chart._unbindEvents`\n* `Chart.updateDataset` was renamed to `Chart._updateDataset`\n* `Chart.updateDatasets` was renamed to `Chart._updateDatasets`\n* `Chart.updateLayout` was renamed to `Chart._updateLayout`\n* `DatasetController.destroy` was renamed to `DatasetController._destroy`\n* `DatasetController.insertElements` was renamed to `DatasetController._insertElements`\n* `DatasetController.onDataPop` was renamed to `DatasetController._onDataPop`\n* `DatasetController.onDataPush` was renamed to `DatasetController._onDataPush`\n* `DatasetController.onDataShift` was renamed to `DatasetController._onDataShift`\n* `DatasetController.onDataSplice` was renamed to `DatasetController._onDataSplice`\n* `DatasetController.onDataUnshift` was renamed to `DatasetController._onDataUnshift`\n* `DatasetController.removeElements` was renamed to `DatasetController._removeElements`\n* `DatasetController.resyncElements` was renamed to `DatasetController._resyncElements`\n* `LayoutItem.isFullWidth` was renamed to `LayoutItem.isFullSize`\n* `RadialLinearScale.setReductions` was renamed to `RadialLinearScale._setReductions`\n* `RadialLinearScale.pointLabels` was renamed to `RadialLinearScale._pointLabels`\n* `Scale.handleMargins` was renamed to `Scale._handleMargins`\n\n### Changed\n\nThe APIs listed in this section have changed in signature or behaviour from version 2.\n\n#### Changed in Scales\n\n* `Scale.getLabelForIndex` was replaced by `scale.getLabelForValue`\n* `Scale.getPixelForValue` now only requires one parameter. For the `TimeScale` that parameter must be millis since the epoch. As a performance optimization, it may take an optional second parameter, giving the index of the data point.\n\n##### Changed in Ticks\n\n* `Scale.afterBuildTicks` now has no parameters like the other callbacks\n* `Scale.buildTicks` is now expected to return tick objects\n* `Scale.convertTicksToLabels` was renamed to `generateTickLabels`. It is now expected to set the label property on the ticks given as input\n* `Scale.ticks` now contains objects instead of strings\n* When the `autoSkip` option is enabled, `Scale.ticks` now contains only the non-skipped ticks instead of all ticks.\n* Ticks are now always generated in monotonically increasing order\n\n##### Changed in Time Scale\n\n* `getValueForPixel` now returns milliseconds since the epoch\n\n#### Changed in Controllers\n\n##### Core Controller\n\n* The first parameter to `updateHoverStyle` is now an array of objects containing the `element`, `datasetIndex`, and `index`\n* The signature or `resize` changed, the first `silent` parameter was removed.\n\n##### Dataset Controllers\n\n* `updateElement` was replaced with `updateElements` now taking the elements to update, the `start` index, `count`, and `mode`\n* `setHoverStyle` and `removeHoverStyle` now additionally take the `datasetIndex` and `index`\n\n#### Changed in Interactions\n\n* Interaction mode methods now return an array of objects containing the `element`, `datasetIndex`, and `index`\n\n#### Changed in Layout\n\n* `ILayoutItem.update` no longer has a return value\n\n#### Changed in Helpers\n\nAll helpers are now exposed in a flat hierarchy, e.g., `Chart.helpers.canvas.clipArea` -> `Chart.helpers.clipArea`\n\n##### Canvas Helper\n\n* The second parameter to `drawPoint` is now the full options object, so `style`, `rotation`, and `radius` are no longer passed explicitly\n* `helpers.getMaximumHeight` was replaced by `helpers.dom.getMaximumSize`\n* `helpers.getMaximumWidth` was replaced by `helpers.dom.getMaximumSize`\n* `helpers.clear` was renamed to `helpers.clearCanvas` and now takes `canvas` and optionally `ctx` as parameter(s).\n* `helpers.retinaScale` accepts optional third parameter `forceStyle`, which forces overriding current canvas style. `forceRatio` no longer falls back to `window.devicePixelRatio`, instead it defaults to `1`.\n\n#### Changed in Platform\n\n* `Chart.platform` is no longer the platform object used by charts. Every chart instance now has a separate platform instance.\n* `Chart.platforms` is an object that contains two usable platform classes, `BasicPlatform` and `DomPlatform`. It also contains `BasePlatform`, a class that all platforms must extend from.\n* If the canvas passed in is an instance of `OffscreenCanvas`, the `BasicPlatform` is automatically used.\n* `isAttached` method was added to platform.\n\n#### Changed in IPlugin interface\n\n* All plugin hooks have unified signature with 3 arguments: `chart`, `args` and `options`. This means change in signature for these hooks: `beforeInit`, `afterInit`, `reset`, `beforeLayout`, `afterLayout`, `beforeRender`, `afterRender`, `beforeDraw`, `afterDraw`, `beforeDatasetsDraw`, `afterDatasetsDraw`, `beforeEvent`, `afterEvent`, `resize`, `destroy`.\n* `afterDatasetsUpdate`, `afterUpdate`, `beforeDatasetsUpdate`, and `beforeUpdate` now receive `args` object as second argument. `options` argument is always the last and thus was moved from 2nd to 3rd place.\n* `afterEvent` and `beforeEvent` now receive a wrapped `event` as the `event` property of the second argument. The native event is available via `args.event.native`.\n* Initial `resize` is no longer silent. Meaning that `resize` event can fire between `beforeInit` and `afterInit`\n* New hooks: `install`, `start`, `stop`, and `uninstall`\n* `afterEvent` should notify about changes that need a render by setting `args.changed` to true. Because the `args` are shared with all plugins, it should only be set to true and not false.\n"
  },
  {
    "path": "docs/migration/v4-migration.md",
    "content": "# 4.x Migration Guide\n\nChart.js 4.0 introduces a number of breaking changes. We tried keeping the amount of breaking changes to a minimum. For some features and bug fixes it was necessary to break backwards compatibility, but we aimed to do so only when worth the benefit.\n\n## End user migration\n\n### Charts\n\n* Charts don't override the default tooltip callbacks, so all chart types have the same-looking tooltips.\n* Default scale override has been removed if the configured scale starts with `x`/`y`. Defining `xAxes` in your config will now create a second scale instead of overriding the default `x` axis.\n\n### Options\n\nA number of changes were made to the configuration options passed to the `Chart` constructor. Those changes are documented below.\n\n#### Specific changes\n\n* The radialLinear grid indexable and scriptable options don't decrease the index of the specified grid line anymore.\n* The `destroy` plugin hook has been removed and replaced with `afterDestroy`.\n* Ticks callback on time scale now receives timestamp instead of a formatted label.\n* `scales[id].grid.drawBorder` has been renamed to `scales[id].border.display`.\n* `scales[id].grid.borderWidth` has been renamed to `scales[id].border.width`.\n* `scales[id].grid.borderColor` has been renamed to `scales[id].border.color`.\n* `scales[id].grid.borderDash` has been renamed to `scales[id].border.dash`.\n* `scales[id].grid.borderDashOffset` has been renamed to `scales[id].border.dashOffset`.\n* The z index for the border of a scale is now configurable instead of being 1 higher as the grid z index.\n* Linear scales now add and subtracts `5%` of the max value to the range if the min and max are the same instead of `1`.\n* If the tooltip callback returns `undefined`, then the default callback will be used.\n* `maintainAspectRatio` respects container height.\n* Time and timeseries scales use `ticks.stepSize` instead of `time.stepSize`, which has been removed.\n* `maxTickslimit` won't be used for the ticks in `autoSkip` if the determined max ticks is less then the `maxTicksLimit`.\n* `dist/chart.js` has been removed.\n* `dist/chart.min.js` has been renamed to `dist/chart.umd.min.js` (and before 4.5.0 `dist/chart.umd.js`).\n* `dist/chart.esm.js` has been renamed to `dist/chart.js`.\n\n#### Type changes\n* The order of the `ChartMeta` parameters have been changed from `<Element, DatasetElement, Type>` to `<Type, Element, DatasetElement>`.\n\n### General\n* Chart.js becomes an [ESM-only package](https://nodejs.org/api/esm.html) ([the UMD bundle is still available](../getting-started/installation.md#cdn)). To use Chart.js, your project should also be an ES module. Make sure to have this in your `package.json`:\n  ```json\n  {\n    \"type\": \"module\"\n  }\n  ```\n  If you are experiencing problems with [Jest](https://jestjs.io), follow its [documentation](https://jestjs.io/docs/ecmascript-modules) to enable the ESM support. Or, we can recommend you migrating to [Vitest](https://vitest.dev/). Vitest has the ESM support out of the box and [almost the same API as Jest](https://vitest.dev/guide/migration.html#migrating-from-jest). See an [example of migration](https://github.com/reactchartjs/react-chartjs-2/commit/7f3ec96101d21e43cae8cbfe5e09a46a17cff1ef).\n* Removed fallback to `fontColor` for the legend text and strikethrough color.\n* Removed `config._chart` fallback for `this.chart` in the filler plugin.\n* Removed `this._chart` in the filler plugin.\n"
  },
  {
    "path": "docs/package.json",
    "content": "{\n    \"name\": \"docs\",\n    \"private\": \"true\",\n    \"version\": \"4.0.0-dev\",\n    \"license\": \"MIT\",\n    \"type\": \"module\",\n    \"scripts\": {\n        \"build\": \"vuepress build --no-cache\",\n        \"dev\": \"vuepress dev --no-cache\"\n    },\n    \"devDependencies\": {\n        \"@simonbrunel/vuepress-plugin-versions\": \"^0.2.0\",\n        \"@vuepress/plugin-google-analytics\": \"^1.9.7\",\n        \"@vuepress/plugin-html-redirect\": \"^0.1.2\",\n        \"markdown-it\": \"^12.3.2\",\n        \"markdown-it-include\": \"^2.0.0\",\n        \"typedoc\": \"^0.23.10\",\n        \"typedoc-plugin-markdown\": \"^3.13.4\",\n        \"typescript\": \"^4.7.4\",\n        \"vue\": \"^2.6.14\",\n        \"vue-tabs-component\": \"^1.5.0\",\n        \"vuepress\": \"^1.9.7\",\n        \"vuepress-plugin-code-copy\": \"^1.0.6\",\n        \"vuepress-plugin-flexsearch\": \"^0.3.0\",\n        \"vuepress-plugin-redirect\": \"^1.2.5\",\n        \"vuepress-plugin-tabs\": \"^0.3.0\",\n        \"vuepress-plugin-typedoc\": \"^0.11.0\",\n        \"vuepress-theme-chartjs\": \"^0.2.0\",\n        \"webpack\": \"^4.46.0\"\n    }\n}\n"
  },
  {
    "path": "docs/samples/.eslintrc.yml",
    "content": "rules:\n  no-console: \"off\"\n"
  },
  {
    "path": "docs/samples/advanced/data-decimation.md",
    "content": "# Data Decimation\n\nThis example shows how to use the built-in data decimation to reduce the number of points drawn on the graph for improved performance.\n\n```js chart-editor\n// <block:actions:3>\nconst actions = [\n  {\n    name: 'No decimation (default)',\n    handler(chart) {\n      chart.options.plugins.decimation.enabled = false;\n      chart.update();\n    }\n  },\n  {\n    name: 'min-max decimation',\n    handler(chart) {\n      chart.options.plugins.decimation.algorithm = 'min-max';\n      chart.options.plugins.decimation.enabled = true;\n      chart.update();\n    },\n  },\n  {\n    name: 'LTTB decimation (50 samples)',\n    handler(chart) {\n      chart.options.plugins.decimation.algorithm = 'lttb';\n      chart.options.plugins.decimation.enabled = true;\n      chart.options.plugins.decimation.samples = 50;\n      chart.update();\n    }\n  },\n  {\n    name: 'LTTB decimation (500 samples)',\n    handler(chart) {\n      chart.options.plugins.decimation.algorithm = 'lttb';\n      chart.options.plugins.decimation.enabled = true;\n      chart.options.plugins.decimation.samples = 500;\n      chart.update();\n    }\n  }\n];\n// </block:actions>\n\n// <block:data:1>\nconst NUM_POINTS = 100000;\nUtils.srand(10);\n\n// parseISODate returns a luxon date object to work with in the samples\n// We will create points every 30s starting from this point in time\nconst start = Utils.parseISODate('2021-04-01T00:00:00Z').toMillis();\nconst pointData = [];\n\nfor (let i = 0; i < NUM_POINTS; ++i) {\n  // Most data will be in the range [0, 20) but some rare data will be in the range [0, 100)\n  const max = Math.random() < 0.001 ? 100 : 20;\n  pointData.push({x: start + (i * 30000), y: Utils.rand(0, max)});\n}\n\nconst data = {\n  datasets: [{\n    borderColor: Utils.CHART_COLORS.red,\n    borderWidth: 1,\n    data: pointData,\n    label: 'Large Dataset',\n    radius: 0,\n  }]\n};\n// </block:data>\n\n// <block:decimation:0>\nconst decimation = {\n  enabled: false,\n  algorithm: 'min-max',\n};\n// </block:decimation>\n\n// <block:setup:2>\nconst config = {\n  type: 'line',\n  data: data,\n  options: {\n    // Turn off animations and data parsing for performance\n    animation: false,\n    parsing: false,\n\n    interaction: {\n      mode: 'nearest',\n      axis: 'x',\n      intersect: false\n    },\n    plugins: {\n      decimation: decimation,\n    },\n    scales: {\n      x: {\n        type: 'time',\n        ticks: {\n          source: 'auto',\n          // Disabled rotation for performance\n          maxRotation: 0,\n          autoSkip: true,\n        }\n      }\n    }\n  }\n};\n// </block:setup>\n\nmodule.exports = {\n  actions: actions,\n  config: config,\n};\n```\n## Docs\n* [Data Decimation](../../configuration/decimation.md)\n* [Line](../../charts/line.md)\n* [Time Scale](../../axes/cartesian/time.md)\n\n"
  },
  {
    "path": "docs/samples/advanced/derived-axis-type.md",
    "content": "# Derived Axis Type\n\n```js chart-editor\n// <block:setup:1>\nconst DATA_COUNT = 12;\nconst NUMBER_CFG = {count: DATA_COUNT, min: 0, max: 1000};\nconst labels = Utils.months({count: DATA_COUNT});\nconst data = {\n  labels: labels,\n  datasets: [\n    {\n      label: 'My First dataset',\n      data: Utils.numbers(NUMBER_CFG),\n      borderColor: Utils.CHART_COLORS.red,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),\n      fill: false,\n    }\n  ],\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'line',\n  data,\n  options: {\n    responsive: true,\n    scales: {\n      x: {\n        display: true,\n      },\n      y: {\n        display: true,\n        type: 'log2',\n      }\n    }\n  }\n};\n\n// </block:config>\n\nmodule.exports = {\n  actions: [],\n  config: config,\n};\n```\n\n## Log2 axis implementation\n\n<<< @/scripts/log2.js\n\n## Docs\n* [Data structures (`labels`)](../../general/data-structures.md)\n* [Line](../../charts/line.md)\n* [New Axes](../../developers/axes.md)\n"
  },
  {
    "path": "docs/samples/advanced/derived-chart-type.md",
    "content": "# Derived Chart Type\n\n```js chart-editor\n// <block:setup:1>\nconst DATA_COUNT = 7;\nconst NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100, rmin: 1, rmax: 20};\nconst data = {\n  datasets: [\n    {\n      label: 'My First dataset',\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),\n      borderColor: Utils.CHART_COLORS.blue,\n      borderWidth: 1,\n      boxStrokeStyle: 'red',\n      data: Utils.bubbles(NUMBER_CFG)\n    }\n  ],\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'derivedBubble',\n  data: data,\n  options: {\n    responsive: true,\n    plugins: {\n      title: {\n        display: true,\n        text: 'Derived Chart Type'\n      },\n    }\n  }\n};\n\n// </block:config>\n\nmodule.exports = {\n  actions: [],\n  config: config,\n};\n```\n\n## DerivedBubble Implementation\n\n<<< @/scripts/derived-bubble.js\n\n## Docs\n* [Bubble Chart](../../charts/bubble.md)\n* [New Charts](../../developers/charts.md)\n"
  },
  {
    "path": "docs/samples/advanced/linear-gradient.md",
    "content": "# Linear Gradient\n\n```js chart-editor\n// <block:actions:3>\nconst actions = [\n  {\n    name: 'Randomize',\n    handler(chart) {\n      chart.data.datasets.forEach(dataset => {\n        dataset.data = Utils.numbers({count: chart.data.labels.length, min: -100, max: 100});\n      });\n      chart.update();\n    }\n  },\n  {\n    name: 'Add Data',\n    handler(chart) {\n      const data = chart.data;\n      if (data.datasets.length > 0) {\n        data.labels = Utils.months({count: data.labels.length + 1});\n\n        for (let index = 0; index < data.datasets.length; ++index) {\n          data.datasets[index].data.push(Utils.rand(-100, 100));\n        }\n\n        chart.update();\n      }\n    }\n  },\n  {\n    name: 'Remove Data',\n    handler(chart) {\n      chart.data.labels.splice(-1, 1); // remove the label first\n\n      chart.data.datasets.forEach(dataset => {\n        dataset.data.pop();\n      });\n\n      chart.update();\n    }\n  }\n];\n// </block:actions>\n\n// <block:getGradient:0>\nlet width, height, gradient;\nfunction getGradient(ctx, chartArea) {\n  const chartWidth = chartArea.right - chartArea.left;\n  const chartHeight = chartArea.bottom - chartArea.top;\n  if (!gradient || width !== chartWidth || height !== chartHeight) {\n    // Create the gradient because this is either the first render\n    // or the size of the chart has changed\n    width = chartWidth;\n    height = chartHeight;\n    gradient = ctx.createLinearGradient(0, chartArea.bottom, 0, chartArea.top);\n    gradient.addColorStop(0, Utils.CHART_COLORS.blue);\n    gradient.addColorStop(0.5, Utils.CHART_COLORS.yellow);\n    gradient.addColorStop(1, Utils.CHART_COLORS.red);\n  }\n\n  return gradient;\n}\n// </block:getGradient>\n\n// <block:setup:2>\nconst DATA_COUNT = 7;\nconst NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100};\nconst labels = Utils.months({count: 7});\n\nconst data = {\n  labels: labels,\n  datasets: [\n    {\n      label: 'Dataset 1',\n      data: Utils.numbers(NUMBER_CFG),\n      borderColor: function(context) {\n        const chart = context.chart;\n        const {ctx, chartArea} = chart;\n\n        if (!chartArea) {\n          // This case happens on initial chart load\n          return;\n        }\n        return getGradient(ctx, chartArea);\n      },\n    },\n  ]\n};\n// </block:setup>\n\n// <block:config:1>\nconst config = {\n  type: 'line',\n  data: data,\n  options: {\n    responsive: true,\n    plugins: {\n      legend: {\n        position: 'top',\n      },\n    }\n  },\n};\n// </block:config>\n\nmodule.exports = {\n  actions: actions,\n  config: config,\n};\n```\n\n## Docs\n* [Colors](../../general/colors.md)\n  * [Patterns and Gradients](../../general/colors.md#patterns-and-gradients)  \n* [Data structures (`labels`)](../../general/data-structures.md)\n* [Options](../../general/options.md)\n  * [Scriptable Options](../../general/options.md#scriptable-options)\n* [Line](../../charts/line.md)\n"
  },
  {
    "path": "docs/samples/advanced/programmatic-events.md",
    "content": "# Programmatic Event Triggers\n\n```js chart-editor\n// <block:hover:0>\nfunction triggerHover(chart) {\n  if (chart.getActiveElements().length > 0) {\n    chart.setActiveElements([]);\n  } else {\n    chart.setActiveElements([\n      {\n        datasetIndex: 0,\n        index: 0,\n      }, {\n        datasetIndex: 1,\n        index: 0,\n      }\n    ]);\n  }\n  chart.update();\n}\n// </block:hover>\n\n// <block:tooltip:1>\nfunction triggerTooltip(chart) {\n  const tooltip = chart.tooltip;\n  if (tooltip.getActiveElements().length > 0) {\n    tooltip.setActiveElements([], {x: 0, y: 0});\n  } else {\n    const chartArea = chart.chartArea;\n    tooltip.setActiveElements([\n      {\n        datasetIndex: 0,\n        index: 2,\n      }, {\n        datasetIndex: 1,\n        index: 2,\n      }\n    ],\n    {\n      x: (chartArea.left + chartArea.right) / 2,\n      y: (chartArea.top + chartArea.bottom) / 2,\n    });\n  }\n\n  chart.update();\n}\n// </block:tooltip>\n\n// <block:actions:2>\nconst actions = [\n  {\n    name: 'Trigger Hover',\n    handler: triggerHover\n  },\n  {\n    name: 'Trigger Tooltip',\n    handler: triggerTooltip\n  }\n];\n// </block:actions>\n\n// <block:setup:4>\nconst DATA_COUNT = 7;\nconst NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100};\n\nconst labels = Utils.months({count: 7});\nconst data = {\n  labels: labels,\n  datasets: [\n    {\n      label: 'Dataset 1',\n      data: Utils.numbers(NUMBER_CFG),\n      borderColor: Utils.CHART_COLORS.red,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),\n      hoverBorderWidth: 5,\n      hoverBorderColor: 'green',\n    },\n    {\n      label: 'Dataset 2',\n      data: Utils.numbers(NUMBER_CFG),\n      borderColor: Utils.CHART_COLORS.blue,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),\n      hoverBorderWidth: 5,\n      hoverBorderColor: 'green',\n    }\n  ]\n};\n// </block:setup>\n\n// <block:config:3>\nconst config = {\n  type: 'bar',\n  data: data,\n  options: {\n  },\n};\n// </block:config>\n\nmodule.exports = {\n  actions: actions,\n  config: config,\n};\n```\n\n## API\n* [Chart](../../api/classes/Chart.md)\n  * [`setActiveElements`](../../api/classes/Chart.md#setactiveelements)\n* [TooltipModel](../../api/interfaces/TooltipModel.md)\n  * [`setActiveElements`](../../api/interfaces/TooltipModel.md#setactiveelements)\n\n## Docs\n* [Bar](../../charts/bar.md)\n    * [Interactions (`hoverBorderColor`)](../../charts/bar.md#interactions)\n* [Interactions](../../configuration/interactions.md)\n* [Tooltip](../../configuration/tooltip.md)\n"
  },
  {
    "path": "docs/samples/advanced/progress-bar.md",
    "content": "# Animation Progress Bar\n\n## Initial animation\n\n<progress id=\"initialProgress\" max=\"1\" value=\"0\" style=\"width: 100%\"></progress>\n\n## Other animations\n\n<progress id=\"animationProgress\" max=\"1\" value=\"0\" style=\"width: 100%\"></progress>\n\n```js chart-editor\n// <block:actions:2>\nconst actions = [\n  {\n    name: 'Randomize',\n    handler(chart) {\n      chart.data.datasets.forEach(dataset => {\n        dataset.data = Utils.numbers({count: chart.data.labels.length, min: -100, max: 100});\n      });\n      chart.update();\n    }\n  },\n  {\n    name: 'Add Dataset',\n    handler(chart) {\n      const data = chart.data;\n      const dsColor = Utils.namedColor(chart.data.datasets.length);\n      const newDataset = {\n        label: 'Dataset ' + (data.datasets.length + 1),\n        backgroundColor: Utils.transparentize(dsColor, 0.5),\n        borderColor: dsColor,\n        data: Utils.numbers({count: data.labels.length, min: -100, max: 100}),\n      };\n      chart.data.datasets.push(newDataset);\n      chart.update();\n    }\n  },\n  {\n    name: 'Add Data',\n    handler(chart) {\n      const data = chart.data;\n      if (data.datasets.length > 0) {\n        data.labels = Utils.months({count: data.labels.length + 1});\n\n        for (let index = 0; index < data.datasets.length; ++index) {\n          data.datasets[index].data.push(Utils.rand(-100, 100));\n        }\n\n        chart.update();\n      }\n    }\n  },\n  {\n    name: 'Remove Dataset',\n    handler(chart) {\n      chart.data.datasets.pop();\n      chart.update();\n    }\n  },\n  {\n    name: 'Remove Data',\n    handler(chart) {\n      chart.data.labels.splice(-1, 1); // remove the label first\n\n      chart.data.datasets.forEach(dataset => {\n        dataset.data.pop();\n      });\n\n      chart.update();\n    }\n  }\n];\n// </block:actions>\n\n// <block:setup:1>\nconst initProgress = document.getElementById('initialProgress');\nconst progress = document.getElementById('animationProgress');\n\nconst DATA_COUNT = 7;\nconst NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100};\n\nconst labels = Utils.months({count: 7});\nconst data = {\n  labels: labels,\n  datasets: [\n    {\n      label: 'Dataset 1',\n      data: Utils.numbers(NUMBER_CFG),\n      borderColor: Utils.CHART_COLORS.red,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),\n    },\n    {\n      label: 'Dataset 2',\n      data: Utils.numbers(NUMBER_CFG),\n      borderColor: Utils.CHART_COLORS.blue,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),\n    }\n  ]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'line',\n  data: data,\n  options: {\n    animation: {\n      duration: 2000,\n      onProgress: function(context) {\n        if (context.initial) {\n          initProgress.value = context.currentStep / context.numSteps;\n        } else {\n          progress.value = context.currentStep / context.numSteps;\n        }\n      },\n      onComplete: function(context) {\n        if (context.initial) {\n          console.log('Initial animation finished');\n        } else {\n          console.log('animation finished');\n        }\n      }\n    },\n    interaction: {\n      mode: 'nearest',\n      axis: 'x',\n      intersect: false\n    },\n    plugins: {\n      title: {\n        display: true,\n        text: 'Chart.js Line Chart - Animation Progress Bar'\n      }\n    },\n  },\n};\n// </block:config>\n\nmodule.exports = {\n  actions: actions,\n  config: config,\n  output: 'console.log output is displayed here'\n};\n```\n\n## Docs\n* [Animations](../../configuration/animations.md)\n  * [Animation Callbacks](../../configuration/animations.md#animation-callbacks)\n* [Data structures (`labels`)](../../general/data-structures.md)\n* [Line](../../charts/line.md)\n* [Options](../../general/options.md)\n  * [Scriptable Options](../../general/options.md#scriptable-options)\n"
  },
  {
    "path": "docs/samples/advanced/radial-gradient.md",
    "content": "# Radial Gradient\n\n```js chart-editor\n// <block:setup:3>\nconst DATA_COUNT = 5;\nUtils.srand(110);\n\nconst chartColors = Utils.CHART_COLORS;\nconst colors = [chartColors.red, chartColors.orange, chartColors.yellow, chartColors.green, chartColors.blue];\n\nconst cache = new Map();\nlet width = null;\nlet height = null;\n\nconst actions = [\n  {\n    name: 'Randomize',\n    handler(chart) {\n      chart.data.datasets.forEach(dataset => {\n        dataset.data = generateData();\n      });\n      chart.update();\n    }\n  },\n];\n// </block:setup>\n\n// <block:createRadialGradient3:0>\nfunction createRadialGradient3(context, c1, c2, c3) {\n  const chartArea = context.chart.chartArea;\n  if (!chartArea) {\n    // This case happens on initial chart load\n    return;\n  }\n\n  const chartWidth = chartArea.right - chartArea.left;\n  const chartHeight = chartArea.bottom - chartArea.top;\n  if (width !== chartWidth || height !== chartHeight) {\n    cache.clear();\n  }\n  let gradient = cache.get(c1 + c2 + c3);\n  if (!gradient) {\n    // Create the gradient because this is either the first render\n    // or the size of the chart has changed\n    width = chartWidth;\n    height = chartHeight;\n    const centerX = (chartArea.left + chartArea.right) / 2;\n    const centerY = (chartArea.top + chartArea.bottom) / 2;\n    const r = Math.min(\n      (chartArea.right - chartArea.left) / 2,\n      (chartArea.bottom - chartArea.top) / 2\n    );\n    const ctx = context.chart.ctx;\n    gradient = ctx.createRadialGradient(centerX, centerY, 0, centerX, centerY, r);\n    gradient.addColorStop(0, c1);\n    gradient.addColorStop(0.5, c2);\n    gradient.addColorStop(1, c3);\n    cache.set(c1 + c2 + c3, gradient);\n  }\n\n  return gradient;\n}\n// </block:createRadialGradient3>\n\n// <block:data:2>\nfunction generateData() {\n  return Utils.numbers({\n    count: DATA_COUNT,\n    min: 0,\n    max: 100\n  });\n}\n\nconst data = {\n  labels: Utils.months({count: DATA_COUNT}),\n  datasets: [{\n    data: generateData()\n  }]\n};\n// </block:data>\n\n// <block:config:1>\nconst config = {\n  type: 'polarArea',\n  data: data,\n  options: {\n    plugins: {\n      legend: false,\n      tooltip: false,\n    },\n    elements: {\n      arc: {\n        backgroundColor: function(context) {\n          let c = colors[context.dataIndex];\n          if (!c) {\n            return;\n          }\n          if (context.active) {\n            c = helpers.getHoverColor(c);\n          }\n          const mid = helpers.color(c).desaturate(0.2).darken(0.2).rgbString();\n          const start = helpers.color(c).lighten(0.2).rotate(270).rgbString();\n          const end = helpers.color(c).lighten(0.1).rgbString();\n          return createRadialGradient3(context, start, mid, end);\n        },\n      }\n    }\n  }\n};\n// </block:config>\n\nmodule.exports = {\n  actions,\n  config,\n};\n```\n\n## Docs\n* [Polar Area Chart](../../charts/polar.md)\n  * [Styling](../../charts/polar.md#styling)\n* [Options](../../general/options.md)\n  * [Scriptable Options](../../general/options.md#scriptable-options)"
  },
  {
    "path": "docs/samples/animations/delay.md",
    "content": "# Delay\n\n```js chart-editor\n// <block:actions:2>\nconst actions = [\n  {\n    name: 'Randomize',\n    handler(chart) {\n      chart.data.datasets.forEach(dataset => {\n        dataset.data = Utils.numbers({count: chart.data.labels.length, min: -100, max: 100});\n      });\n      chart.update();\n    }\n  },\n];\n// </block:actions>\n\n// <block:setup:1>\nconst DATA_COUNT = 7;\nconst NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100};\n\nconst labels = Utils.months({count: 7});\nconst data = {\n  labels: labels,\n  datasets: [\n    {\n      label: 'Dataset 1',\n      data: Utils.numbers(NUMBER_CFG),\n      backgroundColor: Utils.CHART_COLORS.red,\n    },\n    {\n      label: 'Dataset 2',\n      data: Utils.numbers(NUMBER_CFG),\n      backgroundColor: Utils.CHART_COLORS.blue,\n    },\n    {\n      label: 'Dataset 3',\n      data: Utils.numbers(NUMBER_CFG),\n      backgroundColor: Utils.CHART_COLORS.green,\n    },\n  ]\n};\n// </block:setup>\n\n// <block:config:0>\nlet delayed;\nconst config = {\n  type: 'bar',\n  data: data,\n  options: {\n    animation: {\n      onComplete: () => {\n        delayed = true;\n      },\n      delay: (context) => {\n        let delay = 0;\n        if (context.type === 'data' && context.mode === 'default' && !delayed) {\n          delay = context.dataIndex * 300 + context.datasetIndex * 100;\n        }\n        return delay;\n      },\n    },\n    scales: {\n      x: {\n        stacked: true,\n      },\n      y: {\n        stacked: true\n      }\n    }\n  }\n};\n// </block:config>\n\nmodule.exports = {\n  actions: actions,\n  config: config,\n};\n```\n## Docs\n* [Animations](../../configuration/animations.md)\n  * [animation (`delay`)](../../configuration/animations.md#animation)\n  * [Animation Callbacks](../../configuration/animations.md#animation-callbacks)\n* [Bar](../../charts/bar.md)\n  * [Stacked Bar Chart](../../charts/bar.md#stacked-bar-chart)\n* [Options](../../general/options.md)\n  * [Scriptable Options](../../general/options.md#scriptable-options)\n"
  },
  {
    "path": "docs/samples/animations/drop.md",
    "content": "# Drop\n\n```js chart-editor\n// <block:actions:2>\nconst actions = [\n  {\n    name: 'Randomize',\n    handler(chart) {\n      chart.data.datasets.forEach(dataset => {\n        dataset.data = Utils.numbers({count: chart.data.labels.length, min: -100, max: 100});\n      });\n      chart.update();\n    }\n  },\n  {\n    name: 'Add Dataset',\n    handler(chart) {\n      const data = chart.data;\n      const dsColor = Utils.namedColor(chart.data.datasets.length);\n      const newDataset = {\n        label: 'Dataset ' + (data.datasets.length + 1),\n        backgroundColor: Utils.transparentize(dsColor, 0.5),\n        borderColor: dsColor,\n        data: Utils.numbers({count: data.labels.length, min: -100, max: 100}),\n      };\n      chart.data.datasets.push(newDataset);\n      chart.update();\n    }\n  },\n  {\n    name: 'Add Data',\n    handler(chart) {\n      const data = chart.data;\n      if (data.datasets.length > 0) {\n        data.labels = Utils.months({count: data.labels.length + 1});\n\n        for (let index = 0; index < data.datasets.length; ++index) {\n          data.datasets[index].data.push(Utils.rand(-100, 100));\n        }\n\n        chart.update();\n      }\n    }\n  },\n  {\n    name: 'Remove Dataset',\n    handler(chart) {\n      chart.data.datasets.pop();\n      chart.update();\n    }\n  },\n  {\n    name: 'Remove Data',\n    handler(chart) {\n      chart.data.labels.splice(-1, 1); // remove the label first\n\n      chart.data.datasets.forEach(dataset => {\n        dataset.data.pop();\n      });\n\n      chart.update();\n    }\n  }\n];\n// </block:actions>\n\n// <block:setup:1>\nconst DATA_COUNT = 7;\nconst NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100};\n\nconst labels = Utils.months({count: 7});\nconst data = {\n  labels: labels,\n  datasets: [\n    {\n      label: 'Dataset 1',\n      animations: {\n        y: {\n          duration: 2000,\n          delay: 500\n        }\n      },\n      data: Utils.numbers(NUMBER_CFG),\n      borderColor: Utils.CHART_COLORS.red,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),\n      fill: 1,\n      tension: 0.5\n    },\n    {\n      label: 'Dataset 2',\n      data: Utils.numbers(NUMBER_CFG),\n      borderColor: Utils.CHART_COLORS.blue,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),\n    }\n  ]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'line',\n  data: data,\n  options: {\n    animations: {\n      y: {\n        easing: 'easeInOutElastic',\n        from: (ctx) => {\n          if (ctx.type === 'data') {\n            if (ctx.mode === 'default' && !ctx.dropped) {\n              ctx.dropped = true;\n              return 0;\n            }\n          }\n        }\n      }\n    },\n  },\n};\n// </block:config>\n\nmodule.exports = {\n  actions: actions,\n  config: config,\n};\n```\n## Docs\n* [Area](../../charts/area.md)\n* [Animations](../../configuration/animations.md)\n  * [animation (`easing`)](../../configuration/animations.md#animation)\n  * [animations (`from`)](../../configuration/animations.md#animations-2)\n* [Line](../../charts/line.md)\n  * [Line Styling](../../charts/line.md#line-styling)\n    * `fill`\n    * `tension`\n* [Options](../../general/options.md)\n  * [Scriptable Options](../../general/options.md#scriptable-options)\n"
  },
  {
    "path": "docs/samples/animations/loop.md",
    "content": "# Loop\n\n```js chart-editor\n// <block:actions:2>\nconst actions = [\n  {\n    name: 'Randomize',\n    handler(chart) {\n      chart.data.datasets.forEach(dataset => {\n        dataset.data = Utils.numbers({count: chart.data.labels.length, min: -100, max: 100});\n      });\n      chart.update();\n    }\n  },\n  {\n    name: 'Add Dataset',\n    handler(chart) {\n      const data = chart.data;\n      const dsColor = Utils.namedColor(chart.data.datasets.length);\n      const newDataset = {\n        label: 'Dataset ' + (data.datasets.length + 1),\n        backgroundColor: Utils.transparentize(dsColor, 0.5),\n        borderColor: dsColor,\n        data: Utils.numbers({count: data.labels.length, min: -100, max: 100}),\n      };\n      chart.data.datasets.push(newDataset);\n      chart.update();\n    }\n  },\n  {\n    name: 'Add Data',\n    handler(chart) {\n      const data = chart.data;\n      if (data.datasets.length > 0) {\n        data.labels = Utils.months({count: data.labels.length + 1});\n\n        for (let index = 0; index < data.datasets.length; ++index) {\n          data.datasets[index].data.push(Utils.rand(-100, 100));\n        }\n\n        chart.update();\n      }\n    }\n  },\n  {\n    name: 'Remove Dataset',\n    handler(chart) {\n      chart.data.datasets.pop();\n      chart.update();\n    }\n  },\n  {\n    name: 'Remove Data',\n    handler(chart) {\n      chart.data.labels.splice(-1, 1); // remove the label first\n\n      chart.data.datasets.forEach(dataset => {\n        dataset.data.pop();\n      });\n\n      chart.update();\n    }\n  }\n];\n// </block:actions>\n\n// <block:setup:1>\nconst DATA_COUNT = 7;\nconst NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100};\n\nconst labels = Utils.months({count: DATA_COUNT});\nconst data = {\n  labels: labels,\n  datasets: [\n    {\n      label: 'Dataset 1',\n      data: Utils.numbers(NUMBER_CFG),\n      borderColor: Utils.CHART_COLORS.red,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),\n      tension: 0.4,\n    },\n    {\n      label: 'Dataset 2',\n      data: Utils.numbers(NUMBER_CFG),\n      borderColor: Utils.CHART_COLORS.blue,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),\n      tension: 0.2,\n    }\n  ]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'line',\n  data: data,\n  options: {\n    animations: {\n      radius: {\n        duration: 400,\n        easing: 'linear',\n        loop: (context) => context.active\n      }\n    },\n    hoverRadius: 12,\n    hoverBackgroundColor: 'yellow',\n    interaction: {\n      mode: 'nearest',\n      intersect: false,\n      axis: 'x'\n    },\n    plugins: {\n      tooltip: {\n        enabled: false\n      }\n    }\n  },\n};\n// </block:config>\n\nmodule.exports = {\n  actions: actions,\n  config: config,\n};\n```\n## Docs\n* [Animations](../../configuration/animations.md)\n  * [animation](../../configuration/animations.md#animation)\n    * `duration`\n    * `easing`\n    * **`loop`**\n  * [Default animations (`radius`)](../../configuration/animations.md#default-animations)\n* [Data structures (`labels`)](../../general/data-structures.md)\n* [Elements](../../configuration/elements.md)\n  * [Point Configuration](../../configuration/elements.md#point-configuration)\n    * `hoverRadius`\n    * `hoverBackgroundColor`\n* [Line](../../charts/line.md)\n* [Options](../../general/options.md)\n  * [Scriptable Options](../../general/options.md#scriptable-options)\n* [Tooltip (`enabled`)](../../configuration/tooltip.md)\n"
  },
  {
    "path": "docs/samples/animations/progressive-line-easing.md",
    "content": "# Progressive Line With Easing\n\n```js chart-editor\n\n// <block:data:2>\nconst data = [];\nconst data2 = [];\nlet prev = 100;\nlet prev2 = 80;\nfor (let i = 0; i < 1000; i++) {\n  prev += 5 - Math.random() * 10;\n  data.push({x: i, y: prev});\n  prev2 += 5 - Math.random() * 10;\n  data2.push({x: i, y: prev2});\n}\n// </block:data>\n\n// <block:animation:1>\nlet easing = helpers.easingEffects.easeOutQuad;\nlet restart = false;\nconst totalDuration = 5000;\nconst duration = (ctx) => easing(ctx.index / data.length) * totalDuration / data.length;\nconst delay = (ctx) => easing(ctx.index / data.length) * totalDuration;\nconst previousY = (ctx) => ctx.index === 0 ? ctx.chart.scales.y.getPixelForValue(100) : ctx.chart.getDatasetMeta(ctx.datasetIndex).data[ctx.index - 1].getProps(['y'], true).y;\nconst animation = {\n  x: {\n    type: 'number',\n    easing: 'linear',\n    duration: duration,\n    from: NaN, // the point is initially skipped\n    delay(ctx) {\n      if (ctx.type !== 'data' || ctx.xStarted) {\n        return 0;\n      }\n      ctx.xStarted = true;\n      return delay(ctx);\n    }\n  },\n  y: {\n    type: 'number',\n    easing: 'linear',\n    duration: duration,\n    from: previousY,\n    delay(ctx) {\n      if (ctx.type !== 'data' || ctx.yStarted) {\n        return 0;\n      }\n      ctx.yStarted = true;\n      return delay(ctx);\n    }\n  }\n};\n// </block:animation>\n\n// <block:config:0>\nconst config = {\n  type: 'line',\n  data: {\n    datasets: [{\n      borderColor: Utils.CHART_COLORS.red,\n      borderWidth: 1,\n      radius: 0,\n      data: data,\n    },\n    {\n      borderColor: Utils.CHART_COLORS.blue,\n      borderWidth: 1,\n      radius: 0,\n      data: data2,\n    }]\n  },\n  options: {\n    animation,\n    interaction: {\n      intersect: false\n    },\n    plugins: {\n      legend: false,\n      title: {\n        display: true,\n        text: () => easing.name\n      }\n    },\n    scales: {\n      x: {\n        type: 'linear'\n      }\n    }\n  }\n};\n// </block:config>\n\n// <block:actions:2>\nfunction restartAnims(chart) {\n  chart.stop();\n  const meta0 = chart.getDatasetMeta(0);\n  const meta1 = chart.getDatasetMeta(1);\n  for (let i = 0; i < data.length; i++) {\n    const ctx0 = meta0.controller.getContext(i);\n    const ctx1 = meta1.controller.getContext(i);\n    ctx0.xStarted = ctx0.yStarted = false;\n    ctx1.xStarted = ctx1.yStarted = false;\n  }\n  chart.update();\n}\n\nconst actions = [\n  {\n    name: 'easeOutQuad',\n    handler(chart) {\n      easing = helpers.easingEffects.easeOutQuad;\n      restartAnims(chart);\n    }\n  },\n  {\n    name: 'easeOutCubic',\n    handler(chart) {\n      easing = helpers.easingEffects.easeOutCubic;\n      restartAnims(chart);\n    }\n  },\n  {\n    name: 'easeOutQuart',\n    handler(chart) {\n      easing = helpers.easingEffects.easeOutQuart;\n      restartAnims(chart);\n    }\n  },\n  {\n    name: 'easeOutQuint',\n    handler(chart) {\n      easing = helpers.easingEffects.easeOutQuint;\n      restartAnims(chart);\n    }\n  },\n  {\n    name: 'easeInQuad',\n    handler(chart) {\n      easing = helpers.easingEffects.easeInQuad;\n      restartAnims(chart);\n    }\n  },\n  {\n    name: 'easeInCubic',\n    handler(chart) {\n      easing = helpers.easingEffects.easeInCubic;\n      restartAnims(chart);\n    }\n  },\n  {\n    name: 'easeInQuart',\n    handler(chart) {\n      easing = helpers.easingEffects.easeInQuart;\n      restartAnims(chart);\n    }\n  },\n  {\n    name: 'easeInQuint',\n    handler(chart) {\n      easing = helpers.easingEffects.easeInQuint;\n      restartAnims(chart);\n    }\n  },\n];\n// </block:actions>\n\nmodule.exports = {\n  config,\n  actions\n};\n\n```\n## Api \n* [Chart](../../api/classes/Chart.md)\n  * [`getDatasetMeta`](../../api/classes/Chart.md#getdatasetmeta)\n* [Scale](../../api/classes/Scale.md)\n  * [`getPixelForValue`](../../api/classes/Scale.md#getpixelforvalue)\n## Docs\n* [Animations](../../configuration/animations.md)\n  * [animation](../../configuration/animations.md#animation)\n    * `delay`\n    * `duration`\n    * `easing`\n    * `loop`\n  * [Easing](../../configuration/animations.md#easing)\n* [Line](../../charts/line.md)\n* [Options](../../general/options.md)\n  * [Scriptable Options](../../general/options.md#scriptable-options)\n    * [Data Context](../../general/options.md#data)\n"
  },
  {
    "path": "docs/samples/animations/progressive-line.md",
    "content": "# Progressive Line\n\n```js chart-editor\n\n// <block:data:2>\nconst data = [];\nconst data2 = [];\nlet prev = 100;\nlet prev2 = 80;\nfor (let i = 0; i < 1000; i++) {\n  prev += 5 - Math.random() * 10;\n  data.push({x: i, y: prev});\n  prev2 += 5 - Math.random() * 10;\n  data2.push({x: i, y: prev2});\n}\n// </block:data>\n\n// <block:animation:1>\nconst totalDuration = 10000;\nconst delayBetweenPoints = totalDuration / data.length;\nconst previousY = (ctx) => ctx.index === 0 ? ctx.chart.scales.y.getPixelForValue(100) : ctx.chart.getDatasetMeta(ctx.datasetIndex).data[ctx.index - 1].getProps(['y'], true).y;\nconst animation = {\n  x: {\n    type: 'number',\n    easing: 'linear',\n    duration: delayBetweenPoints,\n    from: NaN, // the point is initially skipped\n    delay(ctx) {\n      if (ctx.type !== 'data' || ctx.xStarted) {\n        return 0;\n      }\n      ctx.xStarted = true;\n      return ctx.index * delayBetweenPoints;\n    }\n  },\n  y: {\n    type: 'number',\n    easing: 'linear',\n    duration: delayBetweenPoints,\n    from: previousY,\n    delay(ctx) {\n      if (ctx.type !== 'data' || ctx.yStarted) {\n        return 0;\n      }\n      ctx.yStarted = true;\n      return ctx.index * delayBetweenPoints;\n    }\n  }\n};\n// </block:animation>\n\n// <block:config:0>\nconst config = {\n  type: 'line',\n  data: {\n    datasets: [{\n      borderColor: Utils.CHART_COLORS.red,\n      borderWidth: 1,\n      radius: 0,\n      data: data,\n    },\n    {\n      borderColor: Utils.CHART_COLORS.blue,\n      borderWidth: 1,\n      radius: 0,\n      data: data2,\n    }]\n  },\n  options: {\n    animation,\n    interaction: {\n      intersect: false\n    },\n    plugins: {\n      legend: false\n    },\n    scales: {\n      x: {\n        type: 'linear'\n      }\n    }\n  }\n};\n// </block:config>\n\nmodule.exports = {\n  config\n};\n\n```\n\n## Api \n* [Chart](../../api/classes/Chart.md)\n  * [`getDatasetMeta`](../../api/classes/Chart.md#getdatasetmeta)\n* [Scale](../../api/classes/Scale.md)\n  * [`getPixelForValue`](../../api/classes/Scale.md#getpixelforvalue)\n## Docs\n* [Animations](../../configuration/animations.md)\n  * [animation](../../configuration/animations.md#animation)\n    * `delay`\n    * `duration`\n    * `easing`\n    * `loop`\n* [Line](../../charts/line.md)\n* [Options](../../general/options.md)\n  * [Scriptable Options](../../general/options.md#scriptable-options)\n    * [Data Context](../../general/options.md#data)\n"
  },
  {
    "path": "docs/samples/area/line-boundaries.md",
    "content": "# Line Chart Boundaries\n\n```js chart-editor\n// <block:setup:2>\nconst inputs = {\n  min: -100,\n  max: 100,\n  count: 8,\n  decimals: 2,\n  continuity: 1\n};\n\nconst generateLabels = () => {\n  return Utils.months({count: inputs.count});\n};\n\nconst generateData = () => (Utils.numbers(inputs));\n// </block:setup>\n\n// <block:data:0>\nconst data = {\n  labels: generateLabels(),\n  datasets: [\n    {\n      label: 'Dataset',\n      data: generateData(),\n      borderColor: Utils.CHART_COLORS.red,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red),\n      fill: false\n    }\n  ]\n};\n// </block:data>\n\n// <block:actions:3>\nlet smooth = false;\n\nconst actions = [\n  {\n    name: 'Fill: false (default)',\n    handler: (chart) => {\n      chart.data.datasets.forEach(dataset => {\n        dataset.fill = false;\n      });\n      chart.update();\n    }\n  },\n  {\n    name: 'Fill: origin',\n    handler: (chart) => {\n      chart.data.datasets.forEach(dataset => {\n        dataset.fill = 'origin';\n      });\n      chart.update();\n    }\n  },\n  {\n    name: 'Fill: start',\n    handler: (chart) => {\n      chart.data.datasets.forEach(dataset => {\n        dataset.fill = 'start';\n      });\n      chart.update();\n    }\n  },\n  {\n    name: 'Fill: end',\n    handler: (chart) => {\n      chart.data.datasets.forEach(dataset => {\n        dataset.fill = 'end';\n      });\n      chart.update();\n    }\n  },\n  {\n    name: 'Randomize',\n    handler(chart) {\n      chart.data.datasets.forEach(dataset => {\n        dataset.data = generateData();\n      });\n      chart.update();\n    }\n  },\n  {\n    name: 'Smooth',\n    handler(chart) {\n      smooth = !smooth;\n      chart.options.elements.line.tension = smooth ? 0.4 : 0;\n      chart.update();\n    }\n  }\n];\n// </block:actions>\n\n// <block:config:1>\nconst config = {\n  type: 'line',\n  data: data,\n  options: {\n    plugins: {\n      filler: {\n        propagate: false,\n      },\n      title: {\n        display: true,\n        text: (ctx) => 'Fill: ' + ctx.chart.data.datasets[0].fill\n      }\n    },\n    interaction: {\n      intersect: false,\n    }\n  },\n};\n// </block:config>\n\nmodule.exports = {\n  actions: actions,\n  config: config,\n};\n```\n\n## Docs\n* [Area](../../charts/area.md)\n  * [Filling modes](../../charts/area.md#filling-modes)\n    * Boundary: `'start'`, `'end'`, `'origin'`\n* [Line](../../charts/line.md)\n* [Data structures (`labels`)](../../general/data-structures.md)\n"
  },
  {
    "path": "docs/samples/area/line-datasets.md",
    "content": "# Line Chart Datasets\n\n```js chart-editor\n// <block:setup:2>\nconst inputs = {\n  min: 20,\n  max: 80,\n  count: 8,\n  decimals: 2,\n  continuity: 1\n};\n\nconst generateLabels = () => {\n  return Utils.months({count: inputs.count});\n};\n\nconst generateData = () => (Utils.numbers(inputs));\n\nUtils.srand(42);\n// </block:setup>\n\n// <block:data:0>\nconst data = {\n  labels: generateLabels(),\n  datasets: [\n    {\n      label: 'D0',\n      data: generateData(),\n      borderColor: Utils.CHART_COLORS.red,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red),\n      hidden: true\n    },\n    {\n      label: 'D1',\n      data: generateData(),\n      borderColor: Utils.CHART_COLORS.orange,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.orange),\n      fill: '-1'\n    },\n    {\n      label: 'D2',\n      data: generateData(),\n      borderColor: Utils.CHART_COLORS.yellow,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.yellow),\n      hidden: true,\n      fill: 1\n    },\n    {\n      label: 'D3',\n      data: generateData(),\n      borderColor: Utils.CHART_COLORS.green,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.green),\n      fill: '-1'\n    },\n    {\n      label: 'D4',\n      data: generateData(),\n      borderColor: Utils.CHART_COLORS.blue,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue),\n      fill: '-1'\n    },\n    {\n      label: 'D5',\n      data: generateData(),\n      borderColor: Utils.CHART_COLORS.grey,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.grey),\n      fill: '+2'\n    },\n    {\n      label: 'D6',\n      data: generateData(),\n      borderColor: Utils.CHART_COLORS.purple,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.purple),\n      fill: false\n    },\n    {\n      label: 'D7',\n      data: generateData(),\n      borderColor: Utils.CHART_COLORS.red,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red),\n      fill: 8\n    },\n    {\n      label: 'D8',\n      data: generateData(),\n      borderColor: Utils.CHART_COLORS.orange,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.orange),\n      fill: 'end',\n      hidden: true\n    },\n    {\n      label: 'D9',\n      data: generateData(),\n      borderColor: Utils.CHART_COLORS.yellow,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.yellow),\n      fill: {above: 'blue', below: 'red', target: {value: 350}}\n    }\n  ]\n};\n// </block:data>\n\n// <block:actions:3>\nlet smooth = false;\nlet propagate = false;\n\nconst actions = [\n  {\n    name: 'Randomize',\n    handler(chart) {\n      chart.data.datasets.forEach(dataset => {\n        dataset.data = generateData();\n      });\n      chart.update();\n    }\n  },\n  {\n    name: 'Propagate',\n    handler(chart) {\n      propagate = !propagate;\n      chart.options.plugins.filler.propagate = propagate;\n      chart.update();\n    }\n  },\n  {\n    name: 'Smooth',\n    handler(chart) {\n      smooth = !smooth;\n      chart.options.elements.line.tension = smooth ? 0.4 : 0;\n      chart.update();\n    }\n  }\n];\n// </block:actions>\n\n// <block:config:1>\nconst config = {\n  type: 'line',\n  data: data,\n  options: {\n    scales: {\n      y: {\n        stacked: true\n      }\n    },\n    plugins: {\n      filler: {\n        propagate: false\n      },\n      'samples-filler-analyser': {\n        target: 'chart-analyser'\n      }\n    },\n    interaction: {\n      intersect: false,\n    },\n  },\n};\n// </block:config>\n\nmodule.exports = {\n  actions: actions,\n  config: config,\n};\n```\n\n<div id=\"chart-analyser\" class=\"analyser\"></div>\n\n## Docs\n* [Area](../../charts/area.md)\n  * [Filling modes](../../charts/area.md#filling-modes)\n* [Line](../../charts/line.md)\n* [Data structures (`labels`)](../../general/data-structures.md)\n* [Axes scales](../../axes/)\n  * [Common options to all axes (`stacked`)](../../axes/#common-options-to-all-axes)\n"
  },
  {
    "path": "docs/samples/area/line-drawtime.md",
    "content": "# Line Chart drawTime\n\n```js chart-editor\n// <block:setup:2>\nconst inputs = {\n  min: -100,\n  max: 100,\n  count: 8,\n  decimals: 2,\n  continuity: 1\n};\n\nconst generateLabels = () => {\n  return Utils.months({count: inputs.count});\n};\n\nUtils.srand(3);\nconst generateData = () => (Utils.numbers(inputs));\n// </block:setup>\n\n// <block:data:0>\nconst data = {\n  labels: generateLabels(),\n  datasets: [\n    {\n      label: 'Dataset 1',\n      data: generateData(),\n      borderColor: Utils.CHART_COLORS.red,\n      backgroundColor: Utils.CHART_COLORS.red,\n      fill: true\n    },\n    {\n      label: 'Dataset 2',\n      data: generateData(),\n      borderColor: Utils.CHART_COLORS.blue,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue),\n      fill: true\n    }\n  ]\n};\n// </block:data>\n\n// <block:actions:3>\nlet smooth = false;\n\nconst actions = [\n  {\n    name: 'drawTime: beforeDatasetDraw (default)',\n    handler: (chart) => {\n      chart.options.plugins.filler.drawTime = 'beforeDatasetDraw';\n      chart.update();\n    }\n  },\n  {\n    name: 'drawTime: beforeDatasetsDraw',\n    handler: (chart) => {\n      chart.options.plugins.filler.drawTime = 'beforeDatasetsDraw';\n      chart.update();\n    }\n  },\n  {\n    name: 'drawTime: beforeDraw',\n    handler: (chart) => {\n      chart.options.plugins.filler.drawTime = 'beforeDraw';\n      chart.update();\n    }\n  },\n  {\n    name: 'Randomize',\n    handler(chart) {\n      chart.data.datasets.forEach(dataset => {\n        dataset.data = generateData();\n      });\n      chart.update();\n    }\n  },\n  {\n    name: 'Smooth',\n    handler(chart) {\n      smooth = !smooth;\n      chart.options.elements.line.tension = smooth ? 0.4 : 0;\n      chart.update();\n    }\n  }\n];\n// </block:actions>\n\n// <block:config:1>\nconst config = {\n  type: 'line',\n  data: data,\n  options: {\n    plugins: {\n      filler: {\n        propagate: false,\n      },\n      title: {\n        display: true,\n        text: (ctx) => 'drawTime: ' + ctx.chart.options.plugins.filler.drawTime\n      }\n    },\n    pointBackgroundColor: '#fff',\n    radius: 10,\n    interaction: {\n      intersect: false,\n    }\n  },\n};\n// </block:config>\n\nmodule.exports = {\n  actions: actions,\n  config: config,\n};\n```\n## Docs\n* [Area](../../charts/area.md)\n  * [Configuration (`drawTime`)](../../charts/area.md#configuration)\n* [Line](../../charts/line.md)\n  * [Line Styling (`tension`)](../../charts/line.md#line-styling)\n* [Data structures (`labels`)](../../general/data-structures.md)\n"
  },
  {
    "path": "docs/samples/area/line-stacked.md",
    "content": "# Line Chart Stacked\n\n```js chart-editor\n// <block:actions:2>\nconst actions = [\n  {\n    name: 'Stacked: true',\n    handler: (chart) => {\n      chart.options.scales.y.stacked = true;\n      chart.update();\n    }\n  },\n  {\n    name: 'Stacked: false (default)',\n    handler: (chart) => {\n      chart.options.scales.y.stacked = false;\n      chart.update();\n    }\n  },\n  {\n    name: 'Stacked Single',\n    handler: (chart) => {\n      chart.options.scales.y.stacked = 'single';\n      chart.update();\n    }\n  },\n  {\n    name: 'Randomize',\n    handler(chart) {\n      chart.data.datasets.forEach(dataset => {\n        dataset.data = Utils.numbers({count: chart.data.labels.length, min: -100, max: 100});\n      });\n      chart.update();\n    }\n  },\n  {\n    name: 'Add Dataset',\n    handler(chart) {\n      const data = chart.data;\n      const dsColor = Utils.namedColor(chart.data.datasets.length);\n      const newDataset = {\n        label: 'Dataset ' + (data.datasets.length + 1),\n        backgroundColor: dsColor,\n        borderColor: dsColor,\n        fill: true,\n        data: Utils.numbers({count: data.labels.length, min: -100, max: 100}),\n      };\n      chart.data.datasets.push(newDataset);\n      chart.update();\n    }\n  },\n  {\n    name: 'Add Data',\n    handler(chart) {\n      const data = chart.data;\n      if (data.datasets.length > 0) {\n        data.labels = Utils.months({count: data.labels.length + 1});\n\n        for (let index = 0; index < data.datasets.length; ++index) {\n          data.datasets[index].data.push(Utils.rand(-100, 100));\n        }\n\n        chart.update();\n      }\n    }\n  },\n  {\n    name: 'Remove Dataset',\n    handler(chart) {\n      chart.data.datasets.pop();\n      chart.update();\n    }\n  },\n  {\n    name: 'Remove Data',\n    handler(chart) {\n      chart.data.labels.splice(-1, 1); // remove the label first\n\n      chart.data.datasets.forEach(dataset => {\n        dataset.data.pop();\n      });\n\n      chart.update();\n    }\n  }\n];\n// </block:actions>\n\n// <block:setup:1>\nconst DATA_COUNT = 7;\nconst NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100};\n\nconst labels = Utils.months({count: 7});\nconst data = {\n  labels: labels,\n  datasets: [\n    {\n      label: 'My First dataset',\n      data: Utils.numbers(NUMBER_CFG),\n      borderColor: Utils.CHART_COLORS.red,\n      backgroundColor: Utils.CHART_COLORS.red,\n      fill: true\n    },\n    {\n      label: 'My Second dataset',\n      data: Utils.numbers(NUMBER_CFG),\n      borderColor: Utils.CHART_COLORS.blue,\n      backgroundColor: Utils.CHART_COLORS.blue,\n      fill: true\n    },\n    {\n      label: 'My Third dataset',\n      data: Utils.numbers(NUMBER_CFG),\n      borderColor: Utils.CHART_COLORS.green,\n      backgroundColor: Utils.CHART_COLORS.green,\n      fill: true\n    },\n    {\n      label: 'My Fourth dataset',\n      data: Utils.numbers(NUMBER_CFG),\n      borderColor: Utils.CHART_COLORS.yellow,\n      backgroundColor: Utils.CHART_COLORS.yellow,\n      fill: true\n    }\n  ]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'line',\n  data: data,\n  options: {\n    responsive: true,\n    plugins: {\n      title: {\n        display: true,\n        text: (ctx) => 'Chart.js Line Chart - stacked=' + ctx.chart.options.scales.y.stacked\n      },\n      tooltip: {\n        mode: 'index'\n      },\n    },\n    interaction: {\n      mode: 'nearest',\n      axis: 'x',\n      intersect: false\n    },\n    scales: {\n      x: {\n        title: {\n          display: true,\n          text: 'Month'\n        }\n      },\n      y: {\n        stacked: true,\n        title: {\n          display: true,\n          text: 'Value'\n        }\n      }\n    }\n  }\n};\n// </block:config>\n\nmodule.exports = {\n  actions: actions,\n  config: config\n};\n```\n\n## Docs\n* [Area](../../charts/area.md)\n  * [Filling modes](../../charts/area.md#filling-modes)\n* [Line](../../charts/line.md)\n* [Data structures (`labels`)](../../general/data-structures.md)\n* [Axes scales](../../axes/)\n  * [Common options to all axes (`stacked`)](../../axes/#common-options-to-all-axes)\n"
  },
  {
    "path": "docs/samples/area/radar.md",
    "content": "# Radar Chart Stacked\n\n```js chart-editor\n// <block:setup:1>\nconst inputs = {\n  min: 8,\n  max: 16,\n  count: 8,\n  decimals: 2,\n  continuity: 1\n};\n\nconst generateLabels = () => {\n  return Utils.months({count: inputs.count});\n};\n\nconst generateData = () => {\n  const values = Utils.numbers(inputs);\n  inputs.from = values;\n  return values;\n};\n\nconst labels = Utils.months({count: 8});\nconst data = {\n  labels: generateLabels(),\n  datasets: [\n    {\n      label: 'D0',\n      data: generateData(),\n      borderColor: Utils.CHART_COLORS.red,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red),\n    },\n    {\n      label: 'D1',\n      data: generateData(),\n      borderColor: Utils.CHART_COLORS.orange,\n      hidden: true,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.orange),\n      fill: '-1'\n    },\n    {\n      label: 'D2',\n      data: generateData(),\n      borderColor: Utils.CHART_COLORS.yellow,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.yellow),\n      fill: 1\n    },\n    {\n      label: 'D3',\n      data: generateData(),\n      borderColor: Utils.CHART_COLORS.green,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.green),\n      fill: false\n    },\n    {\n      label: 'D4',\n      data: generateData(),\n      borderColor: Utils.CHART_COLORS.blue,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue),\n      fill: '-1'\n    },\n    {\n      label: 'D5',\n      data: generateData(),\n      borderColor: Utils.CHART_COLORS.purple,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.purple),\n      fill: '-1'\n    },\n    {\n      label: 'D6',\n      data: generateData(),\n      borderColor: Utils.CHART_COLORS.grey,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.grey),\n      fill: {value: 85}\n    }\n  ]\n};\n// </block:setup>\n\n// <block:actions:2>\nlet smooth = false;\nlet propagate = false;\n\nconst actions = [\n  {\n    name: 'Randomize',\n    handler(chart) {\n      inputs.from = [];\n      chart.data.datasets.forEach(dataset => {\n        dataset.data = generateData();\n      });\n      chart.update();\n    }\n  },\n  {\n    name: 'Propagate',\n    handler(chart) {\n      propagate = !propagate;\n      chart.options.plugins.filler.propagate = propagate;\n      chart.update();\n\n    }\n  },\n  {\n    name: 'Smooth',\n    handler(chart) {\n      smooth = !smooth;\n      chart.options.elements.line.tension = smooth ? 0.4 : 0;\n      chart.update();\n    }\n  }\n];\n// </block:actions>\n\n// <block:config:0>\nconst config = {\n  type: 'radar',\n  data: data,\n  options: {\n    plugins: {\n      filler: {\n        propagate: false\n      },\n      'samples-filler-analyser': {\n        target: 'chart-analyser'\n      }\n    },\n    interaction: {\n      intersect: false\n    }\n  }\n};\n// </block:config>\n\nmodule.exports = {\n  actions: actions,\n  config: config\n};\n```\n\n<div id=\"chart-analyser\" class=\"analyser\"></div>\n\n## Docs\n* [Area](../../charts/area.md)\n  * [Filling modes](../../charts/area.md#filling-modes)\n  * [`propagate`](../../charts/area.md#propagate)\n* [Radar](../../charts/radar.md)\n* [Data structures (`labels`)](../../general/data-structures.md)\n"
  },
  {
    "path": "docs/samples/bar/border-radius.md",
    "content": "# Bar Chart Border Radius\n\n```js chart-editor\n// <block:actions:2>\nconst actions = [\n  {\n    name: 'Randomize',\n    handler(chart) {\n      chart.data.datasets.forEach(dataset => {\n        dataset.data = Utils.numbers({count: chart.data.labels.length, min: -100, max: 100});\n      });\n      chart.update();\n    }\n  },\n];\n// </block:actions>\n\n// <block:setup:1>\nconst DATA_COUNT = 7;\nconst NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100};\n\nconst labels = Utils.months({count: 7});\nconst data = {\n  labels: labels,\n  datasets: [\n    {\n      label: 'Fully Rounded',\n      data: Utils.numbers(NUMBER_CFG),\n      borderColor: Utils.CHART_COLORS.red,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),\n      borderWidth: 2,\n      borderRadius: Number.MAX_VALUE,\n      borderSkipped: false,\n    },\n    {\n      label: 'Small Radius',\n      data: Utils.numbers(NUMBER_CFG),\n      borderColor: Utils.CHART_COLORS.blue,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),\n      borderWidth: 2,\n      borderRadius: 5,\n      borderSkipped: false,\n    }\n  ]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'bar',\n  data: data,\n  options: {\n    responsive: true,\n    plugins: {\n      legend: {\n        position: 'top',\n      },\n      title: {\n        display: true,\n        text: 'Chart.js Bar Chart'\n      }\n    }\n  },\n};\n// </block:config>\n\nmodule.exports = {\n  actions: actions,\n  config: config,\n};\n```\n\n## Docs\n* [Bar](../../charts/bar.md)\n  * [`borderRadius`](../../charts/bar.md#borderradius)\n* [Data structures (`labels`)](../../general/data-structures.md)\n"
  },
  {
    "path": "docs/samples/bar/floating.md",
    "content": "# Floating Bars\n\nUsing `[number, number][]` as the type for `data` to define the beginning and end value for each bar. This is instead of having every bar start at 0.\n\n```js chart-editor\n// <block:actions:2>\nconst actions = [\n  {\n    name: 'Randomize',\n    handler(chart) {\n      chart.data.datasets.forEach(dataset => {\n        dataset.data = chart.data.labels.map(() => {\n          return [Utils.rand(-100, 100), Utils.rand(-100, 100)];\n        });\n      });\n      chart.update();\n    }\n  },\n];\n// </block:actions>\n\n// <block:setup:1>\nconst DATA_COUNT = 7;\nconst NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100};\n\nconst labels = Utils.months({count: 7});\nconst data = {\n  labels: labels,\n  datasets: [\n    {\n      label: 'Dataset 1',\n      data: labels.map(() => {\n        return [Utils.rand(-100, 100), Utils.rand(-100, 100)];\n      }),\n      backgroundColor: Utils.CHART_COLORS.red,\n    },\n    {\n      label: 'Dataset 2',\n      data: labels.map(() => {\n        return [Utils.rand(-100, 100), Utils.rand(-100, 100)];\n      }),\n      backgroundColor: Utils.CHART_COLORS.blue,\n    },\n  ]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'bar',\n  data: data,\n  options: {\n    responsive: true,\n    plugins: {\n      legend: {\n        position: 'top',\n      },\n      title: {\n        display: true,\n        text: 'Chart.js Floating Bar Chart'\n      }\n    }\n  }\n};\n// </block:config>\n\nmodule.exports = {\n  actions: actions,\n  config: config,\n};\n```\n## Docs\n* [Bar](../../charts/bar.md)\n* [Data structures (`labels`)](../../general/data-structures.md)\n"
  },
  {
    "path": "docs/samples/bar/horizontal.md",
    "content": "# Horizontal Bar Chart\n\n```js chart-editor\n// <block:actions:2>\nconst actions = [\n  {\n    name: 'Randomize',\n    handler(chart) {\n      chart.data.datasets.forEach(dataset => {\n        dataset.data = Utils.numbers({count: chart.data.labels.length, min: -100, max: 100});\n      });\n      chart.update();\n    }\n  },\n  {\n    name: 'Add Dataset',\n    handler(chart) {\n      const data = chart.data;\n      const dsColor = Utils.namedColor(chart.data.datasets.length);\n      const newDataset = {\n        label: 'Dataset ' + (data.datasets.length + 1),\n        backgroundColor: Utils.transparentize(dsColor, 0.5),\n        borderColor: dsColor,\n        borderWidth: 1,\n        data: Utils.numbers({count: data.labels.length, min: -100, max: 100}),\n      };\n      chart.data.datasets.push(newDataset);\n      chart.update();\n    }\n  },\n  {\n    name: 'Add Data',\n    handler(chart) {\n      const data = chart.data;\n      if (data.datasets.length > 0) {\n        data.labels = Utils.months({count: data.labels.length + 1});\n\n        for (let index = 0; index < data.datasets.length; ++index) {\n          data.datasets[index].data.push(Utils.rand(-100, 100));\n        }\n\n        chart.update();\n      }\n    }\n  },\n  {\n    name: 'Remove Dataset',\n    handler(chart) {\n      chart.data.datasets.pop();\n      chart.update();\n    }\n  },\n  {\n    name: 'Remove Data',\n    handler(chart) {\n      chart.data.labels.splice(-1, 1); // remove the label first\n\n      chart.data.datasets.forEach(dataset => {\n        dataset.data.pop();\n      });\n\n      chart.update();\n    }\n  }\n];\n// </block:actions>\n\n// <block:setup:1>\nconst DATA_COUNT = 7;\nconst NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100};\n\nconst labels = Utils.months({count: 7});\nconst data = {\n  labels: labels,\n  datasets: [\n    {\n      label: 'Dataset 1',\n      data: Utils.numbers(NUMBER_CFG),\n      borderColor: Utils.CHART_COLORS.red,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),\n    },\n    {\n      label: 'Dataset 2',\n      data: Utils.numbers(NUMBER_CFG),\n      borderColor: Utils.CHART_COLORS.blue,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),\n    }\n  ]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'bar',\n  data: data,\n  options: {\n    indexAxis: 'y',\n    // Elements options apply to all of the options unless overridden in a dataset\n    // In this case, we are setting the border of each horizontal bar to be 2px wide\n    elements: {\n      bar: {\n        borderWidth: 2,\n      }\n    },\n    responsive: true,\n    plugins: {\n      legend: {\n        position: 'right',\n      },\n      title: {\n        display: true,\n        text: 'Chart.js Horizontal Bar Chart'\n      }\n    }\n  },\n};\n// </block:config>\n\nmodule.exports = {\n  actions: actions,\n  config: config,\n};\n```\n\n## Docs\n* [Bar](../../charts/bar.md)\n  * [Horizontal Bar Chart](../../charts/bar.md#horizontal-bar-chart)\n\n"
  },
  {
    "path": "docs/samples/bar/stacked-groups.md",
    "content": "# Stacked Bar Chart with Groups\n\nUsing the `stack` property to divide datasets into multiple stacks.\n\n```js chart-editor\n// <block:actions:2>\nconst actions = [\n  {\n    name: 'Randomize',\n    handler(chart) {\n      chart.data.datasets.forEach(dataset => {\n        dataset.data = Utils.numbers({count: chart.data.labels.length, min: -100, max: 100});\n      });\n      chart.update();\n    }\n  },\n];\n// </block:actions>\n\n// <block:setup:1>\nconst DATA_COUNT = 7;\nconst NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100};\n\nconst labels = Utils.months({count: 7});\nconst data = {\n  labels: labels,\n  datasets: [\n    {\n      label: 'Dataset 1',\n      data: Utils.numbers(NUMBER_CFG),\n      backgroundColor: Utils.CHART_COLORS.red,\n      stack: 'Stack 0',\n    },\n    {\n      label: 'Dataset 2',\n      data: Utils.numbers(NUMBER_CFG),\n      backgroundColor: Utils.CHART_COLORS.blue,\n      stack: 'Stack 0',\n    },\n    {\n      label: 'Dataset 3',\n      data: Utils.numbers(NUMBER_CFG),\n      backgroundColor: Utils.CHART_COLORS.green,\n      stack: 'Stack 1',\n    },\n  ]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'bar',\n  data: data,\n  options: {\n    plugins: {\n      title: {\n        display: true,\n        text: 'Chart.js Bar Chart - Stacked'\n      },\n    },\n    responsive: true,\n    interaction: {\n      intersect: false,\n    },\n    scales: {\n      x: {\n        stacked: true,\n      },\n      y: {\n        stacked: true\n      }\n    }\n  }\n};\n// </block:config>\n\nmodule.exports = {\n  actions: actions,\n  config: config,\n};\n```\n\n## Docs\n* [Bar](../../charts/bar.md)\n  * [Stacked Bar Chart](../../charts/bar.md#stacked-bar-chart)\n* [Data structures (`labels`)](../../general/data-structures.md)\n  * [Dataset Configuration (`stack`)](../../general/data-structures.md#dataset-configuration)\n\n"
  },
  {
    "path": "docs/samples/bar/stacked.md",
    "content": "# Stacked Bar Chart\n\n```js chart-editor\n// <block:actions:2>\nconst actions = [\n  {\n    name: 'Randomize',\n    handler(chart) {\n      chart.data.datasets.forEach(dataset => {\n        dataset.data = Utils.numbers({count: chart.data.labels.length, min: -100, max: 100});\n      });\n      chart.update();\n    }\n  },\n];\n// </block:actions>\n\n// <block:setup:1>\nconst DATA_COUNT = 7;\nconst NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100};\n\nconst labels = Utils.months({count: 7});\nconst data = {\n  labels: labels,\n  datasets: [\n    {\n      label: 'Dataset 1',\n      data: Utils.numbers(NUMBER_CFG),\n      backgroundColor: Utils.CHART_COLORS.red,\n    },\n    {\n      label: 'Dataset 2',\n      data: Utils.numbers(NUMBER_CFG),\n      backgroundColor: Utils.CHART_COLORS.blue,\n    },\n    {\n      label: 'Dataset 3',\n      data: Utils.numbers(NUMBER_CFG),\n      backgroundColor: Utils.CHART_COLORS.green,\n    },\n  ]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'bar',\n  data: data,\n  options: {\n    plugins: {\n      title: {\n        display: true,\n        text: 'Chart.js Bar Chart - Stacked'\n      },\n    },\n    responsive: true,\n    scales: {\n      x: {\n        stacked: true,\n      },\n      y: {\n        stacked: true\n      }\n    }\n  }\n};\n// </block:config>\n\nmodule.exports = {\n  actions: actions,\n  config: config,\n};\n```\n## Docs\n* [Bar](../../charts/bar.md)\n  * [Stacked Bar Chart](../../charts/bar.md#stacked-bar-chart)\n\n"
  },
  {
    "path": "docs/samples/bar/vertical.md",
    "content": "# Vertical Bar Chart\n\n```js chart-editor\n// <block:actions:2>\nconst actions = [\n  {\n    name: 'Randomize',\n    handler(chart) {\n      chart.data.datasets.forEach(dataset => {\n        dataset.data = Utils.numbers({count: chart.data.labels.length, min: -100, max: 100});\n      });\n      chart.update();\n    }\n  },\n  {\n    name: 'Add Dataset',\n    handler(chart) {\n      const data = chart.data;\n      const dsColor = Utils.namedColor(chart.data.datasets.length);\n      const newDataset = {\n        label: 'Dataset ' + (data.datasets.length + 1),\n        backgroundColor: Utils.transparentize(dsColor, 0.5),\n        borderColor: dsColor,\n        borderWidth: 1,\n        data: Utils.numbers({count: data.labels.length, min: -100, max: 100}),\n      };\n      chart.data.datasets.push(newDataset);\n      chart.update();\n    }\n  },\n  {\n    name: 'Add Data',\n    handler(chart) {\n      const data = chart.data;\n      if (data.datasets.length > 0) {\n        data.labels = Utils.months({count: data.labels.length + 1});\n\n        for (let index = 0; index < data.datasets.length; ++index) {\n          data.datasets[index].data.push(Utils.rand(-100, 100));\n        }\n\n        chart.update();\n      }\n    }\n  },\n  {\n    name: 'Remove Dataset',\n    handler(chart) {\n      chart.data.datasets.pop();\n      chart.update();\n    }\n  },\n  {\n    name: 'Remove Data',\n    handler(chart) {\n      chart.data.labels.splice(-1, 1); // remove the label first\n\n      chart.data.datasets.forEach(dataset => {\n        dataset.data.pop();\n      });\n\n      chart.update();\n    }\n  }\n];\n// </block:actions>\n\n// <block:setup:1>\nconst DATA_COUNT = 7;\nconst NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100};\n\nconst labels = Utils.months({count: 7});\nconst data = {\n  labels: labels,\n  datasets: [\n    {\n      label: 'Dataset 1',\n      data: Utils.numbers(NUMBER_CFG),\n      borderColor: Utils.CHART_COLORS.red,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),\n    },\n    {\n      label: 'Dataset 2',\n      data: Utils.numbers(NUMBER_CFG),\n      borderColor: Utils.CHART_COLORS.blue,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),\n    }\n  ]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'bar',\n  data: data,\n  options: {\n    responsive: true,\n    plugins: {\n      legend: {\n        position: 'top',\n      },\n      title: {\n        display: true,\n        text: 'Chart.js Bar Chart'\n      }\n    }\n  },\n};\n// </block:config>\n\nmodule.exports = {\n  actions: actions,\n  config: config,\n};\n```\n\n## Docs\n* [Bar](../../charts/bar.md)\n* [Data structures (`labels`)](../../general/data-structures.md)\n"
  },
  {
    "path": "docs/samples/information.md",
    "content": "# Chart.js Samples\n\nYou can navigate through the samples via the sidebar.\n\nAlternatively, you can run them locally. To do so, clone the [Chart.js repository](https://github.com/chartjs/Chart.js) from GitHub, run `pnpm ci` to install all packages, then run `pnpm run docs:dev` to build the documentation. As soon as the build is done, you can go to [localhost:8080/samples](http://localhost:8080/samples/) to see the samples.\n\n## Out of the box working samples\nThese samples are made for demonstration purposes only. They won't work out of the box if you copy paste them into your own website. This is because of how the docs are getting built. Some boilerplate code gets hidden.\nFor a sample that can be copied and pasted and used directly you can check the [usage page](../getting-started/usage.md).\n\n## Autogenerated data\nThe data used in the samples is autogenerated using custom functions. These functions do not ship with the library, for more information about this you can check the [utils page](./utils.md).\n\n## Actions block\nThe samples have an `actions` code block. These actions are not part of Chart.js. They are internally transformed to separate buttons together with `onClick` listeners by a plugin we use in the documentation. To implement such actions yourself you can make some buttons and add `onClick` event listeners to them. Then in these event listeners you can call your variable in which you made the chart and do the logic that the button is supposed to do.\n"
  },
  {
    "path": "docs/samples/legend/events.md",
    "content": "# Events\n\nThis sample demonstrates how to use the event hooks to highlight chart elements.\n\n```js chart-editor\n\n// <block:data:3>\nconst data = {\n  labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],\n  datasets: [{\n    label: '# of Votes',\n    data: [12, 19, 3, 5, 2, 3],\n    borderWidth: 1,\n    backgroundColor: ['#CB4335', '#1F618D', '#F1C40F', '#27AE60', '#884EA0', '#D35400'],\n  }]\n};\n// </block:data>\n\n// <block:handleHover:1>\n// Append '4d' to the colors (alpha channel), except for the hovered index\nfunction handleHover(evt, item, legend) {\n  legend.chart.data.datasets[0].backgroundColor.forEach((color, index, colors) => {\n    colors[index] = index === item.index || color.length === 9 ? color : color + '4D';\n  });\n  legend.chart.update();\n}\n// </block:handleHover>\n\n// <block:handleLeave:2>\n// Removes the alpha channel from background colors\nfunction handleLeave(evt, item, legend) {\n  legend.chart.data.datasets[0].backgroundColor.forEach((color, index, colors) => {\n    colors[index] = color.length === 9 ? color.slice(0, -2) : color;\n  });\n  legend.chart.update();\n}\n// </block:handleLeave>\n\n// <block:config:0>\nconst config = {\n  type: 'pie',\n  data: data,\n  options: {\n    plugins: {\n      legend: {\n        onHover: handleHover,\n        onLeave: handleLeave\n      }\n    }\n  }\n};\n// </block:config>\n\nmodule.exports = {\n  config\n};\n```\n\n## Docs\n* [Doughnut and Pie Charts](../../charts/doughnut.md)\n* [Legend](../../configuration/legend.md)\n  * `onHover`\n  * `onLeave`"
  },
  {
    "path": "docs/samples/legend/html.md",
    "content": "# HTML Legend\n\nThis example shows how to create a custom HTML legend using a plugin and connect it to the chart in lieu of the default on-canvas legend.  \nFor an html legend to work you need to place an empty div at your web page with the ID you provide in the options to bind to like so: `<div id=\"legend-container\"></div>`.  \n\n<div id=\"legend-container\"></div>\n\n```js chart-editor\n// <block:plugin:0>\nconst getOrCreateLegendList = (chart, id) => {\n  const legendContainer = document.getElementById(id);\n  let listContainer = legendContainer.querySelector('ul');\n\n  if (!listContainer) {\n    listContainer = document.createElement('ul');\n    listContainer.style.display = 'flex';\n    listContainer.style.flexDirection = 'row';\n    listContainer.style.margin = 0;\n    listContainer.style.padding = 0;\n\n    legendContainer.appendChild(listContainer);\n  }\n\n  return listContainer;\n};\n\nconst htmlLegendPlugin = {\n  id: 'htmlLegend',\n  afterUpdate(chart, args, options) {\n    const ul = getOrCreateLegendList(chart, options.containerID);\n\n    // Remove old legend items\n    while (ul.firstChild) {\n      ul.firstChild.remove();\n    }\n\n    // Reuse the built-in legendItems generator\n    const items = chart.options.plugins.legend.labels.generateLabels(chart);\n\n    items.forEach(item => {\n      const li = document.createElement('li');\n      li.style.alignItems = 'center';\n      li.style.cursor = 'pointer';\n      li.style.display = 'flex';\n      li.style.flexDirection = 'row';\n      li.style.marginLeft = '10px';\n\n      li.onclick = () => {\n        const {type} = chart.config;\n        if (type === 'pie' || type === 'doughnut') {\n          // Pie and doughnut charts only have a single dataset and visibility is per item\n          chart.toggleDataVisibility(item.index);\n        } else {\n          chart.setDatasetVisibility(item.datasetIndex, !chart.isDatasetVisible(item.datasetIndex));\n        }\n        chart.update();\n      };\n\n      // Color box\n      const boxSpan = document.createElement('span');\n      boxSpan.style.background = item.fillStyle;\n      boxSpan.style.borderColor = item.strokeStyle;\n      boxSpan.style.borderWidth = item.lineWidth + 'px';\n      boxSpan.style.display = 'inline-block';\n      boxSpan.style.flexShrink = 0;\n      boxSpan.style.height = '20px';\n      boxSpan.style.marginRight = '10px';\n      boxSpan.style.width = '20px';\n\n      // Text\n      const textContainer = document.createElement('p');\n      textContainer.style.color = item.fontColor;\n      textContainer.style.margin = 0;\n      textContainer.style.padding = 0;\n      textContainer.style.textDecoration = item.hidden ? 'line-through' : '';\n\n      const text = document.createTextNode(item.text);\n      textContainer.appendChild(text);\n\n      li.appendChild(boxSpan);\n      li.appendChild(textContainer);\n      ul.appendChild(li);\n    });\n  }\n};\n// </block:plugin>\n\n// <block:data:1>\nconst NUM_DATA = 7;\nconst NUM_CFG = {count: NUM_DATA, min: 0, max: 100};\nconst data = {\n  labels: Utils.months({count: NUM_DATA}),\n  datasets: [\n    {\n      label: 'Dataset: 1',\n      data: Utils.numbers(NUM_CFG),\n      borderColor: Utils.CHART_COLORS.red,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),\n      fill: false,\n    },\n    {\n      label: 'Dataset: 1',\n      data: Utils.numbers(NUM_CFG),\n      borderColor: Utils.CHART_COLORS.blue,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),\n      fill: false,\n    },\n  ],\n};\n// </block:data>\n\n// <block:setup:2>\nconst config = {\n  type: 'line',\n  data: data,\n  options: {\n    plugins: {\n      htmlLegend: {\n        // ID of the container to put the legend in\n        containerID: 'legend-container',\n      },\n      legend: {\n        display: false,\n      }\n    }\n  },\n  plugins: [htmlLegendPlugin],\n};\n// </block:setup>\n\nmodule.exports = {\n  actions: [],\n  config: config,\n};\n```\n\n## Docs \n* [Data structures (`labels`)](../../general/data-structures.md)\n* [Line](../../charts/line.md)\n* [Legend](../../configuration/legend.md)\n  * `display: false`\n* [Plugins](../../developers/plugins.md)  \n"
  },
  {
    "path": "docs/samples/legend/point-style.md",
    "content": "# Point Style\n\nThis sample show how to use the dataset point style in the legend instead of a rectangle to identify each dataset..\n\n```js chart-editor\n// <block:actions:2>\nconst actions = [\n  {\n    name: 'Toggle Point Style',\n    handler(chart) {\n      chart.options.plugins.legend.labels.usePointStyle = !chart.options.plugins.legend.labels.usePointStyle;\n      chart.update();\n    }\n  },\n];\n// </block:actions>\n\n// <block:setup:1>\nconst DATA_COUNT = 7;\nconst NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100};\nconst data = {\n  labels: Utils.months({count: DATA_COUNT}),\n  datasets: [\n    {\n      label: 'Dataset 1',\n      data: Utils.numbers(NUMBER_CFG),\n      fill: false,\n      borderColor: Utils.CHART_COLORS.red,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),\n      borderWidth: 1,\n      pointStyle: 'rectRot',\n      pointRadius: 5,\n      pointBorderColor: 'rgb(0, 0, 0)'\n    },\n  ]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'line',\n  data: data,\n  options: {\n    plugins: {\n      legend: {\n        labels: {\n          usePointStyle: true,\n        },\n      }\n    }\n  }\n};\n// </block:config>\n\nmodule.exports = {\n  actions: actions,\n  config: config,\n};\n```\n\n## Docs \n* [Data structures (`labels`)](../../general/data-structures.md)\n* [Line](../../charts/line.md)\n* [Legend](../../configuration/legend.md)\n  * [Legend Label Configuration](../../configuration/legend.md#legend-label-configuration)\n    * `usePointStyle`\n* [Elements](../../configuration/elements.md)\n  * [Point Configuration](../../configuration/elements.md#point-configuration)\n  * [Point Styles](../../configuration/elements.md#point-styles)\n"
  },
  {
    "path": "docs/samples/legend/position.md",
    "content": "# Position\n\nThis sample show how to change the position of the chart legend.\n\n```js chart-editor\n// <block:actions:2>\nconst actions = [\n  {\n    name: 'Position: top',\n    handler(chart) {\n      chart.options.plugins.legend.position = 'top';\n      chart.update();\n    }\n  },\n  {\n    name: 'Position: right',\n    handler(chart) {\n      chart.options.plugins.legend.position = 'right';\n      chart.update();\n    }\n  },\n  {\n    name: 'Position: bottom',\n    handler(chart) {\n      chart.options.plugins.legend.position = 'bottom';\n      chart.update();\n    }\n  },\n  {\n    name: 'Position: left',\n    handler(chart) {\n      chart.options.plugins.legend.position = 'left';\n      chart.update();\n    }\n  },\n];\n// </block:actions>\n\n\n// <block:setup:1>\nconst DATA_COUNT = 7;\nconst NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100};\nconst data = {\n  labels: Utils.months({count: DATA_COUNT}),\n  datasets: [\n    {\n      label: 'Dataset 1',\n      data: Utils.numbers(NUMBER_CFG),\n      fill: false,\n      borderColor: Utils.CHART_COLORS.red,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),\n    },\n  ]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'line',\n  data: data,\n};\n// </block:config>\n\nmodule.exports = {\n  actions: actions,\n  config: config,\n};\n```\n\n## Docs \n* [Data structures (`labels`)](../../general/data-structures.md)\n* [Line](../../charts/line.md)\n* [Legend](../../configuration/legend.md)\n  * [Position](../../configuration/legend.md#position)\n"
  },
  {
    "path": "docs/samples/legend/title.md",
    "content": "# Alignment and Title Position\n\nThis sample show how to configure the alignment and title position of the chart legend.\n\n```js chart-editor\n// <block:actions:2>\nconst actions = [\n  {\n    name: 'Title Position: start',\n    handler(chart) {\n      chart.options.plugins.legend.align = 'start';\n      chart.options.plugins.legend.title.position = 'start';\n      chart.update();\n    }\n  },\n  {\n    name: 'Title Position: center (default)',\n    handler(chart) {\n      chart.options.plugins.legend.align = 'center';\n      chart.options.plugins.legend.title.position = 'center';\n      chart.update();\n    }\n  },\n  {\n    name: 'Title Position: end',\n    handler(chart) {\n      chart.options.plugins.legend.align = 'end';\n      chart.options.plugins.legend.title.position = 'end';\n      chart.update();\n    }\n  },\n];\n// </block:actions>\n\n\n// <block:setup:1>\nconst DATA_COUNT = 7;\nconst NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100};\nconst data = {\n  labels: Utils.months({count: DATA_COUNT}),\n  datasets: [\n    {\n      label: 'Dataset 1',\n      data: Utils.numbers(NUMBER_CFG),\n      fill: false,\n      borderColor: Utils.CHART_COLORS.red,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),\n    },\n  ]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'line',\n  data: data,\n  options: {\n    plugins: {\n      legend: {\n        title: {\n          display: true,\n          text: 'Legend Title',\n        }\n      }\n    }\n  }\n};\n// </block:config>\n\nmodule.exports = {\n  actions: actions,\n  config: config,\n};\n```\n\n## Docs \n* [Data structures (`labels`)](../../general/data-structures.md)\n* [Line](../../charts/line.md)\n* [Legend](../../configuration/legend.md)"
  },
  {
    "path": "docs/samples/line/interpolation.md",
    "content": "# Interpolation Modes\n\n```js chart-editor\n// <block:setup:1>\nconst DATA_COUNT = 12;\nconst labels = [];\nfor (let i = 0; i < DATA_COUNT; ++i) {\n  labels.push(i.toString());\n}\nconst datapoints = [0, 20, 20, 60, 60, 120, NaN, 180, 120, 125, 105, 110, 170];\nconst data = {\n  labels: labels,\n  datasets: [\n    {\n      label: 'Cubic interpolation (monotone)',\n      data: datapoints,\n      borderColor: Utils.CHART_COLORS.red,\n      fill: false,\n      cubicInterpolationMode: 'monotone',\n      tension: 0.4\n    }, {\n      label: 'Cubic interpolation',\n      data: datapoints,\n      borderColor: Utils.CHART_COLORS.blue,\n      fill: false,\n      tension: 0.4\n    }, {\n      label: 'Linear interpolation (default)',\n      data: datapoints,\n      borderColor: Utils.CHART_COLORS.green,\n      fill: false\n    }\n  ]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'line',\n  data: data,\n  options: {\n    responsive: true,\n    plugins: {\n      title: {\n        display: true,\n        text: 'Chart.js Line Chart - Cubic interpolation mode'\n      },\n    },\n    interaction: {\n      intersect: false,\n    },\n    scales: {\n      x: {\n        display: true,\n        title: {\n          display: true\n        }\n      },\n      y: {\n        display: true,\n        title: {\n          display: true,\n          text: 'Value'\n        },\n        suggestedMin: -10,\n        suggestedMax: 200\n      }\n    }\n  },\n};\n// </block:config>\n\nmodule.exports = {\n  actions: [],\n  config: config,\n};\n```\n\n## Docs \n* [Line](../../charts/line.md)\n  * [`cubicInterpolationMode`](../../charts/line.md#cubicinterpolationmode)\n  * [Line Styling (`tension`)](../../charts/line.md#line-styling)\n\n"
  },
  {
    "path": "docs/samples/line/line.md",
    "content": "# Line Chart\n\n```js chart-editor\n// <block:actions:2>\nconst actions = [\n  {\n    name: 'Randomize',\n    handler(chart) {\n      chart.data.datasets.forEach(dataset => {\n        dataset.data = Utils.numbers({count: chart.data.labels.length, min: -100, max: 100});\n      });\n      chart.update();\n    }\n  },\n  {\n    name: 'Add Dataset',\n    handler(chart) {\n      const data = chart.data;\n      const dsColor = Utils.namedColor(chart.data.datasets.length);\n      const newDataset = {\n        label: 'Dataset ' + (data.datasets.length + 1),\n        backgroundColor: Utils.transparentize(dsColor, 0.5),\n        borderColor: dsColor,\n        data: Utils.numbers({count: data.labels.length, min: -100, max: 100}),\n      };\n      chart.data.datasets.push(newDataset);\n      chart.update();\n    }\n  },\n  {\n    name: 'Add Data',\n    handler(chart) {\n      const data = chart.data;\n      if (data.datasets.length > 0) {\n        data.labels = Utils.months({count: data.labels.length + 1});\n\n        for (let index = 0; index < data.datasets.length; ++index) {\n          data.datasets[index].data.push(Utils.rand(-100, 100));\n        }\n\n        chart.update();\n      }\n    }\n  },\n  {\n    name: 'Remove Dataset',\n    handler(chart) {\n      chart.data.datasets.pop();\n      chart.update();\n    }\n  },\n  {\n    name: 'Remove Data',\n    handler(chart) {\n      chart.data.labels.splice(-1, 1); // remove the label first\n\n      chart.data.datasets.forEach(dataset => {\n        dataset.data.pop();\n      });\n\n      chart.update();\n    }\n  }\n];\n// </block:actions>\n\n// <block:setup:1>\nconst DATA_COUNT = 7;\nconst NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100};\n\nconst labels = Utils.months({count: 7});\nconst data = {\n  labels: labels,\n  datasets: [\n    {\n      label: 'Dataset 1',\n      data: Utils.numbers(NUMBER_CFG),\n      borderColor: Utils.CHART_COLORS.red,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),\n    },\n    {\n      label: 'Dataset 2',\n      data: Utils.numbers(NUMBER_CFG),\n      borderColor: Utils.CHART_COLORS.blue,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),\n    }\n  ]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'line',\n  data: data,\n  options: {\n    responsive: true,\n    plugins: {\n      legend: {\n        position: 'top',\n      },\n      title: {\n        display: true,\n        text: 'Chart.js Line Chart'\n      }\n    }\n  },\n};\n// </block:config>\n\nmodule.exports = {\n  actions: actions,\n  config: config,\n};\n```\n\n## Docs\n* [Line](../../charts/line.md)\n* [Data structures (`labels`)](../../general/data-structures.md)\n"
  },
  {
    "path": "docs/samples/line/multi-axis.md",
    "content": "# Multi Axis Line Chart\n\n```js chart-editor\n// <block:actions:2>\nconst actions = [\n  {\n    name: 'Randomize',\n    handler(chart) {\n      chart.data.datasets.forEach(dataset => {\n        dataset.data = Utils.numbers({count: chart.data.labels.length, min: -100, max: 100});\n      });\n      chart.update();\n    }\n  },\n];\n// </block:actions>\n\n// <block:setup:1>\nconst DATA_COUNT = 7;\nconst NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100};\n\nconst labels = Utils.months({count: 7});\nconst data = {\n  labels: labels,\n  datasets: [\n    {\n      label: 'Dataset 1',\n      data: Utils.numbers(NUMBER_CFG),\n      borderColor: Utils.CHART_COLORS.red,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),\n      yAxisID: 'y',\n    },\n    {\n      label: 'Dataset 2',\n      data: Utils.numbers(NUMBER_CFG),\n      borderColor: Utils.CHART_COLORS.blue,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),\n      yAxisID: 'y1',\n    }\n  ]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'line',\n  data: data,\n  options: {\n    responsive: true,\n    interaction: {\n      mode: 'index',\n      intersect: false,\n    },\n    stacked: false,\n    plugins: {\n      title: {\n        display: true,\n        text: 'Chart.js Line Chart - Multi Axis'\n      }\n    },\n    scales: {\n      y: {\n        type: 'linear',\n        display: true,\n        position: 'left',\n      },\n      y1: {\n        type: 'linear',\n        display: true,\n        position: 'right',\n\n        // grid line settings\n        grid: {\n          drawOnChartArea: false, // only want the grid lines for one axis to show up\n        },\n      },\n    }\n  },\n};\n// </block:config>\n\nmodule.exports = {\n  actions: actions,\n  config: config,\n};\n```\n\n## Docs\n* [Axes scales](../../axes/)\n* [Cartesian Axes](../../axes/cartesian/)\n  * [Axis Position](../../axes/cartesian/#axis-position)\n* [Data structures (`labels`)](../../general/data-structures.md)\n* [Line](../../charts/line.md)\n\n"
  },
  {
    "path": "docs/samples/line/point-styling.md",
    "content": "# Point Styling\n\n```js chart-editor\n// <block:actions:2>\nconst actions = [\n  {\n    name: 'pointStyle: circle (default)',\n    handler: (chart) => {\n      chart.data.datasets.forEach(dataset => {\n        dataset.pointStyle = 'circle';\n      });\n      chart.update();\n    }\n  },\n  {\n    name: 'pointStyle: cross',\n    handler: (chart) => {\n      chart.data.datasets.forEach(dataset => {\n        dataset.pointStyle = 'cross';\n      });\n      chart.update();\n    }\n  },\n  {\n    name: 'pointStyle: crossRot',\n    handler: (chart) => {\n      chart.data.datasets.forEach(dataset => {\n        dataset.pointStyle = 'crossRot';\n      });\n      chart.update();\n    }\n  },\n  {\n    name: 'pointStyle: dash',\n    handler: (chart) => {\n      chart.data.datasets.forEach(dataset => {\n        dataset.pointStyle = 'dash';\n      });\n      chart.update();\n    }\n  },\n  {\n    name: 'pointStyle: line',\n    handler: (chart) => {\n      chart.data.datasets.forEach(dataset => {\n        dataset.pointStyle = 'line';\n      });\n      chart.update();\n    }\n  },\n  {\n    name: 'pointStyle: rect',\n    handler: (chart) => {\n      chart.data.datasets.forEach(dataset => {\n        dataset.pointStyle = 'rect';\n      });\n      chart.update();\n    }\n  },\n  {\n    name: 'pointStyle: rectRounded',\n    handler: (chart) => {\n      chart.data.datasets.forEach(dataset => {\n        dataset.pointStyle = 'rectRounded';\n      });\n      chart.update();\n    }\n  },\n  {\n    name: 'pointStyle: rectRot',\n    handler: (chart) => {\n      chart.data.datasets.forEach(dataset => {\n        dataset.pointStyle = 'rectRot';\n      });\n      chart.update();\n    }\n  },\n  {\n    name: 'pointStyle: star',\n    handler: (chart) => {\n      chart.data.datasets.forEach(dataset => {\n        dataset.pointStyle = 'star';\n      });\n      chart.update();\n    }\n  },\n  {\n    name: 'pointStyle: triangle',\n    handler: (chart) => {\n      chart.data.datasets.forEach(dataset => {\n        dataset.pointStyle = 'triangle';\n      });\n      chart.update();\n    }\n  },\n  {\n    name: 'pointStyle: false',\n    handler: (chart) => {\n      chart.data.datasets.forEach(dataset => {\n        dataset.pointStyle = false;\n      });\n      chart.update();\n    }\n  }\n];\n// </block:actions>\n\n// <block:setup:1>\nconst data = {\n  labels: ['Day 1', 'Day 2', 'Day 3', 'Day 4', 'Day 5', 'Day 6'],\n  datasets: [\n    {\n      label: 'Dataset',\n      data: Utils.numbers({count: 6, min: -100, max: 100}),\n      borderColor: Utils.CHART_COLORS.red,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),\n      pointStyle: 'circle',\n      pointRadius: 10,\n      pointHoverRadius: 15\n    }\n  ]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'line',\n  data: data,\n  options: {\n    responsive: true,\n    plugins: {\n      title: {\n        display: true,\n        text: (ctx) => 'Point Style: ' + ctx.chart.data.datasets[0].pointStyle,\n      }\n    }\n  }\n};\n// </block:config>\n\nmodule.exports = {\n  actions: actions,\n  config: config,\n};\n```\n\n## Docs\n* [Data structures (`labels`)](../../general/data-structures.md)\n* [Line](../../charts/line.md)\n  * [Point Styling](../../charts/line.md#point-styling)\n"
  },
  {
    "path": "docs/samples/line/segments.md",
    "content": "# Line Segment Styling\nUsing helper functions to style each segment. Gaps in the data ('skipped') are set to dashed lines and segments with values going 'down' are set to a different color.\n\n```js chart-editor\n\n// <block:segmentUtils:1>\nconst skipped = (ctx, value) => ctx.p0.skip || ctx.p1.skip ? value : undefined;\nconst down = (ctx, value) => ctx.p0.parsed.y > ctx.p1.parsed.y ? value : undefined;\n// </block:segmentUtils>\n\n// <block:genericOptions:2>\nconst genericOptions = {\n  fill: false,\n  interaction: {\n    intersect: false\n  },\n  radius: 0,\n};\n// </block:genericOptions>\n\n// <block:config:0>\nconst config = {\n  type: 'line',\n  data: {\n    labels: Utils.months({count: 7}),\n    datasets: [{\n      label: 'My First Dataset',\n      data: [65, 59, NaN, 48, 56, 57, 40],\n      borderColor: 'rgb(75, 192, 192)',\n      segment: {\n        borderColor: ctx => skipped(ctx, 'rgb(0,0,0,0.2)') || down(ctx, 'rgb(192,75,75)'),\n        borderDash: ctx => skipped(ctx, [6, 6]),\n      },\n      spanGaps: true\n    }]\n  },\n  options: genericOptions\n};\n// </block:config>\n\nmodule.exports = {\n  actions: [],\n  config: config,\n};\n```\n\n## Docs\n* [Data structures (`labels`)](../../general/data-structures.md)\n* [Line](../../charts/line.md)\n  * [Line Styling](../../charts/line.md#line-styling)\n  * [Segment](../../charts/line.md#segment)\n* [Options](../../general/options.md)\n  * [Scriptable Options](../../general/options.md#scriptable-options)"
  },
  {
    "path": "docs/samples/line/stepped.md",
    "content": "# Stepped Line Charts\n\n```js chart-editor\n// <block:actions:2>\nconst actions = [\n  {\n    name: 'Step: false (default)',\n    handler: (chart) => {\n      chart.data.datasets.forEach(dataset => {\n        dataset.stepped = false;\n      });\n      chart.update();\n    }\n  },\n  {\n    name: 'Step: true',\n    handler: (chart) => {\n      chart.data.datasets.forEach(dataset => {\n        dataset.stepped = true;\n      });\n      chart.update();\n    }\n  },\n  {\n    name: 'Step: before',\n    handler: (chart) => {\n      chart.data.datasets.forEach(dataset => {\n        dataset.stepped = 'before';\n      });\n      chart.update();\n    }\n  },\n  {\n    name: 'Step: after',\n    handler: (chart) => {\n      chart.data.datasets.forEach(dataset => {\n        dataset.stepped = 'after';\n      });\n      chart.update();\n    }\n  },\n  {\n    name: 'Step: middle',\n    handler: (chart) => {\n      chart.data.datasets.forEach(dataset => {\n        dataset.stepped = 'middle';\n      });\n      chart.update();\n    }\n  }\n];\n// </block:actions>\n\n// <block:setup:1>\nconst data = {\n  labels: ['Day 1', 'Day 2', 'Day 3', 'Day 4', 'Day 5', 'Day 6'],\n  datasets: [\n    {\n      label: 'Dataset',\n      data: Utils.numbers({count: 6, min: -100, max: 100}),\n      borderColor: Utils.CHART_COLORS.red,\n      fill: false,\n      stepped: true,\n    }\n  ]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'line',\n  data: data,\n  options: {\n    responsive: true,\n    interaction: {\n      intersect: false,\n      axis: 'x'\n    },\n    plugins: {\n      title: {\n        display: true,\n        text: (ctx) => 'Step ' + ctx.chart.data.datasets[0].stepped + ' Interpolation',\n      }\n    }\n  }\n};\n// </block:config>\n\nmodule.exports = {\n  actions: actions,\n  config: config,\n};\n```\n\n## Docs\n* [Data structures (`labels`)](../../general/data-structures.md)\n* [Line](../../charts/line.md)\n  * [Stepped](../../charts/line.md#stepped)\n"
  },
  {
    "path": "docs/samples/line/styling.md",
    "content": "# Line Styling\n\n```js chart-editor\n// <block:setup:1>\nconst DATA_COUNT = 7;\nconst NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100};\n\nconst labels = Utils.months({count: DATA_COUNT});\nconst data = {\n  labels: labels,\n  datasets: [\n    {\n      label: 'Unfilled',\n      fill: false,\n      backgroundColor: Utils.CHART_COLORS.blue,\n      borderColor: Utils.CHART_COLORS.blue,\n      data: Utils.numbers(NUMBER_CFG),\n    }, {\n      label: 'Dashed',\n      fill: false,\n      backgroundColor: Utils.CHART_COLORS.green,\n      borderColor: Utils.CHART_COLORS.green,\n      borderDash: [5, 5],\n      data: Utils.numbers(NUMBER_CFG),\n    }, {\n      label: 'Filled',\n      backgroundColor: Utils.CHART_COLORS.red,\n      borderColor: Utils.CHART_COLORS.red,\n      data: Utils.numbers(NUMBER_CFG),\n      fill: true,\n    }\n  ]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'line',\n  data: data,\n  options: {\n    responsive: true,\n    plugins: {\n      title: {\n        display: true,\n        text: 'Chart.js Line Chart'\n      },\n    },\n    interaction: {\n      mode: 'index',\n      intersect: false\n    },\n    scales: {\n      x: {\n        display: true,\n        title: {\n          display: true,\n          text: 'Month'\n        }\n      },\n      y: {\n        display: true,\n        title: {\n          display: true,\n          text: 'Value'\n        }\n      }\n    }\n  },\n};\n// </block:config>\n\nmodule.exports = {\n  actions: [],\n  config: config,\n};\n```\n\n## Docs\n* [Data structures (`labels`)](../../general/data-structures.md)\n* [Line](../../charts/line.md)\n  * [Line Styling](../../charts/line.md#line-styling)\n"
  },
  {
    "path": "docs/samples/other-charts/bubble.md",
    "content": "# Bubble\n\n```js chart-editor\n// <block:setup:1>\nconst DATA_COUNT = 7;\nconst NUMBER_CFG = {count: DATA_COUNT, rmin: 5, rmax: 15, min: 0, max: 100};\n\nconst data = {\n  datasets: [\n    {\n      label: 'Dataset 1',\n      data: Utils.bubbles(NUMBER_CFG),\n      borderColor: Utils.CHART_COLORS.red,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),\n    },\n    {\n      label: 'Dataset 2',\n      data: Utils.bubbles(NUMBER_CFG),\n      borderColor: Utils.CHART_COLORS.orange,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.orange, 0.5),\n    }\n  ]\n};\n// </block:setup>\n\n// <block:actions:2>\nconst actions = [\n  {\n    name: 'Randomize',\n    handler(chart) {\n      chart.data.datasets.forEach(dataset => {\n        dataset.data = Utils.bubbles({count: DATA_COUNT, rmin: 5, rmax: 15, min: 0, max: 100});\n      });\n      chart.update();\n    }\n  },\n  {\n    name: 'Add Dataset',\n    handler(chart) {\n      const chartData = chart.data;\n      const dsColor = Utils.namedColor(chartData.datasets.length);\n      const newDataset = {\n        label: 'Dataset ' + (chartData.datasets.length + 1),\n        backgroundColor: Utils.transparentize(dsColor, 0.5),\n        borderColor: dsColor,\n        data: Utils.bubbles({count: DATA_COUNT, rmin: 5, rmax: 15, min: 0, max: 100}),\n      };\n      chart.data.datasets.push(newDataset);\n      chart.update();\n    }\n  },\n  {\n    name: 'Add Data',\n    handler(chart) {\n      const chartData = chart.data;\n      if (chartData.datasets.length > 0) {\n\n        for (let index = 0; index < chartData.datasets.length; ++index) {\n          chartData.datasets[index].data.push(Utils.bubbles({count: 1, rmin: 5, rmax: 15, min: 0, max: 100})[0]);\n        }\n\n        chart.update();\n      }\n    }\n  },\n  {\n    name: 'Remove Dataset',\n    handler(chart) {\n      chart.data.datasets.pop();\n      chart.update();\n    }\n  },\n  {\n    name: 'Remove Data',\n    handler(chart) {\n      chart.data.datasets.forEach(dataset => {\n        dataset.data.pop();\n      });\n\n      chart.update();\n    }\n  }\n];\n// </block:actions>\n\n// <block:config:0>\nconst config = {\n  type: 'bubble',\n  data: data,\n  options: {\n    responsive: true,\n    plugins: {\n      legend: {\n        position: 'top',\n      },\n      title: {\n        display: true,\n        text: 'Chart.js Bubble Chart'\n      }\n    }\n  },\n};\n// </block:config>\n\nmodule.exports = {\n  actions: actions,\n  config: config,\n};\n```\n\n## Docs\n* [Bubble](../../charts/bubble.md)\n"
  },
  {
    "path": "docs/samples/other-charts/combo-bar-line.md",
    "content": "# Combo bar/line\n\n```js chart-editor\n// <block:actions:2>\nconst actions = [\n  {\n    name: 'Randomize',\n    handler(chart) {\n      chart.data.datasets.forEach(dataset => {\n        dataset.data = Utils.numbers({count: chart.data.labels.length, min: -100, max: 100});\n      });\n      chart.update();\n    }\n  },\n  {\n    name: 'Add Dataset',\n    handler(chart) {\n      const data = chart.data;\n      const dsColor = Utils.namedColor(chart.data.datasets.length);\n      const newDataset = {\n        label: 'Dataset ' + (data.datasets.length + 1),\n        backgroundColor: Utils.transparentize(dsColor, 0.5),\n        borderColor: dsColor,\n        borderWidth: 1,\n        data: Utils.numbers({count: data.labels.length, min: -100, max: 100}),\n      };\n      chart.data.datasets.push(newDataset);\n      chart.update();\n    }\n  },\n  {\n    name: 'Add Data',\n    handler(chart) {\n      const data = chart.data;\n      if (data.datasets.length > 0) {\n        data.labels = Utils.months({count: data.labels.length + 1});\n\n        for (let index = 0; index < data.datasets.length; ++index) {\n          data.datasets[index].data.push(Utils.rand(-100, 100));\n        }\n\n        chart.update();\n      }\n    }\n  },\n  {\n    name: 'Remove Dataset',\n    handler(chart) {\n      chart.data.datasets.pop();\n      chart.update();\n    }\n  },\n  {\n    name: 'Remove Data',\n    handler(chart) {\n      chart.data.labels.splice(-1, 1); // remove the label first\n\n      chart.data.datasets.forEach(dataset => {\n        dataset.data.pop();\n      });\n\n      chart.update();\n    }\n  }\n];\n// </block:actions>\n\n// <block:setup:1>\nconst DATA_COUNT = 7;\nconst NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100};\n\nconst labels = Utils.months({count: 7});\nconst data = {\n  labels: labels,\n  datasets: [\n    {\n      label: 'Dataset 1',\n      data: Utils.numbers(NUMBER_CFG),\n      borderColor: Utils.CHART_COLORS.red,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),\n      order: 1\n    },\n    {\n      label: 'Dataset 2',\n      data: Utils.numbers(NUMBER_CFG),\n      borderColor: Utils.CHART_COLORS.blue,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),\n      type: 'line',\n      order: 0\n    }\n  ]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'bar',\n  data: data,\n  options: {\n    responsive: true,\n    plugins: {\n      legend: {\n        position: 'top',\n      },\n      title: {\n        display: true,\n        text: 'Chart.js Combined Line/Bar Chart'\n      }\n    }\n  },\n};\n// </block:config>\n\nmodule.exports = {\n  actions: actions,\n  config: config,\n};\n```\n\n## Docs\n* [Bar](../../charts/bar.md)\n* [Line](../../charts/line.md)\n* [Data structures (`labels`)](../../general/data-structures.md)\n"
  },
  {
    "path": "docs/samples/other-charts/doughnut.md",
    "content": "# Doughnut\n\n```js chart-editor\n// <block:actions:2>\nconst actions = [\n  {\n    name: 'Randomize',\n    handler(chart) {\n      chart.data.datasets.forEach(dataset => {\n        dataset.data = Utils.numbers({count: chart.data.labels.length, min: 0, max: 100});\n      });\n      chart.update();\n    }\n  },\n  {\n    name: 'Add Dataset',\n    handler(chart) {\n      const data = chart.data;\n      const newDataset = {\n        label: 'Dataset ' + (data.datasets.length + 1),\n        backgroundColor: [],\n        data: [],\n      };\n\n      for (let i = 0; i < data.labels.length; i++) {\n        newDataset.data.push(Utils.numbers({count: 1, min: 0, max: 100}));\n\n        const colorIndex = i % Object.keys(Utils.CHART_COLORS).length;\n        newDataset.backgroundColor.push(Object.values(Utils.CHART_COLORS)[colorIndex]);\n      }\n\n      chart.data.datasets.push(newDataset);\n      chart.update();\n    }\n  },\n  {\n    name: 'Add Data',\n    handler(chart) {\n      const data = chart.data;\n      if (data.datasets.length > 0) {\n        data.labels.push('data #' + (data.labels.length + 1));\n\n        for (let index = 0; index < data.datasets.length; ++index) {\n          data.datasets[index].data.push(Utils.rand(0, 100));\n        }\n\n        chart.update();\n      }\n    }\n  },\n  {\n    name: 'Hide(0)',\n    handler(chart) {\n      chart.hide(0);\n    }\n  },\n  {\n    name: 'Show(0)',\n    handler(chart) {\n      chart.show(0);\n    }\n  },\n  {\n    name: 'Hide (0, 1)',\n    handler(chart) {\n      chart.hide(0, 1);\n    }\n  },\n  {\n    name: 'Show (0, 1)',\n    handler(chart) {\n      chart.show(0, 1);\n    }\n  },\n  {\n    name: 'Remove Dataset',\n    handler(chart) {\n      chart.data.datasets.pop();\n      chart.update();\n    }\n  },\n  {\n    name: 'Remove Data',\n    handler(chart) {\n      chart.data.labels.splice(-1, 1); // remove the label first\n\n      chart.data.datasets.forEach(dataset => {\n        dataset.data.pop();\n      });\n\n      chart.update();\n    }\n  }\n];\n// </block:actions>\n\n// <block:setup:1>\nconst DATA_COUNT = 5;\nconst NUMBER_CFG = {count: DATA_COUNT, min: 0, max: 100};\n\nconst data = {\n  labels: ['Red', 'Orange', 'Yellow', 'Green', 'Blue'],\n  datasets: [\n    {\n      label: 'Dataset 1',\n      data: Utils.numbers(NUMBER_CFG),\n      backgroundColor: Object.values(Utils.CHART_COLORS),\n    }\n  ]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'doughnut',\n  data: data,\n  options: {\n    responsive: true,\n    plugins: {\n      legend: {\n        position: 'top',\n      },\n      title: {\n        display: true,\n        text: 'Chart.js Doughnut Chart'\n      }\n    }\n  },\n};\n// </block:config>\n\nmodule.exports = {\n  actions: actions,\n  config: config,\n};\n```\n\n## Docs\n* [Doughnut and Pie Charts](../../charts/doughnut.md)\n"
  },
  {
    "path": "docs/samples/other-charts/multi-series-pie.md",
    "content": "# Multi Series Pie\n\n```js chart-editor\n// <block:setup:1>\nconst DATA_COUNT = 5;\nconst NUMBER_CFG = {count: DATA_COUNT, min: 0, max: 100};\n\nconst labels = Utils.months({count: 7});\nconst data = {\n  labels: ['Overall Yay', 'Overall Nay', 'Group A Yay', 'Group A Nay', 'Group B Yay', 'Group B Nay', 'Group C Yay', 'Group C Nay'],\n  datasets: [\n    {\n      backgroundColor: ['#AAA', '#777'],\n      data: [21, 79]\n    },\n    {\n      backgroundColor: ['hsl(0, 100%, 60%)', 'hsl(0, 100%, 35%)'],\n      data: [33, 67]\n    },\n    {\n      backgroundColor: ['hsl(100, 100%, 60%)', 'hsl(100, 100%, 35%)'],\n      data: [20, 80]\n    },\n    {\n      backgroundColor: ['hsl(180, 100%, 60%)', 'hsl(180, 100%, 35%)'],\n      data: [10, 90]\n    }\n  ]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'pie',\n  data: data,\n  options: {\n    responsive: true,\n    plugins: {\n      legend: {\n        labels: {\n          generateLabels: function(chart) {\n            // Get the default label list\n            const original = Chart.overrides.pie.plugins.legend.labels.generateLabels;\n            const labelsOriginal = original.call(this, chart);\n\n            // Build an array of colors used in the datasets of the chart\n            let datasetColors = chart.data.datasets.map(function(e) {\n              return e.backgroundColor;\n            });\n            datasetColors = datasetColors.flat();\n\n            // Modify the color and hide state of each label\n            labelsOriginal.forEach(label => {\n              // There are twice as many labels as there are datasets. This converts the label index into the corresponding dataset index\n              label.datasetIndex = (label.index - label.index % 2) / 2;\n\n              // The hidden state must match the dataset's hidden state\n              label.hidden = !chart.isDatasetVisible(label.datasetIndex);\n\n              // Change the color to match the dataset\n              label.fillStyle = datasetColors[label.index];\n            });\n\n            return labelsOriginal;\n          }\n        },\n        onClick: function(mouseEvent, legendItem, legend) {\n          // toggle the visibility of the dataset from what it currently is\n          legend.chart.getDatasetMeta(\n            legendItem.datasetIndex\n          ).hidden = legend.chart.isDatasetVisible(legendItem.datasetIndex);\n          legend.chart.update();\n        }\n      },\n      tooltip: {\n        callbacks: {\n          title: function(context) {\n            const labelIndex = (context[0].datasetIndex * 2) + context[0].dataIndex;\n            return context[0].chart.data.labels[labelIndex] + ': ' + context[0].formattedValue;\n          }\n        }\n      }\n    }\n  },\n};\n// </block:config>\n\nmodule.exports = {\n  config: config,\n};\n```\n\n## Docs\n* [Doughnut and Pie Charts](../../charts/doughnut.md)\n* [Options](../../general/options.md)\n  * [Scriptable Options](../../general/options.md#scriptable-options)"
  },
  {
    "path": "docs/samples/other-charts/pie.md",
    "content": "# Pie\n\n```js chart-editor\n// <block:actions:2>\nconst actions = [\n  {\n    name: 'Randomize',\n    handler(chart) {\n      chart.data.datasets.forEach(dataset => {\n        dataset.data = Utils.numbers({count: chart.data.labels.length, min: 0, max: 100});\n      });\n      chart.update();\n    }\n  },\n  {\n    name: 'Add Dataset',\n    handler(chart) {\n      const data = chart.data;\n      const newDataset = {\n        label: 'Dataset ' + (data.datasets.length + 1),\n        backgroundColor: [],\n        data: [],\n      };\n\n      for (let i = 0; i < data.labels.length; i++) {\n        newDataset.data.push(Utils.numbers({count: 1, min: 0, max: 100}));\n\n        const colorIndex = i % Object.keys(Utils.CHART_COLORS).length;\n        newDataset.backgroundColor.push(Object.values(Utils.CHART_COLORS)[colorIndex]);\n      }\n\n      chart.data.datasets.push(newDataset);\n      chart.update();\n    }\n  },\n  {\n    name: 'Add Data',\n    handler(chart) {\n      const data = chart.data;\n      if (data.datasets.length > 0) {\n        data.labels.push('data #' + (data.labels.length + 1));\n\n        for (let index = 0; index < data.datasets.length; ++index) {\n          data.datasets[index].data.push(Utils.rand(0, 100));\n        }\n\n        chart.update();\n      }\n    }\n  },\n  {\n    name: 'Remove Dataset',\n    handler(chart) {\n      chart.data.datasets.pop();\n      chart.update();\n    }\n  },\n  {\n    name: 'Remove Data',\n    handler(chart) {\n      chart.data.labels.splice(-1, 1); // remove the label first\n\n      chart.data.datasets.forEach(dataset => {\n        dataset.data.pop();\n      });\n\n      chart.update();\n    }\n  }\n];\n// </block:actions>\n\n// <block:setup:1>\nconst DATA_COUNT = 5;\nconst NUMBER_CFG = {count: DATA_COUNT, min: 0, max: 100};\n\nconst data = {\n  labels: ['Red', 'Orange', 'Yellow', 'Green', 'Blue'],\n  datasets: [\n    {\n      label: 'Dataset 1',\n      data: Utils.numbers(NUMBER_CFG),\n      backgroundColor: Object.values(Utils.CHART_COLORS),\n    }\n  ]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'pie',\n  data: data,\n  options: {\n    responsive: true,\n    plugins: {\n      legend: {\n        position: 'top',\n      },\n      title: {\n        display: true,\n        text: 'Chart.js Pie Chart'\n      }\n    }\n  },\n};\n// </block:config>\n\nmodule.exports = {\n  actions: actions,\n  config: config,\n};\n```\n## Docs\n* [Doughnut and Pie Charts](../../charts/doughnut.md)\n"
  },
  {
    "path": "docs/samples/other-charts/polar-area-center-labels.md",
    "content": "# Polar area centered point labels\n\n```js chart-editor\n// <block:actions:2>\nconst actions = [\n  {\n    name: 'Randomize',\n    handler(chart) {\n      chart.data.datasets.forEach(dataset => {\n        dataset.data = Utils.numbers({count: chart.data.labels.length, min: 0, max: 100});\n      });\n      chart.update();\n    }\n  },\n  {\n    name: 'Add Data',\n    handler(chart) {\n      const data = chart.data;\n      if (data.datasets.length > 0) {\n        data.labels.push('data #' + (data.labels.length + 1));\n\n        for (let index = 0; index < data.datasets.length; ++index) {\n          data.datasets[index].data.push(Utils.rand(0, 100));\n        }\n\n        chart.update();\n      }\n    }\n  },\n  {\n    name: 'Remove Data',\n    handler(chart) {\n      chart.data.labels.splice(-1, 1); // remove the label first\n\n      chart.data.datasets.forEach(dataset => {\n        dataset.data.pop();\n      });\n\n      chart.update();\n    }\n  }\n];\n// </block:actions>\n\n// <block:setup:1>\nconst DATA_COUNT = 5;\nconst NUMBER_CFG = {count: DATA_COUNT, min: 0, max: 100};\n\nconst labels = ['Red', 'Orange', 'Yellow', 'Green', 'Blue'];\nconst data = {\n  labels: labels,\n  datasets: [\n    {\n      label: 'Dataset 1',\n      data: Utils.numbers(NUMBER_CFG),\n      backgroundColor: [\n        Utils.transparentize(Utils.CHART_COLORS.red, 0.5),\n        Utils.transparentize(Utils.CHART_COLORS.orange, 0.5),\n        Utils.transparentize(Utils.CHART_COLORS.yellow, 0.5),\n        Utils.transparentize(Utils.CHART_COLORS.green, 0.5),\n        Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),\n      ]\n    }\n  ]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'polarArea',\n  data: data,\n  options: {\n    responsive: true,\n    scales: {\n      r: {\n        pointLabels: {\n          display: true,\n          centerPointLabels: true,\n          font: {\n            size: 18\n          }\n        }\n      }\n    },\n    plugins: {\n      legend: {\n        position: 'top',\n      },\n      title: {\n        display: true,\n        text: 'Chart.js Polar Area Chart With Centered Point Labels'\n      }\n    }\n  },\n};\n// </block:config>\n\nmodule.exports = {\n  actions: actions,\n  config: config,\n};\n```\n\n## Docs\n* [Polar Area Chart](../../charts/polar.md)\n* [Linear Radial Axis](../../axes/radial/linear.md)\n  * [Point Label Options (`centerPointLabels`)](../../axes/radial/linear.md#point-label-options)"
  },
  {
    "path": "docs/samples/other-charts/polar-area.md",
    "content": "# Polar area\n\n```js chart-editor\n// <block:actions:2>\nconst actions = [\n  {\n    name: 'Randomize',\n    handler(chart) {\n      chart.data.datasets.forEach(dataset => {\n        dataset.data = Utils.numbers({count: chart.data.labels.length, min: 0, max: 100});\n      });\n      chart.update();\n    }\n  },\n  {\n    name: 'Add Data',\n    handler(chart) {\n      const data = chart.data;\n      if (data.datasets.length > 0) {\n        data.labels.push('data #' + (data.labels.length + 1));\n\n        for (let index = 0; index < data.datasets.length; ++index) {\n          data.datasets[index].data.push(Utils.rand(0, 100));\n        }\n\n        chart.update();\n      }\n    }\n  },\n  {\n    name: 'Remove Data',\n    handler(chart) {\n      chart.data.labels.splice(-1, 1); // remove the label first\n\n      chart.data.datasets.forEach(dataset => {\n        dataset.data.pop();\n      });\n\n      chart.update();\n    }\n  }\n];\n// </block:actions>\n\n// <block:setup:1>\nconst DATA_COUNT = 5;\nconst NUMBER_CFG = {count: DATA_COUNT, min: 0, max: 100};\n\nconst labels = ['Red', 'Orange', 'Yellow', 'Green', 'Blue'];\nconst data = {\n  labels: labels,\n  datasets: [\n    {\n      label: 'Dataset 1',\n      data: Utils.numbers(NUMBER_CFG),\n      backgroundColor: [\n        Utils.transparentize(Utils.CHART_COLORS.red, 0.5),\n        Utils.transparentize(Utils.CHART_COLORS.orange, 0.5),\n        Utils.transparentize(Utils.CHART_COLORS.yellow, 0.5),\n        Utils.transparentize(Utils.CHART_COLORS.green, 0.5),\n        Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),\n      ]\n    }\n  ]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'polarArea',\n  data: data,\n  options: {\n    responsive: true,\n    plugins: {\n      legend: {\n        position: 'top',\n      },\n      title: {\n        display: true,\n        text: 'Chart.js Polar Area Chart'\n      }\n    }\n  },\n};\n// </block:config>\n\nmodule.exports = {\n  actions: actions,\n  config: config,\n};\n```\n\n## Docs\n* [Polar Area Chart](../../charts/polar.md)\n* [Radial linear scale](../../axes/radial/linear.md)\n"
  },
  {
    "path": "docs/samples/other-charts/radar-skip-points.md",
    "content": "# Radar skip points\n\n```js chart-editor\n// <block:actions:2>\nconst actions = [\n  {\n    name: 'Randomize',\n    handler(chart) {\n      chart.data.datasets.forEach((dataset, i) => {\n        const data = Utils.numbers({count: chart.data.labels.length, min: 0, max: 100});\n\n        if (i === 0) {\n          data[0] = null;\n        } else if (i === 1) {\n          data[Number.parseInt(data.length / 2, 10)] = null;\n        } else {\n          data[data.length - 1] = null;\n        }\n\n        dataset.data = data;\n      });\n      chart.update();\n    }\n  }\n];\n// </block:actions>\n\n// <block:setup:1>\nconst DATA_COUNT = 7;\nconst NUMBER_CFG = {count: DATA_COUNT, min: 0, max: 100};\n\nconst labels = Utils.months({count: 7});\nconst dataFirstSkip = Utils.numbers(NUMBER_CFG);\nconst dataMiddleSkip = Utils.numbers(NUMBER_CFG);\nconst dataLastSkip = Utils.numbers(NUMBER_CFG);\n\ndataFirstSkip[0] = null;\ndataMiddleSkip[Number.parseInt(dataMiddleSkip.length / 2, 10)] = null;\ndataLastSkip[dataLastSkip.length - 1] = null;\n\nconst data = {\n  labels: labels,\n  datasets: [\n    {\n      label: 'Skip first dataset',\n      data: dataFirstSkip,\n      borderColor: Utils.CHART_COLORS.red,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),\n    },\n    {\n      label: 'Skip mid dataset',\n      data: dataMiddleSkip,\n      borderColor: Utils.CHART_COLORS.blue,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),\n    },\n    {\n      label: 'Skip last dataset',\n      data: dataLastSkip,\n      borderColor: Utils.CHART_COLORS.green,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.green, 0.5),\n    }\n  ]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'radar',\n  data: data,\n  options: {\n    responsive: true,\n    plugins: {\n      title: {\n        display: true,\n        text: 'Chart.js Radar Skip Points Chart'\n      }\n    }\n  },\n};\n// </block:config>\n\nmodule.exports = {\n  actions: actions,\n  config: config\n};\n```\n\n## Docs\n* [Radar](../../charts/radar.md)\n* [Data structures (`labels`)](../../general/data-structures.md)\n* [Radial linear scale](../../axes/radial/linear.md)\n"
  },
  {
    "path": "docs/samples/other-charts/radar.md",
    "content": "# Radar\n\n```js chart-editor\n// <block:actions:2>\nconst actions = [\n  {\n    name: 'Randomize',\n    handler(chart) {\n      chart.data.datasets.forEach(dataset => {\n        dataset.data = Utils.numbers({count: chart.data.labels.length, min: 0, max: 100});\n      });\n      chart.update();\n    }\n  },\n  {\n    name: 'Add Dataset',\n    handler(chart) {\n      const data = chart.data;\n      const dsColor = Utils.namedColor(chart.data.datasets.length);\n      const newDataset = {\n        label: 'Dataset ' + (data.datasets.length + 1),\n        backgroundColor: Utils.transparentize(dsColor, 0.5),\n        borderColor: dsColor,\n        data: Utils.numbers({count: data.labels.length, min: 0, max: 100}),\n      };\n      chart.data.datasets.push(newDataset);\n      chart.update();\n    }\n  },\n  {\n    name: 'Add Data',\n    handler(chart) {\n      const data = chart.data;\n      if (data.datasets.length > 0) {\n        data.labels = Utils.months({count: data.labels.length + 1});\n\n        for (let index = 0; index < data.datasets.length; ++index) {\n          data.datasets[index].data.push(Utils.rand(0, 100));\n        }\n\n        chart.update();\n      }\n    }\n  },\n  {\n    name: 'Remove Dataset',\n    handler(chart) {\n      chart.data.datasets.pop();\n      chart.update();\n    }\n  },\n  {\n    name: 'Remove Data',\n    handler(chart) {\n      chart.data.labels.splice(-1, 1); // remove the label first\n\n      chart.data.datasets.forEach(dataset => {\n        dataset.data.pop();\n      });\n\n      chart.update();\n    }\n  }\n];\n// </block:actions>\n\n// <block:setup:1>\nconst DATA_COUNT = 7;\nconst NUMBER_CFG = {count: DATA_COUNT, min: 0, max: 100};\n\nconst labels = Utils.months({count: 7});\nconst data = {\n  labels: labels,\n  datasets: [\n    {\n      label: 'Dataset 1',\n      data: Utils.numbers(NUMBER_CFG),\n      borderColor: Utils.CHART_COLORS.red,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),\n    },\n    {\n      label: 'Dataset 2',\n      data: Utils.numbers(NUMBER_CFG),\n      borderColor: Utils.CHART_COLORS.blue,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),\n    }\n  ]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'radar',\n  data: data,\n  options: {\n    responsive: true,\n    plugins: {\n      title: {\n        display: true,\n        text: 'Chart.js Radar Chart'\n      }\n    }\n  },\n};\n// </block:config>\n\nmodule.exports = {\n  actions: actions,\n  config: config,\n};\n```\n\n## Docs\n* [Radar](../../charts/radar.md)\n* [Data structures (`labels`)](../../general/data-structures.md)\n* [Radial linear scale](../../axes/radial/linear.md)\n"
  },
  {
    "path": "docs/samples/other-charts/scatter-multi-axis.md",
    "content": "# Scatter - Multi axis\n\n```js chart-editor\n// <block:setup:1>\nconst DATA_COUNT = 7;\nconst NUMBER_CFG = {count: DATA_COUNT, rmin: 1, rmax: 1, min: -100, max: 100};\n\nconst data = {\n  datasets: [\n    {\n      label: 'Dataset 1',\n      data: Utils.bubbles(NUMBER_CFG),\n      borderColor: Utils.CHART_COLORS.red,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),\n      yAxisID: 'y',\n    },\n    {\n      label: 'Dataset 2',\n      data: Utils.bubbles(NUMBER_CFG),\n      borderColor: Utils.CHART_COLORS.orange,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.orange, 0.5),\n      yAxisID: 'y2',\n    }\n  ]\n};\n// </block:setup>\n\n// <block:actions:2>\nconst actions = [\n  {\n    name: 'Randomize',\n    handler(chart) {\n      chart.data.datasets.forEach(dataset => {\n        dataset.data = Utils.bubbles({count: DATA_COUNT, rmin: 1, rmax: 1, min: -100, max: 100});\n      });\n      chart.update();\n    }\n  },\n  {\n    name: 'Add Dataset',\n    handler(chart) {\n      const chartData = chart.data;\n      const dsColor = Utils.namedColor(chartData.datasets.length);\n      const newDataset = {\n        label: 'Dataset ' + (chartData.datasets.length + 1),\n        backgroundColor: Utils.transparentize(dsColor, 0.5),\n        borderColor: dsColor,\n        data: Utils.bubbles({count: DATA_COUNT, rmin: 1, rmax: 1, min: -100, max: 100}),\n      };\n      chart.data.datasets.push(newDataset);\n      chart.update();\n    }\n  },\n  {\n    name: 'Add Data',\n    handler(chart) {\n      const chartData = chart.data;\n      if (chartData.datasets.length > 0) {\n\n        for (let index = 0; index < chartData.datasets.length; ++index) {\n          chartData.datasets[index].data.push(Utils.bubbles({count: 1, rmin: 1, rmax: 1, min: -100, max: 100})[0]);\n        }\n\n        chart.update();\n      }\n    }\n  },\n  {\n    name: 'Remove Dataset',\n    handler(chart) {\n      chart.data.datasets.pop();\n      chart.update();\n    }\n  },\n  {\n    name: 'Remove Data',\n    handler(chart) {\n      chart.data.datasets.forEach(dataset => {\n        dataset.data.pop();\n      });\n\n      chart.update();\n    }\n  }\n];\n// </block:actions>\n\n// <block:config:0>\nconst config = {\n  type: 'scatter',\n  data: data,\n  options: {\n    responsive: true,\n    plugins: {\n      legend: {\n        position: 'top',\n      },\n      title: {\n        display: true,\n        text: 'Chart.js Scatter Multi Axis Chart'\n      }\n    },\n    scales: {\n      y: {\n        type: 'linear', // only linear but allow scale type registration. This allows extensions to exist solely for log scale for instance\n        position: 'left',\n        ticks: {\n          color: Utils.CHART_COLORS.red\n        }\n      },\n      y2: {\n        type: 'linear', // only linear but allow scale type registration. This allows extensions to exist solely for log scale for instance\n        position: 'right',\n        reverse: true,\n        ticks: {\n          color: Utils.CHART_COLORS.blue\n        },\n        grid: {\n          drawOnChartArea: false // only want the grid lines for one axis to show up\n        }\n      }\n    }\n  },\n};\n// </block:config>\n\nmodule.exports = {\n  actions: actions,\n  config: config,\n};\n```\n\n## Docs\n* [Scatter](../../charts/scatter.md)\n* [Cartesian Axes](../../axes/cartesian/)\n  * [Axis Position](../../axes/cartesian/#axis-position)\n"
  },
  {
    "path": "docs/samples/other-charts/scatter.md",
    "content": "# Scatter\n\n```js chart-editor\n// <block:setup:1>\nconst DATA_COUNT = 7;\nconst NUMBER_CFG = {count: DATA_COUNT, rmin: 1, rmax: 1, min: 0, max: 100};\n\nconst data = {\n  datasets: [\n    {\n      label: 'Dataset 1',\n      data: Utils.bubbles(NUMBER_CFG),\n      borderColor: Utils.CHART_COLORS.red,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),\n    },\n    {\n      label: 'Dataset 2',\n      data: Utils.bubbles(NUMBER_CFG),\n      borderColor: Utils.CHART_COLORS.orange,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.orange, 0.5),\n    }\n  ]\n};\n// </block:setup>\n\n// <block:actions:2>\nconst actions = [\n  {\n    name: 'Randomize',\n    handler(chart) {\n      chart.data.datasets.forEach(dataset => {\n        dataset.data = Utils.bubbles({count: DATA_COUNT, rmin: 1, rmax: 1, min: 0, max: 100});\n      });\n      chart.update();\n    }\n  },\n  {\n    name: 'Add Dataset',\n    handler(chart) {\n      const chartData = chart.data;\n      const dsColor = Utils.namedColor(chartData.datasets.length);\n      const newDataset = {\n        label: 'Dataset ' + (chartData.datasets.length + 1),\n        backgroundColor: Utils.transparentize(dsColor, 0.5),\n        borderColor: dsColor,\n        data: Utils.bubbles({count: DATA_COUNT, rmin: 1, rmax: 1, min: 0, max: 100}),\n      };\n      chart.data.datasets.push(newDataset);\n      chart.update();\n    }\n  },\n  {\n    name: 'Add Data',\n    handler(chart) {\n      const chartData = chart.data;\n      if (chartData.datasets.length > 0) {\n\n        for (let index = 0; index < chartData.datasets.length; ++index) {\n          chartData.datasets[index].data.push(Utils.bubbles({count: 1, rmin: 1, rmax: 1, min: 0, max: 100})[0]);\n        }\n\n        chart.update();\n      }\n    }\n  },\n  {\n    name: 'Remove Dataset',\n    handler(chart) {\n      chart.data.datasets.pop();\n      chart.update();\n    }\n  },\n  {\n    name: 'Remove Data',\n    handler(chart) {\n      chart.data.datasets.forEach(dataset => {\n        dataset.data.pop();\n      });\n\n      chart.update();\n    }\n  }\n];\n// </block:actions>\n\n// <block:config:0>\nconst config = {\n  type: 'scatter',\n  data: data,\n  options: {\n    responsive: true,\n    plugins: {\n      legend: {\n        position: 'top',\n      },\n      title: {\n        display: true,\n        text: 'Chart.js Scatter Chart'\n      }\n    }\n  },\n};\n// </block:config>\n\nmodule.exports = {\n  actions: actions,\n  config: config,\n};\n```\n\n## Docs\n* [Scatter](../../charts/scatter.md)\n"
  },
  {
    "path": "docs/samples/other-charts/stacked-bar-line.md",
    "content": "# Stacked bar/line\n\n```js chart-editor\n// <block:actions:2>\nconst actions = [\n  {\n    name: 'Randomize',\n    handler(chart) {\n      chart.data.datasets.forEach(dataset => {\n        dataset.data = Utils.numbers({count: chart.data.labels.length, min: 0, max: 100});\n      });\n      chart.update();\n    }\n  },\n  {\n    name: 'Add Dataset',\n    handler(chart) {\n      const data = chart.data;\n      const dsColor = Utils.namedColor(chart.data.datasets.length);\n      const newDataset = {\n        label: 'Dataset ' + (data.datasets.length + 1),\n        backgroundColor: Utils.transparentize(dsColor, 0.5),\n        borderColor: dsColor,\n        borderWidth: 1,\n        stack: 'combined',\n        data: Utils.numbers({count: data.labels.length, min: 0, max: 100}),\n      };\n      chart.data.datasets.push(newDataset);\n      chart.update();\n    }\n  },\n  {\n    name: 'Add Data',\n    handler(chart) {\n      const data = chart.data;\n      if (data.datasets.length > 0) {\n        data.labels = Utils.months({count: data.labels.length + 1});\n\n        for (let index = 0; index < data.datasets.length; ++index) {\n          data.datasets[index].data.push(Utils.rand(0, 100));\n        }\n\n        chart.update();\n      }\n    }\n  },\n  {\n    name: 'Remove Dataset',\n    handler(chart) {\n      chart.data.datasets.pop();\n      chart.update();\n    }\n  },\n  {\n    name: 'Remove Data',\n    handler(chart) {\n      chart.data.labels.splice(-1, 1); // remove the label first\n\n      chart.data.datasets.forEach(dataset => {\n        dataset.data.pop();\n      });\n\n      chart.update();\n    }\n  }\n];\n// </block:actions>\n\n// <block:setup:1>\nconst DATA_COUNT = 7;\nconst NUMBER_CFG = {count: DATA_COUNT, min: 0, max: 100};\n\nconst labels = Utils.months({count: 7});\nconst data = {\n  labels: labels,\n  datasets: [\n    {\n      label: 'Dataset 1',\n      data: Utils.numbers(NUMBER_CFG),\n      borderColor: Utils.CHART_COLORS.red,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),\n      stack: 'combined',\n      type: 'bar'\n    },\n    {\n      label: 'Dataset 2',\n      data: Utils.numbers(NUMBER_CFG),\n      borderColor: Utils.CHART_COLORS.blue,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),\n      stack: 'combined'\n    }\n  ]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'line',\n  data: data,\n  options: {\n    plugins: {\n      title: {\n        display: true,\n        text: 'Chart.js Stacked Line/Bar Chart'\n      }\n    },\n    scales: {\n      y: {\n        stacked: true\n      }\n    }\n  },\n};\n// </block:config>\n\nmodule.exports = {\n  actions: actions,\n  config: config,\n};\n```\n\n## Docs\n* [Axes scales](../../axes/)\n  * [Common options to all axes (`stacked`)](../../axes/#common-options-to-all-axes)\n  * [Stacking](../../axes/#stacking)\n* [Bar](../../charts/bar.md)\n* [Line](../../charts/line.md)\n* [Data structures (`labels`)](../../general/data-structures.md)\n  * [Dataset Configuration (`stack`)](../../general/data-structures.md#dataset-configuration)\n\n"
  },
  {
    "path": "docs/samples/plugins/chart-area-border.md",
    "content": "# Chart Area Border\n\n```js chart-editor\n// <block:data:2>\nconst DATA_COUNT = 7;\nconst NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100};\nconst labels = Utils.months({count: 7});\nconst data = {\n  labels: labels,\n  datasets: [\n    {\n      label: 'Dataset 1',\n      data: Utils.numbers(NUMBER_CFG),\n      borderColor: Utils.CHART_COLORS.red,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),\n    },\n    {\n      label: 'Dataset 2',\n      data: Utils.numbers(NUMBER_CFG),\n      borderColor: Utils.CHART_COLORS.blue,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),\n    }\n  ]\n};\n// </block:data>\n\n// <block:plugin:1>\nconst chartAreaBorder = {\n  id: 'chartAreaBorder',\n  beforeDraw(chart, args, options) {\n    const {ctx, chartArea: {left, top, width, height}} = chart;\n    ctx.save();\n    ctx.strokeStyle = options.borderColor;\n    ctx.lineWidth = options.borderWidth;\n    ctx.setLineDash(options.borderDash || []);\n    ctx.lineDashOffset = options.borderDashOffset;\n    ctx.strokeRect(left, top, width, height);\n    ctx.restore();\n  }\n};\n// </block:plugin>\n\n// <block:config:0>\nconst config = {\n  type: 'line',\n  data: data,\n  options: {\n    plugins: {\n      chartAreaBorder: {\n        borderColor: 'red',\n        borderWidth: 2,\n        borderDash: [5, 5],\n        borderDashOffset: 2,\n      }\n    }\n  },\n  plugins: [chartAreaBorder]\n};\n// </block:config>\n\nmodule.exports = {\n  config: config,\n};\n```\n\n## Docs\n* [Line](../../charts/line.md)\n* [Data structures (`labels`)](../../general/data-structures.md)\n* [Plugins](../../developers/plugins.md)\n"
  },
  {
    "path": "docs/samples/plugins/doughnut-empty-state.md",
    "content": "# Doughnut Empty State\n\n```js chart-editor\n// <block:data:2>\nconst data = {\n  labels: [],\n  datasets: [\n    {\n      label: 'Dataset 1',\n      data: []\n    }\n  ]\n};\n// </block:data>\n\n// <block:plugin:1>\nconst plugin = {\n  id: 'emptyDoughnut',\n  afterDraw(chart, args, options) {\n    const {datasets} = chart.data;\n    const {color, width, radiusDecrease} = options;\n    let hasData = false;\n\n    for (let i = 0; i < datasets.length; i += 1) {\n      const dataset = datasets[i];\n      hasData |= dataset.data.length > 0;\n    }\n\n    if (!hasData) {\n      const {chartArea: {left, top, right, bottom}, ctx} = chart;\n      const centerX = (left + right) / 2;\n      const centerY = (top + bottom) / 2;\n      const r = Math.min(right - left, bottom - top) / 2;\n\n      ctx.beginPath();\n      ctx.lineWidth = width || 2;\n      ctx.strokeStyle = color || 'rgba(255, 128, 0, 0.5)';\n      ctx.arc(centerX, centerY, (r - radiusDecrease || 0), 0, 2 * Math.PI);\n      ctx.stroke();\n    }\n  }\n};\n// </block:plugin>\n\n// <block:config:0>\nconst config = {\n  type: 'doughnut',\n  data: data,\n  options: {\n    plugins: {\n      emptyDoughnut: {\n        color: 'rgba(255, 128, 0, 0.5)',\n        width: 2,\n        radiusDecrease: 20\n      }\n    }\n  },\n  plugins: [plugin]\n};\n// </block:config>\n\nconst actions = [\n  {\n    name: 'Randomize',\n    handler(chart) {\n      chart.data.datasets.forEach(dataset => {\n        dataset.data = Utils.points(NUMBER_CFG);\n      });\n      chart.update();\n    }\n  },\n];\n\nmodule.exports = {\n  actions,\n  config,\n};\n```\n\n## Docs\n* [Data structures (`labels`)](../../general/data-structures.md)\n* [Plugins](../../developers/plugins.md)\n* [Doughnut and Pie Charts](../../charts/doughnut.md)\n"
  },
  {
    "path": "docs/samples/plugins/quadrants.md",
    "content": "# Quadrants\n\n```js chart-editor\n// <block:data:2>\nconst DATA_COUNT = 7;\nconst NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100};\nconst data = {\n  datasets: [\n    {\n      label: 'Dataset 1',\n      data: Utils.points(NUMBER_CFG),\n      borderColor: Utils.CHART_COLORS.red,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),\n    },\n    {\n      label: 'Dataset 2',\n      data: Utils.points(NUMBER_CFG),\n      borderColor: Utils.CHART_COLORS.blue,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),\n    }\n  ]\n};\n// </block:data>\n\n// <block:plugin:1>\nconst quadrants = {\n  id: 'quadrants',\n  beforeDraw(chart, args, options) {\n    const {ctx, chartArea: {left, top, right, bottom}, scales: {x, y}} = chart;\n    const midX = x.getPixelForValue(0);\n    const midY = y.getPixelForValue(0);\n    ctx.save();\n    ctx.fillStyle = options.topLeft;\n    ctx.fillRect(left, top, midX - left, midY - top);\n    ctx.fillStyle = options.topRight;\n    ctx.fillRect(midX, top, right - midX, midY - top);\n    ctx.fillStyle = options.bottomRight;\n    ctx.fillRect(midX, midY, right - midX, bottom - midY);\n    ctx.fillStyle = options.bottomLeft;\n    ctx.fillRect(left, midY, midX - left, bottom - midY);\n    ctx.restore();\n  }\n};\n// </block:plugin>\n\n// <block:config:0>\nconst config = {\n  type: 'scatter',\n  data: data,\n  options: {\n    plugins: {\n      quadrants: {\n        topLeft: Utils.CHART_COLORS.red,\n        topRight: Utils.CHART_COLORS.blue,\n        bottomRight: Utils.CHART_COLORS.green,\n        bottomLeft: Utils.CHART_COLORS.yellow,\n      }\n    }\n  },\n  plugins: [quadrants]\n};\n// </block:config>\n\nconst actions = [\n  {\n    name: 'Randomize',\n    handler(chart) {\n      chart.data.datasets.forEach(dataset => {\n        dataset.data = Utils.points(NUMBER_CFG);\n      });\n      chart.update();\n    }\n  },\n];\n\nmodule.exports = {\n  actions,\n  config,\n};\n```\n\n## Docs\n* [Data structures (`labels`)](../../general/data-structures.md)\n* [Plugins](../../developers/plugins.md)\n* [Scatter](../../charts/scatter.md)\n"
  },
  {
    "path": "docs/samples/scale-options/center.md",
    "content": "# Center Positioning\n\nThis sample show how to place the axis in the center of the chart area, instead of at the edges.\n\n```js chart-editor\n// <block:actions:2>\nconst actions = [\n  {\n    name: 'Default Positions',\n    handler(chart) {\n      chart.options.scales.x.position = 'bottom';\n      chart.options.scales.y.position = 'left';\n      chart.update();\n    }\n  },\n  {\n    name: 'Position: center',\n    handler(chart) {\n      chart.options.scales.x.position = 'center';\n      chart.options.scales.y.position = 'center';\n      chart.update();\n    }\n  },\n  {\n    name: 'Position: Vertical: x=-60, Horizontal: y=30',\n    handler(chart) {\n      chart.options.scales.x.position = {y: 30};\n      chart.options.scales.y.position = {x: -60};\n      chart.update();\n    }\n  },\n];\n// </block:actions>\n\n\n// <block:setup:1>\nconst DATA_COUNT = 6;\nconst NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100};\nconst data = {\n  datasets: [\n    {\n      label: 'Dataset 1',\n      data: Utils.points(NUMBER_CFG),\n      fill: false,\n      borderColor: Utils.CHART_COLORS.red,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),\n    },\n    {\n      label: 'Dataset 2',\n      data: Utils.points(NUMBER_CFG),\n      fill: false,\n      borderColor: Utils.CHART_COLORS.blue,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),\n    }\n  ]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'scatter',\n  data: data,\n  options: {\n    responsive: true,\n    plugins: {\n      title: {\n        display: true,\n        text: 'Axis Center Positioning'\n      }\n    },\n    scales: {\n      x: {\n        min: -100,\n        max: 100,\n      },\n      y: {\n        min: -100,\n        max: 100,\n      }\n    }\n  },\n};\n// </block:config>\n\nmodule.exports = {\n  actions: actions,\n  config: config,\n};\n```\n\n## Docs\n* [Scatter](../../charts/scatter.md)\n* [Cartesian Axes](../../axes/cartesian/)\n  * [Axis Position](../../axes/cartesian/#axis-position)"
  },
  {
    "path": "docs/samples/scale-options/grid.md",
    "content": "# Grid Configuration\n\nThis sample shows how to use scriptable grid options for an axis to control styling. In this case, the Y axis grid lines are colored based on their value. In addition, booleans are provided to toggle different parts of the X axis grid visibility.\n\n```js chart-editor\n// <block:actions:2>\nconst actions = [\n  {\n    name: 'Randomize',\n    handler(chart) {\n      chart.data.datasets.forEach(dataset => {\n        dataset.data = Utils.numbers({count: chart.data.labels.length, min: -100, max: 100});\n      });\n      chart.update();\n    }\n  },\n];\n// </block:actions>\n\n// <block:setup:1>\nconst DATA_COUNT = 7;\nconst data = {\n  labels: Utils.months({count: DATA_COUNT}),\n  datasets: [\n    {\n      label: 'Dataset 1',\n      data: [10, 30, 39, 20, 25, 34, -10],\n      fill: false,\n      borderColor: Utils.CHART_COLORS.red,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),\n    },\n    {\n      label: 'Dataset 2',\n      data: [18, 33, 22, 19, 11, -39, 30],\n      fill: false,\n      borderColor: Utils.CHART_COLORS.blue,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),\n    }\n  ]\n};\n// </block:setup>\n\n// <block:config:0>\n// Change these settings to change the display for different parts of the X axis\n// grid configuration\nconst DISPLAY = true;\nconst BORDER = true;\nconst CHART_AREA = true;\nconst TICKS = true;\n\nconst config = {\n  type: 'line',\n  data: data,\n  options: {\n    responsive: true,\n    plugins: {\n      title: {\n        display: true,\n        text: 'Grid Line Settings'\n      }\n    },\n    scales: {\n      x: {\n        border: {\n          display: BORDER\n        },\n        grid: {\n          display: DISPLAY,\n          drawOnChartArea: CHART_AREA,\n          drawTicks: TICKS,\n        }\n      },\n      y: {\n        border: {\n          display: false\n        },\n        grid: {\n          color: function(context) {\n            if (context.tick.value > 0) {\n              return Utils.CHART_COLORS.green;\n            } else if (context.tick.value < 0) {\n              return Utils.CHART_COLORS.red;\n            }\n\n            return '#000000';\n          },\n        },\n      }\n    }\n  },\n};\n// </block:config>\n\nmodule.exports = {\n  actions: actions,\n  config: config,\n};\n```\n\n## Docs\n* [Line](../../charts/line.md)\n* [Options](../../general/options.md)\n  * [Scriptable Options](../../general/options.md#scriptable-options)\n    * [Tick Context](../../general/options.md#tick)\n* [Data structures (`labels`)](../../general/data-structures.md)\n* [Axes Styling](../../axes/styling.md)\n  * [Grid Line Configuration](../../axes/styling.md#grid-line-configuration)"
  },
  {
    "path": "docs/samples/scale-options/ticks.md",
    "content": "# Tick Configuration\n\nThis sample shows how to use different tick features to control how tick labels are shown on the X axis. These features include:\n\n* Multi-line labels\n* Filtering labels\n* Changing the tick color\n* Changing the tick alignment for the X axis\n\n```js chart-editor\n// <block:actions:2>\nconst actions = [\n  {\n    name: 'Alignment: start',\n    handler(chart) {\n      chart.options.scales.x.ticks.align = 'start';\n      chart.update();\n    }\n  },\n  {\n    name: 'Alignment: center (default)',\n    handler(chart) {\n      chart.options.scales.x.ticks.align = 'center';\n      chart.update();\n    }\n  },\n  {\n    name: 'Alignment: end',\n    handler(chart) {\n      chart.options.scales.x.ticks.align = 'end';\n      chart.update();\n    }\n  },\n];\n// </block:actions>\n\n\n// <block:setup:1>\nconst DATA_COUNT = 12;\nconst NUMBER_CFG = {count: DATA_COUNT, min: 0, max: 100};\nconst data = {\n  labels: [['June', '2015'], 'July', 'August', 'September', 'October', 'November', 'December', ['January', '2016'], 'February', 'March', 'April', 'May'],\n  datasets: [\n    {\n      label: 'Dataset 1',\n      data: Utils.numbers(NUMBER_CFG),\n      fill: false,\n      borderColor: Utils.CHART_COLORS.red,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),\n    },\n    {\n      label: 'Dataset 2',\n      data: Utils.numbers(NUMBER_CFG),\n      fill: false,\n      borderColor: Utils.CHART_COLORS.blue,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),\n    }\n  ]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'line',\n  data: data,\n  options: {\n    responsive: true,\n    plugins: {\n      title: {\n        display: true,\n        text: 'Chart with Tick Configuration'\n      }\n    },\n    scales: {\n      x: {\n        ticks: {\n          // For a category axis, the val is the index so the lookup via getLabelForValue is needed\n          callback: function(val, index) {\n            // Hide every 2nd tick label\n            return index % 2 === 0 ? this.getLabelForValue(val) : '';\n          },\n          color: 'red',\n        }\n      }\n    }\n  },\n};\n// </block:config>\n\nmodule.exports = {\n  actions: actions,\n  config: config,\n};\n```\n\n## Docs\n* [Line](../../charts/line.md)\n* [Options](../../general/options.md)\n  * [Scriptable Options](../../general/options.md#scriptable-options)\n    * [Tick Context](../../general/options.md#tick)\n* [Data structures (`labels`)](../../general/data-structures.md)\n* [Axes Styling](../../axes/styling.md)\n  * [Tick Configuration](../../axes/styling.md#tick-configuration)"
  },
  {
    "path": "docs/samples/scale-options/titles.md",
    "content": "# Title Configuration\n\nThis sample shows how to configure the title of an axis including alignment, font, and color.\n\n```js chart-editor\n// <block:setup:1>\nconst DATA_COUNT = 7;\nconst NUMBER_CFG = {count: DATA_COUNT, min: 0, max: 100};\nconst data = {\n  labels: Utils.months({count: DATA_COUNT}),\n  datasets: [\n    {\n      label: 'Dataset 1',\n      data: Utils.numbers(NUMBER_CFG),\n      fill: false,\n      borderColor: Utils.CHART_COLORS.red,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),\n    },\n    {\n      label: 'Dataset 2',\n      data: Utils.numbers(NUMBER_CFG),\n      fill: false,\n      borderColor: Utils.CHART_COLORS.blue,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),\n    }\n  ]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'line',\n  data: data,\n  options: {\n    responsive: true,\n    scales: {\n      x: {\n        display: true,\n        title: {\n          display: true,\n          text: 'Month',\n          color: '#911',\n          font: {\n            family: 'Comic Sans MS',\n            size: 20,\n            weight: 'bold',\n            lineHeight: 1.2,\n          },\n          padding: {top: 20, left: 0, right: 0, bottom: 0}\n        }\n      },\n      y: {\n        display: true,\n        title: {\n          display: true,\n          text: 'Value',\n          color: '#191',\n          font: {\n            family: 'Times',\n            size: 20,\n            style: 'normal',\n            lineHeight: 1.2\n          },\n          padding: {top: 30, left: 0, right: 0, bottom: 0}\n        }\n      }\n    }\n  },\n};\n// </block:config>\n\nmodule.exports = {\n  actions: [],\n  config: config,\n};\n```\n\n## Docs\n* [Line](../../charts/line.md)\n* [Data structures (`labels`)](../../general/data-structures.md)\n* [Axes Styling](../../axes/styling.md)\n* [Cartesian Axes](../../axes/cartesian/)\n  * [Common options to all cartesian axes](../../axes/cartesian/#common-options-to-all-cartesian-axes)\n* [Labeling Axes](../../axes/labelling.md)\n  * [Scale Title Configuration](../../axes/labelling.md#scale-title-configuration)"
  },
  {
    "path": "docs/samples/scales/linear-min-max-suggested.md",
    "content": "# Linear Scale - Suggested Min-Max\n\n```js chart-editor\n// <block:setup:1>\nconst DATA_COUNT = 7;\nconst NUMBER_CFG = {count: DATA_COUNT, min: 0, max: 100};\n\nconst labels = Utils.months({count: 7});\nconst data = {\n  labels: labels,\n  datasets: [\n    {\n      label: 'Dataset 1',\n      data: [10, 30, 39, 20, 25, 34, -10],\n      borderColor: Utils.CHART_COLORS.red,\n      backgroundColor: Utils.CHART_COLORS.red,\n    },\n    {\n      label: 'Dataset 2',\n      data: [18, 33, 22, 19, 11, 39, 30],\n      borderColor: Utils.CHART_COLORS.blue,\n      backgroundColor: Utils.CHART_COLORS.blue,\n    }\n  ]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'line',\n  data: data,\n  options: {\n    responsive: true,\n    plugins: {\n      title: {\n        display: true,\n        text: 'Suggested Min and Max Settings'\n      }\n    },\n    scales: {\n      y: {\n        // the data minimum used for determining the ticks is Math.min(dataMin, suggestedMin)\n        suggestedMin: 30,\n\n        // the data maximum used for determining the ticks is Math.max(dataMax, suggestedMax)\n        suggestedMax: 50,\n      }\n    }\n  },\n};\n// </block:config>\n\nmodule.exports = {\n  config: config,\n};\n```\n\n## Docs\n* [Line](../../charts/line.md)\n* [Data structures (`labels`)](../../general/data-structures.md)\n* [Axes scales](../../axes/)\n  * [Common options to all axes](../../axes/#common-options-to-all-axes)\n  * [Axis Range Settings](../../axes/#axis-range-settings)\n"
  },
  {
    "path": "docs/samples/scales/linear-min-max.md",
    "content": "# Linear Scale - Min-Max\n\n```js chart-editor\n// <block:setup:1>\nconst DATA_COUNT = 7;\nconst NUMBER_CFG = {count: DATA_COUNT, min: 0, max: 100};\n\nconst labels = Utils.months({count: 7});\nconst data = {\n  labels: labels,\n  datasets: [\n    {\n      label: 'Dataset 1',\n      data: [10, 30, 50, 20, 25, 44, -10],\n      borderColor: Utils.CHART_COLORS.red,\n      backgroundColor: Utils.CHART_COLORS.red,\n    },\n    {\n      label: 'Dataset 2',\n      data: [100, 33, 22, 19, 11, 49, 30],\n      borderColor: Utils.CHART_COLORS.blue,\n      backgroundColor: Utils.CHART_COLORS.blue,\n    }\n  ]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'line',\n  data: data,\n  options: {\n    responsive: true,\n    plugins: {\n      title: {\n        display: true,\n        text: 'Min and Max Settings'\n      }\n    },\n    scales: {\n      y: {\n        min: 10,\n        max: 50,\n      }\n    }\n  },\n};\n// </block:config>\n\nmodule.exports = {\n  config: config,\n};\n```\n\n## Docs\n* [Line](../../charts/line.md)\n* [Data structures (`labels`)](../../general/data-structures.md)\n* [Axes scales](../../axes/)\n  * [Common options to all axes (`min`,`max`)](../../axes/#common-options-to-all-axes)\n "
  },
  {
    "path": "docs/samples/scales/linear-step-size.md",
    "content": "# Linear Scale - Step Size\n\n```js chart-editor\n// <block:actions:2>\nconst actions = [\n  {\n    name: 'Randomize',\n    handler(chart) {\n      chart.data.datasets.forEach(dataset => {\n        dataset.data = Utils.numbers({count: chart.data.labels.length, min: 0, max: 100});\n      });\n      chart.update();\n    }\n  },\n  {\n    name: 'Add Dataset',\n    handler(chart) {\n      const data = chart.data;\n      const dsColor = Utils.namedColor(chart.data.datasets.length);\n      const newDataset = {\n        label: 'Dataset ' + (data.datasets.length + 1),\n        backgroundColor: dsColor,\n        borderColor: dsColor,\n        data: Utils.numbers({count: data.labels.length, min: 0, max: 100}),\n      };\n      chart.data.datasets.push(newDataset);\n      chart.update();\n    }\n  },\n  {\n    name: 'Add Data',\n    handler(chart) {\n      const data = chart.data;\n      if (data.datasets.length > 0) {\n        data.labels = Utils.months({count: data.labels.length + 1});\n\n        for (let index = 0; index < data.datasets.length; ++index) {\n          data.datasets[index].data.push(Utils.rand(0, 100));\n        }\n\n        chart.update();\n      }\n    }\n  },\n  {\n    name: 'Remove Dataset',\n    handler(chart) {\n      chart.data.datasets.pop();\n      chart.update();\n    }\n  },\n  {\n    name: 'Remove Data',\n    handler(chart) {\n      chart.data.labels.splice(-1, 1); // remove the label first\n\n      chart.data.datasets.forEach(dataset => {\n        dataset.data.pop();\n      });\n\n      chart.update();\n    }\n  }\n];\n// </block:actions>\n\n// <block:setup:1>\nconst DATA_COUNT = 7;\nconst NUMBER_CFG = {count: DATA_COUNT, min: 0, max: 100};\n\nconst labels = Utils.months({count: 7});\nconst data = {\n  labels: labels,\n  datasets: [\n    {\n      label: 'Dataset 1',\n      data: Utils.numbers(NUMBER_CFG),\n      borderColor: Utils.CHART_COLORS.red,\n      backgroundColor: Utils.CHART_COLORS.red,\n    },\n    {\n      label: 'Dataset 2',\n      data: Utils.numbers(NUMBER_CFG),\n      borderColor: Utils.CHART_COLORS.blue,\n      backgroundColor: Utils.CHART_COLORS.blue,\n    }\n  ]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'line',\n  data: data,\n  options: {\n    responsive: true,\n    plugins: {\n      tooltip: {\n        mode: 'index',\n        intersect: false\n      },\n      title: {\n        display: true,\n        text: 'Chart.js Line Chart'\n      }\n    },\n    hover: {\n      mode: 'index',\n      intersect: false\n    },\n    scales: {\n      x: {\n        title: {\n          display: true,\n          text: 'Month'\n        }\n      },\n      y: {\n        title: {\n          display: true,\n          text: 'Value'\n        },\n        min: 0,\n        max: 100,\n        ticks: {\n          // forces step size to be 50 units\n          stepSize: 50\n        }\n      }\n    }\n  },\n};\n// </block:config>\n\nmodule.exports = {\n  actions: actions,\n  config: config,\n};\n```\n\n## Docs\n* [Line](../../charts/line.md)\n* [Data structures (`labels`)](../../general/data-structures.md)\n* [Axes scales](../../axes/)\n  * [Common options to all axes (`min`,`max`)](../../axes/#common-options-to-all-axes)\n* [Linear Axis](../../axes/cartesian/linear.md)\n  * [Linear Axis specific tick options (`stepSize`)](../../axes/cartesian/linear.md#linear-axis-specific-tick-options)\n  * [Step Size](../../axes/cartesian/linear.md#step-size)\n"
  },
  {
    "path": "docs/samples/scales/log.md",
    "content": "# Log Scale\n\n```js chart-editor\n// <block:actions:2>\nconst logNumbers = (num) => {\n  const data = [];\n\n  for (let i = 0; i < num; ++i) {\n    data.push(Math.ceil(Math.random() * 10.0) * Math.pow(10, Math.ceil(Math.random() * 5)));\n  }\n\n  return data;\n};\n\nconst actions = [\n  {\n    name: 'Randomize',\n    handler(chart) {\n      chart.data.datasets.forEach(dataset => {\n        dataset.data = logNumbers(chart.data.labels.length);\n      });\n      chart.update();\n    }\n  },\n];\n// </block:actions>\n\n// <block:setup:1>\nconst DATA_COUNT = 7;\nconst NUMBER_CFG = {count: DATA_COUNT, min: 0, max: 100};\n\nconst labels = Utils.months({count: 7});\nconst data = {\n  labels: labels,\n  datasets: [\n    {\n      label: 'Dataset 1',\n      data: logNumbers(DATA_COUNT),\n      borderColor: Utils.CHART_COLORS.red,\n      backgroundColor: Utils.CHART_COLORS.red,\n      fill: false,\n    },\n  ]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'line',\n  data: data,\n  options: {\n    responsive: true,\n    plugins: {\n      title: {\n        display: true,\n        text: 'Chart.js Line Chart - Logarithmic'\n      }\n    },\n    scales: {\n      x: {\n        display: true,\n      },\n      y: {\n        display: true,\n        type: 'logarithmic',\n      }\n    }\n  },\n};\n// </block:config>\n\nmodule.exports = {\n  actions: actions,\n  config: config,\n};\n```\n\n## Docs\n* [Line](../../charts/line.md)\n* [Logarithmic Axis](../../axes/cartesian/logarithmic.md)\n* [Data structures (`labels`)](../../general/data-structures.md)\n\n"
  },
  {
    "path": "docs/samples/scales/stacked.md",
    "content": "# Stacked Linear / Category\n\n```js chart-editor\n// <block:setup:1>\nconst DATA_COUNT = 7;\nconst NUMBER_CFG = {count: DATA_COUNT, min: 0, max: 100};\n\nconst labels = Utils.months({count: 7});\nconst data = {\n  labels: labels,\n  datasets: [\n    {\n      label: 'Dataset 1',\n      data: [10, 30, 50, 20, 25, 44, -10],\n      borderColor: Utils.CHART_COLORS.red,\n      backgroundColor: Utils.CHART_COLORS.red,\n    },\n    {\n      label: 'Dataset 2',\n      data: ['ON', 'ON', 'OFF', 'ON', 'OFF', 'OFF', 'ON'],\n      borderColor: Utils.CHART_COLORS.blue,\n      backgroundColor: Utils.CHART_COLORS.blue,\n      stepped: true,\n      yAxisID: 'y2',\n    }\n  ]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'line',\n  data: data,\n  options: {\n    responsive: true,\n    plugins: {\n      title: {\n        display: true,\n        text: 'Stacked scales',\n      },\n    },\n    scales: {\n      y: {\n        type: 'linear',\n        position: 'left',\n        stack: 'demo',\n        stackWeight: 2,\n        border: {\n          color: Utils.CHART_COLORS.red\n        }\n      },\n      y2: {\n        type: 'category',\n        labels: ['ON', 'OFF'],\n        offset: true,\n        position: 'left',\n        stack: 'demo',\n        stackWeight: 1,\n        border: {\n          color: Utils.CHART_COLORS.blue\n        }\n      }\n    }\n  },\n};\n// </block:config>\n\nmodule.exports = {\n  config: config,\n};\n```\n\n## Docs\n* [Line](../../charts/line.md)\n* [Axes scales](../../axes/)\n  * [Stacking](../../axes/#stacking)\n* [Data structures (`labels`)](../../general/data-structures.md)\n"
  },
  {
    "path": "docs/samples/scales/time-combo.md",
    "content": "# Time Scale - Combo Chart\n\n```js chart-editor\n// <block:actions:2>\nconst actions = [\n  {\n    name: 'Randomize',\n    handler(chart) {\n      chart.data.datasets.forEach(dataset => {\n        dataset.data = Utils.numbers({count: chart.data.labels.length, min: 0, max: 100});\n      });\n      chart.update();\n    }\n  },\n];\n// </block:actions>\n\n// <block:setup:1>\nconst DATA_COUNT = 7;\nconst NUMBER_CFG = {count: DATA_COUNT, min: 0, max: 100};\n\nconst labels = [];\n\nfor (let i = 0; i < DATA_COUNT; ++i) {\n  labels.push(Utils.newDate(i));\n}\n\nconst data = {\n  labels: labels,\n  datasets: [{\n    type: 'bar',\n    label: 'Dataset 1',\n    backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),\n    borderColor: Utils.CHART_COLORS.red,\n    data: Utils.numbers(NUMBER_CFG),\n  }, {\n    type: 'bar',\n    label: 'Dataset 2',\n    backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),\n    borderColor: Utils.CHART_COLORS.blue,\n    data: Utils.numbers(NUMBER_CFG),\n  }, {\n    type: 'line',\n    label: 'Dataset 3',\n    backgroundColor: Utils.transparentize(Utils.CHART_COLORS.green, 0.5),\n    borderColor: Utils.CHART_COLORS.green,\n    fill: false,\n    data: Utils.numbers(NUMBER_CFG),\n  }]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'line',\n  data: data,\n  options: {\n    plugins: {\n      title: {\n        text: 'Chart.js Combo Time Scale',\n        display: true\n      }\n    },\n    scales: {\n      x: {\n        type: 'time',\n        display: true,\n        offset: true,\n        ticks: {\n          source: 'data'\n        },\n        time: {\n          unit: 'day'\n        },\n      },\n    },\n  },\n};\n// </block:config>\n\nmodule.exports = {\n  actions: actions,\n  config: config,\n};\n```\n\n## Docs\n* [Bar](../../charts/bar.md)\n* [Line](../../charts/line.md)\n* [Data structures (`labels`)](../../general/data-structures.md)\n* [Time Scale](../../axes/cartesian/time.md)\n"
  },
  {
    "path": "docs/samples/scales/time-line.md",
    "content": "# Time Scale\n\n```js chart-editor\n// <block:actions:2>\nconst actions = [\n  {\n    name: 'Randomize',\n    handler(chart) {\n      chart.data.datasets.forEach(dataset => {\n        dataset.data.forEach(function(dataObj, j) {\n          const newVal = Utils.rand(0, 100);\n\n          if (typeof dataObj === 'object') {\n            dataObj.y = newVal;\n          } else {\n            dataset.data[j] = newVal;\n          }\n        });\n      });\n      chart.update();\n    }\n  },\n];\n// </block:actions>\n\n// <block:setup:1>\nconst DATA_COUNT = 7;\nconst NUMBER_CFG = {count: DATA_COUNT, min: 0, max: 100};\n\nconst data = {\n  labels: [ // Date Objects\n    Utils.newDate(0),\n    Utils.newDate(1),\n    Utils.newDate(2),\n    Utils.newDate(3),\n    Utils.newDate(4),\n    Utils.newDate(5),\n    Utils.newDate(6)\n  ],\n  datasets: [{\n    label: 'My First dataset',\n    backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),\n    borderColor: Utils.CHART_COLORS.red,\n    fill: false,\n    data: Utils.numbers(NUMBER_CFG),\n  }, {\n    label: 'My Second dataset',\n    backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),\n    borderColor: Utils.CHART_COLORS.blue,\n    fill: false,\n    data: Utils.numbers(NUMBER_CFG),\n  }, {\n    label: 'Dataset with point data',\n    backgroundColor: Utils.transparentize(Utils.CHART_COLORS.green, 0.5),\n    borderColor: Utils.CHART_COLORS.green,\n    fill: false,\n    data: [{\n      x: Utils.newDateString(0),\n      y: Utils.rand(0, 100)\n    }, {\n      x: Utils.newDateString(5),\n      y: Utils.rand(0, 100)\n    }, {\n      x: Utils.newDateString(7),\n      y: Utils.rand(0, 100)\n    }, {\n      x: Utils.newDateString(15),\n      y: Utils.rand(0, 100)\n    }],\n  }]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'line',\n  data: data,\n  options: {\n    plugins: {\n      title: {\n        text: 'Chart.js Time Scale',\n        display: true\n      }\n    },\n    scales: {\n      x: {\n        type: 'time',\n        time: {\n          // Luxon format string\n          tooltipFormat: 'DD T'\n        },\n        title: {\n          display: true,\n          text: 'Date'\n        }\n      },\n      y: {\n        title: {\n          display: true,\n          text: 'value'\n        }\n      }\n    },\n  },\n};\n// </block:config>\n\nmodule.exports = {\n  actions: actions,\n  config: config,\n};\n```\n\n## Docs\n* [Line](../../charts/line.md)\n* [Time Cartesian Axis](../../axes/cartesian/time.md)"
  },
  {
    "path": "docs/samples/scales/time-max-span.md",
    "content": "# Time Scale - Max Span\n\n```js chart-editor\n// <block:actions:2>\nconst actions = [\n  {\n    name: 'Randomize',\n    handler(chart) {\n      chart.data.datasets.forEach(dataset => {\n        dataset.data.forEach(function(dataObj, j) {\n          const newVal = Utils.rand(0, 100);\n\n          if (typeof dataObj === 'object') {\n            dataObj.y = newVal;\n          } else {\n            dataset.data[j] = newVal;\n          }\n        });\n      });\n      chart.update();\n    }\n  },\n];\n// </block:actions>\n\n// <block:setup:1>\nconst data = {\n  datasets: [{\n    label: 'Dataset with string point data',\n    backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),\n    borderColor: Utils.CHART_COLORS.red,\n    fill: false,\n    data: [{\n      x: Utils.newDateString(0),\n      y: Utils.rand(0, 100)\n    }, {\n      x: Utils.newDateString(2),\n      y: Utils.rand(0, 100)\n    }, {\n      x: Utils.newDateString(4),\n      y: Utils.rand(0, 100)\n    }, {\n      x: Utils.newDateString(6),\n      y: Utils.rand(0, 100)\n    }],\n  }, {\n    label: 'Dataset with date object point data',\n    backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),\n    borderColor: Utils.CHART_COLORS.blue,\n    fill: false,\n    data: [{\n      x: Utils.newDate(0),\n      y: Utils.rand(0, 100)\n    }, {\n      x: Utils.newDate(2),\n      y: Utils.rand(0, 100)\n    }, {\n      x: Utils.newDate(5),\n      y: Utils.rand(0, 100)\n    }, {\n      x: Utils.newDate(6),\n      y: Utils.rand(0, 100)\n    }]\n  }]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'line',\n  data: data,\n  options: {\n    spanGaps: 1000 * 60 * 60 * 24 * 2, // 2 days\n    responsive: true,\n    interaction: {\n      mode: 'nearest',\n    },\n    plugins: {\n      title: {\n        display: true,\n        text: 'Chart.js Time - spanGaps: 172800000 (2 days in ms)'\n      },\n    },\n    scales: {\n      x: {\n        type: 'time',\n        display: true,\n        title: {\n          display: true,\n          text: 'Date'\n        },\n        ticks: {\n          autoSkip: false,\n          maxRotation: 0,\n          major: {\n            enabled: true\n          },\n          // color: function(context) {\n          //   return context.tick && context.tick.major ? '#FF0000' : 'rgba(0,0,0,0.1)';\n          // },\n          font: function(context) {\n            if (context.tick && context.tick.major) {\n              return {\n                weight: 'bold',\n              };\n            }\n          }\n        }\n      },\n      y: {\n        display: true,\n        title: {\n          display: true,\n          text: 'value'\n        }\n      }\n    }\n  },\n};\n// </block:config>\n\nmodule.exports = {\n  actions: [],\n  config: config,\n};\n```\n\n## Docs\n* [Line](../../charts/line.md)\n  * [`spanGaps`](../../charts/line.md#line-styling)\n* [Time Scale](../../axes/cartesian/time.md)\n"
  },
  {
    "path": "docs/samples/scriptable/bar.md",
    "content": "# Bar Chart\nDemo selecting bar color based on the bar's y value.\n\n```js chart-editor\n// <block:setup:2>\nconst DATA_COUNT = 16;\nUtils.srand(110);\n\nconst actions = [\n  {\n    name: 'Randomize',\n    handler(chart) {\n      chart.data.datasets.forEach(dataset => {\n        dataset.data = generateData();\n      });\n      chart.update();\n    }\n  },\n];\n// </block:setup>\n\n// <block:data:1>\nfunction generateData() {\n  return Utils.numbers({\n    count: DATA_COUNT,\n    min: -100,\n    max: 100\n  });\n}\n\nconst data = {\n  labels: Utils.months({count: DATA_COUNT}),\n  datasets: [{\n    data: generateData(),\n  }]\n};\n// </block:data>\n\n// <block:options:0>\nfunction colorize(opaque) {\n  return (ctx) => {\n    const v = ctx.parsed.y;\n    const c = v < -50 ? '#D60000'\n      : v < 0 ? '#F46300'\n      : v < 50 ? '#0358B6'\n      : '#44DE28';\n\n    return opaque ? c : Utils.transparentize(c, 1 - Math.abs(v / 150));\n  };\n}\n\nconst config = {\n  type: 'bar',\n  data: data,\n  options: {\n    plugins: {\n      legend: false,\n    },\n    elements: {\n      bar: {\n        backgroundColor: colorize(false),\n        borderColor: colorize(true),\n        borderWidth: 2\n      }\n    }\n  }\n};\n// </block:options>\n\nmodule.exports = {\n  actions,\n  config,\n};\n```\n\n## Docs\n* [Bar](../../charts/bar.md)\n* [Data structures (`labels`)](../../general/data-structures.md)\n  * [Dataset Configuration (`stack`)](../../general/data-structures.md#dataset-configuration)\n* [Options](../../general/options.md)\n  * [Scriptable Options](../../general/options.md#scriptable-options)\n"
  },
  {
    "path": "docs/samples/scriptable/bubble.md",
    "content": "# Bubble Chart\n\n```js chart-editor\n// <block:setup:2>\nconst DATA_COUNT = 16;\nconst MIN_XY = -150;\nconst MAX_XY = 100;\nUtils.srand(110);\n\nconst actions = [\n  {\n    name: 'Randomize',\n    handler(chart) {\n      chart.data.datasets.forEach(dataset => {\n        dataset.data = generateData();\n      });\n      chart.update();\n    }\n  },\n];\n// </block:setup>\n\n// <block:data:1>\nfunction generateData() {\n  const data = [];\n  let i;\n\n  for (i = 0; i < DATA_COUNT; ++i) {\n    data.push({\n      x: Utils.rand(MIN_XY, MAX_XY),\n      y: Utils.rand(MIN_XY, MAX_XY),\n      v: Utils.rand(0, 1000)\n    });\n  }\n\n  return data;\n}\n\nconst data = {\n  datasets: [{\n    data: generateData()\n  }, {\n    data: generateData()\n  }]\n};\n// </block:data>\n\n// <block:options:0>\nfunction channelValue(x, y, values) {\n  return x < 0 && y < 0 ? values[0] : x < 0 ? values[1] : y < 0 ? values[2] : values[3];\n}\n\nfunction colorize(opaque, context) {\n  const value = context.raw;\n  const x = value.x / 100;\n  const y = value.y / 100;\n  const r = channelValue(x, y, [250, 150, 50, 0]);\n  const g = channelValue(x, y, [0, 50, 150, 250]);\n  const b = channelValue(x, y, [0, 150, 150, 250]);\n  const a = opaque ? 1 : 0.5 * value.v / 1000;\n\n  return 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')';\n}\n\nconst config = {\n  type: 'bubble',\n  data: data,\n  options: {\n    aspectRatio: 1,\n    plugins: {\n      legend: false,\n      tooltip: false,\n    },\n    elements: {\n      point: {\n        backgroundColor: colorize.bind(null, false),\n\n        borderColor: colorize.bind(null, true),\n\n        borderWidth: function(context) {\n          return Math.min(Math.max(1, context.datasetIndex + 1), 8);\n        },\n\n        hoverBackgroundColor: 'transparent',\n\n        hoverBorderColor: function(context) {\n          return Utils.color(context.datasetIndex);\n        },\n\n        hoverBorderWidth: function(context) {\n          return Math.round(8 * context.raw.v / 1000);\n        },\n\n        radius: function(context) {\n          const size = context.chart.width;\n          const base = Math.abs(context.raw.v) / 1000;\n          return (size / 24) * base;\n        }\n      }\n    }\n  }\n};\n// </block:options>\n\nmodule.exports = {\n  actions,\n  config,\n};\n```\n\n## Docs\n* [Bubble](../../charts/bubble.md)\n* [Options](../../general/options.md)\n  * [Scriptable Options](../../general/options.md#scriptable-options)"
  },
  {
    "path": "docs/samples/scriptable/line.md",
    "content": "# Line Chart\n\n```js chart-editor\n// <block:setup:2>\nconst DATA_COUNT = 12;\nUtils.srand(110);\n\nconst actions = [\n  {\n    name: 'Randomize',\n    handler(chart) {\n      chart.data.datasets.forEach(dataset => {\n        dataset.data = generateData();\n      });\n      chart.update();\n    }\n  },\n];\n// </block:setup>\n\n// <block:data:1>\nfunction generateData() {\n  return Utils.numbers({\n    count: DATA_COUNT,\n    min: 0,\n    max: 100\n  });\n}\n\nconst data = {\n  labels: Utils.months({count: DATA_COUNT}),\n  datasets: [{\n    data: generateData()\n  }]\n};\n// </block:data>\n\n// <block:options:0>\nfunction getLineColor(ctx) {\n  return Utils.color(ctx.datasetIndex);\n}\n\nfunction alternatePointStyles(ctx) {\n  const index = ctx.dataIndex;\n  return index % 2 === 0 ? 'circle' : 'rect';\n}\n\nfunction makeHalfAsOpaque(ctx) {\n  return Utils.transparentize(getLineColor(ctx));\n}\n\nfunction adjustRadiusBasedOnData(ctx) {\n  const v = ctx.parsed.y;\n  return v < 10 ? 5\n    : v < 25 ? 7\n    : v < 50 ? 9\n    : v < 75 ? 11\n    : 15;\n}\n\nconst config = {\n  type: 'line',\n  data: data,\n  options: {\n    plugins: {\n      legend: false,\n      tooltip: true,\n    },\n    elements: {\n      line: {\n        fill: false,\n        backgroundColor: getLineColor,\n        borderColor: getLineColor,\n      },\n      point: {\n        backgroundColor: getLineColor,\n        hoverBackgroundColor: makeHalfAsOpaque,\n        radius: adjustRadiusBasedOnData,\n        pointStyle: alternatePointStyles,\n        hoverRadius: 15,\n      }\n    }\n  }\n};\n// </block:options>\n\nmodule.exports = {\n  actions,\n  config,\n};\n```\n\n## Docs\n* [Line](../../charts/line.md)\n  * [Point Styling](../../charts/line.md#point-styling)\n* [Options](../../general/options.md)\n  * [Scriptable Options](../../general/options.md#scriptable-options)\n* [Data structures (`labels`)](../../general/data-structures.md)\n\n"
  },
  {
    "path": "docs/samples/scriptable/pie.md",
    "content": "# Pie Chart\n\n```js chart-editor\n// <block:setup:2>\nconst DATA_COUNT = 5;\nUtils.srand(110);\n\nconst actions = [\n  {\n    name: 'Randomize',\n    handler(chart) {\n      chart.data.datasets.forEach(dataset => {\n        dataset.data = generateData();\n      });\n      chart.update();\n    }\n  },\n  {\n    name: 'Toggle Doughnut View',\n    handler(chart) {\n      if (chart.options.cutout) {\n        chart.options.cutout = 0;\n      } else {\n        chart.options.cutout = '50%';\n      }\n      chart.update();\n    }\n  }\n];\n// </block:setup>\n\n// <block:data:1>\nfunction generateData() {\n  return Utils.numbers({\n    count: DATA_COUNT,\n    min: -100,\n    max: 100\n  });\n}\n\nconst data = {\n  datasets: [{\n    data: generateData()\n  }]\n};\n// </block:data>\n\n// <block:options:0>\nfunction colorize(opaque, hover, ctx) {\n  const v = ctx.parsed;\n  const c = v < -50 ? '#D60000'\n    : v < 0 ? '#F46300'\n    : v < 50 ? '#0358B6'\n    : '#44DE28';\n\n  const opacity = hover ? 1 - Math.abs(v / 150) - 0.2 : 1 - Math.abs(v / 150);\n\n  return opaque ? c : Utils.transparentize(c, opacity);\n}\n\nfunction hoverColorize(ctx) {\n  return colorize(false, true, ctx);\n}\n\nconst config = {\n  type: 'pie',\n  data: data,\n  options: {\n    plugins: {\n      legend: false,\n      tooltip: false,\n    },\n    elements: {\n      arc: {\n        backgroundColor: colorize.bind(null, false, false),\n        hoverBackgroundColor: hoverColorize\n      }\n    }\n  }\n};\n// </block:options>\n\nmodule.exports = {\n  actions,\n  config,\n};\n```\n\n## Docs\n* [Options](../../general/options.md)\n  * [Scriptable Options](../../general/options.md#scriptable-options)\n* [Doughnut and Pie Charts](../../charts/doughnut.md) "
  },
  {
    "path": "docs/samples/scriptable/polar.md",
    "content": "# Polar Area Chart\n\n```js chart-editor\n// <block:setup:2>\nconst DATA_COUNT = 7;\nUtils.srand(110);\n\nconst actions = [\n  {\n    name: 'Randomize',\n    handler(chart) {\n      chart.data.datasets.forEach(dataset => {\n        dataset.data = generateData();\n      });\n      chart.update();\n    }\n  },\n];\n// </block:setup>\n\n// <block:data:1>\nfunction generateData() {\n  return Utils.numbers({\n    count: DATA_COUNT,\n    min: 0,\n    max: 100\n  });\n}\n\nconst data = {\n  labels: Utils.months({count: DATA_COUNT}),\n  datasets: [{\n    data: generateData()\n  }]\n};\n// </block:data>\n\n// <block:options:0>\nfunction colorize(opaque, hover, ctx) {\n  const v = ctx.raw;\n  const c = v < 35 ? '#D60000'\n    : v < 55 ? '#F46300'\n    : v < 75 ? '#0358B6'\n    : '#44DE28';\n\n  const opacity = hover ? 1 - Math.abs(v / 150) - 0.2 : 1 - Math.abs(v / 150);\n\n  return opaque ? c : Utils.transparentize(c, opacity);\n}\n\nfunction hoverColorize(ctx) {\n  return colorize(false, true, ctx);\n}\n\nconst config = {\n  type: 'polarArea',\n  data: data,\n  options: {\n    plugins: {\n      legend: false,\n      tooltip: false,\n    },\n    elements: {\n      arc: {\n        backgroundColor: colorize.bind(null, false, false),\n        hoverBackgroundColor: hoverColorize\n      }\n    }\n  }\n};\n// </block:options>\n\nmodule.exports = {\n  actions,\n  config,\n};\n```\n\n## Docs\n* [Options](../../general/options.md)\n  * [Scriptable Options](../../general/options.md#scriptable-options)\n* [Polar Area Chart](../../charts/polar.md)\n"
  },
  {
    "path": "docs/samples/scriptable/radar.md",
    "content": "# Radar Chart\n\n```js chart-editor\n// <block:setup:2>\nconst DATA_COUNT = 7;\nUtils.srand(110);\n\nconst actions = [\n  {\n    name: 'Randomize',\n    handler(chart) {\n      chart.data.datasets.forEach(dataset => {\n        dataset.data = generateData();\n      });\n      chart.update();\n    }\n  },\n];\n// </block:setup>\n\n// <block:data:1>\nfunction generateData() {\n  return Utils.numbers({\n    count: DATA_COUNT,\n    min: 0,\n    max: 100\n  });\n}\n\nconst data = {\n  labels: [['Eating', 'Dinner'], ['Drinking', 'Water'], 'Sleeping', ['Designing', 'Graphics'], 'Coding', 'Cycling', 'Running'],\n  datasets: [{\n    data: generateData()\n  }]\n};\n// </block:data>\n\n// <block:options:0>\nfunction getLineColor(ctx) {\n  return Utils.color(ctx.datasetIndex);\n}\n\nfunction alternatePointStyles(ctx) {\n  const index = ctx.dataIndex;\n  return index % 2 === 0 ? 'circle' : 'rect';\n}\n\nfunction makeHalfAsOpaque(ctx) {\n  return Utils.transparentize(getLineColor(ctx));\n}\n\nfunction make20PercentOpaque(ctx) {\n  return Utils.transparentize(getLineColor(ctx), 0.8);\n}\n\nfunction adjustRadiusBasedOnData(ctx) {\n  const v = ctx.parsed.y;\n  return v < 10 ? 5\n    : v < 25 ? 7\n    : v < 50 ? 9\n    : v < 75 ? 11\n    : 15;\n}\n\nconst config = {\n  type: 'radar',\n  data: data,\n  options: {\n    plugins: {\n      legend: false,\n      tooltip: false,\n    },\n    elements: {\n      line: {\n        backgroundColor: make20PercentOpaque,\n        borderColor: getLineColor,\n      },\n      point: {\n        backgroundColor: getLineColor,\n        hoverBackgroundColor: makeHalfAsOpaque,\n        radius: adjustRadiusBasedOnData,\n        pointStyle: alternatePointStyles,\n        hoverRadius: 15,\n      }\n    }\n  }\n};\n// </block:options>\n\nmodule.exports = {\n  actions,\n  config,\n};\n```\n\n## Docs\n* [Options](../../general/options.md)\n  * [Scriptable Options](../../general/options.md#scriptable-options)\n* [Radar](../../charts/radar.md)\n"
  },
  {
    "path": "docs/samples/subtitle/basic.md",
    "content": "# Basic\n\nThis sample shows basic usage of subtitle.\n\n```js chart-editor\n// <block:setup:1>\nconst DATA_COUNT = 7;\nconst NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100};\nconst data = {\n  labels: Utils.months({count: DATA_COUNT}),\n  datasets: [\n    {\n      label: 'Dataset 1',\n      data: Utils.numbers(NUMBER_CFG),\n      fill: false,\n      borderColor: Utils.CHART_COLORS.red,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),\n    },\n  ]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'line',\n  data: data,\n  options: {\n    plugins: {\n      title: {\n        display: true,\n        text: 'Chart Title',\n      },\n      subtitle: {\n        display: true,\n        text: 'Chart Subtitle',\n        color: 'blue',\n        font: {\n          size: 12,\n          family: 'tahoma',\n          weight: 'normal',\n          style: 'italic'\n        },\n        padding: {\n          bottom: 10\n        }\n      }\n    }\n  }\n};\n// </block:config>\n\nmodule.exports = {\n  config: config,\n};\n```\n\n## Docs \n* [Data structures (`labels`)](../../general/data-structures.md)\n* [Line](../../charts/line.md)\n* [Title](../../configuration/title.md)\n* [Subtitle](../../configuration/subtitle.md)\n"
  },
  {
    "path": "docs/samples/title/alignment.md",
    "content": "# Alignment\n\nThis sample show how to configure the alignment of the chart title\n\n```js chart-editor\n// <block:actions:2>\nconst actions = [\n  {\n    name: 'Title Alignment: start',\n    handler(chart) {\n      chart.options.plugins.title.align = 'start';\n      chart.update();\n    }\n  },\n  {\n    name: 'Title Alignment: center (default)',\n    handler(chart) {\n      chart.options.plugins.title.align = 'center';\n      chart.update();\n    }\n  },\n  {\n    name: 'Title Alignment: end',\n    handler(chart) {\n      chart.options.plugins.title.align = 'end';\n      chart.update();\n    }\n  },\n];\n// </block:actions>\n\n\n// <block:setup:1>\nconst DATA_COUNT = 7;\nconst NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100};\nconst data = {\n  labels: Utils.months({count: DATA_COUNT}),\n  datasets: [\n    {\n      label: 'Dataset 1',\n      data: Utils.numbers(NUMBER_CFG),\n      fill: false,\n      borderColor: Utils.CHART_COLORS.red,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),\n    },\n  ]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'line',\n  data: data,\n  options: {\n    plugins: {\n      title: {\n        display: true,\n        text: 'Chart Title',\n      }\n    }\n  }\n};\n// </block:config>\n\nmodule.exports = {\n  actions: actions,\n  config: config,\n};\n```\n\n## Docs \n* [Data structures (`labels`)](../../general/data-structures.md)\n* [Line](../../charts/line.md)\n* [Title](../../configuration/title.md)"
  },
  {
    "path": "docs/samples/tooltip/content.md",
    "content": "# Custom Tooltip Content\n\nThis sample shows how to use the tooltip callbacks to add additional content to the tooltip.\n\n```js chart-editor\n// <block:footer:2>\nconst footer = (tooltipItems) => {\n  let sum = 0;\n\n  tooltipItems.forEach(function(tooltipItem) {\n    sum += tooltipItem.parsed.y;\n  });\n  return 'Sum: ' + sum;\n};\n\n// </block:footer>\n\n// <block:setup:1>\nconst DATA_COUNT = 7;\nconst NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100, decimals: 0};\nconst data = {\n  labels: Utils.months({count: DATA_COUNT}),\n  datasets: [\n    {\n      label: 'Dataset 1',\n      data: Utils.numbers(NUMBER_CFG),\n      fill: false,\n      borderColor: Utils.CHART_COLORS.red,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),\n    },\n    {\n      label: 'Dataset 2',\n      data: Utils.numbers(NUMBER_CFG),\n      fill: false,\n      borderColor: Utils.CHART_COLORS.blue,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),\n    },\n  ]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'line',\n  data: data,\n  options: {\n    interaction: {\n      intersect: false,\n      mode: 'index',\n    },\n    plugins: {\n      tooltip: {\n        callbacks: {\n          footer: footer,\n        }\n      }\n    }\n  }\n};\n// </block:config>\n\nmodule.exports = {\n  actions: [],\n  config: config,\n};\n```\n\n## Docs \n* [Data structures (`labels`)](../../general/data-structures.md)\n* [Line](../../charts/line.md)\n* [Tooltip](../../configuration/tooltip.md)\n  * [Tooltip Callbacks](../../configuration/tooltip.md#tooltip-callbacks)\n"
  },
  {
    "path": "docs/samples/tooltip/html.md",
    "content": "# External HTML Tooltip\n\nThis sample shows how to use the external tooltip functionality to generate an HTML tooltip.\n\n```js chart-editor\n// <block:external:2>\nconst getOrCreateTooltip = (chart) => {\n  let tooltipEl = chart.canvas.parentNode.querySelector('div');\n\n  if (!tooltipEl) {\n    tooltipEl = document.createElement('div');\n    tooltipEl.style.background = 'rgba(0, 0, 0, 0.7)';\n    tooltipEl.style.borderRadius = '3px';\n    tooltipEl.style.color = 'white';\n    tooltipEl.style.opacity = 1;\n    tooltipEl.style.pointerEvents = 'none';\n    tooltipEl.style.position = 'absolute';\n    tooltipEl.style.transform = 'translate(-50%, 0)';\n    tooltipEl.style.transition = 'all .1s ease';\n\n    const table = document.createElement('table');\n    table.style.margin = '0px';\n\n    tooltipEl.appendChild(table);\n    chart.canvas.parentNode.appendChild(tooltipEl);\n  }\n\n  return tooltipEl;\n};\n\nconst externalTooltipHandler = (context) => {\n  // Tooltip Element\n  const {chart, tooltip} = context;\n  const tooltipEl = getOrCreateTooltip(chart);\n\n  // Hide if no tooltip\n  if (tooltip.opacity === 0) {\n    tooltipEl.style.opacity = 0;\n    return;\n  }\n\n  // Set Text\n  if (tooltip.body) {\n    const titleLines = tooltip.title || [];\n    const bodyLines = tooltip.body.map(b => b.lines);\n\n    const tableHead = document.createElement('thead');\n\n    titleLines.forEach(title => {\n      const tr = document.createElement('tr');\n      tr.style.borderWidth = 0;\n\n      const th = document.createElement('th');\n      th.style.borderWidth = 0;\n      const text = document.createTextNode(title);\n\n      th.appendChild(text);\n      tr.appendChild(th);\n      tableHead.appendChild(tr);\n    });\n\n    const tableBody = document.createElement('tbody');\n    bodyLines.forEach((body, i) => {\n      const colors = tooltip.labelColors[i];\n\n      const span = document.createElement('span');\n      span.style.background = colors.backgroundColor;\n      span.style.borderColor = colors.borderColor;\n      span.style.borderWidth = '2px';\n      span.style.marginRight = '10px';\n      span.style.height = '10px';\n      span.style.width = '10px';\n      span.style.display = 'inline-block';\n\n      const tr = document.createElement('tr');\n      tr.style.backgroundColor = 'inherit';\n      tr.style.borderWidth = 0;\n\n      const td = document.createElement('td');\n      td.style.borderWidth = 0;\n\n      const text = document.createTextNode(body);\n\n      td.appendChild(span);\n      td.appendChild(text);\n      tr.appendChild(td);\n      tableBody.appendChild(tr);\n    });\n\n    const tableRoot = tooltipEl.querySelector('table');\n\n    // Remove old children\n    while (tableRoot.firstChild) {\n      tableRoot.firstChild.remove();\n    }\n\n    // Add new children\n    tableRoot.appendChild(tableHead);\n    tableRoot.appendChild(tableBody);\n  }\n\n  const {offsetLeft: positionX, offsetTop: positionY} = chart.canvas;\n\n  // Display, position, and set styles for font\n  tooltipEl.style.opacity = 1;\n  tooltipEl.style.left = positionX + tooltip.caretX + 'px';\n  tooltipEl.style.top = positionY + tooltip.caretY + 'px';\n  tooltipEl.style.font = tooltip.options.bodyFont.string;\n  tooltipEl.style.padding = tooltip.options.padding + 'px ' + tooltip.options.padding + 'px';\n};\n// </block:external>\n\n// <block:setup:1>\nconst DATA_COUNT = 7;\nconst NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100, decimals: 0};\nconst data = {\n  labels: Utils.months({count: DATA_COUNT}),\n  datasets: [\n    {\n      label: 'Dataset 1',\n      data: Utils.numbers(NUMBER_CFG),\n      fill: false,\n      borderColor: Utils.CHART_COLORS.red,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),\n    },\n    {\n      label: 'Dataset 2',\n      data: Utils.numbers(NUMBER_CFG),\n      fill: false,\n      borderColor: Utils.CHART_COLORS.blue,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),\n    },\n  ]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'line',\n  data: data,\n  options: {\n    interaction: {\n      mode: 'index',\n      intersect: false,\n    },\n    plugins: {\n      title: {\n        display: true,\n        text: 'Chart.js Line Chart - External Tooltips'\n      },\n      tooltip: {\n        enabled: false,\n        position: 'nearest',\n        external: externalTooltipHandler\n      }\n    }\n  }\n};\n// </block:config>\n\nmodule.exports = {\n  actions: [],\n  config: config,\n};\n```\n\n## Docs \n* [Data structures (`labels`)](../../general/data-structures.md)\n* [Line](../../charts/line.md)\n* [Tooltip](../../configuration/tooltip.md)\n  * [External (Custom) Tooltips](../../configuration/tooltip.md#external-custom-tooltips)\n  "
  },
  {
    "path": "docs/samples/tooltip/interactions.md",
    "content": "# Interaction Modes\n\nThis sample shows how to use the tooltip position mode setting.\n\n```js chart-editor\n// <block:actions:2>\nconst actions = [\n  {\n    name: 'Mode: index',\n    handler(chart) {\n      chart.options.interaction.axis = 'xy';\n      chart.options.interaction.mode = 'index';\n      chart.update();\n    }\n  },\n  {\n    name: 'Mode: dataset',\n    handler(chart) {\n      chart.options.interaction.axis = 'xy';\n      chart.options.interaction.mode = 'dataset';\n      chart.update();\n    }\n  },\n  {\n    name: 'Mode: point',\n    handler(chart) {\n      chart.options.interaction.axis = 'xy';\n      chart.options.interaction.mode = 'point';\n      chart.update();\n    }\n  },\n  {\n    name: 'Mode: nearest, axis: xy',\n    handler(chart) {\n      chart.options.interaction.axis = 'xy';\n      chart.options.interaction.mode = 'nearest';\n      chart.update();\n    }\n  },\n  {\n    name: 'Mode: nearest, axis: x',\n    handler(chart) {\n      chart.options.interaction.axis = 'x';\n      chart.options.interaction.mode = 'nearest';\n      chart.update();\n    }\n  },\n  {\n    name: 'Mode: nearest, axis: y',\n    handler(chart) {\n      chart.options.interaction.axis = 'y';\n      chart.options.interaction.mode = 'nearest';\n      chart.update();\n    }\n  },\n  {\n    name: 'Mode: x',\n    handler(chart) {\n      chart.options.interaction.mode = 'x';\n      chart.update();\n    }\n  },\n  {\n    name: 'Mode: y',\n    handler(chart) {\n      chart.options.interaction.mode = 'y';\n      chart.update();\n    }\n  },\n  {\n    name: 'Toggle Intersect',\n    handler(chart) {\n      chart.options.interaction.intersect = !chart.options.interaction.intersect;\n      chart.update();\n    }\n  },\n];\n// </block:actions>\n\n// <block:setup:1>\nconst DATA_COUNT = 7;\nconst NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100};\nconst data = {\n  labels: Utils.months({count: DATA_COUNT}),\n  datasets: [\n    {\n      label: 'Dataset 1',\n      data: Utils.numbers(NUMBER_CFG),\n      fill: false,\n      borderColor: Utils.CHART_COLORS.red,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),\n    },\n    {\n      label: 'Dataset 2',\n      data: Utils.numbers(NUMBER_CFG),\n      fill: false,\n      borderColor: Utils.CHART_COLORS.blue,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),\n    },\n  ]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'line',\n  data: data,\n  options: {\n    interaction: {\n      intersect: false,\n      mode: 'index',\n    },\n    plugins: {\n      title: {\n        display: true,\n        text: (ctx) => {\n          const {axis = 'xy', intersect, mode} = ctx.chart.options.interaction;\n          return 'Mode: ' + mode + ', axis: ' + axis + ', intersect: ' + intersect;\n        }\n      },\n    }\n  }\n};\n// </block:config>\n\nmodule.exports = {\n  actions: actions,\n  config: config,\n};\n```\n\n## Docs \n* [Data structures (`labels`)](../../general/data-structures.md)\n* [Line](../../charts/line.md)\n* [Tooltip](../../configuration/tooltip.md)\n* [Interactions](../../configuration/interactions.md)\n"
  },
  {
    "path": "docs/samples/tooltip/point-style.md",
    "content": "# Point Style\n\nThis sample shows how to use the dataset point style in the tooltip instead of a rectangle to identify each dataset.\n\n```js chart-editor\n// <block:actions:2>\nconst actions = [\n  {\n    name: 'Toggle Tooltip Point Style',\n    handler(chart) {\n      chart.options.plugins.tooltip.usePointStyle = !chart.options.plugins.tooltip.usePointStyle;\n      chart.update();\n    }\n  },\n];\n// </block:actions>\n\n// <block:setup:1>\nconst DATA_COUNT = 7;\nconst NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100};\nconst data = {\n  labels: Utils.months({count: DATA_COUNT}),\n  datasets: [\n    {\n      label: 'Triangles',\n      data: Utils.numbers(NUMBER_CFG),\n      fill: false,\n      borderColor: Utils.CHART_COLORS.red,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),\n      pointStyle: 'triangle',\n      pointRadius: 6,\n    },\n    {\n      label: 'Circles',\n      data: Utils.numbers(NUMBER_CFG),\n      fill: false,\n      borderColor: Utils.CHART_COLORS.blue,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),\n      pointStyle: 'circle',\n      pointRadius: 6,\n    },\n    {\n      label: 'Stars',\n      data: Utils.numbers(NUMBER_CFG),\n      fill: false,\n      borderColor: Utils.CHART_COLORS.green,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.green, 0.5),\n      pointStyle: 'star',\n      pointRadius: 6,\n    }\n  ]\n};\n// </block:setup>\n\n// <block:config:0>\nconst config = {\n  type: 'line',\n  data: data,\n  options: {\n    interaction: {\n      mode: 'index',\n    },\n    plugins: {\n      title: {\n        display: true,\n        text: (ctx) => 'Tooltip point style: ' + ctx.chart.options.plugins.tooltip.usePointStyle,\n      },\n      tooltip: {\n        usePointStyle: true,\n      }\n    }\n  }\n};\n// </block:config>\n\nmodule.exports = {\n  actions: actions,\n  config: config,\n};\n```\n\n## Docs \n* [Data structures (`labels`)](../../general/data-structures.md)\n* [Line](../../charts/line.md)\n* [Tooltip](../../configuration/tooltip.md)\n  * `usePointStyle`\n* [Elements](../../configuration/elements.md)\n  * [Point Styles](../../configuration/elements.md#point-styles)\n\n"
  },
  {
    "path": "docs/samples/tooltip/position.md",
    "content": "# Position\n\nThis sample shows how to use the tooltip position mode setting.\n\n```js chart-editor\n// <block:actions:3>\nconst actions = [\n  {\n    name: 'Position: average',\n    handler(chart) {\n      chart.options.plugins.tooltip.position = 'average';\n      chart.update();\n    }\n  },\n  {\n    name: 'Position: nearest',\n    handler(chart) {\n      chart.options.plugins.tooltip.position = 'nearest';\n      chart.update();\n    }\n  },\n  {\n    name: 'Position: bottom (custom)',\n    handler(chart) {\n      chart.options.plugins.tooltip.position = 'bottom';\n      chart.update();\n    }\n  },\n];\n// </block:actions>\n\n// <block:setup:2>\nconst DATA_COUNT = 7;\nconst NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100};\nconst data = {\n  labels: Utils.months({count: DATA_COUNT}),\n  datasets: [\n    {\n      label: 'Dataset 1',\n      data: Utils.numbers(NUMBER_CFG),\n      fill: false,\n      borderColor: Utils.CHART_COLORS.red,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),\n    },\n    {\n      label: 'Dataset 2',\n      data: Utils.numbers(NUMBER_CFG),\n      fill: false,\n      borderColor: Utils.CHART_COLORS.blue,\n      backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),\n    },\n  ]\n};\n// </block:setup>\n\n// <block:positioner:1>\n// Create a custom tooltip positioner to put at the bottom of the chart area\ncomponents.Tooltip.positioners.bottom = function(items) {\n  const pos = components.Tooltip.positioners.average(items);\n\n  // Happens when nothing is found\n  if (pos === false) {\n    return false;\n  }\n\n  const chart = this.chart;\n\n  return {\n    x: pos.x,\n    y: chart.chartArea.bottom,\n    xAlign: 'center',\n    yAlign: 'bottom',\n  };\n};\n\n// </block:positioner>\n\n// <block:config:0>\nconst config = {\n  type: 'line',\n  data: data,\n  options: {\n    interaction: {\n      intersect: false,\n      mode: 'index',\n    },\n    plugins: {\n      title: {\n        display: true,\n        text: (ctx) => 'Tooltip position mode: ' + ctx.chart.options.plugins.tooltip.position,\n      },\n    }\n  }\n};\n// </block:config>\n\nmodule.exports = {\n  actions: actions,\n  config: config,\n};\n```\n\n## Docs \n* [Data structures (`labels`)](../../general/data-structures.md)\n* [Line](../../charts/line.md)\n* [Tooltip](../../configuration/tooltip.md)\n  * [Position Modes](../../configuration/tooltip.md#position-modes)\n  * [Custom Position Modes](../../configuration/tooltip.md#custom-position-modes)"
  },
  {
    "path": "docs/samples/utils.md",
    "content": "# Utils\n\n## Disclaimer\nThe Utils file contains multiple helper functions that the chart.js sample pages use to generate charts.\nThese functions are subject to change, including but not limited to breaking changes without prior notice.\n\nBecause of this please don't rely on this file in production environments.\n\n## Functions\n\n<<< @/scripts/utils.js\n\n[File on github](https://github.com/chartjs/Chart.js/blob/master/docs/scripts/utils.js)\n\n## Components\n\nSome of the samples make reference to a `components` object. This is an artifact of using a module bundler to build the samples. The creation of that components object is shown below. If chart.js is included as a browser script, these items are accessible via the `Chart` object, i.e `Chart.Tooltip`.\n\n<<< @/scripts/components.js\n\n[File on github](https://github.com/chartjs/Chart.js/blob/master/docs/scripts/components.js)\n"
  },
  {
    "path": "docs/scripts/analyzer.js",
    "content": "export default {\n  id: 'samples-filler-analyser',\n\n  beforeInit: function(chart, args, options) {\n    this.element = document.getElementById(options.target);\n  },\n\n  afterUpdate: function(chart) {\n    var datasets = chart.data.datasets;\n    var element = this.element;\n    var stats = [];\n    var meta, i, ilen, dataset;\n\n    if (!element) {\n      return;\n    }\n\n    for (i = 0, ilen = datasets.length; i < ilen; ++i) {\n      meta = chart.getDatasetMeta(i).$filler;\n      if (meta) {\n        dataset = datasets[i];\n        stats.push({\n          fill: dataset.fill,\n          target: meta.fill,\n          visible: meta.visible,\n          index: i\n        });\n      }\n    }\n\n    this.element.innerHTML = '<table>' +\n      '<tr>' +\n        '<th>Dataset</th>' +\n        '<th>Fill</th>' +\n        '<th>Target (visibility)</th>' +\n      '</tr>' +\n      stats.map(function(stat) {\n        var target = stat.target;\n        var row =\n          '<td><b>' + stat.index + '</b></td>' +\n          '<td>' + JSON.stringify(stat.fill) + '</td>';\n\n        if (target === false) {\n          target = 'none';\n        } else if (isFinite(target)) {\n          target = 'dataset ' + target;\n        } else {\n          target = 'boundary \"' + target + '\"';\n        }\n\n        if (stat.visible) {\n          row += '<td>' + target + '</td>';\n        } else {\n          row += '<td>(hidden)</td>';\n        }\n\n        return '<tr>' + row + '</tr>';\n      }).join('') + '</table>';\n  }\n};\n"
  },
  {
    "path": "docs/scripts/components.js",
    "content": "// Add Chart components needed in samples here.\n// Usable through `components[name]`.\nexport {Tooltip} from '../../dist/chart.js';\n"
  },
  {
    "path": "docs/scripts/derived-bubble.js",
    "content": "import {Chart, BubbleController} from 'chart.js';\n\nclass Custom extends BubbleController {\n  draw() {\n    // Call bubble controller method to draw all the points\n    super.draw(arguments);\n\n    // Now we can do some custom drawing for this dataset.\n    // Here we'll draw a box around the first point in each dataset,\n    // using `boxStrokeStyle` dataset option for color\n    var meta = this.getMeta();\n    var pt0 = meta.data[0];\n\n    const {x, y} = pt0.getProps(['x', 'y']);\n    const {radius} = pt0.options;\n\n    var ctx = this.chart.ctx;\n    ctx.save();\n    ctx.strokeStyle = this.options.boxStrokeStyle;\n    ctx.lineWidth = 1;\n    ctx.strokeRect(x - radius, y - radius, 2 * radius, 2 * radius);\n    ctx.restore();\n  }\n}\nCustom.id = 'derivedBubble';\nCustom.defaults = {\n  // Custom defaults. Bubble defaults are inherited.\n  boxStrokeStyle: 'red'\n};\n// Overrides are only inherited, but not merged if defined\n// Custom.overrides = Chart.overrides.bubble;\n\n// Stores the controller so that the chart initialization routine can look it up\nChart.register(Custom);\n"
  },
  {
    "path": "docs/scripts/helpers.js",
    "content": "// Add helpers needed in samples here.\n// Usable through `helpers[name]`.\nexport {color, getHoverColor, easingEffects} from '../../dist/helpers.js';\n"
  },
  {
    "path": "docs/scripts/log2.js",
    "content": "import {Scale, LinearScale} from 'chart.js';\n\nexport default class Log2Axis extends Scale {\n  constructor(cfg) {\n    super(cfg);\n    this._startValue = undefined;\n    this._valueRange = 0;\n  }\n\n  parse(raw, index) {\n    const value = LinearScale.prototype.parse.apply(this, [raw, index]);\n    return isFinite(value) && value > 0 ? value : null;\n  }\n\n  determineDataLimits() {\n    const {min, max} = this.getMinMax(true);\n    this.min = isFinite(min) ? Math.max(0, min) : null;\n    this.max = isFinite(max) ? Math.max(0, max) : null;\n  }\n\n  buildTicks() {\n    const ticks = [];\n\n    let power = Math.floor(Math.log2(this.min || 1));\n    let maxPower = Math.ceil(Math.log2(this.max || 2));\n    while (power <= maxPower) {\n      ticks.push({value: Math.pow(2, power)});\n      power += 1;\n    }\n\n    this.min = ticks[0].value;\n    this.max = ticks[ticks.length - 1].value;\n    return ticks;\n  }\n\n  /**\n   * @protected\n   */\n  configure() {\n    const start = this.min;\n\n    super.configure();\n\n    this._startValue = Math.log2(start);\n    this._valueRange = Math.log2(this.max) - Math.log2(start);\n  }\n\n  getPixelForValue(value) {\n    if (value === undefined || value === 0) {\n      value = this.min;\n    }\n\n    return this.getPixelForDecimal(value === this.min ? 0\n      : (Math.log2(value) - this._startValue) / this._valueRange);\n  }\n\n  getValueForPixel(pixel) {\n    const decimal = this.getDecimalForPixel(pixel);\n    return Math.pow(2, this._startValue + decimal * this._valueRange);\n  }\n}\n\nLog2Axis.id = 'log2';\nLog2Axis.defaults = {};\n\n// The derived axis is registered like this:\n// Chart.register(Log2Axis);\n"
  },
  {
    "path": "docs/scripts/register.js",
    "content": "import {Chart, registerables} from '../../dist/chart.js';\nimport Log2Axis from './log2';\nimport './derived-bubble';\nimport analyzer from './analyzer';\n\nChart.register(...registerables);\nChart.register(Log2Axis);\nChart.register(analyzer);\n"
  },
  {
    "path": "docs/scripts/utils.js",
    "content": "import colorLib from '@kurkle/color';\nimport {DateTime} from 'luxon';\nimport 'chartjs-adapter-luxon';\nimport {valueOrDefault} from '../../dist/helpers.js';\n\n// Adapted from http://indiegamr.com/generate-repeatable-random-numbers-in-js/\nvar _seed = Date.now();\n\nexport function srand(seed) {\n  _seed = seed;\n}\n\nexport function rand(min, max) {\n  min = valueOrDefault(min, 0);\n  max = valueOrDefault(max, 0);\n  _seed = (_seed * 9301 + 49297) % 233280;\n  return min + (_seed / 233280) * (max - min);\n}\n\nexport function numbers(config) {\n  var cfg = config || {};\n  var min = valueOrDefault(cfg.min, 0);\n  var max = valueOrDefault(cfg.max, 100);\n  var from = valueOrDefault(cfg.from, []);\n  var count = valueOrDefault(cfg.count, 8);\n  var decimals = valueOrDefault(cfg.decimals, 8);\n  var continuity = valueOrDefault(cfg.continuity, 1);\n  var dfactor = Math.pow(10, decimals) || 0;\n  var data = [];\n  var i, value;\n\n  for (i = 0; i < count; ++i) {\n    value = (from[i] || 0) + this.rand(min, max);\n    if (this.rand() <= continuity) {\n      data.push(Math.round(dfactor * value) / dfactor);\n    } else {\n      data.push(null);\n    }\n  }\n\n  return data;\n}\n\nexport function points(config) {\n  const xs = this.numbers(config);\n  const ys = this.numbers(config);\n  return xs.map((x, i) => ({x, y: ys[i]}));\n}\n\nexport function bubbles(config) {\n  return this.points(config).map(pt => {\n    pt.r = this.rand(config.rmin, config.rmax);\n    return pt;\n  });\n}\n\nexport function labels(config) {\n  var cfg = config || {};\n  var min = cfg.min || 0;\n  var max = cfg.max || 100;\n  var count = cfg.count || 8;\n  var step = (max - min) / count;\n  var decimals = cfg.decimals || 8;\n  var dfactor = Math.pow(10, decimals) || 0;\n  var prefix = cfg.prefix || '';\n  var values = [];\n  var i;\n\n  for (i = min; i < max; i += step) {\n    values.push(prefix + Math.round(dfactor * i) / dfactor);\n  }\n\n  return values;\n}\n\nconst MONTHS = [\n  'January',\n  'February',\n  'March',\n  'April',\n  'May',\n  'June',\n  'July',\n  'August',\n  'September',\n  'October',\n  'November',\n  'December'\n];\n\nexport function months(config) {\n  var cfg = config || {};\n  var count = cfg.count || 12;\n  var section = cfg.section;\n  var values = [];\n  var i, value;\n\n  for (i = 0; i < count; ++i) {\n    value = MONTHS[Math.ceil(i) % 12];\n    values.push(value.substring(0, section));\n  }\n\n  return values;\n}\n\nconst COLORS = [\n  '#4dc9f6',\n  '#f67019',\n  '#f53794',\n  '#537bc4',\n  '#acc236',\n  '#166a8f',\n  '#00a950',\n  '#58595b',\n  '#8549ba'\n];\n\nexport function color(index) {\n  return COLORS[index % COLORS.length];\n}\n\nexport function transparentize(value, opacity) {\n  var alpha = opacity === undefined ? 0.5 : 1 - opacity;\n  return colorLib(value).alpha(alpha).rgbString();\n}\n\nexport const CHART_COLORS = {\n  red: 'rgb(255, 99, 132)',\n  orange: 'rgb(255, 159, 64)',\n  yellow: 'rgb(255, 205, 86)',\n  green: 'rgb(75, 192, 192)',\n  blue: 'rgb(54, 162, 235)',\n  purple: 'rgb(153, 102, 255)',\n  grey: 'rgb(201, 203, 207)'\n};\n\nconst NAMED_COLORS = [\n  CHART_COLORS.red,\n  CHART_COLORS.orange,\n  CHART_COLORS.yellow,\n  CHART_COLORS.green,\n  CHART_COLORS.blue,\n  CHART_COLORS.purple,\n  CHART_COLORS.grey,\n];\n\nexport function namedColor(index) {\n  return NAMED_COLORS[index % NAMED_COLORS.length];\n}\n\nexport function newDate(days) {\n  return DateTime.now().plus({days}).toJSDate();\n}\n\nexport function newDateString(days) {\n  return DateTime.now().plus({days}).toISO();\n}\n\nexport function parseISODate(str) {\n  return DateTime.fromISO(str);\n}\n"
  },
  {
    "path": "helpers/helpers.cjs",
    "content": "module.exports = require('../dist/helpers.cjs');\n"
  },
  {
    "path": "helpers/helpers.d.ts",
    "content": "export * from '../dist/helpers/index.js';\n"
  },
  {
    "path": "helpers/helpers.js",
    "content": "export * from '../dist/helpers.js';\n"
  },
  {
    "path": "helpers/package.json",
    "content": "{\n    \"name\": \"chart.js-helpers\",\n    \"private\": true,\n    \"description\": \"Helpers package. Exists to support bundlers without exports support such as webpack 4.\",\n    \"type\": \"module\",\n    \"main\": \"./helpers.cjs\",\n    \"module\": \"./helpers.js\",\n    \"exports\": {\n        \"types\": \"./helpers.d.ts\",\n        \"import\": \"./helpers.js\",\n        \"require\": \"./helpers.cjs\"\n    },\n    \"types\": \"./helpers.d.ts\"\n}\n"
  },
  {
    "path": "karma.conf.cjs",
    "content": "/* eslint-disable global-require */\nconst jasmineSeedReporter = require('./test/seed-reporter.cjs');\nconst commonjs = require('@rollup/plugin-commonjs');\nconst istanbul = require('rollup-plugin-istanbul');\nconst json = require('@rollup/plugin-json');\nconst resolve = require('@rollup/plugin-node-resolve').default;\nconst yargs = require('yargs');\n\nmodule.exports = async function(karma) {\n  const builds = (await import('./rollup.config.js')).default;\n\n  const args = yargs\n    .option('verbose', {default: false})\n    .argv;\n\n  const grep = (args.grep === true || args.grep === undefined) ? '' : args.grep;\n  const specPattern = 'test/specs/**/*' + grep + '*.js';\n\n  // Use the same rollup config as our dist files: when debugging (npm run dev),\n  // we will prefer the unminified build which is easier to browse and works\n  // better with source mapping. In other cases, pick the minified build to\n  // make sure that the minification process (terser) doesn't break anything.\n  const regex = /chart\\.umd(\\.min)?\\.js$/;\n  const build = builds.filter(v => v.output.file && v.output.file.match(regex))[0];\n\n  if (karma.autoWatch) {\n    build.plugins.pop();\n  }\n\n  if (args.coverage) {\n    build.plugins.push(\n      istanbul({exclude: ['node_modules/**/*.js', 'package.json']})\n    );\n  }\n\n  // workaround a karma bug where it doesn't resolve dependencies correctly in\n  // the same way that Node does\n  // https://github.com/pnpm/pnpm/issues/720#issuecomment-954120387\n  const plugins = Object.keys(require('./package').devDependencies).flatMap(\n    (packageName) => {\n      if (!packageName.startsWith('karma-')) {\n        return [];\n      }\n      return [require(packageName)];\n    }\n  );\n\n  plugins.push(jasmineSeedReporter);\n\n  karma.set({\n    frameworks: ['jasmine'],\n    plugins,\n    reporters: ['spec', 'kjhtml', 'jasmine-seed'],\n    browsers: (args.browsers || 'chrome,firefox').split(','),\n    logLevel: karma.LOG_INFO,\n\n    client: {\n      jasmine: {\n        stopOnSpecFailure: !!karma.autoWatch\n      }\n    },\n\n    specReporter: {\n      // maxLogLines: 5,             // limit number of lines logged per test\n      suppressErrorSummary: true, // do not print error summary\n      suppressFailed: false,      // do not print information about failed tests\n      suppressPassed: true,      // do not print information about passed tests\n      suppressSkipped: false,      // do not print information about skipped tests\n      showSpecTiming: false,      // print the time elapsed for each spec\n      failFast: false              // test would finish with error when a first fail occurs.\n    },\n\n    // Explicitly disable hardware acceleration to make image\n    // diff more stable when ran on Travis and dev machine.\n    // https://github.com/chartjs/Chart.js/pull/5629\n    // Since FF 110 https://github.com/chartjs/Chart.js/issues/11164\n    customLaunchers: {\n      chrome: {\n        base: 'Chrome',\n        flags: [\n          '--disable-accelerated-2d-canvas',\n          '--disable-background-timer-throttling',\n          '--disable-backgrounding-occluded-windows',\n          '--disable-renderer-backgrounding'\n        ]\n      },\n      firefox: {\n        base: 'Firefox',\n        prefs: {\n          'layers.acceleration.disabled': true,\n          'gfx.canvas.accelerated': false\n        }\n      },\n      safari: {\n        base: 'SafariPrivate'\n      },\n      edge: {\n        base: 'Edge'\n      }\n    },\n\n    files: [\n      {pattern: 'test/fixtures/**/*.js', included: false},\n      {pattern: 'test/fixtures/**/*.json', included: false},\n      {pattern: 'test/fixtures/**/*.png', included: false},\n      'node_modules/moment/min/moment.min.js',\n      'node_modules/moment-timezone/builds/moment-timezone-with-data.min.js',\n      {pattern: 'test/index.js', watched: false},\n      {pattern: 'test/BasicChartWebWorker.js', included: false},\n      {pattern: 'src/index.umd.ts', watched: false},\n      'node_modules/chartjs-adapter-moment/dist/chartjs-adapter-moment.js',\n      {pattern: specPattern}\n    ],\n\n    preprocessors: {\n      'test/index.js': ['rollup'],\n      'src/index.umd.ts': ['sources']\n    },\n\n    rollupPreprocessor: {\n      plugins: [\n        json(),\n        resolve(),\n        commonjs({exclude: ['src/**', 'test/**']}),\n      ],\n      output: {\n        name: 'test',\n        format: 'umd',\n        sourcemap: karma.autoWatch ? 'inline' : false\n      }\n    },\n\n    customPreprocessors: {\n      sources: {\n        base: 'rollup',\n        options: build\n      }\n    },\n\n    // These settings deal with browser disconnects. We had seen test flakiness from Firefox\n    // [Firefox 56.0.0 (Linux 0.0.0)]: Disconnected (1 times), because no message in 10000 ms.\n    // https://github.com/jasmine/jasmine/issues/1327#issuecomment-332939551\n    browserDisconnectTolerance: 3\n  });\n\n  if (args.coverage) {\n    karma.reporters.push('coverage');\n    karma.coverageReporter = {\n      dir: 'coverage/',\n      reporters: [\n        {type: 'html', subdir: 'html'},\n        {type: 'lcovonly', subdir: (browser) => browser.toLowerCase().split(/[ /-]/)[0]}\n      ]\n    };\n  }\n};\n"
  },
  {
    "path": "package.json",
    "content": "{\n    \"name\": \"chart.js\",\n    \"homepage\": \"https://www.chartjs.org\",\n    \"description\": \"Simple HTML5 charts using the canvas element.\",\n    \"version\": \"4.5.1\",\n    \"license\": \"MIT\",\n    \"type\": \"module\",\n    \"sideEffects\": [\n        \"./auto/auto.js\",\n        \"./auto/auto.cjs\",\n        \"./dist/chart.umd.min.js\",\n        \"./dist/chart.umd.js\"\n    ],\n    \"jsdelivr\": \"./dist/chart.umd.min.js\",\n    \"unpkg\": \"./dist/chart.umd.min.js\",\n    \"main\": \"./dist/chart.cjs\",\n    \"module\": \"./dist/chart.js\",\n    \"exports\": {\n        \".\": {\n            \"types\": \"./dist/types.d.ts\",\n            \"import\": \"./dist/chart.js\",\n            \"require\": \"./dist/chart.cjs\"\n        },\n        \"./auto\": {\n            \"types\": \"./auto/auto.d.ts\",\n            \"import\": \"./auto/auto.js\",\n            \"require\": \"./auto/auto.cjs\"\n        },\n        \"./helpers\": {\n            \"types\": \"./helpers/helpers.d.ts\",\n            \"import\": \"./helpers/helpers.js\",\n            \"require\": \"./helpers/helpers.cjs\"\n        }\n    },\n    \"types\": \"./dist/types.d.ts\",\n    \"keywords\": [\n        \"canvas\",\n        \"charts\",\n        \"data\",\n        \"graphs\",\n        \"html5\",\n        \"responsive\"\n    ],\n    \"repository\": {\n        \"type\": \"git\",\n        \"url\": \"https://github.com/chartjs/Chart.js.git\"\n    },\n    \"bugs\": {\n        \"url\": \"https://github.com/chartjs/Chart.js/issues\"\n    },\n    \"files\": [\n        \"auto/**\",\n        \"dist/**\",\n        \"!dist/docs/**\",\n        \"helpers/**\"\n    ],\n    \"scripts\": {\n        \"autobuild\": \"rollup -c -w\",\n        \"copyDeclarations\": \"node -e \\\"fs.cpSync('./src/types/', './dist/types/', {recursive:true})\\\"\",\n        \"emitDeclarations\": \"tsc --emitDeclarationOnly && pnpm copyDeclarations\",\n        \"build\": \"rollup -c && pnpm emitDeclarations\",\n        \"dev\": \"karma start ./karma.conf.cjs --auto-watch --no-single-run --browsers chrome --grep\",\n        \"dev:ff\": \"karma start ./karma.conf.cjs --auto-watch --no-single-run --browsers firefox --grep\",\n        \"docs\": \"pnpm run build && pnpm --filter \\\"./docs/**\\\" build\",\n        \"docs:dev\": \"pnpm run build && pnpm --filter \\\"./docs/**\\\" dev\",\n        \"lint-js\": \"eslint \\\"src/**/*.{js,ts}\\\" \\\"test/**/*.js\\\" \\\"docs/**/*.js\\\" --cache\",\n        \"lint-md\": \"eslint \\\"**/*.md\\\" --cache\",\n        \"lint-types\": \"pnpm build && node test/types/autogen.js && tsc -p test/types\",\n        \"lint\": \"concurrently \\\"pnpm:lint-*\\\"\",\n        \"test\": \"pnpm lint && pnpm test-ci\",\n        \"test-ci\": \"concurrently \\\"pnpm:test-ci-*\\\"\",\n        \"test-ci-karma\": \"cross-env NODE_ENV=test karma start ./karma.conf.cjs --auto-watch --single-run --coverage --grep\",\n        \"test-ci-integration\": \"pnpm --filter \\\"./test/integration/**\\\" test\"\n    },\n    \"dependencies\": {\n        \"@kurkle/color\": \"^0.3.0\"\n    },\n    \"devDependencies\": {\n        \"@rollup/plugin-commonjs\": \"^23.0.2\",\n        \"@rollup/plugin-inject\": \"^5.0.2\",\n        \"@rollup/plugin-json\": \"^5.0.1\",\n        \"@rollup/plugin-node-resolve\": \"^15.0.1\",\n        \"@swc/core\": \"^1.3.18\",\n        \"@types/estree\": \"^1.0.0\",\n        \"@types/offscreencanvas\": \"^2019.7.0\",\n        \"@typescript-eslint/eslint-plugin\": \"^5.32.0\",\n        \"@typescript-eslint/parser\": \"^5.32.0\",\n        \"chartjs-adapter-luxon\": \"^1.2.0\",\n        \"chartjs-adapter-moment\": \"^1.0.0\",\n        \"chartjs-test-utils\": \"^0.4.0\",\n        \"concurrently\": \"^7.3.0\",\n        \"coveralls\": \"^3.1.1\",\n        \"cross-env\": \"^7.0.3\",\n        \"eslint\": \"^8.21.0\",\n        \"eslint-config-chartjs\": \"^0.3.0\",\n        \"eslint-plugin-es\": \"^4.1.0\",\n        \"eslint-plugin-html\": \"^7.1.0\",\n        \"eslint-plugin-markdown\": \"^3.0.0\",\n        \"esm\": \"^3.2.25\",\n        \"glob\": \"^8.0.3\",\n        \"jasmine\": \"^3.7.0\",\n        \"jasmine-core\": \"^3.7.1\",\n        \"karma\": \"^6.3.2\",\n        \"karma-chrome-launcher\": \"^3.1.0\",\n        \"karma-coverage\": \"^2.0.3\",\n        \"karma-edge-launcher\": \"^0.4.2\",\n        \"karma-firefox-launcher\": \"^2.1.0\",\n        \"karma-jasmine\": \"^4.0.1\",\n        \"karma-jasmine-html-reporter\": \"^1.5.4\",\n        \"karma-rollup-preprocessor\": \"7.0.7\",\n        \"karma-safari-private-launcher\": \"^1.0.0\",\n        \"karma-spec-reporter\": \"0.0.32\",\n        \"luxon\": \"^3.0.1\",\n        \"moment\": \"^2.29.4\",\n        \"moment-timezone\": \"^0.5.34\",\n        \"pixelmatch\": \"^5.3.0\",\n        \"rollup\": \"^3.3.0\",\n        \"rollup-plugin-cleanup\": \"^3.2.1\",\n        \"rollup-plugin-istanbul\": \"^4.0.0\",\n        \"rollup-plugin-swc3\": \"^0.7.0\",\n        \"rollup-plugin-terser\": \"^7.0.2\",\n        \"typescript\": \"^4.7.4\",\n        \"yargs\": \"^17.5.1\"\n    },\n    \"engines\": {\n        \"pnpm\": \">=8\"\n    },\n    \"packageManager\": \"pnpm@8.13.0\",\n    \"pnpm\": {\n        \"overrides\": {\n            \"html-entities\": \"1.4.0\"\n        },\n        \"peerDependencyRules\": {\n            \"ignoreMissing\": [\n                \"chart.js\"\n            ]\n        }\n    }\n}\n"
  },
  {
    "path": "pnpm-workspace.yaml",
    "content": "packages:\n  - 'docs'\n  - 'test/integration/*'\n"
  },
  {
    "path": "rollup.config.js",
    "content": "import cleanup from 'rollup-plugin-cleanup';\nimport json from '@rollup/plugin-json';\nimport resolve from '@rollup/plugin-node-resolve';\nimport {swc} from 'rollup-plugin-swc3';\nimport {terser} from 'rollup-plugin-terser';\nimport {readFileSync} from 'fs';\n\nconst {version, homepage} = JSON.parse(readFileSync('./package.json'));\n\nconst banner = `/*!\n * Chart.js v${version}\n * ${homepage}\n * (c) ${(new Date(process.env.SOURCE_DATE_EPOCH ? (process.env.SOURCE_DATE_EPOCH * 1000) : new Date().getTime())).getFullYear()} Chart.js Contributors\n * Released under the MIT License\n */`;\nconst extensions = ['.js', '.ts'];\nconst plugins = (minify) =>\n  [\n    json(),\n    resolve({\n      extensions\n    }),\n    swc({\n      jsc: {\n        parser: {\n          syntax: 'typescript'\n        },\n        target: 'es2022'\n      },\n      module: {\n        type: 'es6'\n      },\n      sourceMaps: true\n    }),\n    minify\n      ? terser({\n        output: {\n          preamble: banner\n        }\n      })\n      : cleanup({\n        comments: ['some', /__PURE__/]\n      })\n  ];\n\nexport default [\n  // UMD build\n  // dist/chart.umd.min.js\n  {\n    input: 'src/index.umd.ts',\n    plugins: plugins(true),\n    output: {\n      name: 'Chart',\n      file: 'dist/chart.umd.min.js',\n      format: 'umd',\n      indent: false,\n      sourcemap: true,\n    },\n  },\n\n  // UMD build\n  // dist/chart.umd.js (old filename)\n  {\n    input: 'src/index.umd.ts',\n    plugins: plugins(true),\n    output: {\n      name: 'Chart',\n      file: 'dist/chart.umd.js',\n      format: 'umd',\n      indent: false,\n      sourcemap: true,\n    },\n  },\n\n  // ES6 builds\n  // dist/chart.js\n  // helpers/*.js\n  {\n    input: {\n      'dist/chart': 'src/index.ts',\n      'dist/helpers': 'src/helpers/index.ts'\n    },\n    plugins: plugins(),\n    external: _ => (/node_modules/).test(_),\n    output: {\n      dir: './',\n      chunkFileNames: 'dist/chunks/[name].js',\n      entryFileNames: '[name].js',\n      banner,\n      format: 'esm',\n      indent: false,\n      sourcemap: true,\n    },\n  },\n\n  // CommonJS builds\n  // dist/chart.js\n  // helpers/*.js\n  {\n    input: {\n      'dist/chart': 'src/index.ts',\n      'dist/helpers': 'src/helpers/index.ts'\n    },\n    plugins: plugins(),\n    external: _ => (/node_modules/).test(_),\n    output: {\n      dir: './',\n      chunkFileNames: 'dist/chunks/[name].cjs',\n      entryFileNames: '[name].cjs',\n      banner,\n      format: 'commonjs',\n      indent: false,\n      sourcemap: true,\n    },\n  }\n];\n"
  },
  {
    "path": "scripts/deploy-docs.sh",
    "content": "#!/bin/bash\n\nset -e\n\nsource ./scripts/utils.sh\n\nTARGET_DIR='gh-pages'\nTARGET_BRANCH='master'\nTARGET_REPO_URL=\"https://$GITHUB_TOKEN@github.com/chartjs/chartjs.github.io.git\"\n\nVERSION=$1\nMODE=$2\nTAG=$(tag_from_version \"$VERSION\" \"$MODE\")\n\nfunction move_sample_redirect {\n    local tag=$1\n\n    cp ../scripts/sample-redirect-template.html samples/$tag/index.html\n    sed -i -E \"s/TAG/$tag/g\" samples/$tag/index.html\n}\n\nfunction deploy_tagged_files {\n    local tag=$1\n    rm -rf \"docs/$tag\"\n    cp -r ../dist/docs docs/$tag\n    rm -rf \"samples/$tag\"\n    mkdir \"samples/$tag\"\n\n    move_sample_redirect $tag\n\n    deploy_versioned_files $tag\n}\n\nfunction deploy_versioned_files {\n    local version=$1\n    local in_files='../dist/chart*.js'\n    local out_path='./dist'\n    rm -rf $out_path/$version\n    mkdir -p $out_path/$version\n    cp -r $in_files $out_path/$version\n}\n\n# Clone the repository and checkout the gh-pages branch\ngit clone $TARGET_REPO_URL $TARGET_DIR\ncd $TARGET_DIR\ngit checkout $TARGET_BRANCH\n\n# https://www.chartjs.org/dist/$VERSION\nif [[\"$VERSION\" != \"$TAG\"]]; then\n  deploy_versioned_files $VERSION\nfi\n\n# https://www.chartjs.org/dist/$TAG\n# https://www.chartjs.org/docs/$TAG\n# https://www.chartjs.org/samples/$TAG\ndeploy_tagged_files $TAG\n\ngit add --all\n\ngit remote add auth-origin $TARGET_REPO_URL\ngit config --global user.email \"$GH_AUTH_EMAIL\"\ngit config --global user.name \"Chart.js\"\ngit commit -m \"Deploy $VERSION from $GITHUB_REPOSITORY\" -m \"Commit: $GITHUB_SHA\"\ngit push -q auth-origin $TARGET_BRANCH\ngit remote rm auth-origin\n\n# Cleanup\ncd ..\nrm -rf $TARGET_DIR\n"
  },
  {
    "path": "scripts/docs-config.sh",
    "content": "#!/bin/bash\n\nset -e\n\nsource ./scripts/utils.sh\n\nVERSION=$1\nMODE=$2\n\nTAG=$(tag_from_version \"$VERSION\" \"$MODE\")\n\nsed -i -e \"s/VERSION/$TAG/g\" \"docs/.vuepress/config.ts\"\n"
  },
  {
    "path": "scripts/publish.sh",
    "content": "#!/bin/bash\n\nset -e\n\nNPM_TAG=\"next\"\n\nif [[ \"$VERSION\" =~ ^[^-]+$ ]]; then\n    echo \"Release tag indicates a full release. Releasing as \\\"latest\\\".\"\n    NPM_TAG=\"latest\"\nfi\n\nnpm publish --tag \"$NPM_TAG\"\n"
  },
  {
    "path": "scripts/sample-redirect-template.html",
    "content": "<!doctype html>\n<html>\n\n<head>\n  <title>Chart.js | Samples</title>\n\n  <link rel=\"canonical\" href=\"/docs/TAG/samples/\" />\n  <meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\" />\n  <meta http-equiv=\"refresh\" content=\"0;url=/docs/TAG/samples/\" />\n\n  <link rel=\"icon\" href=\"../favicon.ico\" />\n  <link rel=\"stylesheet\" type=\"text/css\" href=\"../styles.css\">\n  <script>\n    (function (i, s, o, g, r, a, m) {\n      i['GoogleAnalyticsObject'] = r; i[r] = i[r] || function () {\n        (i[r].q = i[r].q || []).push(arguments)\n      }, i[r].l = 1 * new Date(); a = s.createElement(o),\n        m = s.getElementsByTagName(o)[0]; a.async = 1; a.src = g; m.parentNode.insertBefore(a, m)\n    })(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');\n\n    ga('create', 'UA-28909194-3', 'auto');\n    ga('send', 'pageview');\n  </script>\n</head>\n\n\n<body>\n  <script>location = '/docs/TAG/samples/'</script>\n</body>\n\n\n</html>"
  },
  {
    "path": "scripts/utils.sh",
    "content": "#!/bin/bash\n\n# tag is next|latest|master|x.x.x\n# https://www.chartjs.org/dist/$tag/\n# https://www.chartjs.org/docs/$tag/\n# https://www.chartjs.org/samples/$tag/\nfunction tag_from_version {\n    local version=$1\n    local mode=$2\n    local tag=''\n    if [ \"$version\" == \"master\" ]; then\n        tag=master\n    elif [[ \"$version\" =~ ^[^-]+$ ]]; then\n      if [[ \"$mode\" == \"release\" ]]; then\n        tag=$version\n      else\n        tag=latest\n      fi\n    else\n        tag=next\n    fi\n    echo $tag\n}\n"
  },
  {
    "path": "src/controllers/controller.bar.js",
    "content": "import DatasetController from '../core/core.datasetController.js';\nimport {\n  _arrayUnique, isArray, isNullOrUndef,\n  valueOrDefault, resolveObjectKey, sign, defined\n} from '../helpers/index.js';\n\nfunction getAllScaleValues(scale, type) {\n  if (!scale._cache.$bar) {\n    const visibleMetas = scale.getMatchingVisibleMetas(type);\n    let values = [];\n\n    for (let i = 0, ilen = visibleMetas.length; i < ilen; i++) {\n      values = values.concat(visibleMetas[i].controller.getAllParsedValues(scale));\n    }\n    scale._cache.$bar = _arrayUnique(values.sort((a, b) => a - b));\n  }\n  return scale._cache.$bar;\n}\n\n/**\n * Computes the \"optimal\" sample size to maintain bars equally sized while preventing overlap.\n * @private\n */\nfunction computeMinSampleSize(meta) {\n  const scale = meta.iScale;\n  const values = getAllScaleValues(scale, meta.type);\n  let min = scale._length;\n  let i, ilen, curr, prev;\n  const updateMinAndPrev = () => {\n    if (curr === 32767 || curr === -32768) {\n      // Ignore truncated pixels\n      return;\n    }\n    if (defined(prev)) {\n      // curr - prev === 0 is ignored\n      min = Math.min(min, Math.abs(curr - prev) || min);\n    }\n    prev = curr;\n  };\n\n  for (i = 0, ilen = values.length; i < ilen; ++i) {\n    curr = scale.getPixelForValue(values[i]);\n    updateMinAndPrev();\n  }\n\n  prev = undefined;\n  for (i = 0, ilen = scale.ticks.length; i < ilen; ++i) {\n    curr = scale.getPixelForTick(i);\n    updateMinAndPrev();\n  }\n\n  return min;\n}\n\n/**\n * Computes an \"ideal\" category based on the absolute bar thickness or, if undefined or null,\n * uses the smallest interval (see computeMinSampleSize) that prevents bar overlapping. This\n * mode currently always generates bars equally sized (until we introduce scriptable options?).\n * @private\n */\nfunction computeFitCategoryTraits(index, ruler, options, stackCount) {\n  const thickness = options.barThickness;\n  let size, ratio;\n\n  if (isNullOrUndef(thickness)) {\n    size = ruler.min * options.categoryPercentage;\n    ratio = options.barPercentage;\n  } else {\n    // When bar thickness is enforced, category and bar percentages are ignored.\n    // Note(SB): we could add support for relative bar thickness (e.g. barThickness: '50%')\n    // and deprecate barPercentage since this value is ignored when thickness is absolute.\n    size = thickness * stackCount;\n    ratio = 1;\n  }\n\n  return {\n    chunk: size / stackCount,\n    ratio,\n    start: ruler.pixels[index] - (size / 2)\n  };\n}\n\n/**\n * Computes an \"optimal\" category that globally arranges bars side by side (no gap when\n * percentage options are 1), based on the previous and following categories. This mode\n * generates bars with different widths when data are not evenly spaced.\n * @private\n */\nfunction computeFlexCategoryTraits(index, ruler, options, stackCount) {\n  const pixels = ruler.pixels;\n  const curr = pixels[index];\n  let prev = index > 0 ? pixels[index - 1] : null;\n  let next = index < pixels.length - 1 ? pixels[index + 1] : null;\n  const percent = options.categoryPercentage;\n\n  if (prev === null) {\n    // first data: its size is double based on the next point or,\n    // if it's also the last data, we use the scale size.\n    prev = curr - (next === null ? ruler.end - ruler.start : next - curr);\n  }\n\n  if (next === null) {\n    // last data: its size is also double based on the previous point.\n    next = curr + curr - prev;\n  }\n\n  const start = curr - (curr - Math.min(prev, next)) / 2 * percent;\n  const size = Math.abs(next - prev) / 2 * percent;\n\n  return {\n    chunk: size / stackCount,\n    ratio: options.barPercentage,\n    start\n  };\n}\n\nfunction parseFloatBar(entry, item, vScale, i) {\n  const startValue = vScale.parse(entry[0], i);\n  const endValue = vScale.parse(entry[1], i);\n  const min = Math.min(startValue, endValue);\n  const max = Math.max(startValue, endValue);\n  let barStart = min;\n  let barEnd = max;\n\n  if (Math.abs(min) > Math.abs(max)) {\n    barStart = max;\n    barEnd = min;\n  }\n\n  // Store `barEnd` (furthest away from origin) as parsed value,\n  // to make stacking straight forward\n  item[vScale.axis] = barEnd;\n\n  item._custom = {\n    barStart,\n    barEnd,\n    start: startValue,\n    end: endValue,\n    min,\n    max\n  };\n}\n\nfunction parseValue(entry, item, vScale, i) {\n  if (isArray(entry)) {\n    parseFloatBar(entry, item, vScale, i);\n  } else {\n    item[vScale.axis] = vScale.parse(entry, i);\n  }\n  return item;\n}\n\nfunction parseArrayOrPrimitive(meta, data, start, count) {\n  const iScale = meta.iScale;\n  const vScale = meta.vScale;\n  const labels = iScale.getLabels();\n  const singleScale = iScale === vScale;\n  const parsed = [];\n  let i, ilen, item, entry;\n\n  for (i = start, ilen = start + count; i < ilen; ++i) {\n    entry = data[i];\n    item = {};\n    item[iScale.axis] = singleScale || iScale.parse(labels[i], i);\n    parsed.push(parseValue(entry, item, vScale, i));\n  }\n  return parsed;\n}\n\nfunction isFloatBar(custom) {\n  return custom && custom.barStart !== undefined && custom.barEnd !== undefined;\n}\n\nfunction barSign(size, vScale, actualBase) {\n  if (size !== 0) {\n    return sign(size);\n  }\n  return (vScale.isHorizontal() ? 1 : -1) * (vScale.min >= actualBase ? 1 : -1);\n}\n\nfunction borderProps(properties) {\n  let reverse, start, end, top, bottom;\n  if (properties.horizontal) {\n    reverse = properties.base > properties.x;\n    start = 'left';\n    end = 'right';\n  } else {\n    reverse = properties.base < properties.y;\n    start = 'bottom';\n    end = 'top';\n  }\n  if (reverse) {\n    top = 'end';\n    bottom = 'start';\n  } else {\n    top = 'start';\n    bottom = 'end';\n  }\n  return {start, end, reverse, top, bottom};\n}\n\nfunction setBorderSkipped(properties, options, stack, index) {\n  let edge = options.borderSkipped;\n  const res = {};\n\n  if (!edge) {\n    properties.borderSkipped = res;\n    return;\n  }\n\n  if (edge === true) {\n    properties.borderSkipped = {top: true, right: true, bottom: true, left: true};\n    return;\n  }\n\n  const {start, end, reverse, top, bottom} = borderProps(properties);\n\n  if (edge === 'middle' && stack) {\n    properties.enableBorderRadius = true;\n    if ((stack._top || 0) === index) {\n      edge = top;\n    } else if ((stack._bottom || 0) === index) {\n      edge = bottom;\n    } else {\n      res[parseEdge(bottom, start, end, reverse)] = true;\n      edge = top;\n    }\n  }\n\n  res[parseEdge(edge, start, end, reverse)] = true;\n  properties.borderSkipped = res;\n}\n\nfunction parseEdge(edge, a, b, reverse) {\n  if (reverse) {\n    edge = swap(edge, a, b);\n    edge = startEnd(edge, b, a);\n  } else {\n    edge = startEnd(edge, a, b);\n  }\n  return edge;\n}\n\nfunction swap(orig, v1, v2) {\n  return orig === v1 ? v2 : orig === v2 ? v1 : orig;\n}\n\nfunction startEnd(v, start, end) {\n  return v === 'start' ? start : v === 'end' ? end : v;\n}\n\nfunction setInflateAmount(properties, {inflateAmount}, ratio) {\n  properties.inflateAmount = inflateAmount === 'auto'\n    ? ratio === 1 ? 0.33 : 0\n    : inflateAmount;\n}\n\nexport default class BarController extends DatasetController {\n\n  static id = 'bar';\n\n  /**\n   * @type {any}\n   */\n  static defaults = {\n    datasetElementType: false,\n    dataElementType: 'bar',\n\n    categoryPercentage: 0.8,\n    barPercentage: 0.9,\n    grouped: true,\n\n    animations: {\n      numbers: {\n        type: 'number',\n        properties: ['x', 'y', 'base', 'width', 'height']\n      }\n    }\n  };\n\n  /**\n   * @type {any}\n   */\n  static overrides = {\n    scales: {\n      _index_: {\n        type: 'category',\n        offset: true,\n        grid: {\n          offset: true\n        }\n      },\n      _value_: {\n        type: 'linear',\n        beginAtZero: true,\n      }\n    }\n  };\n\n\n  /**\n\t * Overriding primitive data parsing since we support mixed primitive/array\n\t * data for float bars\n\t * @protected\n\t */\n  parsePrimitiveData(meta, data, start, count) {\n    return parseArrayOrPrimitive(meta, data, start, count);\n  }\n\n  /**\n\t * Overriding array data parsing since we support mixed primitive/array\n\t * data for float bars\n\t * @protected\n\t */\n  parseArrayData(meta, data, start, count) {\n    return parseArrayOrPrimitive(meta, data, start, count);\n  }\n\n  /**\n\t * Overriding object data parsing since we support mixed primitive/array\n\t * value-scale data for float bars\n\t * @protected\n\t */\n  parseObjectData(meta, data, start, count) {\n    const {iScale, vScale} = meta;\n    const {xAxisKey = 'x', yAxisKey = 'y'} = this._parsing;\n    const iAxisKey = iScale.axis === 'x' ? xAxisKey : yAxisKey;\n    const vAxisKey = vScale.axis === 'x' ? xAxisKey : yAxisKey;\n    const parsed = [];\n    let i, ilen, item, obj;\n    for (i = start, ilen = start + count; i < ilen; ++i) {\n      obj = data[i];\n      item = {};\n      item[iScale.axis] = iScale.parse(resolveObjectKey(obj, iAxisKey), i);\n      parsed.push(parseValue(resolveObjectKey(obj, vAxisKey), item, vScale, i));\n    }\n    return parsed;\n  }\n\n  /**\n\t * @protected\n\t */\n  updateRangeFromParsed(range, scale, parsed, stack) {\n    super.updateRangeFromParsed(range, scale, parsed, stack);\n    const custom = parsed._custom;\n    if (custom && scale === this._cachedMeta.vScale) {\n      // float bar: only one end of the bar is considered by `super`\n      range.min = Math.min(range.min, custom.min);\n      range.max = Math.max(range.max, custom.max);\n    }\n  }\n\n  /**\n\t * @return {number|boolean}\n\t * @protected\n\t */\n  getMaxOverflow() {\n    return 0;\n  }\n\n  /**\n\t * @protected\n\t */\n  getLabelAndValue(index) {\n    const meta = this._cachedMeta;\n    const {iScale, vScale} = meta;\n    const parsed = this.getParsed(index);\n    const custom = parsed._custom;\n    const value = isFloatBar(custom)\n      ? '[' + custom.start + ', ' + custom.end + ']'\n      : '' + vScale.getLabelForValue(parsed[vScale.axis]);\n\n    return {\n      label: '' + iScale.getLabelForValue(parsed[iScale.axis]),\n      value\n    };\n  }\n\n  initialize() {\n    this.enableOptionSharing = true;\n\n    super.initialize();\n\n    const meta = this._cachedMeta;\n    meta.stack = this.getDataset().stack;\n  }\n\n  update(mode) {\n    const meta = this._cachedMeta;\n    this.updateElements(meta.data, 0, meta.data.length, mode);\n  }\n\n  updateElements(bars, start, count, mode) {\n    const reset = mode === 'reset';\n    const {index, _cachedMeta: {vScale}} = this;\n    const base = vScale.getBasePixel();\n    const horizontal = vScale.isHorizontal();\n    const ruler = this._getRuler();\n    const {sharedOptions, includeOptions} = this._getSharedOptions(start, mode);\n\n    for (let i = start; i < start + count; i++) {\n      const parsed = this.getParsed(i);\n      const vpixels = reset || isNullOrUndef(parsed[vScale.axis]) ? {base, head: base} : this._calculateBarValuePixels(i);\n      const ipixels = this._calculateBarIndexPixels(i, ruler);\n      const stack = (parsed._stacks || {})[vScale.axis];\n\n      const properties = {\n        horizontal,\n        base: vpixels.base,\n        enableBorderRadius: !stack || isFloatBar(parsed._custom) || (index === stack._top || index === stack._bottom),\n        x: horizontal ? vpixels.head : ipixels.center,\n        y: horizontal ? ipixels.center : vpixels.head,\n        height: horizontal ? ipixels.size : Math.abs(vpixels.size),\n        width: horizontal ? Math.abs(vpixels.size) : ipixels.size\n      };\n\n      if (includeOptions) {\n        properties.options = sharedOptions || this.resolveDataElementOptions(i, bars[i].active ? 'active' : mode);\n      }\n      const options = properties.options || bars[i].options;\n      setBorderSkipped(properties, options, stack, index);\n      setInflateAmount(properties, options, ruler.ratio);\n      this.updateElement(bars[i], i, properties, mode);\n    }\n  }\n\n  /**\n\t * Returns the stacks based on groups and bar visibility.\n\t * @param {number} [last] - The dataset index\n\t * @param {number} [dataIndex] - The data index of the ruler\n\t * @returns {string[]} The list of stack IDs\n\t * @private\n\t */\n  _getStacks(last, dataIndex) {\n    const {iScale} = this._cachedMeta;\n    const metasets = iScale.getMatchingVisibleMetas(this._type)\n      .filter(meta => meta.controller.options.grouped);\n    const stacked = iScale.options.stacked;\n    const stacks = [];\n    const currentParsed = this._cachedMeta.controller.getParsed(dataIndex);\n    const iScaleValue = currentParsed && currentParsed[iScale.axis];\n\n    const skipNull = (meta) => {\n      const parsed = meta._parsed.find(item => item[iScale.axis] === iScaleValue);\n      const val = parsed && parsed[meta.vScale.axis];\n\n      if (isNullOrUndef(val) || isNaN(val)) {\n        return true;\n      }\n    };\n\n    for (const meta of metasets) {\n      if (dataIndex !== undefined && skipNull(meta)) {\n        continue;\n      }\n\n      // stacked   | meta.stack\n      //           | found | not found | undefined\n      // false     |   x   |     x     |     x\n      // true      |       |     x     |\n      // undefined |       |     x     |     x\n      if (stacked === false || stacks.indexOf(meta.stack) === -1 ||\n\t\t\t\t(stacked === undefined && meta.stack === undefined)) {\n        stacks.push(meta.stack);\n      }\n      if (meta.index === last) {\n        break;\n      }\n    }\n\n    // No stacks? that means there is no visible data. Let's still initialize an `undefined`\n    // stack where possible invisible bars will be located.\n    // https://github.com/chartjs/Chart.js/issues/6368\n    if (!stacks.length) {\n      stacks.push(undefined);\n    }\n\n    return stacks;\n  }\n\n  /**\n\t * Returns the effective number of stacks based on groups and bar visibility.\n\t * @private\n\t */\n  _getStackCount(index) {\n    return this._getStacks(undefined, index).length;\n  }\n\n  _getAxisCount() {\n    return this._getAxis().length;\n  }\n\n  getFirstScaleIdForIndexAxis() {\n    const scales = this.chart.scales;\n    const indexScaleId = this.chart.options.indexAxis;\n    return Object.keys(scales).filter(key => scales[key].axis === indexScaleId).shift();\n  }\n\n  _getAxis() {\n    const axis = {};\n    const firstScaleAxisId = this.getFirstScaleIdForIndexAxis();\n    for (const dataset of this.chart.data.datasets) {\n      axis[valueOrDefault(\n        this.chart.options.indexAxis === 'x' ? dataset.xAxisID : dataset.yAxisID, firstScaleAxisId\n      )] = true;\n    }\n    return Object.keys(axis);\n  }\n\n  /**\n\t * Returns the stack index for the given dataset based on groups and bar visibility.\n\t * @param {number} [datasetIndex] - The dataset index\n\t * @param {string} [name] - The stack name to find\n   * @param {number} [dataIndex]\n\t * @returns {number} The stack index\n\t * @private\n\t */\n  _getStackIndex(datasetIndex, name, dataIndex) {\n    const stacks = this._getStacks(datasetIndex, dataIndex);\n    const index = (name !== undefined)\n      ? stacks.indexOf(name)\n      : -1; // indexOf returns -1 if element is not present\n\n    return (index === -1)\n      ? stacks.length - 1\n      : index;\n  }\n\n  /**\n\t * @private\n\t */\n  _getRuler() {\n    const opts = this.options;\n    const meta = this._cachedMeta;\n    const iScale = meta.iScale;\n    const pixels = [];\n    let i, ilen;\n\n    for (i = 0, ilen = meta.data.length; i < ilen; ++i) {\n      pixels.push(iScale.getPixelForValue(this.getParsed(i)[iScale.axis], i));\n    }\n\n    const barThickness = opts.barThickness;\n    const min = barThickness || computeMinSampleSize(meta);\n\n    return {\n      min,\n      pixels,\n      start: iScale._startPixel,\n      end: iScale._endPixel,\n      stackCount: this._getStackCount(),\n      scale: iScale,\n      grouped: opts.grouped,\n      // bar thickness ratio used for non-grouped bars\n      ratio: barThickness ? 1 : opts.categoryPercentage * opts.barPercentage\n    };\n  }\n\n  /**\n\t * Note: pixel values are not clamped to the scale area.\n\t * @private\n\t */\n  _calculateBarValuePixels(index) {\n    const {_cachedMeta: {vScale, _stacked, index: datasetIndex}, options: {base: baseValue, minBarLength}} = this;\n    const actualBase = baseValue || 0;\n    const parsed = this.getParsed(index);\n    const custom = parsed._custom;\n    const floating = isFloatBar(custom);\n    let value = parsed[vScale.axis];\n    let start = 0;\n    let length = _stacked ? this.applyStack(vScale, parsed, _stacked) : value;\n    let head, size;\n\n    if (length !== value) {\n      start = length - value;\n      length = value;\n    }\n\n    if (floating) {\n      value = custom.barStart;\n      length = custom.barEnd - custom.barStart;\n      // bars crossing origin are not stacked\n      if (value !== 0 && sign(value) !== sign(custom.barEnd)) {\n        start = 0;\n      }\n      start += value;\n    }\n\n    const startValue = !isNullOrUndef(baseValue) && !floating ? baseValue : start;\n    let base = vScale.getPixelForValue(startValue);\n\n    if (this.chart.getDataVisibility(index)) {\n      head = vScale.getPixelForValue(start + length);\n    } else {\n      // When not visible, no height\n      head = base;\n    }\n\n    size = head - base;\n\n    if (Math.abs(size) < minBarLength) {\n      size = barSign(size, vScale, actualBase) * minBarLength;\n      if (value === actualBase) {\n        base -= size / 2;\n      }\n      const startPixel = vScale.getPixelForDecimal(0);\n      const endPixel = vScale.getPixelForDecimal(1);\n      const min = Math.min(startPixel, endPixel);\n      const max = Math.max(startPixel, endPixel);\n      base = Math.max(Math.min(base, max), min);\n      head = base + size;\n\n      if (_stacked && !floating) {\n        // visual data coordinates after applying minBarLength\n        parsed._stacks[vScale.axis]._visualValues[datasetIndex] = vScale.getValueForPixel(head) - vScale.getValueForPixel(base);\n      }\n    }\n\n    if (base === vScale.getPixelForValue(actualBase)) {\n      const halfGrid = sign(size) * vScale.getLineWidthForValue(actualBase) / 2;\n      base += halfGrid;\n      size -= halfGrid;\n    }\n\n    return {\n      size,\n      base,\n      head,\n      center: head + size / 2\n    };\n  }\n\n  /**\n\t * @private\n\t */\n  _calculateBarIndexPixels(index, ruler) {\n    const scale = ruler.scale;\n    const options = this.options;\n    const skipNull = options.skipNull;\n    const maxBarThickness = valueOrDefault(options.maxBarThickness, Infinity);\n    let center, size;\n    const axisCount = this._getAxisCount();\n    if (ruler.grouped) {\n      const stackCount = skipNull ? this._getStackCount(index) : ruler.stackCount;\n      const range = options.barThickness === 'flex'\n        ? computeFlexCategoryTraits(index, ruler, options, stackCount * axisCount)\n        : computeFitCategoryTraits(index, ruler, options, stackCount * axisCount);\n      const axisID = this.chart.options.indexAxis === 'x' ? this.getDataset().xAxisID : this.getDataset().yAxisID;\n      const axisNumber = this._getAxis().indexOf(valueOrDefault(axisID, this.getFirstScaleIdForIndexAxis()));\n      const stackIndex = this._getStackIndex(this.index, this._cachedMeta.stack, skipNull ? index : undefined) + axisNumber;\n      center = range.start + (range.chunk * stackIndex) + (range.chunk / 2);\n      size = Math.min(maxBarThickness, range.chunk * range.ratio);\n    } else {\n      // For non-grouped bar charts, exact pixel values are used\n      center = scale.getPixelForValue(this.getParsed(index)[scale.axis], index);\n      size = Math.min(maxBarThickness, ruler.min * ruler.ratio);\n    }\n\n\n    return {\n      base: center - size / 2,\n      head: center + size / 2,\n      center,\n      size\n    };\n  }\n\n  draw() {\n    const meta = this._cachedMeta;\n    const vScale = meta.vScale;\n    const rects = meta.data;\n    const ilen = rects.length;\n    let i = 0;\n\n    for (; i < ilen; ++i) {\n      if (this.getParsed(i)[vScale.axis] !== null && !rects[i].hidden) {\n        rects[i].draw(this._ctx);\n      }\n    }\n  }\n\n}\n"
  },
  {
    "path": "src/controllers/controller.bubble.js",
    "content": "import DatasetController from '../core/core.datasetController.js';\nimport {valueOrDefault} from '../helpers/helpers.core.js';\n\nexport default class BubbleController extends DatasetController {\n\n  static id = 'bubble';\n\n  /**\n   * @type {any}\n   */\n  static defaults = {\n    datasetElementType: false,\n    dataElementType: 'point',\n\n    animations: {\n      numbers: {\n        type: 'number',\n        properties: ['x', 'y', 'borderWidth', 'radius']\n      }\n    }\n  };\n\n  /**\n   * @type {any}\n   */\n  static overrides = {\n    scales: {\n      x: {\n        type: 'linear'\n      },\n      y: {\n        type: 'linear'\n      }\n    }\n  };\n\n  initialize() {\n    this.enableOptionSharing = true;\n    super.initialize();\n  }\n\n  /**\n\t * Parse array of primitive values\n\t * @protected\n\t */\n  parsePrimitiveData(meta, data, start, count) {\n    const parsed = super.parsePrimitiveData(meta, data, start, count);\n    for (let i = 0; i < parsed.length; i++) {\n      parsed[i]._custom = this.resolveDataElementOptions(i + start).radius;\n    }\n    return parsed;\n  }\n\n  /**\n\t * Parse array of arrays\n\t * @protected\n\t */\n  parseArrayData(meta, data, start, count) {\n    const parsed = super.parseArrayData(meta, data, start, count);\n    for (let i = 0; i < parsed.length; i++) {\n      const item = data[start + i];\n      parsed[i]._custom = valueOrDefault(item[2], this.resolveDataElementOptions(i + start).radius);\n    }\n    return parsed;\n  }\n\n  /**\n\t * Parse array of objects\n\t * @protected\n\t */\n  parseObjectData(meta, data, start, count) {\n    const parsed = super.parseObjectData(meta, data, start, count);\n    for (let i = 0; i < parsed.length; i++) {\n      const item = data[start + i];\n      parsed[i]._custom = valueOrDefault(item && item.r && +item.r, this.resolveDataElementOptions(i + start).radius);\n    }\n    return parsed;\n  }\n\n  /**\n\t * @protected\n\t */\n  getMaxOverflow() {\n    const data = this._cachedMeta.data;\n\n    let max = 0;\n    for (let i = data.length - 1; i >= 0; --i) {\n      max = Math.max(max, data[i].size(this.resolveDataElementOptions(i)) / 2);\n    }\n    return max > 0 && max;\n  }\n\n  /**\n\t * @protected\n\t */\n  getLabelAndValue(index) {\n    const meta = this._cachedMeta;\n    const labels = this.chart.data.labels || [];\n    const {xScale, yScale} = meta;\n    const parsed = this.getParsed(index);\n    const x = xScale.getLabelForValue(parsed.x);\n    const y = yScale.getLabelForValue(parsed.y);\n    const r = parsed._custom;\n\n    return {\n      label: labels[index] || '',\n      value: '(' + x + ', ' + y + (r ? ', ' + r : '') + ')'\n    };\n  }\n\n  update(mode) {\n    const points = this._cachedMeta.data;\n\n    // Update Points\n    this.updateElements(points, 0, points.length, mode);\n  }\n\n  updateElements(points, start, count, mode) {\n    const reset = mode === 'reset';\n    const {iScale, vScale} = this._cachedMeta;\n    const {sharedOptions, includeOptions} = this._getSharedOptions(start, mode);\n    const iAxis = iScale.axis;\n    const vAxis = vScale.axis;\n\n    for (let i = start; i < start + count; i++) {\n      const point = points[i];\n      const parsed = !reset && this.getParsed(i);\n      const properties = {};\n      const iPixel = properties[iAxis] = reset ? iScale.getPixelForDecimal(0.5) : iScale.getPixelForValue(parsed[iAxis]);\n      const vPixel = properties[vAxis] = reset ? vScale.getBasePixel() : vScale.getPixelForValue(parsed[vAxis]);\n\n      properties.skip = isNaN(iPixel) || isNaN(vPixel);\n\n      if (includeOptions) {\n        properties.options = sharedOptions || this.resolveDataElementOptions(i, point.active ? 'active' : mode);\n\n        if (reset) {\n          properties.options.radius = 0;\n        }\n      }\n\n      this.updateElement(point, i, properties, mode);\n    }\n  }\n\n  /**\n\t * @param {number} index\n\t * @param {string} [mode]\n\t * @protected\n\t */\n  resolveDataElementOptions(index, mode) {\n    const parsed = this.getParsed(index);\n    let values = super.resolveDataElementOptions(index, mode);\n\n    // In case values were cached (and thus frozen), we need to clone the values\n    if (values.$shared) {\n      values = Object.assign({}, values, {$shared: false});\n    }\n\n    // Custom radius resolution\n    const radius = values.radius;\n    if (mode !== 'active') {\n      values.radius = 0;\n    }\n    values.radius += valueOrDefault(parsed && parsed._custom, radius);\n\n    return values;\n  }\n}\n"
  },
  {
    "path": "src/controllers/controller.doughnut.js",
    "content": "import DatasetController from '../core/core.datasetController.js';\nimport {isObject, resolveObjectKey, toPercentage, toDimension, valueOrDefault} from '../helpers/helpers.core.js';\nimport {formatNumber} from '../helpers/helpers.intl.js';\nimport {toRadians, PI, TAU, HALF_PI, _angleBetween} from '../helpers/helpers.math.js';\n\n/**\n * @typedef { import('../core/core.controller.js').default } Chart\n */\n\nfunction getRatioAndOffset(rotation, circumference, cutout) {\n  let ratioX = 1;\n  let ratioY = 1;\n  let offsetX = 0;\n  let offsetY = 0;\n  // If the chart's circumference isn't a full circle, calculate size as a ratio of the width/height of the arc\n  if (circumference < TAU) {\n    const startAngle = rotation;\n    const endAngle = startAngle + circumference;\n    const startX = Math.cos(startAngle);\n    const startY = Math.sin(startAngle);\n    const endX = Math.cos(endAngle);\n    const endY = Math.sin(endAngle);\n    const calcMax = (angle, a, b) => _angleBetween(angle, startAngle, endAngle, true) ? 1 : Math.max(a, a * cutout, b, b * cutout);\n    const calcMin = (angle, a, b) => _angleBetween(angle, startAngle, endAngle, true) ? -1 : Math.min(a, a * cutout, b, b * cutout);\n    const maxX = calcMax(0, startX, endX);\n    const maxY = calcMax(HALF_PI, startY, endY);\n    const minX = calcMin(PI, startX, endX);\n    const minY = calcMin(PI + HALF_PI, startY, endY);\n    ratioX = (maxX - minX) / 2;\n    ratioY = (maxY - minY) / 2;\n    offsetX = -(maxX + minX) / 2;\n    offsetY = -(maxY + minY) / 2;\n  }\n  return {ratioX, ratioY, offsetX, offsetY};\n}\n\nexport default class DoughnutController extends DatasetController {\n\n  static id = 'doughnut';\n\n  /**\n   * @type {any}\n   */\n  static defaults = {\n    datasetElementType: false,\n    dataElementType: 'arc',\n    animation: {\n      // Boolean - Whether we animate the rotation of the Doughnut\n      animateRotate: true,\n      // Boolean - Whether we animate scaling the Doughnut from the centre\n      animateScale: false\n    },\n    animations: {\n      numbers: {\n        type: 'number',\n        properties: ['circumference', 'endAngle', 'innerRadius', 'outerRadius', 'startAngle', 'x', 'y', 'offset', 'borderWidth', 'spacing']\n      },\n    },\n    // The percentage of the chart that we cut out of the middle.\n    cutout: '50%',\n\n    // The rotation of the chart, where the first data arc begins.\n    rotation: 0,\n\n    // The total circumference of the chart.\n    circumference: 360,\n\n    // The outer radius of the chart\n    radius: '100%',\n\n    // Spacing between arcs\n    spacing: 0,\n\n    indexAxis: 'r',\n  };\n\n  static descriptors = {\n    _scriptable: (name) => name !== 'spacing',\n    _indexable: (name) => name !== 'spacing' && !name.startsWith('borderDash') && !name.startsWith('hoverBorderDash'),\n  };\n\n  /**\n   * @type {any}\n   */\n  static overrides = {\n    aspectRatio: 1,\n\n    // Need to override these to give a nice default\n    plugins: {\n      legend: {\n        labels: {\n          generateLabels(chart) {\n            const data = chart.data;\n            const {labels: {pointStyle, textAlign, color, useBorderRadius, borderRadius}} = chart.legend.options;\n            if (data.labels.length && data.datasets.length) {\n              return data.labels.map((label, i) => {\n                const meta = chart.getDatasetMeta(0);\n                const style = meta.controller.getStyle(i);\n\n                return {\n                  text: label,\n                  fillStyle: style.backgroundColor,\n                  fontColor: color,\n                  hidden: !chart.getDataVisibility(i),\n                  lineDash: style.borderDash,\n                  lineDashOffset: style.borderDashOffset,\n                  lineJoin: style.borderJoinStyle,\n                  lineWidth: style.borderWidth,\n                  strokeStyle: style.borderColor,\n                  textAlign: textAlign,\n                  pointStyle: pointStyle,\n                  borderRadius: useBorderRadius && (borderRadius || style.borderRadius),\n                  // Extra data used for toggling the correct item\n                  index: i\n                };\n              });\n            }\n            return [];\n          }\n        },\n\n        onClick(e, legendItem, legend) {\n          legend.chart.toggleDataVisibility(legendItem.index);\n          legend.chart.update();\n        }\n      }\n    }\n  };\n\n  constructor(chart, datasetIndex) {\n    super(chart, datasetIndex);\n\n    this.enableOptionSharing = true;\n    this.innerRadius = undefined;\n    this.outerRadius = undefined;\n    this.offsetX = undefined;\n    this.offsetY = undefined;\n  }\n\n  linkScales() {}\n\n  /**\n\t * Override data parsing, since we are not using scales\n\t */\n  parse(start, count) {\n    const data = this.getDataset().data;\n    const meta = this._cachedMeta;\n\n    if (this._parsing === false) {\n      meta._parsed = data;\n    } else {\n      let getter = (i) => +data[i];\n\n      if (isObject(data[start])) {\n        const {key = 'value'} = this._parsing;\n        getter = (i) => +resolveObjectKey(data[i], key);\n      }\n\n      let i, ilen;\n      for (i = start, ilen = start + count; i < ilen; ++i) {\n        meta._parsed[i] = getter(i);\n      }\n    }\n  }\n\n  /**\n\t * @private\n\t */\n  _getRotation() {\n    return toRadians(this.options.rotation - 90);\n  }\n\n  /**\n\t * @private\n\t */\n  _getCircumference() {\n    return toRadians(this.options.circumference);\n  }\n\n  /**\n\t * Get the maximal rotation & circumference extents\n\t * across all visible datasets.\n\t */\n  _getRotationExtents() {\n    let min = TAU;\n    let max = -TAU;\n\n    for (let i = 0; i < this.chart.data.datasets.length; ++i) {\n      if (this.chart.isDatasetVisible(i) && this.chart.getDatasetMeta(i).type === this._type) {\n        const controller = this.chart.getDatasetMeta(i).controller;\n        const rotation = controller._getRotation();\n        const circumference = controller._getCircumference();\n\n        min = Math.min(min, rotation);\n        max = Math.max(max, rotation + circumference);\n      }\n    }\n\n    return {\n      rotation: min,\n      circumference: max - min,\n    };\n  }\n\n  /**\n\t * @param {string} mode\n\t */\n  update(mode) {\n    const chart = this.chart;\n    const {chartArea} = chart;\n    const meta = this._cachedMeta;\n    const arcs = meta.data;\n    const spacing = this.getMaxBorderWidth() + this.getMaxOffset(arcs) + this.options.spacing;\n    const maxSize = Math.max((Math.min(chartArea.width, chartArea.height) - spacing) / 2, 0);\n    const cutout = Math.min(toPercentage(this.options.cutout, maxSize), 1);\n    const chartWeight = this._getRingWeight(this.index);\n\n    // Compute the maximal rotation & circumference limits.\n    // If we only consider our dataset, this can cause problems when two datasets\n    // are both less than a circle with different rotations (starting angles)\n    const {circumference, rotation} = this._getRotationExtents();\n    const {ratioX, ratioY, offsetX, offsetY} = getRatioAndOffset(rotation, circumference, cutout);\n    const maxWidth = (chartArea.width - spacing) / ratioX;\n    const maxHeight = (chartArea.height - spacing) / ratioY;\n    const maxRadius = Math.max(Math.min(maxWidth, maxHeight) / 2, 0);\n    const outerRadius = toDimension(this.options.radius, maxRadius);\n    const innerRadius = Math.max(outerRadius * cutout, 0);\n    const radiusLength = (outerRadius - innerRadius) / this._getVisibleDatasetWeightTotal();\n    this.offsetX = offsetX * outerRadius;\n    this.offsetY = offsetY * outerRadius;\n\n    meta.total = this.calculateTotal();\n\n    this.outerRadius = outerRadius - radiusLength * this._getRingWeightOffset(this.index);\n    this.innerRadius = Math.max(this.outerRadius - radiusLength * chartWeight, 0);\n\n    this.updateElements(arcs, 0, arcs.length, mode);\n  }\n\n  /**\n   * @private\n   */\n  _circumference(i, reset) {\n    const opts = this.options;\n    const meta = this._cachedMeta;\n    const circumference = this._getCircumference();\n    if ((reset && opts.animation.animateRotate) || !this.chart.getDataVisibility(i) || meta._parsed[i] === null || meta.data[i].hidden) {\n      return 0;\n    }\n    return this.calculateCircumference(meta._parsed[i] * circumference / TAU);\n  }\n\n  updateElements(arcs, start, count, mode) {\n    const reset = mode === 'reset';\n    const chart = this.chart;\n    const chartArea = chart.chartArea;\n    const opts = chart.options;\n    const animationOpts = opts.animation;\n    const centerX = (chartArea.left + chartArea.right) / 2;\n    const centerY = (chartArea.top + chartArea.bottom) / 2;\n    const animateScale = reset && animationOpts.animateScale;\n    const innerRadius = animateScale ? 0 : this.innerRadius;\n    const outerRadius = animateScale ? 0 : this.outerRadius;\n    const {sharedOptions, includeOptions} = this._getSharedOptions(start, mode);\n    let startAngle = this._getRotation();\n    let i;\n\n    for (i = 0; i < start; ++i) {\n      startAngle += this._circumference(i, reset);\n    }\n\n    for (i = start; i < start + count; ++i) {\n      const circumference = this._circumference(i, reset);\n      const arc = arcs[i];\n      const properties = {\n        x: centerX + this.offsetX,\n        y: centerY + this.offsetY,\n        startAngle,\n        endAngle: startAngle + circumference,\n        circumference,\n        outerRadius,\n        innerRadius\n      };\n      if (includeOptions) {\n        properties.options = sharedOptions || this.resolveDataElementOptions(i, arc.active ? 'active' : mode);\n      }\n      startAngle += circumference;\n\n      this.updateElement(arc, i, properties, mode);\n    }\n  }\n\n  calculateTotal() {\n    const meta = this._cachedMeta;\n    const metaData = meta.data;\n    let total = 0;\n    let i;\n\n    for (i = 0; i < metaData.length; i++) {\n      const value = meta._parsed[i];\n      if (value !== null && !isNaN(value) && this.chart.getDataVisibility(i) && !metaData[i].hidden) {\n        total += Math.abs(value);\n      }\n    }\n\n    return total;\n  }\n\n  calculateCircumference(value) {\n    const total = this._cachedMeta.total;\n    if (total > 0 && !isNaN(value)) {\n      return TAU * (Math.abs(value) / total);\n    }\n    return 0;\n  }\n\n  getLabelAndValue(index) {\n    const meta = this._cachedMeta;\n    const chart = this.chart;\n    const labels = chart.data.labels || [];\n    const value = formatNumber(meta._parsed[index], chart.options.locale);\n\n    return {\n      label: labels[index] || '',\n      value,\n    };\n  }\n\n  getMaxBorderWidth(arcs) {\n    let max = 0;\n    const chart = this.chart;\n    let i, ilen, meta, controller, options;\n\n    if (!arcs) {\n      // Find the outmost visible dataset\n      for (i = 0, ilen = chart.data.datasets.length; i < ilen; ++i) {\n        if (chart.isDatasetVisible(i)) {\n          meta = chart.getDatasetMeta(i);\n          arcs = meta.data;\n          controller = meta.controller;\n          break;\n        }\n      }\n    }\n\n    if (!arcs) {\n      return 0;\n    }\n\n    for (i = 0, ilen = arcs.length; i < ilen; ++i) {\n      options = controller.resolveDataElementOptions(i);\n      if (options.borderAlign !== 'inner') {\n        max = Math.max(max, options.borderWidth || 0, options.hoverBorderWidth || 0);\n      }\n    }\n    return max;\n  }\n\n  getMaxOffset(arcs) {\n    let max = 0;\n\n    for (let i = 0, ilen = arcs.length; i < ilen; ++i) {\n      const options = this.resolveDataElementOptions(i);\n      max = Math.max(max, options.offset || 0, options.hoverOffset || 0);\n    }\n    return max;\n  }\n\n  /**\n\t * Get radius length offset of the dataset in relation to the visible datasets weights. This allows determining the inner and outer radius correctly\n\t * @private\n\t */\n  _getRingWeightOffset(datasetIndex) {\n    let ringWeightOffset = 0;\n\n    for (let i = 0; i < datasetIndex; ++i) {\n      if (this.chart.isDatasetVisible(i)) {\n        ringWeightOffset += this._getRingWeight(i);\n      }\n    }\n\n    return ringWeightOffset;\n  }\n\n  /**\n\t * @private\n\t */\n  _getRingWeight(datasetIndex) {\n    return Math.max(valueOrDefault(this.chart.data.datasets[datasetIndex].weight, 1), 0);\n  }\n\n  /**\n\t * Returns the sum of all visible data set weights.\n\t * @private\n\t */\n  _getVisibleDatasetWeightTotal() {\n    return this._getRingWeightOffset(this.chart.data.datasets.length) || 1;\n  }\n}\n"
  },
  {
    "path": "src/controllers/controller.line.js",
    "content": "import DatasetController from '../core/core.datasetController.js';\nimport {isNullOrUndef} from '../helpers/index.js';\nimport {isNumber} from '../helpers/helpers.math.js';\nimport {_getStartAndCountOfVisiblePoints, _scaleRangesChanged} from '../helpers/helpers.extras.js';\n\nexport default class LineController extends DatasetController {\n\n  static id = 'line';\n\n  /**\n   * @type {any}\n   */\n  static defaults = {\n    datasetElementType: 'line',\n    dataElementType: 'point',\n\n    showLine: true,\n    spanGaps: false,\n  };\n\n  /**\n   * @type {any}\n   */\n  static overrides = {\n    scales: {\n      _index_: {\n        type: 'category',\n      },\n      _value_: {\n        type: 'linear',\n      },\n    }\n  };\n\n  initialize() {\n    this.enableOptionSharing = true;\n    this.supportsDecimation = true;\n    super.initialize();\n  }\n\n  update(mode) {\n    const meta = this._cachedMeta;\n    const {dataset: line, data: points = [], _dataset} = meta;\n    // @ts-ignore\n    const animationsDisabled = this.chart._animationsDisabled;\n    let {start, count} = _getStartAndCountOfVisiblePoints(meta, points, animationsDisabled);\n\n    this._drawStart = start;\n    this._drawCount = count;\n\n    if (_scaleRangesChanged(meta)) {\n      start = 0;\n      count = points.length;\n    }\n\n    // Update Line\n    line._chart = this.chart;\n    line._datasetIndex = this.index;\n    line._decimated = !!_dataset._decimated;\n    line.points = points;\n\n    const options = this.resolveDatasetElementOptions(mode);\n    if (!this.options.showLine) {\n      options.borderWidth = 0;\n    }\n    options.segment = this.options.segment;\n    this.updateElement(line, undefined, {\n      animated: !animationsDisabled,\n      options\n    }, mode);\n\n    // Update Points\n    this.updateElements(points, start, count, mode);\n  }\n\n  updateElements(points, start, count, mode) {\n    const reset = mode === 'reset';\n    const {iScale, vScale, _stacked, _dataset} = this._cachedMeta;\n    const {sharedOptions, includeOptions} = this._getSharedOptions(start, mode);\n    const iAxis = iScale.axis;\n    const vAxis = vScale.axis;\n    const {spanGaps, segment} = this.options;\n    const maxGapLength = isNumber(spanGaps) ? spanGaps : Number.POSITIVE_INFINITY;\n    const directUpdate = this.chart._animationsDisabled || reset || mode === 'none';\n    const end = start + count;\n    const pointsCount = points.length;\n    let prevParsed = start > 0 && this.getParsed(start - 1);\n\n    for (let i = 0; i < pointsCount; ++i) {\n      const point = points[i];\n      const properties = directUpdate ? point : {};\n\n      if (i < start || i >= end) {\n        properties.skip = true;\n        continue;\n      }\n\n      const parsed = this.getParsed(i);\n      const nullData = isNullOrUndef(parsed[vAxis]);\n      const iPixel = properties[iAxis] = iScale.getPixelForValue(parsed[iAxis], i);\n      const vPixel = properties[vAxis] = reset || nullData ? vScale.getBasePixel() : vScale.getPixelForValue(_stacked ? this.applyStack(vScale, parsed, _stacked) : parsed[vAxis], i);\n\n      properties.skip = isNaN(iPixel) || isNaN(vPixel) || nullData;\n      properties.stop = i > 0 && (Math.abs(parsed[iAxis] - prevParsed[iAxis])) > maxGapLength;\n      if (segment) {\n        properties.parsed = parsed;\n        properties.raw = _dataset.data[i];\n      }\n\n      if (includeOptions) {\n        properties.options = sharedOptions || this.resolveDataElementOptions(i, point.active ? 'active' : mode);\n      }\n\n      if (!directUpdate) {\n        this.updateElement(point, i, properties, mode);\n      }\n\n      prevParsed = parsed;\n    }\n  }\n\n  /**\n\t * @protected\n\t */\n  getMaxOverflow() {\n    const meta = this._cachedMeta;\n    const dataset = meta.dataset;\n    const border = dataset.options && dataset.options.borderWidth || 0;\n    const data = meta.data || [];\n    if (!data.length) {\n      return border;\n    }\n    const firstPoint = data[0].size(this.resolveDataElementOptions(0));\n    const lastPoint = data[data.length - 1].size(this.resolveDataElementOptions(data.length - 1));\n    return Math.max(border, firstPoint, lastPoint) / 2;\n  }\n\n  draw() {\n    const meta = this._cachedMeta;\n    meta.dataset.updateControlPoints(this.chart.chartArea, meta.iScale.axis);\n    super.draw();\n  }\n}\n"
  },
  {
    "path": "src/controllers/controller.pie.js",
    "content": "import DoughnutController from './controller.doughnut.js';\n\n// Pie charts are Doughnut chart with different defaults\nexport default class PieController extends DoughnutController {\n\n  static id = 'pie';\n\n  /**\n   * @type {any}\n   */\n  static defaults = {\n    // The percentage of the chart that we cut out of the middle.\n    cutout: 0,\n\n    // The rotation of the chart, where the first data arc begins.\n    rotation: 0,\n\n    // The total circumference of the chart.\n    circumference: 360,\n\n    // The outer radius of the chart\n    radius: '100%'\n  };\n}\n"
  },
  {
    "path": "src/controllers/controller.polarArea.js",
    "content": "import DatasetController from '../core/core.datasetController.js';\nimport {toRadians, PI, formatNumber, _parseObjectDataRadialScale} from '../helpers/index.js';\n\nexport default class PolarAreaController extends DatasetController {\n\n  static id = 'polarArea';\n\n  /**\n   * @type {any}\n   */\n  static defaults = {\n    dataElementType: 'arc',\n    animation: {\n      animateRotate: true,\n      animateScale: true\n    },\n    animations: {\n      numbers: {\n        type: 'number',\n        properties: ['x', 'y', 'startAngle', 'endAngle', 'innerRadius', 'outerRadius']\n      },\n    },\n    indexAxis: 'r',\n    startAngle: 0,\n  };\n\n  /**\n   * @type {any}\n   */\n  static overrides = {\n    aspectRatio: 1,\n\n    plugins: {\n      legend: {\n        labels: {\n          generateLabels(chart) {\n            const data = chart.data;\n            if (data.labels.length && data.datasets.length) {\n              const {labels: {pointStyle, color}} = chart.legend.options;\n\n              return data.labels.map((label, i) => {\n                const meta = chart.getDatasetMeta(0);\n                const style = meta.controller.getStyle(i);\n\n                return {\n                  text: label,\n                  fillStyle: style.backgroundColor,\n                  strokeStyle: style.borderColor,\n                  fontColor: color,\n                  lineWidth: style.borderWidth,\n                  pointStyle: pointStyle,\n                  hidden: !chart.getDataVisibility(i),\n\n                  // Extra data used for toggling the correct item\n                  index: i\n                };\n              });\n            }\n            return [];\n          }\n        },\n\n        onClick(e, legendItem, legend) {\n          legend.chart.toggleDataVisibility(legendItem.index);\n          legend.chart.update();\n        }\n      }\n    },\n\n    scales: {\n      r: {\n        type: 'radialLinear',\n        angleLines: {\n          display: false\n        },\n        beginAtZero: true,\n        grid: {\n          circular: true\n        },\n        pointLabels: {\n          display: false\n        },\n        startAngle: 0\n      }\n    }\n  };\n\n  constructor(chart, datasetIndex) {\n    super(chart, datasetIndex);\n\n    this.innerRadius = undefined;\n    this.outerRadius = undefined;\n  }\n\n  getLabelAndValue(index) {\n    const meta = this._cachedMeta;\n    const chart = this.chart;\n    const labels = chart.data.labels || [];\n    const value = formatNumber(meta._parsed[index].r, chart.options.locale);\n\n    return {\n      label: labels[index] || '',\n      value,\n    };\n  }\n\n  parseObjectData(meta, data, start, count) {\n    return _parseObjectDataRadialScale.bind(this)(meta, data, start, count);\n  }\n\n  update(mode) {\n    const arcs = this._cachedMeta.data;\n\n    this._updateRadius();\n    this.updateElements(arcs, 0, arcs.length, mode);\n  }\n\n  /**\n   * @protected\n   */\n  getMinMax() {\n    const meta = this._cachedMeta;\n    const range = {min: Number.POSITIVE_INFINITY, max: Number.NEGATIVE_INFINITY};\n\n    meta.data.forEach((element, index) => {\n      const parsed = this.getParsed(index).r;\n\n      if (!isNaN(parsed) && this.chart.getDataVisibility(index)) {\n        if (parsed < range.min) {\n          range.min = parsed;\n        }\n\n        if (parsed > range.max) {\n          range.max = parsed;\n        }\n      }\n    });\n\n    return range;\n  }\n\n  /**\n\t * @private\n\t */\n  _updateRadius() {\n    const chart = this.chart;\n    const chartArea = chart.chartArea;\n    const opts = chart.options;\n    const minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top);\n\n    const outerRadius = Math.max(minSize / 2, 0);\n    const innerRadius = Math.max(opts.cutoutPercentage ? (outerRadius / 100) * (opts.cutoutPercentage) : 1, 0);\n    const radiusLength = (outerRadius - innerRadius) / chart.getVisibleDatasetCount();\n\n    this.outerRadius = outerRadius - (radiusLength * this.index);\n    this.innerRadius = this.outerRadius - radiusLength;\n  }\n\n  updateElements(arcs, start, count, mode) {\n    const reset = mode === 'reset';\n    const chart = this.chart;\n    const opts = chart.options;\n    const animationOpts = opts.animation;\n    const scale = this._cachedMeta.rScale;\n    const centerX = scale.xCenter;\n    const centerY = scale.yCenter;\n    const datasetStartAngle = scale.getIndexAngle(0) - 0.5 * PI;\n    let angle = datasetStartAngle;\n    let i;\n\n    const defaultAngle = 360 / this.countVisibleElements();\n\n    for (i = 0; i < start; ++i) {\n      angle += this._computeAngle(i, mode, defaultAngle);\n    }\n    for (i = start; i < start + count; i++) {\n      const arc = arcs[i];\n      let startAngle = angle;\n      let endAngle = angle + this._computeAngle(i, mode, defaultAngle);\n      let outerRadius = chart.getDataVisibility(i) ? scale.getDistanceFromCenterForValue(this.getParsed(i).r) : 0;\n      angle = endAngle;\n\n      if (reset) {\n        if (animationOpts.animateScale) {\n          outerRadius = 0;\n        }\n        if (animationOpts.animateRotate) {\n          startAngle = endAngle = datasetStartAngle;\n        }\n      }\n\n      const properties = {\n        x: centerX,\n        y: centerY,\n        innerRadius: 0,\n        outerRadius,\n        startAngle,\n        endAngle,\n        options: this.resolveDataElementOptions(i, arc.active ? 'active' : mode)\n      };\n\n      this.updateElement(arc, i, properties, mode);\n    }\n  }\n\n  countVisibleElements() {\n    const meta = this._cachedMeta;\n    let count = 0;\n\n    meta.data.forEach((element, index) => {\n      if (!isNaN(this.getParsed(index).r) && this.chart.getDataVisibility(index)) {\n        count++;\n      }\n    });\n\n    return count;\n  }\n\n  /**\n\t * @private\n\t */\n  _computeAngle(index, mode, defaultAngle) {\n    return this.chart.getDataVisibility(index)\n      ? toRadians(this.resolveDataElementOptions(index, mode).angle || defaultAngle)\n      : 0;\n  }\n}\n"
  },
  {
    "path": "src/controllers/controller.radar.js",
    "content": "import DatasetController from '../core/core.datasetController.js';\nimport {_parseObjectDataRadialScale} from '../helpers/index.js';\n\nexport default class RadarController extends DatasetController {\n\n  static id = 'radar';\n\n  /**\n   * @type {any}\n   */\n  static defaults = {\n    datasetElementType: 'line',\n    dataElementType: 'point',\n    indexAxis: 'r',\n    showLine: true,\n    elements: {\n      line: {\n        fill: 'start'\n      }\n    },\n  };\n\n  /**\n   * @type {any}\n   */\n  static overrides = {\n    aspectRatio: 1,\n\n    scales: {\n      r: {\n        type: 'radialLinear',\n      }\n    }\n  };\n\n  /**\n\t * @protected\n\t */\n  getLabelAndValue(index) {\n    const vScale = this._cachedMeta.vScale;\n    const parsed = this.getParsed(index);\n\n    return {\n      label: vScale.getLabels()[index],\n      value: '' + vScale.getLabelForValue(parsed[vScale.axis])\n    };\n  }\n\n  parseObjectData(meta, data, start, count) {\n    return _parseObjectDataRadialScale.bind(this)(meta, data, start, count);\n  }\n\n  update(mode) {\n    const meta = this._cachedMeta;\n    const line = meta.dataset;\n    const points = meta.data || [];\n    const labels = meta.iScale.getLabels();\n\n    // Update Line\n    line.points = points;\n    // In resize mode only point locations change, so no need to set the points or options.\n    if (mode !== 'resize') {\n      const options = this.resolveDatasetElementOptions(mode);\n      if (!this.options.showLine) {\n        options.borderWidth = 0;\n      }\n\n      const properties = {\n        _loop: true,\n        _fullLoop: labels.length === points.length,\n        options\n      };\n\n      this.updateElement(line, undefined, properties, mode);\n    }\n\n    // Update Points\n    this.updateElements(points, 0, points.length, mode);\n  }\n\n  updateElements(points, start, count, mode) {\n    const scale = this._cachedMeta.rScale;\n    const reset = mode === 'reset';\n\n    for (let i = start; i < start + count; i++) {\n      const point = points[i];\n      const options = this.resolveDataElementOptions(i, point.active ? 'active' : mode);\n      const pointPosition = scale.getPointPositionForValue(i, this.getParsed(i).r);\n\n      const x = reset ? scale.xCenter : pointPosition.x;\n      const y = reset ? scale.yCenter : pointPosition.y;\n\n      const properties = {\n        x,\n        y,\n        angle: pointPosition.angle,\n        skip: isNaN(x) || isNaN(y),\n        options\n      };\n\n      this.updateElement(point, i, properties, mode);\n    }\n  }\n}\n"
  },
  {
    "path": "src/controllers/controller.scatter.js",
    "content": "import DatasetController from '../core/core.datasetController.js';\nimport {isNullOrUndef} from '../helpers/index.js';\nimport {isNumber} from '../helpers/helpers.math.js';\nimport {_getStartAndCountOfVisiblePoints, _scaleRangesChanged} from '../helpers/helpers.extras.js';\n\nexport default class ScatterController extends DatasetController {\n\n  static id = 'scatter';\n\n  /**\n   * @type {any}\n   */\n  static defaults = {\n    datasetElementType: false,\n    dataElementType: 'point',\n    showLine: false,\n    fill: false\n  };\n\n  /**\n   * @type {any}\n   */\n  static overrides = {\n\n    interaction: {\n      mode: 'point'\n    },\n\n    scales: {\n      x: {\n        type: 'linear'\n      },\n      y: {\n        type: 'linear'\n      }\n    }\n  };\n\n  /**\n\t * @protected\n\t */\n  getLabelAndValue(index) {\n    const meta = this._cachedMeta;\n    const labels = this.chart.data.labels || [];\n    const {xScale, yScale} = meta;\n    const parsed = this.getParsed(index);\n    const x = xScale.getLabelForValue(parsed.x);\n    const y = yScale.getLabelForValue(parsed.y);\n\n    return {\n      label: labels[index] || '',\n      value: '(' + x + ', ' + y + ')'\n    };\n  }\n\n  update(mode) {\n    const meta = this._cachedMeta;\n    const {data: points = []} = meta;\n    // @ts-ignore\n    const animationsDisabled = this.chart._animationsDisabled;\n    let {start, count} = _getStartAndCountOfVisiblePoints(meta, points, animationsDisabled);\n\n    this._drawStart = start;\n    this._drawCount = count;\n\n    if (_scaleRangesChanged(meta)) {\n      start = 0;\n      count = points.length;\n    }\n\n    if (this.options.showLine) {\n\n      // https://github.com/chartjs/Chart.js/issues/11333\n      if (!this.datasetElementType) {\n        this.addElements();\n      }\n      const {dataset: line, _dataset} = meta;\n\n      // Update Line\n      line._chart = this.chart;\n      line._datasetIndex = this.index;\n      line._decimated = !!_dataset._decimated;\n      line.points = points;\n\n      const options = this.resolveDatasetElementOptions(mode);\n      options.segment = this.options.segment;\n      this.updateElement(line, undefined, {\n        animated: !animationsDisabled,\n        options\n      }, mode);\n    } else if (this.datasetElementType) {\n      // https://github.com/chartjs/Chart.js/issues/11333\n      delete meta.dataset;\n      this.datasetElementType = false;\n    }\n\n    // Update Points\n    this.updateElements(points, start, count, mode);\n  }\n\n  addElements() {\n    const {showLine} = this.options;\n\n    if (!this.datasetElementType && showLine) {\n      this.datasetElementType = this.chart.registry.getElement('line');\n    }\n\n    super.addElements();\n  }\n\n  updateElements(points, start, count, mode) {\n    const reset = mode === 'reset';\n    const {iScale, vScale, _stacked, _dataset} = this._cachedMeta;\n    const firstOpts = this.resolveDataElementOptions(start, mode);\n    const sharedOptions = this.getSharedOptions(firstOpts);\n    const includeOptions = this.includeOptions(mode, sharedOptions);\n    const iAxis = iScale.axis;\n    const vAxis = vScale.axis;\n    const {spanGaps, segment} = this.options;\n    const maxGapLength = isNumber(spanGaps) ? spanGaps : Number.POSITIVE_INFINITY;\n    const directUpdate = this.chart._animationsDisabled || reset || mode === 'none';\n    let prevParsed = start > 0 && this.getParsed(start - 1);\n\n    for (let i = start; i < start + count; ++i) {\n      const point = points[i];\n      const parsed = this.getParsed(i);\n      const properties = directUpdate ? point : {};\n      const nullData = isNullOrUndef(parsed[vAxis]);\n      const iPixel = properties[iAxis] = iScale.getPixelForValue(parsed[iAxis], i);\n      const vPixel = properties[vAxis] = reset || nullData ? vScale.getBasePixel() : vScale.getPixelForValue(_stacked ? this.applyStack(vScale, parsed, _stacked) : parsed[vAxis], i);\n\n      properties.skip = isNaN(iPixel) || isNaN(vPixel) || nullData;\n      properties.stop = i > 0 && (Math.abs(parsed[iAxis] - prevParsed[iAxis])) > maxGapLength;\n      if (segment) {\n        properties.parsed = parsed;\n        properties.raw = _dataset.data[i];\n      }\n\n      if (includeOptions) {\n        properties.options = sharedOptions || this.resolveDataElementOptions(i, point.active ? 'active' : mode);\n      }\n\n      if (!directUpdate) {\n        this.updateElement(point, i, properties, mode);\n      }\n\n      prevParsed = parsed;\n    }\n\n    this.updateSharedOptions(sharedOptions, mode, firstOpts);\n  }\n\n  /**\n\t * @protected\n\t */\n  getMaxOverflow() {\n    const meta = this._cachedMeta;\n    const data = meta.data || [];\n\n    if (!this.options.showLine) {\n      let max = 0;\n      for (let i = data.length - 1; i >= 0; --i) {\n        max = Math.max(max, data[i].size(this.resolveDataElementOptions(i)) / 2);\n      }\n      return max > 0 && max;\n    }\n\n    const dataset = meta.dataset;\n    const border = dataset.options && dataset.options.borderWidth || 0;\n\n    if (!data.length) {\n      return border;\n    }\n\n    const firstPoint = data[0].size(this.resolveDataElementOptions(0));\n    const lastPoint = data[data.length - 1].size(this.resolveDataElementOptions(data.length - 1));\n    return Math.max(border, firstPoint, lastPoint) / 2;\n  }\n}\n"
  },
  {
    "path": "src/controllers/index.js",
    "content": "export {default as BarController} from './controller.bar.js';\nexport {default as BubbleController} from './controller.bubble.js';\nexport {default as DoughnutController} from './controller.doughnut.js';\nexport {default as LineController} from './controller.line.js';\nexport {default as PolarAreaController} from './controller.polarArea.js';\nexport {default as PieController} from './controller.pie.js';\nexport {default as RadarController} from './controller.radar.js';\nexport {default as ScatterController} from './controller.scatter.js';\n"
  },
  {
    "path": "src/core/core.adapters.ts",
    "content": "/**\n * @namespace Chart._adapters\n * @since 2.8.0\n * @private\n */\n\nimport type {AnyObject} from '../types/basic.js';\nimport type {ChartOptions} from '../types/index.js';\n\nexport type TimeUnit = 'millisecond' | 'second' | 'minute' | 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year';\n\nexport interface DateAdapter<T extends AnyObject = AnyObject> {\n  readonly options: T;\n  /**\n   * Will called with chart options after adapter creation.\n   */\n  init(this: DateAdapter<T>, chartOptions: ChartOptions): void;\n  /**\n   * Returns a map of time formats for the supported formatting units defined\n   * in Unit as well as 'datetime' representing a detailed date/time string.\n   */\n  formats(this: DateAdapter<T>): Record<TimeUnit | 'datetime', string>;\n  /**\n   * Parses the given `value` and return the associated timestamp.\n   * @param value - the value to parse (usually comes from the data)\n   * @param [format] - the expected data format\n   */\n  parse(this: DateAdapter<T>, value: unknown, format?: string): number | null;\n  /**\n   * Returns the formatted date in the specified `format` for a given `timestamp`.\n   * @param timestamp - the timestamp to format\n   * @param format - the date/time token\n   */\n  format(this: DateAdapter<T>, timestamp: number, format: string): string;\n  /**\n   * Adds the specified `amount` of `unit` to the given `timestamp`.\n   * @param timestamp - the input timestamp\n   * @param amount - the amount to add\n   * @param unit - the unit as string\n   */\n  add(this: DateAdapter<T>, timestamp: number, amount: number, unit: TimeUnit): number;\n  /**\n   * Returns the number of `unit` between the given timestamps.\n   * @param a - the input timestamp (reference)\n   * @param b - the timestamp to subtract\n   * @param unit - the unit as string\n   */\n  diff(this: DateAdapter<T>, a: number, b: number, unit: TimeUnit): number;\n  /**\n   * Returns start of `unit` for the given `timestamp`.\n   * @param timestamp - the input timestamp\n   * @param unit - the unit as string\n   * @param [weekday] - the ISO day of the week with 1 being Monday\n   * and 7 being Sunday (only needed if param *unit* is `isoWeek`).\n   */\n  startOf(this: DateAdapter<T>, timestamp: number, unit: TimeUnit | 'isoWeek', weekday?: number | boolean): number;\n  /**\n   * Returns end of `unit` for the given `timestamp`.\n   * @param timestamp - the input timestamp\n   * @param unit - the unit as string\n   */\n  endOf(this: DateAdapter<T>, timestamp: number, unit: TimeUnit): number;\n}\n\nfunction abstract<T = void>(): T {\n  throw new Error('This method is not implemented: Check that a complete date adapter is provided.');\n}\n\n/**\n * Date adapter (current used by the time scale)\n * @namespace Chart._adapters._date\n * @memberof Chart._adapters\n * @private\n */\nclass DateAdapterBase implements DateAdapter {\n\n  /**\n   * Override default date adapter methods.\n   * Accepts type parameter to define options type.\n   * @example\n   * Chart._adapters._date.override<{myAdapterOption: string}>({\n   *   init() {\n   *     console.log(this.options.myAdapterOption);\n   *   }\n   * })\n   */\n  static override<T extends AnyObject = AnyObject>(\n    members: Partial<Omit<DateAdapter<T>, 'options'>>\n  ) {\n    Object.assign(DateAdapterBase.prototype, members);\n  }\n\n  readonly options: AnyObject;\n\n  constructor(options?: AnyObject) {\n    this.options = options || {};\n  }\n\n  // eslint-disable-next-line @typescript-eslint/no-empty-function\n  init() {}\n\n  formats(): Record<TimeUnit | 'datetime', string> {\n    return abstract();\n  }\n\n  parse(): number | null {\n    return abstract();\n  }\n\n  format(): string {\n    return abstract();\n  }\n\n  add(): number {\n    return abstract();\n  }\n\n  diff(): number {\n    return abstract();\n  }\n\n  startOf(): number {\n    return abstract();\n  }\n\n  endOf(): number {\n    return abstract();\n  }\n}\n\nexport default {\n  _date: DateAdapterBase as {\n    new (options?: AnyObject): DateAdapter;\n    override<T extends AnyObject = AnyObject>(\n      members: Partial<Omit<DateAdapter<T>, 'options'>>\n    ): void;\n  }\n};\n"
  },
  {
    "path": "src/core/core.animation.js",
    "content": "import effects from '../helpers/helpers.easing.js';\nimport {resolve} from '../helpers/helpers.options.js';\nimport {color as helpersColor} from '../helpers/helpers.color.js';\n\nconst transparent = 'transparent';\nconst interpolators = {\n  boolean(from, to, factor) {\n    return factor > 0.5 ? to : from;\n  },\n  /**\n   * @param {string} from\n   * @param {string} to\n   * @param {number} factor\n   */\n  color(from, to, factor) {\n    const c0 = helpersColor(from || transparent);\n    const c1 = c0.valid && helpersColor(to || transparent);\n    return c1 && c1.valid\n      ? c1.mix(c0, factor).hexString()\n      : to;\n  },\n  number(from, to, factor) {\n    return from + (to - from) * factor;\n  }\n};\n\nexport default class Animation {\n  constructor(cfg, target, prop, to) {\n    const currentValue = target[prop];\n\n    to = resolve([cfg.to, to, currentValue, cfg.from]);\n    const from = resolve([cfg.from, currentValue, to]);\n\n    this._active = true;\n    this._fn = cfg.fn || interpolators[cfg.type || typeof from];\n    this._easing = effects[cfg.easing] || effects.linear;\n    this._start = Math.floor(Date.now() + (cfg.delay || 0));\n    this._duration = this._total = Math.floor(cfg.duration);\n    this._loop = !!cfg.loop;\n    this._target = target;\n    this._prop = prop;\n    this._from = from;\n    this._to = to;\n    this._promises = undefined;\n  }\n\n  active() {\n    return this._active;\n  }\n\n  update(cfg, to, date) {\n    if (this._active) {\n      this._notify(false);\n\n      const currentValue = this._target[this._prop];\n      const elapsed = date - this._start;\n      const remain = this._duration - elapsed;\n      this._start = date;\n      this._duration = Math.floor(Math.max(remain, cfg.duration));\n      this._total += elapsed;\n      this._loop = !!cfg.loop;\n      this._to = resolve([cfg.to, to, currentValue, cfg.from]);\n      this._from = resolve([cfg.from, currentValue, to]);\n    }\n  }\n\n  cancel() {\n    if (this._active) {\n      // update current evaluated value, for smoother animations\n      this.tick(Date.now());\n      this._active = false;\n      this._notify(false);\n    }\n  }\n\n  tick(date) {\n    const elapsed = date - this._start;\n    const duration = this._duration;\n    const prop = this._prop;\n    const from = this._from;\n    const loop = this._loop;\n    const to = this._to;\n    let factor;\n\n    this._active = from !== to && (loop || (elapsed < duration));\n\n    if (!this._active) {\n      this._target[prop] = to;\n      this._notify(true);\n      return;\n    }\n\n    if (elapsed < 0) {\n      this._target[prop] = from;\n      return;\n    }\n\n    factor = (elapsed / duration) % 2;\n    factor = loop && factor > 1 ? 2 - factor : factor;\n    factor = this._easing(Math.min(1, Math.max(0, factor)));\n\n    this._target[prop] = this._fn(from, to, factor);\n  }\n\n  wait() {\n    const promises = this._promises || (this._promises = []);\n    return new Promise((res, rej) => {\n      promises.push({res, rej});\n    });\n  }\n\n  _notify(resolved) {\n    const method = resolved ? 'res' : 'rej';\n    const promises = this._promises || [];\n    for (let i = 0; i < promises.length; i++) {\n      promises[i][method]();\n    }\n  }\n}\n"
  },
  {
    "path": "src/core/core.animations.defaults.js",
    "content": "const numbers = ['x', 'y', 'borderWidth', 'radius', 'tension'];\nconst colors = ['color', 'borderColor', 'backgroundColor'];\n\nexport function applyAnimationsDefaults(defaults) {\n  defaults.set('animation', {\n    delay: undefined,\n    duration: 1000,\n    easing: 'easeOutQuart',\n    fn: undefined,\n    from: undefined,\n    loop: undefined,\n    to: undefined,\n    type: undefined,\n  });\n\n  defaults.describe('animation', {\n    _fallback: false,\n    _indexable: false,\n    _scriptable: (name) => name !== 'onProgress' && name !== 'onComplete' && name !== 'fn',\n  });\n\n  defaults.set('animations', {\n    colors: {\n      type: 'color',\n      properties: colors\n    },\n    numbers: {\n      type: 'number',\n      properties: numbers\n    },\n  });\n\n  defaults.describe('animations', {\n    _fallback: 'animation',\n  });\n\n  defaults.set('transitions', {\n    active: {\n      animation: {\n        duration: 400\n      }\n    },\n    resize: {\n      animation: {\n        duration: 0\n      }\n    },\n    show: {\n      animations: {\n        colors: {\n          from: 'transparent'\n        },\n        visible: {\n          type: 'boolean',\n          duration: 0 // show immediately\n        },\n      }\n    },\n    hide: {\n      animations: {\n        colors: {\n          to: 'transparent'\n        },\n        visible: {\n          type: 'boolean',\n          easing: 'linear',\n          fn: v => v | 0 // for keeping the dataset visible all the way through the animation\n        },\n      }\n    }\n  });\n}\n"
  },
  {
    "path": "src/core/core.animations.js",
    "content": "import animator from './core.animator.js';\nimport Animation from './core.animation.js';\nimport defaults from './core.defaults.js';\nimport {isArray, isObject} from '../helpers/helpers.core.js';\n\nexport default class Animations {\n  constructor(chart, config) {\n    this._chart = chart;\n    this._properties = new Map();\n    this.configure(config);\n  }\n\n  configure(config) {\n    if (!isObject(config)) {\n      return;\n    }\n\n    const animationOptions = Object.keys(defaults.animation);\n    const animatedProps = this._properties;\n\n    Object.getOwnPropertyNames(config).forEach(key => {\n      const cfg = config[key];\n      if (!isObject(cfg)) {\n        return;\n      }\n      const resolved = {};\n      for (const option of animationOptions) {\n        resolved[option] = cfg[option];\n      }\n\n      (isArray(cfg.properties) && cfg.properties || [key]).forEach((prop) => {\n        if (prop === key || !animatedProps.has(prop)) {\n          animatedProps.set(prop, resolved);\n        }\n      });\n    });\n  }\n\n  /**\n\t * Utility to handle animation of `options`.\n\t * @private\n\t */\n  _animateOptions(target, values) {\n    const newOptions = values.options;\n    const options = resolveTargetOptions(target, newOptions);\n    if (!options) {\n      return [];\n    }\n\n    const animations = this._createAnimations(options, newOptions);\n    if (newOptions.$shared) {\n      // Going to shared options:\n      // After all animations are done, assign the shared options object to the element\n      // So any new updates to the shared options are observed\n      awaitAll(target.options.$animations, newOptions).then(() => {\n        target.options = newOptions;\n      }, () => {\n        // rejected, noop\n      });\n    }\n\n    return animations;\n  }\n\n  /**\n\t * @private\n\t */\n  _createAnimations(target, values) {\n    const animatedProps = this._properties;\n    const animations = [];\n    const running = target.$animations || (target.$animations = {});\n    const props = Object.keys(values);\n    const date = Date.now();\n    let i;\n\n    for (i = props.length - 1; i >= 0; --i) {\n      const prop = props[i];\n      if (prop.charAt(0) === '$') {\n        continue;\n      }\n\n      if (prop === 'options') {\n        animations.push(...this._animateOptions(target, values));\n        continue;\n      }\n      const value = values[prop];\n      let animation = running[prop];\n      const cfg = animatedProps.get(prop);\n\n      if (animation) {\n        if (cfg && animation.active()) {\n          // There is an existing active animation, let's update that\n          animation.update(cfg, value, date);\n          continue;\n        } else {\n          animation.cancel();\n        }\n      }\n      if (!cfg || !cfg.duration) {\n        // not animated, set directly to new value\n        target[prop] = value;\n        continue;\n      }\n\n      running[prop] = animation = new Animation(cfg, target, prop, value);\n      animations.push(animation);\n    }\n    return animations;\n  }\n\n\n  /**\n\t * Update `target` properties to new values, using configured animations\n\t * @param {object} target - object to update\n\t * @param {object} values - new target properties\n\t * @returns {boolean|undefined} - `true` if animations were started\n\t **/\n  update(target, values) {\n    if (this._properties.size === 0) {\n      // Nothing is animated, just apply the new values.\n      Object.assign(target, values);\n      return;\n    }\n\n    const animations = this._createAnimations(target, values);\n\n    if (animations.length) {\n      animator.add(this._chart, animations);\n      return true;\n    }\n  }\n}\n\nfunction awaitAll(animations, properties) {\n  const running = [];\n  const keys = Object.keys(properties);\n  for (let i = 0; i < keys.length; i++) {\n    const anim = animations[keys[i]];\n    if (anim && anim.active()) {\n      running.push(anim.wait());\n    }\n  }\n  // @ts-ignore\n  return Promise.all(running);\n}\n\nfunction resolveTargetOptions(target, newOptions) {\n  if (!newOptions) {\n    return;\n  }\n  let options = target.options;\n  if (!options) {\n    target.options = newOptions;\n    return;\n  }\n  if (options.$shared) {\n    // Going from shared options to distinct one:\n    // Create new options object containing the old shared values and start updating that.\n    target.options = options = Object.assign({}, options, {$shared: false, $animations: {}});\n  }\n  return options;\n}\n"
  },
  {
    "path": "src/core/core.animator.js",
    "content": "import {requestAnimFrame} from '../helpers/helpers.extras.js';\n\n/**\n * @typedef { import('./core.animation.js').default } Animation\n * @typedef { import('./core.controller.js').default } Chart\n */\n\n/**\n * Please use the module's default export which provides a singleton instance\n * Note: class is export for typedoc\n */\nexport class Animator {\n  constructor() {\n    this._request = null;\n    this._charts = new Map();\n    this._running = false;\n    this._lastDate = undefined;\n  }\n\n  /**\n\t * @private\n\t */\n  _notify(chart, anims, date, type) {\n    const callbacks = anims.listeners[type];\n    const numSteps = anims.duration;\n\n    callbacks.forEach(fn => fn({\n      chart,\n      initial: anims.initial,\n      numSteps,\n      currentStep: Math.min(date - anims.start, numSteps)\n    }));\n  }\n\n  /**\n\t * @private\n\t */\n  _refresh() {\n    if (this._request) {\n      return;\n    }\n    this._running = true;\n\n    this._request = requestAnimFrame.call(window, () => {\n      this._update();\n      this._request = null;\n\n      if (this._running) {\n        this._refresh();\n      }\n    });\n  }\n\n  /**\n\t * @private\n\t */\n  _update(date = Date.now()) {\n    let remaining = 0;\n\n    this._charts.forEach((anims, chart) => {\n      if (!anims.running || !anims.items.length) {\n        return;\n      }\n      const items = anims.items;\n      let i = items.length - 1;\n      let draw = false;\n      let item;\n\n      for (; i >= 0; --i) {\n        item = items[i];\n\n        if (item._active) {\n          if (item._total > anims.duration) {\n            // if the animation has been updated and its duration prolonged,\n            // update to total duration of current animations run (for progress event)\n            anims.duration = item._total;\n          }\n          item.tick(date);\n          draw = true;\n        } else {\n          // Remove the item by replacing it with last item and removing the last\n          // A lot faster than splice.\n          items[i] = items[items.length - 1];\n          items.pop();\n        }\n      }\n\n      if (draw) {\n        chart.draw();\n        this._notify(chart, anims, date, 'progress');\n      }\n\n      if (!items.length) {\n        anims.running = false;\n        this._notify(chart, anims, date, 'complete');\n        anims.initial = false;\n      }\n\n      remaining += items.length;\n    });\n\n    this._lastDate = date;\n\n    if (remaining === 0) {\n      this._running = false;\n    }\n  }\n\n  /**\n\t * @private\n\t */\n  _getAnims(chart) {\n    const charts = this._charts;\n    let anims = charts.get(chart);\n    if (!anims) {\n      anims = {\n        running: false,\n        initial: true,\n        items: [],\n        listeners: {\n          complete: [],\n          progress: []\n        }\n      };\n      charts.set(chart, anims);\n    }\n    return anims;\n  }\n\n  /**\n\t * @param {Chart} chart\n\t * @param {string} event - event name\n\t * @param {Function} cb - callback\n\t */\n  listen(chart, event, cb) {\n    this._getAnims(chart).listeners[event].push(cb);\n  }\n\n  /**\n\t * Add animations\n\t * @param {Chart} chart\n\t * @param {Animation[]} items - animations\n\t */\n  add(chart, items) {\n    if (!items || !items.length) {\n      return;\n    }\n    this._getAnims(chart).items.push(...items);\n  }\n\n  /**\n\t * Counts number of active animations for the chart\n\t * @param {Chart} chart\n\t */\n  has(chart) {\n    return this._getAnims(chart).items.length > 0;\n  }\n\n  /**\n\t * Start animating (all charts)\n\t * @param {Chart} chart\n\t */\n  start(chart) {\n    const anims = this._charts.get(chart);\n    if (!anims) {\n      return;\n    }\n    anims.running = true;\n    anims.start = Date.now();\n    anims.duration = anims.items.reduce((acc, cur) => Math.max(acc, cur._duration), 0);\n    this._refresh();\n  }\n\n  running(chart) {\n    if (!this._running) {\n      return false;\n    }\n    const anims = this._charts.get(chart);\n    if (!anims || !anims.running || !anims.items.length) {\n      return false;\n    }\n    return true;\n  }\n\n  /**\n\t * Stop all animations for the chart\n\t * @param {Chart} chart\n\t */\n  stop(chart) {\n    const anims = this._charts.get(chart);\n    if (!anims || !anims.items.length) {\n      return;\n    }\n    const items = anims.items;\n    let i = items.length - 1;\n\n    for (; i >= 0; --i) {\n      items[i].cancel();\n    }\n    anims.items = [];\n    this._notify(chart, anims, Date.now(), 'complete');\n  }\n\n  /**\n\t * Remove chart from Animator\n\t * @param {Chart} chart\n\t */\n  remove(chart) {\n    return this._charts.delete(chart);\n  }\n}\n\n// singleton instance\nexport default /* #__PURE__ */ new Animator();\n"
  },
  {
    "path": "src/core/core.config.js",
    "content": "import defaults, {overrides, descriptors} from './core.defaults.js';\nimport {mergeIf, resolveObjectKey, isArray, isFunction, valueOrDefault, isObject} from '../helpers/helpers.core.js';\nimport {_attachContext, _createResolver, _descriptors} from '../helpers/helpers.config.js';\n\nexport function getIndexAxis(type, options) {\n  const datasetDefaults = defaults.datasets[type] || {};\n  const datasetOptions = (options.datasets || {})[type] || {};\n  return datasetOptions.indexAxis || options.indexAxis || datasetDefaults.indexAxis || 'x';\n}\n\nfunction getAxisFromDefaultScaleID(id, indexAxis) {\n  let axis = id;\n  if (id === '_index_') {\n    axis = indexAxis;\n  } else if (id === '_value_') {\n    axis = indexAxis === 'x' ? 'y' : 'x';\n  }\n  return axis;\n}\n\nfunction getDefaultScaleIDFromAxis(axis, indexAxis) {\n  return axis === indexAxis ? '_index_' : '_value_';\n}\n\nfunction idMatchesAxis(id) {\n  if (id === 'x' || id === 'y' || id === 'r') {\n    return id;\n  }\n}\n\nfunction axisFromPosition(position) {\n  if (position === 'top' || position === 'bottom') {\n    return 'x';\n  }\n  if (position === 'left' || position === 'right') {\n    return 'y';\n  }\n}\n\nexport function determineAxis(id, ...scaleOptions) {\n  if (idMatchesAxis(id)) {\n    return id;\n  }\n  for (const opts of scaleOptions) {\n    const axis = opts.axis\n      || axisFromPosition(opts.position)\n      || id.length > 1 && idMatchesAxis(id[0].toLowerCase());\n    if (axis) {\n      return axis;\n    }\n  }\n  throw new Error(`Cannot determine type of '${id}' axis. Please provide 'axis' or 'position' option.`);\n}\n\nfunction getAxisFromDataset(id, axis, dataset) {\n  if (dataset[axis + 'AxisID'] === id) {\n    return {axis};\n  }\n}\n\nfunction retrieveAxisFromDatasets(id, config) {\n  if (config.data && config.data.datasets) {\n    const boundDs = config.data.datasets.filter((d) => d.xAxisID === id || d.yAxisID === id);\n    if (boundDs.length) {\n      return getAxisFromDataset(id, 'x', boundDs[0]) || getAxisFromDataset(id, 'y', boundDs[0]);\n    }\n  }\n  return {};\n}\n\nfunction mergeScaleConfig(config, options) {\n  const chartDefaults = overrides[config.type] || {scales: {}};\n  const configScales = options.scales || {};\n  const chartIndexAxis = getIndexAxis(config.type, options);\n  const scales = Object.create(null);\n\n  // First figure out first scale id's per axis.\n  Object.keys(configScales).forEach(id => {\n    const scaleConf = configScales[id];\n    if (!isObject(scaleConf)) {\n      return console.error(`Invalid scale configuration for scale: ${id}`);\n    }\n    if (scaleConf._proxy) {\n      return console.warn(`Ignoring resolver passed as options for scale: ${id}`);\n    }\n    const axis = determineAxis(id, scaleConf, retrieveAxisFromDatasets(id, config), defaults.scales[scaleConf.type]);\n    const defaultId = getDefaultScaleIDFromAxis(axis, chartIndexAxis);\n    const defaultScaleOptions = chartDefaults.scales || {};\n    scales[id] = mergeIf(Object.create(null), [{axis}, scaleConf, defaultScaleOptions[axis], defaultScaleOptions[defaultId]]);\n  });\n\n  // Then merge dataset defaults to scale configs\n  config.data.datasets.forEach(dataset => {\n    const type = dataset.type || config.type;\n    const indexAxis = dataset.indexAxis || getIndexAxis(type, options);\n    const datasetDefaults = overrides[type] || {};\n    const defaultScaleOptions = datasetDefaults.scales || {};\n    Object.keys(defaultScaleOptions).forEach(defaultID => {\n      const axis = getAxisFromDefaultScaleID(defaultID, indexAxis);\n      const id = dataset[axis + 'AxisID'] || axis;\n      scales[id] = scales[id] || Object.create(null);\n      mergeIf(scales[id], [{axis}, configScales[id], defaultScaleOptions[defaultID]]);\n    });\n  });\n\n  // apply scale defaults, if not overridden by dataset defaults\n  Object.keys(scales).forEach(key => {\n    const scale = scales[key];\n    mergeIf(scale, [defaults.scales[scale.type], defaults.scale]);\n  });\n\n  return scales;\n}\n\nfunction initOptions(config) {\n  const options = config.options || (config.options = {});\n\n  options.plugins = valueOrDefault(options.plugins, {});\n  options.scales = mergeScaleConfig(config, options);\n}\n\nfunction initData(data) {\n  data = data || {};\n  data.datasets = data.datasets || [];\n  data.labels = data.labels || [];\n  return data;\n}\n\nfunction initConfig(config) {\n  config = config || {};\n  config.data = initData(config.data);\n\n  initOptions(config);\n\n  return config;\n}\n\nconst keyCache = new Map();\nconst keysCached = new Set();\n\nfunction cachedKeys(cacheKey, generate) {\n  let keys = keyCache.get(cacheKey);\n  if (!keys) {\n    keys = generate();\n    keyCache.set(cacheKey, keys);\n    keysCached.add(keys);\n  }\n  return keys;\n}\n\nconst addIfFound = (set, obj, key) => {\n  const opts = resolveObjectKey(obj, key);\n  if (opts !== undefined) {\n    set.add(opts);\n  }\n};\n\nexport default class Config {\n  constructor(config) {\n    this._config = initConfig(config);\n    this._scopeCache = new Map();\n    this._resolverCache = new Map();\n  }\n\n  get platform() {\n    return this._config.platform;\n  }\n\n  get type() {\n    return this._config.type;\n  }\n\n  set type(type) {\n    this._config.type = type;\n  }\n\n  get data() {\n    return this._config.data;\n  }\n\n  set data(data) {\n    this._config.data = initData(data);\n  }\n\n  get options() {\n    return this._config.options;\n  }\n\n  set options(options) {\n    this._config.options = options;\n  }\n\n  get plugins() {\n    return this._config.plugins;\n  }\n\n  update() {\n    const config = this._config;\n    this.clearCache();\n    initOptions(config);\n  }\n\n  clearCache() {\n    this._scopeCache.clear();\n    this._resolverCache.clear();\n  }\n\n  /**\n   * Returns the option scope keys for resolving dataset options.\n   * These keys do not include the dataset itself, because it is not under options.\n   * @param {string} datasetType\n   * @return {string[][]}\n   */\n  datasetScopeKeys(datasetType) {\n    return cachedKeys(datasetType,\n      () => [[\n        `datasets.${datasetType}`,\n        ''\n      ]]);\n  }\n\n  /**\n   * Returns the option scope keys for resolving dataset animation options.\n   * These keys do not include the dataset itself, because it is not under options.\n   * @param {string} datasetType\n   * @param {string} transition\n   * @return {string[][]}\n   */\n  datasetAnimationScopeKeys(datasetType, transition) {\n    return cachedKeys(`${datasetType}.transition.${transition}`,\n      () => [\n        [\n          `datasets.${datasetType}.transitions.${transition}`,\n          `transitions.${transition}`,\n        ],\n        // The following are used for looking up the `animations` and `animation` keys\n        [\n          `datasets.${datasetType}`,\n          ''\n        ]\n      ]);\n  }\n\n  /**\n   * Returns the options scope keys for resolving element options that belong\n   * to an dataset. These keys do not include the dataset itself, because it\n   * is not under options.\n   * @param {string} datasetType\n   * @param {string} elementType\n   * @return {string[][]}\n   */\n  datasetElementScopeKeys(datasetType, elementType) {\n    return cachedKeys(`${datasetType}-${elementType}`,\n      () => [[\n        `datasets.${datasetType}.elements.${elementType}`,\n        `datasets.${datasetType}`,\n        `elements.${elementType}`,\n        ''\n      ]]);\n  }\n\n  /**\n   * Returns the options scope keys for resolving plugin options.\n   * @param {{id: string, additionalOptionScopes?: string[]}} plugin\n   * @return {string[][]}\n   */\n  pluginScopeKeys(plugin) {\n    const id = plugin.id;\n    const type = this.type;\n    return cachedKeys(`${type}-plugin-${id}`,\n      () => [[\n        `plugins.${id}`,\n        ...plugin.additionalOptionScopes || [],\n      ]]);\n  }\n\n  /**\n   * @private\n   */\n  _cachedScopes(mainScope, resetCache) {\n    const _scopeCache = this._scopeCache;\n    let cache = _scopeCache.get(mainScope);\n    if (!cache || resetCache) {\n      cache = new Map();\n      _scopeCache.set(mainScope, cache);\n    }\n    return cache;\n  }\n\n  /**\n   * Resolves the objects from options and defaults for option value resolution.\n   * @param {object} mainScope - The main scope object for options\n   * @param {string[][]} keyLists - The arrays of keys in resolution order\n   * @param {boolean} [resetCache] - reset the cache for this mainScope\n   */\n  getOptionScopes(mainScope, keyLists, resetCache) {\n    const {options, type} = this;\n    const cache = this._cachedScopes(mainScope, resetCache);\n    const cached = cache.get(keyLists);\n    if (cached) {\n      return cached;\n    }\n\n    const scopes = new Set();\n\n    keyLists.forEach(keys => {\n      if (mainScope) {\n        scopes.add(mainScope);\n        keys.forEach(key => addIfFound(scopes, mainScope, key));\n      }\n      keys.forEach(key => addIfFound(scopes, options, key));\n      keys.forEach(key => addIfFound(scopes, overrides[type] || {}, key));\n      keys.forEach(key => addIfFound(scopes, defaults, key));\n      keys.forEach(key => addIfFound(scopes, descriptors, key));\n    });\n\n    const array = Array.from(scopes);\n    if (array.length === 0) {\n      array.push(Object.create(null));\n    }\n    if (keysCached.has(keyLists)) {\n      cache.set(keyLists, array);\n    }\n    return array;\n  }\n\n  /**\n   * Returns the option scopes for resolving chart options\n   * @return {object[]}\n   */\n  chartOptionScopes() {\n    const {options, type} = this;\n\n    return [\n      options,\n      overrides[type] || {},\n      defaults.datasets[type] || {}, // https://github.com/chartjs/Chart.js/issues/8531\n      {type},\n      defaults,\n      descriptors\n    ];\n  }\n\n  /**\n   * @param {object[]} scopes\n   * @param {string[]} names\n   * @param {function|object} context\n   * @param {string[]} [prefixes]\n   * @return {object}\n   */\n  resolveNamedOptions(scopes, names, context, prefixes = ['']) {\n    const result = {$shared: true};\n    const {resolver, subPrefixes} = getResolver(this._resolverCache, scopes, prefixes);\n    let options = resolver;\n    if (needContext(resolver, names)) {\n      result.$shared = false;\n      context = isFunction(context) ? context() : context;\n      // subResolver is passed to scriptable options. It should not resolve to hover options.\n      const subResolver = this.createResolver(scopes, context, subPrefixes);\n      options = _attachContext(resolver, context, subResolver);\n    }\n\n    for (const prop of names) {\n      result[prop] = options[prop];\n    }\n    return result;\n  }\n\n  /**\n   * @param {object[]} scopes\n   * @param {object} [context]\n   * @param {string[]} [prefixes]\n   * @param {{scriptable: boolean, indexable: boolean, allKeys?: boolean}} [descriptorDefaults]\n   */\n  createResolver(scopes, context, prefixes = [''], descriptorDefaults) {\n    const {resolver} = getResolver(this._resolverCache, scopes, prefixes);\n    return isObject(context)\n      ? _attachContext(resolver, context, undefined, descriptorDefaults)\n      : resolver;\n  }\n}\n\nfunction getResolver(resolverCache, scopes, prefixes) {\n  let cache = resolverCache.get(scopes);\n  if (!cache) {\n    cache = new Map();\n    resolverCache.set(scopes, cache);\n  }\n  const cacheKey = prefixes.join();\n  let cached = cache.get(cacheKey);\n  if (!cached) {\n    const resolver = _createResolver(scopes, prefixes);\n    cached = {\n      resolver,\n      subPrefixes: prefixes.filter(p => !p.toLowerCase().includes('hover'))\n    };\n    cache.set(cacheKey, cached);\n  }\n  return cached;\n}\n\nconst hasFunction = value => isObject(value)\n  && Object.getOwnPropertyNames(value).some((key) => isFunction(value[key]));\n\nfunction needContext(proxy, names) {\n  const {isScriptable, isIndexable} = _descriptors(proxy);\n\n  for (const prop of names) {\n    const scriptable = isScriptable(prop);\n    const indexable = isIndexable(prop);\n    const value = (indexable || scriptable) && proxy[prop];\n    if ((scriptable && (isFunction(value) || hasFunction(value)))\n      || (indexable && isArray(value))) {\n      return true;\n    }\n  }\n  return false;\n}\n"
  },
  {
    "path": "src/core/core.controller.js",
    "content": "import animator from './core.animator.js';\nimport defaults, {overrides} from './core.defaults.js';\nimport Interaction from './core.interaction.js';\nimport layouts from './core.layouts.js';\nimport {_detectPlatform} from '../platform/index.js';\nimport PluginService from './core.plugins.js';\nimport registry from './core.registry.js';\nimport Config, {determineAxis, getIndexAxis} from './core.config.js';\nimport {each, callback as callCallback, uid, valueOrDefault, _elementsEqual, isNullOrUndef, setsEqual, defined, isFunction, _isClickEvent} from '../helpers/helpers.core.js';\nimport {clearCanvas, clipArea, createContext, unclipArea, _isPointInArea, _isDomSupported, retinaScale, getDatasetClipArea} from '../helpers/index.js';\n// @ts-ignore\nimport {version} from '../../package.json';\nimport {debounce} from '../helpers/helpers.extras.js';\n\n/**\n * @typedef { import('../types/index.js').ChartEvent } ChartEvent\n * @typedef { import('../types/index.js').Point } Point\n */\n\nconst KNOWN_POSITIONS = ['top', 'bottom', 'left', 'right', 'chartArea'];\nfunction positionIsHorizontal(position, axis) {\n  return position === 'top' || position === 'bottom' || (KNOWN_POSITIONS.indexOf(position) === -1 && axis === 'x');\n}\n\nfunction compare2Level(l1, l2) {\n  return function(a, b) {\n    return a[l1] === b[l1]\n      ? a[l2] - b[l2]\n      : a[l1] - b[l1];\n  };\n}\n\nfunction onAnimationsComplete(context) {\n  const chart = context.chart;\n  const animationOptions = chart.options.animation;\n\n  chart.notifyPlugins('afterRender');\n  callCallback(animationOptions && animationOptions.onComplete, [context], chart);\n}\n\nfunction onAnimationProgress(context) {\n  const chart = context.chart;\n  const animationOptions = chart.options.animation;\n  callCallback(animationOptions && animationOptions.onProgress, [context], chart);\n}\n\n/**\n * Chart.js can take a string id of a canvas element, a 2d context, or a canvas element itself.\n * Attempt to unwrap the item passed into the chart constructor so that it is a canvas element (if possible).\n */\nfunction getCanvas(item) {\n  if (_isDomSupported() && typeof item === 'string') {\n    item = document.getElementById(item);\n  } else if (item && item.length) {\n    // Support for array based queries (such as jQuery)\n    item = item[0];\n  }\n\n  if (item && item.canvas) {\n    // Support for any object associated to a canvas (including a context2d)\n    item = item.canvas;\n  }\n  return item;\n}\n\nconst instances = {};\nconst getChart = (key) => {\n  const canvas = getCanvas(key);\n  return Object.values(instances).filter((c) => c.canvas === canvas).pop();\n};\n\nfunction moveNumericKeys(obj, start, move) {\n  const keys = Object.keys(obj);\n  for (const key of keys) {\n    const intKey = +key;\n    if (intKey >= start) {\n      const value = obj[key];\n      delete obj[key];\n      if (move > 0 || intKey > start) {\n        obj[intKey + move] = value;\n      }\n    }\n  }\n}\n\n/**\n * @param {ChartEvent} e\n * @param {ChartEvent|null} lastEvent\n * @param {boolean} inChartArea\n * @param {boolean} isClick\n * @returns {ChartEvent|null}\n */\nfunction determineLastEvent(e, lastEvent, inChartArea, isClick) {\n  if (!inChartArea || e.type === 'mouseout') {\n    return null;\n  }\n  if (isClick) {\n    return lastEvent;\n  }\n  return e;\n}\n\nclass Chart {\n\n  static defaults = defaults;\n  static instances = instances;\n  static overrides = overrides;\n  static registry = registry;\n  static version = version;\n  static getChart = getChart;\n\n  static register(...items) {\n    registry.add(...items);\n    invalidatePlugins();\n  }\n\n  static unregister(...items) {\n    registry.remove(...items);\n    invalidatePlugins();\n  }\n\n  // eslint-disable-next-line max-statements\n  constructor(item, userConfig) {\n    const config = this.config = new Config(userConfig);\n    const initialCanvas = getCanvas(item);\n    const existingChart = getChart(initialCanvas);\n    if (existingChart) {\n      throw new Error(\n        'Canvas is already in use. Chart with ID \\'' + existingChart.id + '\\'' +\n\t\t\t\t' must be destroyed before the canvas with ID \\'' + existingChart.canvas.id + '\\' can be reused.'\n      );\n    }\n\n    const options = config.createResolver(config.chartOptionScopes(), this.getContext());\n\n    this.platform = new (config.platform || _detectPlatform(initialCanvas))();\n    this.platform.updateConfig(config);\n\n    const context = this.platform.acquireContext(initialCanvas, options.aspectRatio);\n    const canvas = context && context.canvas;\n    const height = canvas && canvas.height;\n    const width = canvas && canvas.width;\n\n    this.id = uid();\n    this.ctx = context;\n    this.canvas = canvas;\n    this.width = width;\n    this.height = height;\n    this._options = options;\n    // Store the previously used aspect ratio to determine if a resize\n    // is needed during updates. Do this after _options is set since\n    // aspectRatio uses a getter\n    this._aspectRatio = this.aspectRatio;\n    this._layers = [];\n    this._metasets = [];\n    this._stacks = undefined;\n    this.boxes = [];\n    this.currentDevicePixelRatio = undefined;\n    this.chartArea = undefined;\n    this._active = [];\n    this._lastEvent = undefined;\n    this._listeners = {};\n    /** @type {?{attach?: function, detach?: function, resize?: function}} */\n    this._responsiveListeners = undefined;\n    this._sortedMetasets = [];\n    this.scales = {};\n    this._plugins = new PluginService();\n    this.$proxies = {};\n    this._hiddenIndices = {};\n    this.attached = false;\n    this._animationsDisabled = undefined;\n    this.$context = undefined;\n    this._doResize = debounce(mode => this.update(mode), options.resizeDelay || 0);\n    this._dataChanges = [];\n\n    // Add the chart instance to the global namespace\n    instances[this.id] = this;\n\n    if (!context || !canvas) {\n      // The given item is not a compatible context2d element, let's return before finalizing\n      // the chart initialization but after setting basic chart / controller properties that\n      // can help to figure out that the chart is not valid (e.g chart.canvas !== null);\n      // https://github.com/chartjs/Chart.js/issues/2807\n      console.error(\"Failed to create chart: can't acquire context from the given item\");\n      return;\n    }\n\n    animator.listen(this, 'complete', onAnimationsComplete);\n    animator.listen(this, 'progress', onAnimationProgress);\n\n    this._initialize();\n    if (this.attached) {\n      this.update();\n    }\n  }\n\n  get aspectRatio() {\n    const {options: {aspectRatio, maintainAspectRatio}, width, height, _aspectRatio} = this;\n    if (!isNullOrUndef(aspectRatio)) {\n      // If aspectRatio is defined in options, use that.\n      return aspectRatio;\n    }\n\n    if (maintainAspectRatio && _aspectRatio) {\n      // If maintainAspectRatio is truthly and we had previously determined _aspectRatio, use that\n      return _aspectRatio;\n    }\n\n    // Calculate\n    return height ? width / height : null;\n  }\n\n  get data() {\n    return this.config.data;\n  }\n\n  set data(data) {\n    this.config.data = data;\n  }\n\n  get options() {\n    return this._options;\n  }\n\n  set options(options) {\n    this.config.options = options;\n  }\n\n  get registry() {\n    return registry;\n  }\n\n  /**\n\t * @private\n\t */\n  _initialize() {\n    // Before init plugin notification\n    this.notifyPlugins('beforeInit');\n\n    if (this.options.responsive) {\n      this.resize();\n    } else {\n      retinaScale(this, this.options.devicePixelRatio);\n    }\n\n    this.bindEvents();\n\n    // After init plugin notification\n    this.notifyPlugins('afterInit');\n\n    return this;\n  }\n\n  clear() {\n    clearCanvas(this.canvas, this.ctx);\n    return this;\n  }\n\n  stop() {\n    animator.stop(this);\n    return this;\n  }\n\n  /**\n\t * Resize the chart to its container or to explicit dimensions.\n\t * @param {number} [width]\n\t * @param {number} [height]\n\t */\n  resize(width, height) {\n    if (!animator.running(this)) {\n      this._resize(width, height);\n    } else {\n      this._resizeBeforeDraw = {width, height};\n    }\n  }\n\n  _resize(width, height) {\n    const options = this.options;\n    const canvas = this.canvas;\n    const aspectRatio = options.maintainAspectRatio && this.aspectRatio;\n    const newSize = this.platform.getMaximumSize(canvas, width, height, aspectRatio);\n    const newRatio = options.devicePixelRatio || this.platform.getDevicePixelRatio();\n    const mode = this.width ? 'resize' : 'attach';\n\n    this.width = newSize.width;\n    this.height = newSize.height;\n    this._aspectRatio = this.aspectRatio;\n    if (!retinaScale(this, newRatio, true)) {\n      return;\n    }\n\n    this.notifyPlugins('resize', {size: newSize});\n\n    callCallback(options.onResize, [this, newSize], this);\n\n    if (this.attached) {\n      if (this._doResize(mode)) {\n        // The resize update is delayed, only draw without updating.\n        this.render();\n      }\n    }\n  }\n\n  ensureScalesHaveIDs() {\n    const options = this.options;\n    const scalesOptions = options.scales || {};\n\n    each(scalesOptions, (axisOptions, axisID) => {\n      axisOptions.id = axisID;\n    });\n  }\n\n  /**\n\t * Builds a map of scale ID to scale object for future lookup.\n\t */\n  buildOrUpdateScales() {\n    const options = this.options;\n    const scaleOpts = options.scales;\n    const scales = this.scales;\n    const updated = Object.keys(scales).reduce((obj, id) => {\n      obj[id] = false;\n      return obj;\n    }, {});\n    let items = [];\n\n    if (scaleOpts) {\n      items = items.concat(\n        Object.keys(scaleOpts).map((id) => {\n          const scaleOptions = scaleOpts[id];\n          const axis = determineAxis(id, scaleOptions);\n          const isRadial = axis === 'r';\n          const isHorizontal = axis === 'x';\n          return {\n            options: scaleOptions,\n            dposition: isRadial ? 'chartArea' : isHorizontal ? 'bottom' : 'left',\n            dtype: isRadial ? 'radialLinear' : isHorizontal ? 'category' : 'linear'\n          };\n        })\n      );\n    }\n\n    each(items, (item) => {\n      const scaleOptions = item.options;\n      const id = scaleOptions.id;\n      const axis = determineAxis(id, scaleOptions);\n      const scaleType = valueOrDefault(scaleOptions.type, item.dtype);\n\n      if (scaleOptions.position === undefined || positionIsHorizontal(scaleOptions.position, axis) !== positionIsHorizontal(item.dposition)) {\n        scaleOptions.position = item.dposition;\n      }\n\n      updated[id] = true;\n      let scale = null;\n      if (id in scales && scales[id].type === scaleType) {\n        scale = scales[id];\n      } else {\n        const scaleClass = registry.getScale(scaleType);\n        scale = new scaleClass({\n          id,\n          type: scaleType,\n          ctx: this.ctx,\n          chart: this\n        });\n        scales[scale.id] = scale;\n      }\n\n      scale.init(scaleOptions, options);\n    });\n    // clear up discarded scales\n    each(updated, (hasUpdated, id) => {\n      if (!hasUpdated) {\n        delete scales[id];\n      }\n    });\n\n    each(scales, (scale) => {\n      layouts.configure(this, scale, scale.options);\n      layouts.addBox(this, scale);\n    });\n  }\n\n  /**\n\t * @private\n\t */\n  _updateMetasets() {\n    const metasets = this._metasets;\n    const numData = this.data.datasets.length;\n    const numMeta = metasets.length;\n\n    metasets.sort((a, b) => a.index - b.index);\n    if (numMeta > numData) {\n      for (let i = numData; i < numMeta; ++i) {\n        this._destroyDatasetMeta(i);\n      }\n      metasets.splice(numData, numMeta - numData);\n    }\n    this._sortedMetasets = metasets.slice(0).sort(compare2Level('order', 'index'));\n  }\n\n  /**\n\t * @private\n\t */\n  _removeUnreferencedMetasets() {\n    const {_metasets: metasets, data: {datasets}} = this;\n    if (metasets.length > datasets.length) {\n      delete this._stacks;\n    }\n    metasets.forEach((meta, index) => {\n      if (datasets.filter(x => x === meta._dataset).length === 0) {\n        this._destroyDatasetMeta(index);\n      }\n    });\n  }\n\n  buildOrUpdateControllers() {\n    const newControllers = [];\n    const datasets = this.data.datasets;\n    let i, ilen;\n\n    this._removeUnreferencedMetasets();\n\n    for (i = 0, ilen = datasets.length; i < ilen; i++) {\n      const dataset = datasets[i];\n      let meta = this.getDatasetMeta(i);\n      const type = dataset.type || this.config.type;\n\n      if (meta.type && meta.type !== type) {\n        this._destroyDatasetMeta(i);\n        meta = this.getDatasetMeta(i);\n      }\n      meta.type = type;\n      meta.indexAxis = dataset.indexAxis || getIndexAxis(type, this.options);\n      meta.order = dataset.order || 0;\n      meta.index = i;\n      meta.label = '' + dataset.label;\n      meta.visible = this.isDatasetVisible(i);\n\n      if (meta.controller) {\n        meta.controller.updateIndex(i);\n        meta.controller.linkScales();\n      } else {\n        const ControllerClass = registry.getController(type);\n        const {datasetElementType, dataElementType} = defaults.datasets[type];\n        Object.assign(ControllerClass, {\n          dataElementType: registry.getElement(dataElementType),\n          datasetElementType: datasetElementType && registry.getElement(datasetElementType)\n        });\n        meta.controller = new ControllerClass(this, i);\n        newControllers.push(meta.controller);\n      }\n    }\n\n    this._updateMetasets();\n    return newControllers;\n  }\n\n  /**\n\t * Reset the elements of all datasets\n\t * @private\n\t */\n  _resetElements() {\n    each(this.data.datasets, (dataset, datasetIndex) => {\n      this.getDatasetMeta(datasetIndex).controller.reset();\n    }, this);\n  }\n\n  /**\n\t* Resets the chart back to its state before the initial animation\n\t*/\n  reset() {\n    this._resetElements();\n    this.notifyPlugins('reset');\n  }\n\n  update(mode) {\n    const config = this.config;\n\n    config.update();\n    const options = this._options = config.createResolver(config.chartOptionScopes(), this.getContext());\n    const animsDisabled = this._animationsDisabled = !options.animation;\n\n    this._updateScales();\n    this._checkEventBindings();\n    this._updateHiddenIndices();\n\n    // plugins options references might have change, let's invalidate the cache\n    // https://github.com/chartjs/Chart.js/issues/5111#issuecomment-355934167\n    this._plugins.invalidate();\n\n    if (this.notifyPlugins('beforeUpdate', {mode, cancelable: true}) === false) {\n      return;\n    }\n\n    // Make sure dataset controllers are updated and new controllers are reset\n    const newControllers = this.buildOrUpdateControllers();\n\n    this.notifyPlugins('beforeElementsUpdate');\n\n    // Make sure all dataset controllers have correct meta data counts\n    let minPadding = 0;\n    for (let i = 0, ilen = this.data.datasets.length; i < ilen; i++) {\n      const {controller} = this.getDatasetMeta(i);\n      const reset = !animsDisabled && newControllers.indexOf(controller) === -1;\n      // New controllers will be reset after the layout pass, so we only want to modify\n      // elements added to new datasets\n      controller.buildOrUpdateElements(reset);\n      minPadding = Math.max(+controller.getMaxOverflow(), minPadding);\n    }\n    minPadding = this._minPadding = options.layout.autoPadding ? minPadding : 0;\n    this._updateLayout(minPadding);\n\n    // Only reset the controllers if we have animations\n    if (!animsDisabled) {\n      // Can only reset the new controllers after the scales have been updated\n      // Reset is done to get the starting point for the initial animation\n      each(newControllers, (controller) => {\n        controller.reset();\n      });\n    }\n\n    this._updateDatasets(mode);\n\n    // Do this before render so that any plugins that need final scale updates can use it\n    this.notifyPlugins('afterUpdate', {mode});\n\n    this._layers.sort(compare2Level('z', '_idx'));\n\n    // Replay last event from before update, or set hover styles on active elements\n    const {_active, _lastEvent} = this;\n    if (_lastEvent) {\n      this._eventHandler(_lastEvent, true);\n    } else if (_active.length) {\n      this._updateHoverStyles(_active, _active, true);\n    }\n\n    this.render();\n  }\n\n  /**\n   * @private\n   */\n  _updateScales() {\n    each(this.scales, (scale) => {\n      layouts.removeBox(this, scale);\n    });\n\n    this.ensureScalesHaveIDs();\n    this.buildOrUpdateScales();\n  }\n\n  /**\n   * @private\n   */\n  _checkEventBindings() {\n    const options = this.options;\n    const existingEvents = new Set(Object.keys(this._listeners));\n    const newEvents = new Set(options.events);\n\n    if (!setsEqual(existingEvents, newEvents) || !!this._responsiveListeners !== options.responsive) {\n      // The configured events have changed. Rebind.\n      this.unbindEvents();\n      this.bindEvents();\n    }\n  }\n\n  /**\n   * @private\n   */\n  _updateHiddenIndices() {\n    const {_hiddenIndices} = this;\n    const changes = this._getUniformDataChanges() || [];\n    for (const {method, start, count} of changes) {\n      const move = method === '_removeElements' ? -count : count;\n      moveNumericKeys(_hiddenIndices, start, move);\n    }\n  }\n\n  /**\n   * @private\n   */\n  _getUniformDataChanges() {\n    const _dataChanges = this._dataChanges;\n    if (!_dataChanges || !_dataChanges.length) {\n      return;\n    }\n\n    this._dataChanges = [];\n    const datasetCount = this.data.datasets.length;\n    const makeSet = (idx) => new Set(\n      _dataChanges\n        .filter(c => c[0] === idx)\n        .map((c, i) => i + ',' + c.splice(1).join(','))\n    );\n\n    const changeSet = makeSet(0);\n    for (let i = 1; i < datasetCount; i++) {\n      if (!setsEqual(changeSet, makeSet(i))) {\n        return;\n      }\n    }\n    return Array.from(changeSet)\n      .map(c => c.split(','))\n      .map(a => ({method: a[1], start: +a[2], count: +a[3]}));\n  }\n\n  /**\n\t * Updates the chart layout unless a plugin returns `false` to the `beforeLayout`\n\t * hook, in which case, plugins will not be called on `afterLayout`.\n\t * @private\n\t */\n  _updateLayout(minPadding) {\n    if (this.notifyPlugins('beforeLayout', {cancelable: true}) === false) {\n      return;\n    }\n\n    layouts.update(this, this.width, this.height, minPadding);\n\n    const area = this.chartArea;\n    const noArea = area.width <= 0 || area.height <= 0;\n\n    this._layers = [];\n    each(this.boxes, (box) => {\n      if (noArea && box.position === 'chartArea') {\n        // Skip drawing and configuring chartArea boxes when chartArea is zero or negative\n        return;\n      }\n\n      // configure is called twice, once in core.scale.update and once here.\n      // Here the boxes are fully updated and at their final positions.\n      if (box.configure) {\n        box.configure();\n      }\n      this._layers.push(...box._layers());\n    }, this);\n\n    this._layers.forEach((item, index) => {\n      item._idx = index;\n    });\n\n    this.notifyPlugins('afterLayout');\n  }\n\n  /**\n\t * Updates all datasets unless a plugin returns `false` to the `beforeDatasetsUpdate`\n\t * hook, in which case, plugins will not be called on `afterDatasetsUpdate`.\n\t * @private\n\t */\n  _updateDatasets(mode) {\n    if (this.notifyPlugins('beforeDatasetsUpdate', {mode, cancelable: true}) === false) {\n      return;\n    }\n\n    for (let i = 0, ilen = this.data.datasets.length; i < ilen; ++i) {\n      this.getDatasetMeta(i).controller.configure();\n    }\n\n    for (let i = 0, ilen = this.data.datasets.length; i < ilen; ++i) {\n      this._updateDataset(i, isFunction(mode) ? mode({datasetIndex: i}) : mode);\n    }\n\n    this.notifyPlugins('afterDatasetsUpdate', {mode});\n  }\n\n  /**\n\t * Updates dataset at index unless a plugin returns `false` to the `beforeDatasetUpdate`\n\t * hook, in which case, plugins will not be called on `afterDatasetUpdate`.\n\t * @private\n\t */\n  _updateDataset(index, mode) {\n    const meta = this.getDatasetMeta(index);\n    const args = {meta, index, mode, cancelable: true};\n\n    if (this.notifyPlugins('beforeDatasetUpdate', args) === false) {\n      return;\n    }\n\n    meta.controller._update(mode);\n\n    args.cancelable = false;\n    this.notifyPlugins('afterDatasetUpdate', args);\n  }\n\n  render() {\n    if (this.notifyPlugins('beforeRender', {cancelable: true}) === false) {\n      return;\n    }\n\n    if (animator.has(this)) {\n      if (this.attached && !animator.running(this)) {\n        animator.start(this);\n      }\n    } else {\n      this.draw();\n      onAnimationsComplete({chart: this});\n    }\n  }\n\n  draw() {\n    let i;\n    if (this._resizeBeforeDraw) {\n      const {width, height} = this._resizeBeforeDraw;\n      // Unset pending resize request now to avoid possible recursion within _resize\n      this._resizeBeforeDraw = null;\n      this._resize(width, height);\n    }\n    this.clear();\n\n    if (this.width <= 0 || this.height <= 0) {\n      return;\n    }\n\n    if (this.notifyPlugins('beforeDraw', {cancelable: true}) === false) {\n      return;\n    }\n\n    // Because of plugin hooks (before/afterDatasetsDraw), datasets can't\n    // currently be part of layers. Instead, we draw\n    // layers <= 0 before(default, backward compat), and the rest after\n    const layers = this._layers;\n    for (i = 0; i < layers.length && layers[i].z <= 0; ++i) {\n      layers[i].draw(this.chartArea);\n    }\n\n    this._drawDatasets();\n\n    // Rest of layers\n    for (; i < layers.length; ++i) {\n      layers[i].draw(this.chartArea);\n    }\n\n    this.notifyPlugins('afterDraw');\n  }\n\n  /**\n\t * @private\n\t */\n  _getSortedDatasetMetas(filterVisible) {\n    const metasets = this._sortedMetasets;\n    const result = [];\n    let i, ilen;\n\n    for (i = 0, ilen = metasets.length; i < ilen; ++i) {\n      const meta = metasets[i];\n      if (!filterVisible || meta.visible) {\n        result.push(meta);\n      }\n    }\n\n    return result;\n  }\n\n  /**\n\t * Gets the visible dataset metas in drawing order\n\t * @return {object[]}\n\t */\n  getSortedVisibleDatasetMetas() {\n    return this._getSortedDatasetMetas(true);\n  }\n\n  /**\n\t * Draws all datasets unless a plugin returns `false` to the `beforeDatasetsDraw`\n\t * hook, in which case, plugins will not be called on `afterDatasetsDraw`.\n\t * @private\n\t */\n  _drawDatasets() {\n    if (this.notifyPlugins('beforeDatasetsDraw', {cancelable: true}) === false) {\n      return;\n    }\n\n    const metasets = this.getSortedVisibleDatasetMetas();\n    for (let i = metasets.length - 1; i >= 0; --i) {\n      this._drawDataset(metasets[i]);\n    }\n\n    this.notifyPlugins('afterDatasetsDraw');\n  }\n\n  /**\n\t * Draws dataset at index unless a plugin returns `false` to the `beforeDatasetDraw`\n\t * hook, in which case, plugins will not be called on `afterDatasetDraw`.\n\t * @private\n\t */\n  _drawDataset(meta) {\n    const ctx = this.ctx;\n    const args = {\n      meta,\n      index: meta.index,\n      cancelable: true\n    };\n    // @ts-expect-error\n    const clip = getDatasetClipArea(this, meta);\n\n    if (this.notifyPlugins('beforeDatasetDraw', args) === false) {\n      return;\n    }\n\n    if (clip) {\n      clipArea(ctx, clip);\n    }\n\n    meta.controller.draw();\n\n    if (clip) {\n      unclipArea(ctx);\n    }\n\n    args.cancelable = false;\n    this.notifyPlugins('afterDatasetDraw', args);\n  }\n\n  /**\n   * Checks whether the given point is in the chart area.\n   * @param {Point} point - in relative coordinates (see, e.g., getRelativePosition)\n   * @returns {boolean}\n   */\n  isPointInArea(point) {\n    return _isPointInArea(point, this.chartArea, this._minPadding);\n  }\n\n  getElementsAtEventForMode(e, mode, options, useFinalPosition) {\n    const method = Interaction.modes[mode];\n    if (typeof method === 'function') {\n      return method(this, e, options, useFinalPosition);\n    }\n\n    return [];\n  }\n\n  getDatasetMeta(datasetIndex) {\n    const dataset = this.data.datasets[datasetIndex];\n    const metasets = this._metasets;\n    let meta = metasets.filter(x => x && x._dataset === dataset).pop();\n\n    if (!meta) {\n      meta = {\n        type: null,\n        data: [],\n        dataset: null,\n        controller: null,\n        hidden: null,\t\t\t// See isDatasetVisible() comment\n        xAxisID: null,\n        yAxisID: null,\n        order: dataset && dataset.order || 0,\n        index: datasetIndex,\n        _dataset: dataset,\n        _parsed: [],\n        _sorted: false\n      };\n      metasets.push(meta);\n    }\n\n    return meta;\n  }\n\n  getContext() {\n    return this.$context || (this.$context = createContext(null, {chart: this, type: 'chart'}));\n  }\n\n  getVisibleDatasetCount() {\n    return this.getSortedVisibleDatasetMetas().length;\n  }\n\n  isDatasetVisible(datasetIndex) {\n    const dataset = this.data.datasets[datasetIndex];\n    if (!dataset) {\n      return false;\n    }\n\n    const meta = this.getDatasetMeta(datasetIndex);\n\n    // meta.hidden is a per chart dataset hidden flag override with 3 states: if true or false,\n    // the dataset.hidden value is ignored, else if null, the dataset hidden state is returned.\n    return typeof meta.hidden === 'boolean' ? !meta.hidden : !dataset.hidden;\n  }\n\n  setDatasetVisibility(datasetIndex, visible) {\n    const meta = this.getDatasetMeta(datasetIndex);\n    meta.hidden = !visible;\n  }\n\n  toggleDataVisibility(index) {\n    this._hiddenIndices[index] = !this._hiddenIndices[index];\n  }\n\n  getDataVisibility(index) {\n    return !this._hiddenIndices[index];\n  }\n\n  /**\n\t * @private\n\t */\n  _updateVisibility(datasetIndex, dataIndex, visible) {\n    const mode = visible ? 'show' : 'hide';\n    const meta = this.getDatasetMeta(datasetIndex);\n    const anims = meta.controller._resolveAnimations(undefined, mode);\n\n    if (defined(dataIndex)) {\n      meta.data[dataIndex].hidden = !visible;\n      this.update();\n    } else {\n      this.setDatasetVisibility(datasetIndex, visible);\n      // Animate visible state, so hide animation can be seen. This could be handled better if update / updateDataset returned a Promise.\n      anims.update(meta, {visible});\n      this.update((ctx) => ctx.datasetIndex === datasetIndex ? mode : undefined);\n    }\n  }\n\n  hide(datasetIndex, dataIndex) {\n    this._updateVisibility(datasetIndex, dataIndex, false);\n  }\n\n  show(datasetIndex, dataIndex) {\n    this._updateVisibility(datasetIndex, dataIndex, true);\n  }\n\n  /**\n\t * @private\n\t */\n  _destroyDatasetMeta(datasetIndex) {\n    const meta = this._metasets[datasetIndex];\n    if (meta && meta.controller) {\n      meta.controller._destroy();\n    }\n    delete this._metasets[datasetIndex];\n  }\n\n  _stop() {\n    let i, ilen;\n    this.stop();\n    animator.remove(this);\n\n    for (i = 0, ilen = this.data.datasets.length; i < ilen; ++i) {\n      this._destroyDatasetMeta(i);\n    }\n  }\n\n  destroy() {\n    this.notifyPlugins('beforeDestroy');\n    const {canvas, ctx} = this;\n\n    this._stop();\n    this.config.clearCache();\n\n    if (canvas) {\n      this.unbindEvents();\n      clearCanvas(canvas, ctx);\n      this.platform.releaseContext(ctx);\n      this.canvas = null;\n      this.ctx = null;\n    }\n\n    delete instances[this.id];\n\n    this.notifyPlugins('afterDestroy');\n  }\n\n  toBase64Image(...args) {\n    return this.canvas.toDataURL(...args);\n  }\n\n  /**\n\t * @private\n\t */\n  bindEvents() {\n    this.bindUserEvents();\n    if (this.options.responsive) {\n      this.bindResponsiveEvents();\n    } else {\n      this.attached = true;\n    }\n  }\n\n  /**\n   * @private\n   */\n  bindUserEvents() {\n    const listeners = this._listeners;\n    const platform = this.platform;\n\n    const _add = (type, listener) => {\n      platform.addEventListener(this, type, listener);\n      listeners[type] = listener;\n    };\n\n    const listener = (e, x, y) => {\n      e.offsetX = x;\n      e.offsetY = y;\n      this._eventHandler(e);\n    };\n\n    each(this.options.events, (type) => _add(type, listener));\n  }\n\n  /**\n   * @private\n   */\n  bindResponsiveEvents() {\n    if (!this._responsiveListeners) {\n      this._responsiveListeners = {};\n    }\n    const listeners = this._responsiveListeners;\n    const platform = this.platform;\n\n    const _add = (type, listener) => {\n      platform.addEventListener(this, type, listener);\n      listeners[type] = listener;\n    };\n    const _remove = (type, listener) => {\n      if (listeners[type]) {\n        platform.removeEventListener(this, type, listener);\n        delete listeners[type];\n      }\n    };\n\n    const listener = (width, height) => {\n      if (this.canvas) {\n        this.resize(width, height);\n      }\n    };\n\n    let detached; // eslint-disable-line prefer-const\n    const attached = () => {\n      _remove('attach', attached);\n\n      this.attached = true;\n      this.resize();\n\n      _add('resize', listener);\n      _add('detach', detached);\n    };\n\n    detached = () => {\n      this.attached = false;\n\n      _remove('resize', listener);\n\n      // Stop animating and remove metasets, so when re-attached, the animations start from beginning.\n      this._stop();\n      this._resize(0, 0);\n\n      _add('attach', attached);\n    };\n\n    if (platform.isAttached(this.canvas)) {\n      attached();\n    } else {\n      detached();\n    }\n  }\n\n  /**\n\t * @private\n\t */\n  unbindEvents() {\n    each(this._listeners, (listener, type) => {\n      this.platform.removeEventListener(this, type, listener);\n    });\n    this._listeners = {};\n\n    each(this._responsiveListeners, (listener, type) => {\n      this.platform.removeEventListener(this, type, listener);\n    });\n    this._responsiveListeners = undefined;\n  }\n\n  updateHoverStyle(items, mode, enabled) {\n    const prefix = enabled ? 'set' : 'remove';\n    let meta, item, i, ilen;\n\n    if (mode === 'dataset') {\n      meta = this.getDatasetMeta(items[0].datasetIndex);\n      meta.controller['_' + prefix + 'DatasetHoverStyle']();\n    }\n\n    for (i = 0, ilen = items.length; i < ilen; ++i) {\n      item = items[i];\n      const controller = item && this.getDatasetMeta(item.datasetIndex).controller;\n      if (controller) {\n        controller[prefix + 'HoverStyle'](item.element, item.datasetIndex, item.index);\n      }\n    }\n  }\n\n  /**\n\t * Get active (hovered) elements\n\t * @returns array\n\t */\n  getActiveElements() {\n    return this._active || [];\n  }\n\n  /**\n\t * Set active (hovered) elements\n\t * @param {array} activeElements New active data points\n\t */\n  setActiveElements(activeElements) {\n    const lastActive = this._active || [];\n    const active = activeElements.map(({datasetIndex, index}) => {\n      const meta = this.getDatasetMeta(datasetIndex);\n      if (!meta) {\n        throw new Error('No dataset found at index ' + datasetIndex);\n      }\n\n      return {\n        datasetIndex,\n        element: meta.data[index],\n        index,\n      };\n    });\n    const changed = !_elementsEqual(active, lastActive);\n\n    if (changed) {\n      this._active = active;\n      // Make sure we don't use the previous mouse event to override the active elements in update.\n      this._lastEvent = null;\n      this._updateHoverStyles(active, lastActive);\n    }\n  }\n\n  /**\n\t * Calls enabled plugins on the specified hook and with the given args.\n\t * This method immediately returns as soon as a plugin explicitly returns false. The\n\t * returned value can be used, for instance, to interrupt the current action.\n\t * @param {string} hook - The name of the plugin method to call (e.g. 'beforeUpdate').\n\t * @param {Object} [args] - Extra arguments to apply to the hook call.\n   * @param {import('./core.plugins.js').filterCallback} [filter] - Filtering function for limiting which plugins are notified\n\t * @returns {boolean} false if any of the plugins return false, else returns true.\n\t */\n  notifyPlugins(hook, args, filter) {\n    return this._plugins.notify(this, hook, args, filter);\n  }\n\n  /**\n   * Check if a plugin with the specific ID is registered and enabled\n   * @param {string} pluginId - The ID of the plugin of which to check if it is enabled\n   * @returns {boolean}\n   */\n  isPluginEnabled(pluginId) {\n    return this._plugins._cache.filter(p => p.plugin.id === pluginId).length === 1;\n  }\n\n  /**\n\t * @private\n\t */\n  _updateHoverStyles(active, lastActive, replay) {\n    const hoverOptions = this.options.hover;\n    const diff = (a, b) => a.filter(x => !b.some(y => x.datasetIndex === y.datasetIndex && x.index === y.index));\n    const deactivated = diff(lastActive, active);\n    const activated = replay ? active : diff(active, lastActive);\n\n    if (deactivated.length) {\n      this.updateHoverStyle(deactivated, hoverOptions.mode, false);\n    }\n\n    if (activated.length && hoverOptions.mode) {\n      this.updateHoverStyle(activated, hoverOptions.mode, true);\n    }\n  }\n\n  /**\n\t * @private\n\t */\n  _eventHandler(e, replay) {\n    const args = {\n      event: e,\n      replay,\n      cancelable: true,\n      inChartArea: this.isPointInArea(e)\n    };\n    const eventFilter = (plugin) => (plugin.options.events || this.options.events).includes(e.native.type);\n\n    if (this.notifyPlugins('beforeEvent', args, eventFilter) === false) {\n      return;\n    }\n\n    const changed = this._handleEvent(e, replay, args.inChartArea);\n\n    args.cancelable = false;\n    this.notifyPlugins('afterEvent', args, eventFilter);\n\n    if (changed || args.changed) {\n      this.render();\n    }\n\n    return this;\n  }\n\n  /**\n\t * Handle an event\n\t * @param {ChartEvent} e the event to handle\n\t * @param {boolean} [replay] - true if the event was replayed by `update`\n   * @param {boolean} [inChartArea] - true if the event is inside chartArea\n\t * @return {boolean} true if the chart needs to re-render\n\t * @private\n\t */\n  _handleEvent(e, replay, inChartArea) {\n    const {_active: lastActive = [], options} = this;\n\n    // If the event is replayed from `update`, we should evaluate with the final positions.\n    //\n    // The `replay`:\n    // It's the last event (excluding click) that has occurred before `update`.\n    // So mouse has not moved. It's also over the chart, because there is a `replay`.\n    //\n    // The why:\n    // If animations are active, the elements haven't moved yet compared to state before update.\n    // But if they will, we are activating the elements that would be active, if this check\n    // was done after the animations have completed. => \"final positions\".\n    // If there is no animations, the \"final\" and \"current\" positions are equal.\n    // This is done so we do not have to evaluate the active elements each animation frame\n    // - it would be expensive.\n    const useFinalPosition = replay;\n    const active = this._getActiveElements(e, lastActive, inChartArea, useFinalPosition);\n    const isClick = _isClickEvent(e);\n    const lastEvent = determineLastEvent(e, this._lastEvent, inChartArea, isClick);\n\n    if (inChartArea) {\n      // Set _lastEvent to null while we are processing the event handlers.\n      // This prevents recursion if the handler calls chart.update()\n      this._lastEvent = null;\n\n      // Invoke onHover hook\n      callCallback(options.onHover, [e, active, this], this);\n\n      if (isClick) {\n        callCallback(options.onClick, [e, active, this], this);\n      }\n    }\n\n    const changed = !_elementsEqual(active, lastActive);\n    if (changed || replay) {\n      this._active = active;\n      this._updateHoverStyles(active, lastActive, replay);\n    }\n\n    this._lastEvent = lastEvent;\n\n    return changed;\n  }\n\n  /**\n   * @param {ChartEvent} e - The event\n   * @param {import('../types/index.js').ActiveElement[]} lastActive - Previously active elements\n   * @param {boolean} inChartArea - Is the event inside chartArea\n   * @param {boolean} useFinalPosition - Should the evaluation be done with current or final (after animation) element positions\n   * @returns {import('../types/index.js').ActiveElement[]} - The active elements\n   * @pravate\n   */\n  _getActiveElements(e, lastActive, inChartArea, useFinalPosition) {\n    if (e.type === 'mouseout') {\n      return [];\n    }\n\n    if (!inChartArea) {\n      // Let user control the active elements outside chartArea. Eg. using Legend.\n      return lastActive;\n    }\n\n    const hoverOptions = this.options.hover;\n    return this.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions, useFinalPosition);\n  }\n}\n\n// @ts-ignore\nfunction invalidatePlugins() {\n  return each(Chart.instances, (chart) => chart._plugins.invalidate());\n}\n\nexport default Chart;\n"
  },
  {
    "path": "src/core/core.datasetController.js",
    "content": "import Animations from './core.animations.js';\nimport defaults from './core.defaults.js';\nimport {isArray, isFinite, isObject, valueOrDefault, resolveObjectKey, defined} from '../helpers/helpers.core.js';\nimport {listenArrayEvents, unlistenArrayEvents} from '../helpers/helpers.collection.js';\nimport {createContext, sign} from '../helpers/index.js';\n\n/**\n * @typedef { import('./core.controller.js').default } Chart\n * @typedef { import('./core.scale.js').default } Scale\n */\n\nfunction scaleClip(scale, allowedOverflow) {\n  const opts = scale && scale.options || {};\n  const reverse = opts.reverse;\n  const min = opts.min === undefined ? allowedOverflow : 0;\n  const max = opts.max === undefined ? allowedOverflow : 0;\n  return {\n    start: reverse ? max : min,\n    end: reverse ? min : max\n  };\n}\n\nfunction defaultClip(xScale, yScale, allowedOverflow) {\n  if (allowedOverflow === false) {\n    return false;\n  }\n  const x = scaleClip(xScale, allowedOverflow);\n  const y = scaleClip(yScale, allowedOverflow);\n\n  return {\n    top: y.end,\n    right: x.end,\n    bottom: y.start,\n    left: x.start\n  };\n}\n\nfunction toClip(value) {\n  let t, r, b, l;\n\n  if (isObject(value)) {\n    t = value.top;\n    r = value.right;\n    b = value.bottom;\n    l = value.left;\n  } else {\n    t = r = b = l = value;\n  }\n\n  return {\n    top: t,\n    right: r,\n    bottom: b,\n    left: l,\n    disabled: value === false\n  };\n}\n\nfunction getSortedDatasetIndices(chart, filterVisible) {\n  const keys = [];\n  const metasets = chart._getSortedDatasetMetas(filterVisible);\n  let i, ilen;\n\n  for (i = 0, ilen = metasets.length; i < ilen; ++i) {\n    keys.push(metasets[i].index);\n  }\n  return keys;\n}\n\nfunction applyStack(stack, value, dsIndex, options = {}) {\n  const keys = stack.keys;\n  const singleMode = options.mode === 'single';\n  let i, ilen, datasetIndex, otherValue;\n\n  if (value === null) {\n    return;\n  }\n\n  let found = false;\n  for (i = 0, ilen = keys.length; i < ilen; ++i) {\n    datasetIndex = +keys[i];\n    if (datasetIndex === dsIndex) {\n      found = true;\n      if (options.all) {\n        continue;\n      }\n      break;\n    }\n    otherValue = stack.values[datasetIndex];\n    if (isFinite(otherValue) && (singleMode || (value === 0 || sign(value) === sign(otherValue)))) {\n      value += otherValue;\n    }\n  }\n\n  if (!found && !options.all) {\n    return 0;\n  }\n\n  return value;\n}\n\nfunction convertObjectDataToArray(data, meta) {\n  const {iScale, vScale} = meta;\n  const iAxisKey = iScale.axis === 'x' ? 'x' : 'y';\n  const vAxisKey = vScale.axis === 'x' ? 'x' : 'y';\n  const keys = Object.keys(data);\n  const adata = new Array(keys.length);\n  let i, ilen, key;\n  for (i = 0, ilen = keys.length; i < ilen; ++i) {\n    key = keys[i];\n    adata[i] = {\n      [iAxisKey]: key,\n      [vAxisKey]: data[key]\n    };\n  }\n  return adata;\n}\n\nfunction isStacked(scale, meta) {\n  const stacked = scale && scale.options.stacked;\n  return stacked || (stacked === undefined && meta.stack !== undefined);\n}\n\nfunction getStackKey(indexScale, valueScale, meta) {\n  return `${indexScale.id}.${valueScale.id}.${meta.stack || meta.type}`;\n}\n\nfunction getUserBounds(scale) {\n  const {min, max, minDefined, maxDefined} = scale.getUserBounds();\n  return {\n    min: minDefined ? min : Number.NEGATIVE_INFINITY,\n    max: maxDefined ? max : Number.POSITIVE_INFINITY\n  };\n}\n\nfunction getOrCreateStack(stacks, stackKey, indexValue) {\n  const subStack = stacks[stackKey] || (stacks[stackKey] = {});\n  return subStack[indexValue] || (subStack[indexValue] = {});\n}\n\nfunction getLastIndexInStack(stack, vScale, positive, type) {\n  for (const meta of vScale.getMatchingVisibleMetas(type).reverse()) {\n    const value = stack[meta.index];\n    if ((positive && value > 0) || (!positive && value < 0)) {\n      return meta.index;\n    }\n  }\n\n  return null;\n}\n\nfunction updateStacks(controller, parsed) {\n  const {chart, _cachedMeta: meta} = controller;\n  const stacks = chart._stacks || (chart._stacks = {}); // map structure is {stackKey: {datasetIndex: value}}\n  const {iScale, vScale, index: datasetIndex} = meta;\n  const iAxis = iScale.axis;\n  const vAxis = vScale.axis;\n  const key = getStackKey(iScale, vScale, meta);\n  const ilen = parsed.length;\n  let stack;\n\n  for (let i = 0; i < ilen; ++i) {\n    const item = parsed[i];\n    const {[iAxis]: index, [vAxis]: value} = item;\n    const itemStacks = item._stacks || (item._stacks = {});\n    stack = itemStacks[vAxis] = getOrCreateStack(stacks, key, index);\n    stack[datasetIndex] = value;\n\n    stack._top = getLastIndexInStack(stack, vScale, true, meta.type);\n    stack._bottom = getLastIndexInStack(stack, vScale, false, meta.type);\n\n    const visualValues = stack._visualValues || (stack._visualValues = {});\n    visualValues[datasetIndex] = value;\n  }\n}\n\nfunction getFirstScaleId(chart, axis) {\n  const scales = chart.scales;\n  return Object.keys(scales).filter(key => scales[key].axis === axis).shift();\n}\n\nfunction createDatasetContext(parent, index) {\n  return createContext(parent,\n    {\n      active: false,\n      dataset: undefined,\n      datasetIndex: index,\n      index,\n      mode: 'default',\n      type: 'dataset'\n    }\n  );\n}\n\nfunction createDataContext(parent, index, element) {\n  return createContext(parent, {\n    active: false,\n    dataIndex: index,\n    parsed: undefined,\n    raw: undefined,\n    element,\n    index,\n    mode: 'default',\n    type: 'data'\n  });\n}\n\nfunction clearStacks(meta, items) {\n  // Not using meta.index here, because it might be already updated if the dataset changed location\n  const datasetIndex = meta.controller.index;\n  const axis = meta.vScale && meta.vScale.axis;\n  if (!axis) {\n    return;\n  }\n\n  items = items || meta._parsed;\n  for (const parsed of items) {\n    const stacks = parsed._stacks;\n    if (!stacks || stacks[axis] === undefined || stacks[axis][datasetIndex] === undefined) {\n      return;\n    }\n    delete stacks[axis][datasetIndex];\n    if (stacks[axis]._visualValues !== undefined && stacks[axis]._visualValues[datasetIndex] !== undefined) {\n      delete stacks[axis]._visualValues[datasetIndex];\n    }\n  }\n}\n\nconst isDirectUpdateMode = (mode) => mode === 'reset' || mode === 'none';\nconst cloneIfNotShared = (cached, shared) => shared ? cached : Object.assign({}, cached);\nconst createStack = (canStack, meta, chart) => canStack && !meta.hidden && meta._stacked\n  && {keys: getSortedDatasetIndices(chart, true), values: null};\n\nexport default class DatasetController {\n\n  /**\n   * @type {any}\n   */\n  static defaults = {};\n\n  /**\n   * Element type used to generate a meta dataset (e.g. Chart.element.LineElement).\n   */\n  static datasetElementType = null;\n\n  /**\n   * Element type used to generate a meta data (e.g. Chart.element.PointElement).\n   */\n  static dataElementType = null;\n\n  /**\n\t * @param {Chart} chart\n\t * @param {number} datasetIndex\n\t */\n  constructor(chart, datasetIndex) {\n    this.chart = chart;\n    this._ctx = chart.ctx;\n    this.index = datasetIndex;\n    this._cachedDataOpts = {};\n    this._cachedMeta = this.getMeta();\n    this._type = this._cachedMeta.type;\n    this.options = undefined;\n    /** @type {boolean | object} */\n    this._parsing = false;\n    this._data = undefined;\n    this._objectData = undefined;\n    this._sharedOptions = undefined;\n    this._drawStart = undefined;\n    this._drawCount = undefined;\n    this.enableOptionSharing = false;\n    this.supportsDecimation = false;\n    this.$context = undefined;\n    this._syncList = [];\n    this.datasetElementType = new.target.datasetElementType;\n    this.dataElementType = new.target.dataElementType;\n\n    this.initialize();\n  }\n\n  initialize() {\n    const meta = this._cachedMeta;\n    this.configure();\n    this.linkScales();\n    meta._stacked = isStacked(meta.vScale, meta);\n    this.addElements();\n\n    if (this.options.fill && !this.chart.isPluginEnabled('filler')) {\n      console.warn(\"Tried to use the 'fill' option without the 'Filler' plugin enabled. Please import and register the 'Filler' plugin and make sure it is not disabled in the options\");\n    }\n  }\n\n  updateIndex(datasetIndex) {\n    if (this.index !== datasetIndex) {\n      clearStacks(this._cachedMeta);\n    }\n    this.index = datasetIndex;\n  }\n\n  linkScales() {\n    const chart = this.chart;\n    const meta = this._cachedMeta;\n    const dataset = this.getDataset();\n\n    const chooseId = (axis, x, y, r) => axis === 'x' ? x : axis === 'r' ? r : y;\n\n    const xid = meta.xAxisID = valueOrDefault(dataset.xAxisID, getFirstScaleId(chart, 'x'));\n    const yid = meta.yAxisID = valueOrDefault(dataset.yAxisID, getFirstScaleId(chart, 'y'));\n    const rid = meta.rAxisID = valueOrDefault(dataset.rAxisID, getFirstScaleId(chart, 'r'));\n    const indexAxis = meta.indexAxis;\n    const iid = meta.iAxisID = chooseId(indexAxis, xid, yid, rid);\n    const vid = meta.vAxisID = chooseId(indexAxis, yid, xid, rid);\n    meta.xScale = this.getScaleForId(xid);\n    meta.yScale = this.getScaleForId(yid);\n    meta.rScale = this.getScaleForId(rid);\n    meta.iScale = this.getScaleForId(iid);\n    meta.vScale = this.getScaleForId(vid);\n  }\n\n  getDataset() {\n    return this.chart.data.datasets[this.index];\n  }\n\n  getMeta() {\n    return this.chart.getDatasetMeta(this.index);\n  }\n\n  /**\n\t * @param {string} scaleID\n\t * @return {Scale}\n\t */\n  getScaleForId(scaleID) {\n    return this.chart.scales[scaleID];\n  }\n\n  /**\n\t * @private\n\t */\n  _getOtherScale(scale) {\n    const meta = this._cachedMeta;\n    return scale === meta.iScale\n      ? meta.vScale\n      : meta.iScale;\n  }\n\n  reset() {\n    this._update('reset');\n  }\n\n  /**\n\t * @private\n\t */\n  _destroy() {\n    const meta = this._cachedMeta;\n    if (this._data) {\n      unlistenArrayEvents(this._data, this);\n    }\n    if (meta._stacked) {\n      clearStacks(meta);\n    }\n  }\n\n  /**\n\t * @private\n\t */\n  _dataCheck() {\n    const dataset = this.getDataset();\n    const data = dataset.data || (dataset.data = []);\n    const _data = this._data;\n\n    // In order to correctly handle data addition/deletion animation (and thus simulate\n    // real-time charts), we need to monitor these data modifications and synchronize\n    // the internal metadata accordingly.\n\n    if (isObject(data)) {\n      const meta = this._cachedMeta;\n      this._data = convertObjectDataToArray(data, meta);\n    } else if (_data !== data) {\n      if (_data) {\n        // This case happens when the user replaced the data array instance.\n        unlistenArrayEvents(_data, this);\n        // Discard old parsed data and stacks\n        const meta = this._cachedMeta;\n        clearStacks(meta);\n        meta._parsed = [];\n      }\n      if (data && Object.isExtensible(data)) {\n        listenArrayEvents(data, this);\n      }\n      this._syncList = [];\n      this._data = data;\n    }\n  }\n\n  addElements() {\n    const meta = this._cachedMeta;\n\n    this._dataCheck();\n\n    if (this.datasetElementType) {\n      meta.dataset = new this.datasetElementType();\n    }\n  }\n\n  buildOrUpdateElements(resetNewElements) {\n    const meta = this._cachedMeta;\n    const dataset = this.getDataset();\n    let stackChanged = false;\n\n    this._dataCheck();\n\n    // make sure cached _stacked status is current\n    const oldStacked = meta._stacked;\n    meta._stacked = isStacked(meta.vScale, meta);\n\n    // detect change in stack option\n    if (meta.stack !== dataset.stack) {\n      stackChanged = true;\n      // remove values from old stack\n      clearStacks(meta);\n      meta.stack = dataset.stack;\n    }\n\n    // Re-sync meta data in case the user replaced the data array or if we missed\n    // any updates and so make sure that we handle number of datapoints changing.\n    this._resyncElements(resetNewElements);\n\n    // if stack changed, update stack values for the whole dataset\n    if (stackChanged || oldStacked !== meta._stacked) {\n      updateStacks(this, meta._parsed);\n      meta._stacked = isStacked(meta.vScale, meta);\n    }\n  }\n\n  /**\n\t * Merges user-supplied and default dataset-level options\n\t * @private\n\t */\n  configure() {\n    const config = this.chart.config;\n    const scopeKeys = config.datasetScopeKeys(this._type);\n    const scopes = config.getOptionScopes(this.getDataset(), scopeKeys, true);\n    this.options = config.createResolver(scopes, this.getContext());\n    this._parsing = this.options.parsing;\n    this._cachedDataOpts = {};\n  }\n\n  /**\n\t * @param {number} start\n\t * @param {number} count\n\t */\n  parse(start, count) {\n    const {_cachedMeta: meta, _data: data} = this;\n    const {iScale, _stacked} = meta;\n    const iAxis = iScale.axis;\n\n    let sorted = start === 0 && count === data.length ? true : meta._sorted;\n    let prev = start > 0 && meta._parsed[start - 1];\n    let i, cur, parsed;\n\n    if (this._parsing === false) {\n      meta._parsed = data;\n      meta._sorted = true;\n      parsed = data;\n    } else {\n      if (isArray(data[start])) {\n        parsed = this.parseArrayData(meta, data, start, count);\n      } else if (isObject(data[start])) {\n        parsed = this.parseObjectData(meta, data, start, count);\n      } else {\n        parsed = this.parsePrimitiveData(meta, data, start, count);\n      }\n\n      const isNotInOrderComparedToPrev = () => cur[iAxis] === null || (prev && cur[iAxis] < prev[iAxis]);\n      for (i = 0; i < count; ++i) {\n        meta._parsed[i + start] = cur = parsed[i];\n        if (sorted) {\n          if (isNotInOrderComparedToPrev()) {\n            sorted = false;\n          }\n          prev = cur;\n        }\n      }\n      meta._sorted = sorted;\n    }\n\n    if (_stacked) {\n      updateStacks(this, parsed);\n    }\n  }\n\n  /**\n\t * Parse array of primitive values\n\t * @param {object} meta - dataset meta\n\t * @param {array} data - data array. Example [1,3,4]\n\t * @param {number} start - start index\n\t * @param {number} count - number of items to parse\n\t * @returns {object} parsed item - item containing index and a parsed value\n\t * for each scale id.\n\t * Example: {xScale0: 0, yScale0: 1}\n\t * @protected\n\t */\n  parsePrimitiveData(meta, data, start, count) {\n    const {iScale, vScale} = meta;\n    const iAxis = iScale.axis;\n    const vAxis = vScale.axis;\n    const labels = iScale.getLabels();\n    const singleScale = iScale === vScale;\n    const parsed = new Array(count);\n    let i, ilen, index;\n\n    for (i = 0, ilen = count; i < ilen; ++i) {\n      index = i + start;\n      parsed[i] = {\n        [iAxis]: singleScale || iScale.parse(labels[index], index),\n        [vAxis]: vScale.parse(data[index], index)\n      };\n    }\n    return parsed;\n  }\n\n  /**\n\t * Parse array of arrays\n\t * @param {object} meta - dataset meta\n\t * @param {array} data - data array. Example [[1,2],[3,4]]\n\t * @param {number} start - start index\n\t * @param {number} count - number of items to parse\n\t * @returns {object} parsed item - item containing index and a parsed value\n\t * for each scale id.\n\t * Example: {x: 0, y: 1}\n\t * @protected\n\t */\n  parseArrayData(meta, data, start, count) {\n    const {xScale, yScale} = meta;\n    const parsed = new Array(count);\n    let i, ilen, index, item;\n\n    for (i = 0, ilen = count; i < ilen; ++i) {\n      index = i + start;\n      item = data[index];\n      parsed[i] = {\n        x: xScale.parse(item[0], index),\n        y: yScale.parse(item[1], index)\n      };\n    }\n    return parsed;\n  }\n\n  /**\n\t * Parse array of objects\n\t * @param {object} meta - dataset meta\n\t * @param {array} data - data array. Example [{x:1, y:5}, {x:2, y:10}]\n\t * @param {number} start - start index\n\t * @param {number} count - number of items to parse\n\t * @returns {object} parsed item - item containing index and a parsed value\n\t * for each scale id. _custom is optional\n\t * Example: {xScale0: 0, yScale0: 1, _custom: {r: 10, foo: 'bar'}}\n\t * @protected\n\t */\n  parseObjectData(meta, data, start, count) {\n    const {xScale, yScale} = meta;\n    const {xAxisKey = 'x', yAxisKey = 'y'} = this._parsing;\n    const parsed = new Array(count);\n    let i, ilen, index, item;\n\n    for (i = 0, ilen = count; i < ilen; ++i) {\n      index = i + start;\n      item = data[index];\n      parsed[i] = {\n        x: xScale.parse(resolveObjectKey(item, xAxisKey), index),\n        y: yScale.parse(resolveObjectKey(item, yAxisKey), index)\n      };\n    }\n    return parsed;\n  }\n\n  /**\n\t * @protected\n\t */\n  getParsed(index) {\n    return this._cachedMeta._parsed[index];\n  }\n\n  /**\n\t * @protected\n\t */\n  getDataElement(index) {\n    return this._cachedMeta.data[index];\n  }\n\n  /**\n\t * @protected\n\t */\n  applyStack(scale, parsed, mode) {\n    const chart = this.chart;\n    const meta = this._cachedMeta;\n    const value = parsed[scale.axis];\n    const stack = {\n      keys: getSortedDatasetIndices(chart, true),\n      values: parsed._stacks[scale.axis]._visualValues\n    };\n    return applyStack(stack, value, meta.index, {mode});\n  }\n\n  /**\n\t * @protected\n\t */\n  updateRangeFromParsed(range, scale, parsed, stack) {\n    const parsedValue = parsed[scale.axis];\n    let value = parsedValue === null ? NaN : parsedValue;\n    const values = stack && parsed._stacks[scale.axis];\n    if (stack && values) {\n      stack.values = values;\n      value = applyStack(stack, parsedValue, this._cachedMeta.index);\n    }\n    range.min = Math.min(range.min, value);\n    range.max = Math.max(range.max, value);\n  }\n\n  /**\n\t * @protected\n\t */\n  getMinMax(scale, canStack) {\n    const meta = this._cachedMeta;\n    const _parsed = meta._parsed;\n    const sorted = meta._sorted && scale === meta.iScale;\n    const ilen = _parsed.length;\n    const otherScale = this._getOtherScale(scale);\n    const stack = createStack(canStack, meta, this.chart);\n    const range = {min: Number.POSITIVE_INFINITY, max: Number.NEGATIVE_INFINITY};\n    const {min: otherMin, max: otherMax} = getUserBounds(otherScale);\n    let i, parsed;\n\n    function _skip() {\n      parsed = _parsed[i];\n      const otherValue = parsed[otherScale.axis];\n      return !isFinite(parsed[scale.axis]) || otherMin > otherValue || otherMax < otherValue;\n    }\n\n    for (i = 0; i < ilen; ++i) {\n      if (_skip()) {\n        continue;\n      }\n      this.updateRangeFromParsed(range, scale, parsed, stack);\n      if (sorted) {\n        // if the data is sorted, we don't need to check further from this end of array\n        break;\n      }\n    }\n    if (sorted) {\n      // in the sorted case, find first non-skipped value from other end of array\n      for (i = ilen - 1; i >= 0; --i) {\n        if (_skip()) {\n          continue;\n        }\n        this.updateRangeFromParsed(range, scale, parsed, stack);\n        break;\n      }\n    }\n    return range;\n  }\n\n  getAllParsedValues(scale) {\n    const parsed = this._cachedMeta._parsed;\n    const values = [];\n    let i, ilen, value;\n\n    for (i = 0, ilen = parsed.length; i < ilen; ++i) {\n      value = parsed[i][scale.axis];\n      if (isFinite(value)) {\n        values.push(value);\n      }\n    }\n    return values;\n  }\n\n  /**\n\t * @return {number|boolean}\n\t * @protected\n\t */\n  getMaxOverflow() {\n    return false;\n  }\n\n  /**\n\t * @protected\n\t */\n  getLabelAndValue(index) {\n    const meta = this._cachedMeta;\n    const iScale = meta.iScale;\n    const vScale = meta.vScale;\n    const parsed = this.getParsed(index);\n    return {\n      label: iScale ? '' + iScale.getLabelForValue(parsed[iScale.axis]) : '',\n      value: vScale ? '' + vScale.getLabelForValue(parsed[vScale.axis]) : ''\n    };\n  }\n\n  /**\n\t * @private\n\t */\n  _update(mode) {\n    const meta = this._cachedMeta;\n    this.update(mode || 'default');\n    meta._clip = toClip(valueOrDefault(this.options.clip, defaultClip(meta.xScale, meta.yScale, this.getMaxOverflow())));\n  }\n\n  /**\n\t * @param {string} mode\n\t */\n  update(mode) {} // eslint-disable-line no-unused-vars\n\n  draw() {\n    const ctx = this._ctx;\n    const chart = this.chart;\n    const meta = this._cachedMeta;\n    const elements = meta.data || [];\n    const area = chart.chartArea;\n    const active = [];\n    const start = this._drawStart || 0;\n    const count = this._drawCount || (elements.length - start);\n    const drawActiveElementsOnTop = this.options.drawActiveElementsOnTop;\n    let i;\n\n    if (meta.dataset) {\n      meta.dataset.draw(ctx, area, start, count);\n    }\n\n    for (i = start; i < start + count; ++i) {\n      const element = elements[i];\n      if (element.hidden) {\n        continue;\n      }\n      if (element.active && drawActiveElementsOnTop) {\n        active.push(element);\n      } else {\n        element.draw(ctx, area);\n      }\n    }\n\n    for (i = 0; i < active.length; ++i) {\n      active[i].draw(ctx, area);\n    }\n  }\n\n  /**\n\t * Returns a set of predefined style properties that should be used to represent the dataset\n\t * or the data if the index is specified\n\t * @param {number} index - data index\n\t * @param {boolean} [active] - true if hover\n\t * @return {object} style object\n\t */\n  getStyle(index, active) {\n    const mode = active ? 'active' : 'default';\n    return index === undefined && this._cachedMeta.dataset\n      ? this.resolveDatasetElementOptions(mode)\n      : this.resolveDataElementOptions(index || 0, mode);\n  }\n\n  /**\n\t * @protected\n\t */\n  getContext(index, active, mode) {\n    const dataset = this.getDataset();\n    let context;\n    if (index >= 0 && index < this._cachedMeta.data.length) {\n      const element = this._cachedMeta.data[index];\n      context = element.$context ||\n        (element.$context = createDataContext(this.getContext(), index, element));\n      context.parsed = this.getParsed(index);\n      context.raw = dataset.data[index];\n      context.index = context.dataIndex = index;\n    } else {\n      context = this.$context ||\n        (this.$context = createDatasetContext(this.chart.getContext(), this.index));\n      context.dataset = dataset;\n      context.index = context.datasetIndex = this.index;\n    }\n\n    context.active = !!active;\n    context.mode = mode;\n    return context;\n  }\n\n  /**\n\t * @param {string} [mode]\n\t * @protected\n\t */\n  resolveDatasetElementOptions(mode) {\n    return this._resolveElementOptions(this.datasetElementType.id, mode);\n  }\n\n  /**\n\t * @param {number} index\n\t * @param {string} [mode]\n\t * @protected\n\t */\n  resolveDataElementOptions(index, mode) {\n    return this._resolveElementOptions(this.dataElementType.id, mode, index);\n  }\n\n  /**\n\t * @private\n\t */\n  _resolveElementOptions(elementType, mode = 'default', index) {\n    const active = mode === 'active';\n    const cache = this._cachedDataOpts;\n    const cacheKey = elementType + '-' + mode;\n    const cached = cache[cacheKey];\n    const sharing = this.enableOptionSharing && defined(index);\n    if (cached) {\n      return cloneIfNotShared(cached, sharing);\n    }\n    const config = this.chart.config;\n    const scopeKeys = config.datasetElementScopeKeys(this._type, elementType);\n    const prefixes = active ? [`${elementType}Hover`, 'hover', elementType, ''] : [elementType, ''];\n    const scopes = config.getOptionScopes(this.getDataset(), scopeKeys);\n    const names = Object.keys(defaults.elements[elementType]);\n    // context is provided as a function, and is called only if needed,\n    // so we don't create a context for each element if not needed.\n    const context = () => this.getContext(index, active, mode);\n    const values = config.resolveNamedOptions(scopes, names, context, prefixes);\n\n    if (values.$shared) {\n      // `$shared` indicates this set of options can be shared between multiple elements.\n      // Sharing is used to reduce number of properties to change during animation.\n      values.$shared = sharing;\n\n      // We cache options by `mode`, which can be 'active' for example. This enables us\n      // to have the 'active' element options and 'default' options to switch between\n      // when interacting.\n      cache[cacheKey] = Object.freeze(cloneIfNotShared(values, sharing));\n    }\n\n    return values;\n  }\n\n\n  /**\n\t * @private\n\t */\n  _resolveAnimations(index, transition, active) {\n    const chart = this.chart;\n    const cache = this._cachedDataOpts;\n    const cacheKey = `animation-${transition}`;\n    const cached = cache[cacheKey];\n    if (cached) {\n      return cached;\n    }\n    let options;\n    if (chart.options.animation !== false) {\n      const config = this.chart.config;\n      const scopeKeys = config.datasetAnimationScopeKeys(this._type, transition);\n      const scopes = config.getOptionScopes(this.getDataset(), scopeKeys);\n      options = config.createResolver(scopes, this.getContext(index, active, transition));\n    }\n    const animations = new Animations(chart, options && options.animations);\n    if (options && options._cacheable) {\n      cache[cacheKey] = Object.freeze(animations);\n    }\n    return animations;\n  }\n\n  /**\n\t * Utility for getting the options object shared between elements\n\t * @protected\n\t */\n  getSharedOptions(options) {\n    if (!options.$shared) {\n      return;\n    }\n    return this._sharedOptions || (this._sharedOptions = Object.assign({}, options));\n  }\n\n  /**\n\t * Utility for determining if `options` should be included in the updated properties\n\t * @protected\n\t */\n  includeOptions(mode, sharedOptions) {\n    return !sharedOptions || isDirectUpdateMode(mode) || this.chart._animationsDisabled;\n  }\n\n  /**\n   * @todo v4, rename to getSharedOptions and remove excess functions\n   */\n  _getSharedOptions(start, mode) {\n    const firstOpts = this.resolveDataElementOptions(start, mode);\n    const previouslySharedOptions = this._sharedOptions;\n    const sharedOptions = this.getSharedOptions(firstOpts);\n    const includeOptions = this.includeOptions(mode, sharedOptions) || (sharedOptions !== previouslySharedOptions);\n    this.updateSharedOptions(sharedOptions, mode, firstOpts);\n    return {sharedOptions, includeOptions};\n  }\n\n  /**\n\t * Utility for updating an element with new properties, using animations when appropriate.\n\t * @protected\n\t */\n  updateElement(element, index, properties, mode) {\n    if (isDirectUpdateMode(mode)) {\n      Object.assign(element, properties);\n    } else {\n      this._resolveAnimations(index, mode).update(element, properties);\n    }\n  }\n\n  /**\n\t * Utility to animate the shared options, that are potentially affecting multiple elements.\n\t * @protected\n\t */\n  updateSharedOptions(sharedOptions, mode, newOptions) {\n    if (sharedOptions && !isDirectUpdateMode(mode)) {\n      this._resolveAnimations(undefined, mode).update(sharedOptions, newOptions);\n    }\n  }\n\n  /**\n\t * @private\n\t */\n  _setStyle(element, index, mode, active) {\n    element.active = active;\n    const options = this.getStyle(index, active);\n    this._resolveAnimations(index, mode, active).update(element, {\n      // When going from active to inactive, we need to update to the shared options.\n      // This way the once hovered element will end up with the same original shared options instance, after animation.\n      options: (!active && this.getSharedOptions(options)) || options\n    });\n  }\n\n  removeHoverStyle(element, datasetIndex, index) {\n    this._setStyle(element, index, 'active', false);\n  }\n\n  setHoverStyle(element, datasetIndex, index) {\n    this._setStyle(element, index, 'active', true);\n  }\n\n  /**\n\t * @private\n\t */\n  _removeDatasetHoverStyle() {\n    const element = this._cachedMeta.dataset;\n\n    if (element) {\n      this._setStyle(element, undefined, 'active', false);\n    }\n  }\n\n  /**\n\t * @private\n\t */\n  _setDatasetHoverStyle() {\n    const element = this._cachedMeta.dataset;\n\n    if (element) {\n      this._setStyle(element, undefined, 'active', true);\n    }\n  }\n\n  /**\n\t * @private\n\t */\n  _resyncElements(resetNewElements) {\n    const data = this._data;\n    const elements = this._cachedMeta.data;\n\n    // Apply changes detected through array listeners\n    for (const [method, arg1, arg2] of this._syncList) {\n      this[method](arg1, arg2);\n    }\n    this._syncList = [];\n\n    const numMeta = elements.length;\n    const numData = data.length;\n    const count = Math.min(numData, numMeta);\n\n    if (count) {\n      // TODO: It is not optimal to always parse the old data\n      // This is done because we are not detecting direct assignments:\n      // chart.data.datasets[0].data[5] = 10;\n      // chart.data.datasets[0].data[5].y = 10;\n      this.parse(0, count);\n    }\n\n    if (numData > numMeta) {\n      this._insertElements(numMeta, numData - numMeta, resetNewElements);\n    } else if (numData < numMeta) {\n      this._removeElements(numData, numMeta - numData);\n    }\n  }\n\n  /**\n\t * @private\n\t */\n  _insertElements(start, count, resetNewElements = true) {\n    const meta = this._cachedMeta;\n    const data = meta.data;\n    const end = start + count;\n    let i;\n\n    const move = (arr) => {\n      arr.length += count;\n      for (i = arr.length - 1; i >= end; i--) {\n        arr[i] = arr[i - count];\n      }\n    };\n    move(data);\n\n    for (i = start; i < end; ++i) {\n      data[i] = new this.dataElementType();\n    }\n\n    if (this._parsing) {\n      move(meta._parsed);\n    }\n    this.parse(start, count);\n\n    if (resetNewElements) {\n      this.updateElements(data, start, count, 'reset');\n    }\n  }\n\n  updateElements(element, start, count, mode) {} // eslint-disable-line no-unused-vars\n\n  /**\n\t * @private\n\t */\n  _removeElements(start, count) {\n    const meta = this._cachedMeta;\n    if (this._parsing) {\n      const removed = meta._parsed.splice(start, count);\n      if (meta._stacked) {\n        clearStacks(meta, removed);\n      }\n    }\n    meta.data.splice(start, count);\n  }\n\n  /**\n\t * @private\n   */\n  _sync(args) {\n    if (this._parsing) {\n      this._syncList.push(args);\n    } else {\n      const [method, arg1, arg2] = args;\n      this[method](arg1, arg2);\n    }\n    this.chart._dataChanges.push([this.index, ...args]);\n  }\n\n  _onDataPush() {\n    const count = arguments.length;\n    this._sync(['_insertElements', this.getDataset().data.length - count, count]);\n  }\n\n  _onDataPop() {\n    this._sync(['_removeElements', this._cachedMeta.data.length - 1, 1]);\n  }\n\n  _onDataShift() {\n    this._sync(['_removeElements', 0, 1]);\n  }\n\n  _onDataSplice(start, count) {\n    if (count) {\n      this._sync(['_removeElements', start, count]);\n    }\n    const newCount = arguments.length - 2;\n    if (newCount) {\n      this._sync(['_insertElements', start, newCount]);\n    }\n  }\n\n  _onDataUnshift() {\n    this._sync(['_insertElements', 0, arguments.length]);\n  }\n}\n"
  },
  {
    "path": "src/core/core.defaults.js",
    "content": "import {getHoverColor} from '../helpers/helpers.color.js';\nimport {isObject, merge, valueOrDefault} from '../helpers/helpers.core.js';\nimport {applyAnimationsDefaults} from './core.animations.defaults.js';\nimport {applyLayoutsDefaults} from './core.layouts.defaults.js';\nimport {applyScaleDefaults} from './core.scale.defaults.js';\n\nexport const overrides = Object.create(null);\nexport const descriptors = Object.create(null);\n\n/**\n * @param {object} node\n * @param {string} key\n * @return {object}\n */\nfunction getScope(node, key) {\n  if (!key) {\n    return node;\n  }\n  const keys = key.split('.');\n  for (let i = 0, n = keys.length; i < n; ++i) {\n    const k = keys[i];\n    node = node[k] || (node[k] = Object.create(null));\n  }\n  return node;\n}\n\nfunction set(root, scope, values) {\n  if (typeof scope === 'string') {\n    return merge(getScope(root, scope), values);\n  }\n  return merge(getScope(root, ''), scope);\n}\n\n/**\n * Please use the module's default export which provides a singleton instance\n * Note: class is exported for typedoc\n */\nexport class Defaults {\n  constructor(_descriptors, _appliers) {\n    this.animation = undefined;\n    this.backgroundColor = 'rgba(0,0,0,0.1)';\n    this.borderColor = 'rgba(0,0,0,0.1)';\n    this.color = '#666';\n    this.datasets = {};\n    this.devicePixelRatio = (context) => context.chart.platform.getDevicePixelRatio();\n    this.elements = {};\n    this.events = [\n      'mousemove',\n      'mouseout',\n      'click',\n      'touchstart',\n      'touchmove'\n    ];\n    this.font = {\n      family: \"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif\",\n      size: 12,\n      style: 'normal',\n      lineHeight: 1.2,\n      weight: null\n    };\n    this.hover = {};\n    this.hoverBackgroundColor = (ctx, options) => getHoverColor(options.backgroundColor);\n    this.hoverBorderColor = (ctx, options) => getHoverColor(options.borderColor);\n    this.hoverColor = (ctx, options) => getHoverColor(options.color);\n    this.indexAxis = 'x';\n    this.interaction = {\n      mode: 'nearest',\n      intersect: true,\n      includeInvisible: false\n    };\n    this.maintainAspectRatio = true;\n    this.onHover = null;\n    this.onClick = null;\n    this.parsing = true;\n    this.plugins = {};\n    this.responsive = true;\n    this.scale = undefined;\n    this.scales = {};\n    this.showLine = true;\n    this.drawActiveElementsOnTop = true;\n\n    this.describe(_descriptors);\n    this.apply(_appliers);\n  }\n\n  /**\n\t * @param {string|object} scope\n\t * @param {object} [values]\n\t */\n  set(scope, values) {\n    return set(this, scope, values);\n  }\n\n  /**\n\t * @param {string} scope\n\t */\n  get(scope) {\n    return getScope(this, scope);\n  }\n\n  /**\n\t * @param {string|object} scope\n\t * @param {object} [values]\n\t */\n  describe(scope, values) {\n    return set(descriptors, scope, values);\n  }\n\n  override(scope, values) {\n    return set(overrides, scope, values);\n  }\n\n  /**\n\t * Routes the named defaults to fallback to another scope/name.\n\t * This routing is useful when those target values, like defaults.color, are changed runtime.\n\t * If the values would be copied, the runtime change would not take effect. By routing, the\n\t * fallback is evaluated at each access, so its always up to date.\n\t *\n\t * Example:\n\t *\n\t * \tdefaults.route('elements.arc', 'backgroundColor', '', 'color')\n\t *   - reads the backgroundColor from defaults.color when undefined locally\n\t *\n\t * @param {string} scope Scope this route applies to.\n\t * @param {string} name Property name that should be routed to different namespace when not defined here.\n\t * @param {string} targetScope The namespace where those properties should be routed to.\n\t * Empty string ('') is the root of defaults.\n\t * @param {string} targetName The target name in the target scope the property should be routed to.\n\t */\n  route(scope, name, targetScope, targetName) {\n    const scopeObject = getScope(this, scope);\n    const targetScopeObject = getScope(this, targetScope);\n    const privateName = '_' + name;\n\n    Object.defineProperties(scopeObject, {\n      // A private property is defined to hold the actual value, when this property is set in its scope (set in the setter)\n      [privateName]: {\n        value: scopeObject[name],\n        writable: true\n      },\n      // The actual property is defined as getter/setter so we can do the routing when value is not locally set.\n      [name]: {\n        enumerable: true,\n        get() {\n          const local = this[privateName];\n          const target = targetScopeObject[targetName];\n          if (isObject(local)) {\n            return Object.assign({}, target, local);\n          }\n          return valueOrDefault(local, target);\n        },\n        set(value) {\n          this[privateName] = value;\n        }\n      }\n    });\n  }\n\n  apply(appliers) {\n    appliers.forEach((apply) => apply(this));\n  }\n}\n\n// singleton instance\nexport default /* #__PURE__ */ new Defaults({\n  _scriptable: (name) => !name.startsWith('on'),\n  _indexable: (name) => name !== 'events',\n  hover: {\n    _fallback: 'interaction'\n  },\n  interaction: {\n    _scriptable: false,\n    _indexable: false,\n  }\n}, [applyAnimationsDefaults, applyLayoutsDefaults, applyScaleDefaults]);\n"
  },
  {
    "path": "src/core/core.element.ts",
    "content": "import type {AnyObject} from '../types/basic.js';\nimport type {Point} from '../types/geometric.js';\nimport type {Animation} from '../types/animation.js';\nimport {isNumber} from '../helpers/helpers.math.js';\n\nexport default class Element<T = AnyObject, O = AnyObject> {\n\n  static defaults = {};\n  static defaultRoutes = undefined;\n\n  x: number;\n  y: number;\n  active = false;\n  options: O;\n  $animations: Record<keyof T, Animation>;\n\n  tooltipPosition(useFinalPosition: boolean): Point {\n    const {x, y} = this.getProps(['x', 'y'], useFinalPosition);\n    return {x, y} as Point;\n  }\n\n  hasValue() {\n    return isNumber(this.x) && isNumber(this.y);\n  }\n\n  /**\n   * Gets the current or final value of each prop. Can return extra properties (whole object).\n   * @param props - properties to get\n   * @param [final] - get the final value (animation target)\n   */\n  getProps<P extends (keyof T)[]>(props: P, final?: boolean): Pick<T, P[number]>;\n  getProps<P extends string>(props: P[], final?: boolean): Partial<Record<P, unknown>>;\n  getProps(props: string[], final?: boolean): Partial<Record<string, unknown>> {\n    const anims = this.$animations;\n    if (!final || !anims) {\n      // let's not create an object, if not needed\n      return this as Record<string, unknown>;\n    }\n    const ret: Record<string, unknown> = {};\n    props.forEach((prop) => {\n      ret[prop] = anims[prop] && anims[prop].active() ? anims[prop]._to : this[prop as string];\n    });\n    return ret;\n  }\n}\n"
  },
  {
    "path": "src/core/core.interaction.js",
    "content": "import {_lookupByKey, _rlookupByKey} from '../helpers/helpers.collection.js';\nimport {getRelativePosition} from '../helpers/helpers.dom.js';\nimport {_angleBetween, getAngleFromPoint} from '../helpers/helpers.math.js';\nimport {_isPointInArea, isNullOrUndef} from '../helpers/index.js';\n\n/**\n * @typedef { import('./core.controller.js').default } Chart\n * @typedef { import('../types/index.js').ChartEvent } ChartEvent\n * @typedef {{axis?: string, intersect?: boolean, includeInvisible?: boolean}} InteractionOptions\n * @typedef {{datasetIndex: number, index: number, element: import('./core.element.js').default}} InteractionItem\n * @typedef { import('../types/index.js').Point } Point\n */\n\n/**\n * Helper function to do binary search when possible\n * @param {object} metaset - the dataset meta\n * @param {string} axis - the axis mode. x|y|xy|r\n * @param {number} value - the value to find\n * @param {boolean} [intersect] - should the element intersect\n * @returns {{lo:number, hi:number}} indices to search data array between\n */\nfunction binarySearch(metaset, axis, value, intersect) {\n  const {controller, data, _sorted} = metaset;\n  const iScale = controller._cachedMeta.iScale;\n  const spanGaps = metaset.dataset ? metaset.dataset.options ? metaset.dataset.options.spanGaps : null : null;\n\n  if (iScale && axis === iScale.axis && axis !== 'r' && _sorted && data.length) {\n    const lookupMethod = iScale._reversePixels ? _rlookupByKey : _lookupByKey;\n    if (!intersect) {\n      const result = lookupMethod(data, axis, value);\n      if (spanGaps) {\n        const {vScale} = controller._cachedMeta;\n        const {_parsed} = metaset;\n\n        const distanceToDefinedLo = (_parsed\n          .slice(0, result.lo + 1)\n          .reverse()\n          .findIndex(\n            point => !isNullOrUndef(point[vScale.axis])));\n        result.lo -= Math.max(0, distanceToDefinedLo);\n\n        const distanceToDefinedHi = (_parsed\n          .slice(result.hi)\n          .findIndex(\n            point => !isNullOrUndef(point[vScale.axis])));\n        result.hi += Math.max(0, distanceToDefinedHi);\n      }\n      return result;\n    } else if (controller._sharedOptions) {\n      // _sharedOptions indicates that each element has equal options -> equal proportions\n      // So we can do a ranged binary search based on the range of first element and\n      // be confident to get the full range of indices that can intersect with the value.\n      const el = data[0];\n      const range = typeof el.getRange === 'function' && el.getRange(axis);\n      if (range) {\n        const start = lookupMethod(data, axis, value - range);\n        const end = lookupMethod(data, axis, value + range);\n        return {lo: start.lo, hi: end.hi};\n      }\n    }\n  }\n  // Default to all elements, when binary search can not be used.\n  return {lo: 0, hi: data.length - 1};\n}\n\n/**\n * Helper function to select candidate elements for interaction\n * @param {Chart} chart - the chart\n * @param {string} axis - the axis mode. x|y|xy|r\n * @param {Point} position - the point to be nearest to, in relative coordinates\n * @param {function} handler - the callback to execute for each visible item\n * @param {boolean} [intersect] - consider intersecting items\n */\nfunction evaluateInteractionItems(chart, axis, position, handler, intersect) {\n  const metasets = chart.getSortedVisibleDatasetMetas();\n  const value = position[axis];\n  for (let i = 0, ilen = metasets.length; i < ilen; ++i) {\n    const {index, data} = metasets[i];\n    const {lo, hi} = binarySearch(metasets[i], axis, value, intersect);\n    for (let j = lo; j <= hi; ++j) {\n      const element = data[j];\n      if (!element.skip) {\n        handler(element, index, j);\n      }\n    }\n  }\n}\n\n/**\n * Get a distance metric function for two points based on the\n * axis mode setting\n * @param {string} axis - the axis mode. x|y|xy|r\n */\nfunction getDistanceMetricForAxis(axis) {\n  const useX = axis.indexOf('x') !== -1;\n  const useY = axis.indexOf('y') !== -1;\n\n  return function(pt1, pt2) {\n    const deltaX = useX ? Math.abs(pt1.x - pt2.x) : 0;\n    const deltaY = useY ? Math.abs(pt1.y - pt2.y) : 0;\n    return Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));\n  };\n}\n\n/**\n * Helper function to get the items that intersect the event position\n * @param {Chart} chart - the chart\n * @param {Point} position - the point to be nearest to, in relative coordinates\n * @param {string} axis - the axis mode. x|y|xy|r\n * @param {boolean} [useFinalPosition] - use the element's animation target instead of current position\n * @param {boolean} [includeInvisible] - include invisible points that are outside of the chart area\n * @return {InteractionItem[]} the nearest items\n */\nfunction getIntersectItems(chart, position, axis, useFinalPosition, includeInvisible) {\n  const items = [];\n\n  if (!includeInvisible && !chart.isPointInArea(position)) {\n    return items;\n  }\n\n  const evaluationFunc = function(element, datasetIndex, index) {\n    if (!includeInvisible && !_isPointInArea(element, chart.chartArea, 0)) {\n      return;\n    }\n    if (element.inRange(position.x, position.y, useFinalPosition)) {\n      items.push({element, datasetIndex, index});\n    }\n  };\n\n  evaluateInteractionItems(chart, axis, position, evaluationFunc, true);\n  return items;\n}\n\n/**\n * Helper function to get the items nearest to the event position for a radial chart\n * @param {Chart} chart - the chart to look at elements from\n * @param {Point} position - the point to be nearest to, in relative coordinates\n * @param {string} axis - the axes along which to measure distance\n * @param {boolean} [useFinalPosition] - use the element's animation target instead of current position\n * @return {InteractionItem[]} the nearest items\n */\nfunction getNearestRadialItems(chart, position, axis, useFinalPosition) {\n  let items = [];\n\n  function evaluationFunc(element, datasetIndex, index) {\n    const {startAngle, endAngle} = element.getProps(['startAngle', 'endAngle'], useFinalPosition);\n    const {angle} = getAngleFromPoint(element, {x: position.x, y: position.y});\n\n    if (_angleBetween(angle, startAngle, endAngle)) {\n      items.push({element, datasetIndex, index});\n    }\n  }\n\n  evaluateInteractionItems(chart, axis, position, evaluationFunc);\n  return items;\n}\n\n/**\n * Helper function to get the items nearest to the event position for a cartesian chart\n * @param {Chart} chart - the chart to look at elements from\n * @param {Point} position - the point to be nearest to, in relative coordinates\n * @param {string} axis - the axes along which to measure distance\n * @param {boolean} [intersect] - if true, only consider items that intersect the position\n * @param {boolean} [useFinalPosition] - use the element's animation target instead of current position\n * @param {boolean} [includeInvisible] - include invisible points that are outside of the chart area\n * @return {InteractionItem[]} the nearest items\n */\nfunction getNearestCartesianItems(chart, position, axis, intersect, useFinalPosition, includeInvisible) {\n  let items = [];\n  const distanceMetric = getDistanceMetricForAxis(axis);\n  let minDistance = Number.POSITIVE_INFINITY;\n\n  function evaluationFunc(element, datasetIndex, index) {\n    const inRange = element.inRange(position.x, position.y, useFinalPosition);\n    if (intersect && !inRange) {\n      return;\n    }\n\n    const center = element.getCenterPoint(useFinalPosition);\n    const pointInArea = !!includeInvisible || chart.isPointInArea(center);\n    if (!pointInArea && !inRange) {\n      return;\n    }\n\n    const distance = distanceMetric(position, center);\n    if (distance < minDistance) {\n      items = [{element, datasetIndex, index}];\n      minDistance = distance;\n    } else if (distance === minDistance) {\n      // Can have multiple items at the same distance in which case we sort by size\n      items.push({element, datasetIndex, index});\n    }\n  }\n\n  evaluateInteractionItems(chart, axis, position, evaluationFunc);\n  return items;\n}\n\n/**\n * Helper function to get the items nearest to the event position considering all visible items in the chart\n * @param {Chart} chart - the chart to look at elements from\n * @param {Point} position - the point to be nearest to, in relative coordinates\n * @param {string} axis - the axes along which to measure distance\n * @param {boolean} [intersect] - if true, only consider items that intersect the position\n * @param {boolean} [useFinalPosition] - use the element's animation target instead of current position\n * @param {boolean} [includeInvisible] - include invisible points that are outside of the chart area\n * @return {InteractionItem[]} the nearest items\n */\nfunction getNearestItems(chart, position, axis, intersect, useFinalPosition, includeInvisible) {\n  if (!includeInvisible && !chart.isPointInArea(position)) {\n    return [];\n  }\n\n  return axis === 'r' && !intersect\n    ? getNearestRadialItems(chart, position, axis, useFinalPosition)\n    : getNearestCartesianItems(chart, position, axis, intersect, useFinalPosition, includeInvisible);\n}\n\n/**\n * Helper function to get the items matching along the given X or Y axis\n * @param {Chart} chart - the chart to look at elements from\n * @param {Point} position - the point to be nearest to, in relative coordinates\n * @param {string} axis - the axis to match\n * @param {boolean} [intersect] - if true, only consider items that intersect the position\n * @param {boolean} [useFinalPosition] - use the element's animation target instead of current position\n * @return {InteractionItem[]} the nearest items\n */\nfunction getAxisItems(chart, position, axis, intersect, useFinalPosition) {\n  const items = [];\n  const rangeMethod = axis === 'x' ? 'inXRange' : 'inYRange';\n  let intersectsItem = false;\n\n  evaluateInteractionItems(chart, axis, position, (element, datasetIndex, index) => {\n    if (element[rangeMethod] && element[rangeMethod](position[axis], useFinalPosition)) {\n      items.push({element, datasetIndex, index});\n      intersectsItem = intersectsItem || element.inRange(position.x, position.y, useFinalPosition);\n    }\n  });\n\n  // If we want to trigger on an intersect and we don't have any items\n  // that intersect the position, return nothing\n  if (intersect && !intersectsItem) {\n    return [];\n  }\n  return items;\n}\n\n/**\n * Contains interaction related functions\n * @namespace Chart.Interaction\n */\nexport default {\n  // Part of the public API to facilitate developers creating their own modes\n  evaluateInteractionItems,\n\n  // Helper function for different modes\n  modes: {\n    /**\n\t\t * Returns items at the same index. If the options.intersect parameter is true, we only return items if we intersect something\n\t\t * If the options.intersect mode is false, we find the nearest item and return the items at the same index as that item\n\t\t * @function Chart.Interaction.modes.index\n\t\t * @since v2.4.0\n\t\t * @param {Chart} chart - the chart we are returning items from\n\t\t * @param {Event} e - the event we are find things at\n\t\t * @param {InteractionOptions} options - options to use\n\t\t * @param {boolean} [useFinalPosition] - use final element position (animation target)\n\t\t * @return {InteractionItem[]} - items that are found\n\t\t */\n    index(chart, e, options, useFinalPosition) {\n      const position = getRelativePosition(e, chart);\n      // Default axis for index mode is 'x' to match old behaviour\n      const axis = options.axis || 'x';\n      const includeInvisible = options.includeInvisible || false;\n      const items = options.intersect\n        ? getIntersectItems(chart, position, axis, useFinalPosition, includeInvisible)\n        : getNearestItems(chart, position, axis, false, useFinalPosition, includeInvisible);\n      const elements = [];\n\n      if (!items.length) {\n        return [];\n      }\n\n      chart.getSortedVisibleDatasetMetas().forEach((meta) => {\n        const index = items[0].index;\n        const element = meta.data[index];\n\n        // don't count items that are skipped (null data)\n        if (element && !element.skip) {\n          elements.push({element, datasetIndex: meta.index, index});\n        }\n      });\n\n      return elements;\n    },\n\n    /**\n\t\t * Returns items in the same dataset. If the options.intersect parameter is true, we only return items if we intersect something\n\t\t * If the options.intersect is false, we find the nearest item and return the items in that dataset\n\t\t * @function Chart.Interaction.modes.dataset\n\t\t * @param {Chart} chart - the chart we are returning items from\n\t\t * @param {Event} e - the event we are find things at\n\t\t * @param {InteractionOptions} options - options to use\n\t\t * @param {boolean} [useFinalPosition] - use final element position (animation target)\n\t\t * @return {InteractionItem[]} - items that are found\n\t\t */\n    dataset(chart, e, options, useFinalPosition) {\n      const position = getRelativePosition(e, chart);\n      const axis = options.axis || 'xy';\n      const includeInvisible = options.includeInvisible || false;\n      let items = options.intersect\n        ? getIntersectItems(chart, position, axis, useFinalPosition, includeInvisible) :\n        getNearestItems(chart, position, axis, false, useFinalPosition, includeInvisible);\n\n      if (items.length > 0) {\n        const datasetIndex = items[0].datasetIndex;\n        const data = chart.getDatasetMeta(datasetIndex).data;\n        items = [];\n        for (let i = 0; i < data.length; ++i) {\n          items.push({element: data[i], datasetIndex, index: i});\n        }\n      }\n\n      return items;\n    },\n\n    /**\n\t\t * Point mode returns all elements that hit test based on the event position\n\t\t * of the event\n\t\t * @function Chart.Interaction.modes.intersect\n\t\t * @param {Chart} chart - the chart we are returning items from\n\t\t * @param {Event} e - the event we are find things at\n\t\t * @param {InteractionOptions} options - options to use\n\t\t * @param {boolean} [useFinalPosition] - use final element position (animation target)\n\t\t * @return {InteractionItem[]} - items that are found\n\t\t */\n    point(chart, e, options, useFinalPosition) {\n      const position = getRelativePosition(e, chart);\n      const axis = options.axis || 'xy';\n      const includeInvisible = options.includeInvisible || false;\n      return getIntersectItems(chart, position, axis, useFinalPosition, includeInvisible);\n    },\n\n    /**\n\t\t * nearest mode returns the element closest to the point\n\t\t * @function Chart.Interaction.modes.intersect\n\t\t * @param {Chart} chart - the chart we are returning items from\n\t\t * @param {Event} e - the event we are find things at\n\t\t * @param {InteractionOptions} options - options to use\n\t\t * @param {boolean} [useFinalPosition] - use final element position (animation target)\n\t\t * @return {InteractionItem[]} - items that are found\n\t\t */\n    nearest(chart, e, options, useFinalPosition) {\n      const position = getRelativePosition(e, chart);\n      const axis = options.axis || 'xy';\n      const includeInvisible = options.includeInvisible || false;\n      return getNearestItems(chart, position, axis, options.intersect, useFinalPosition, includeInvisible);\n    },\n\n    /**\n\t\t * x mode returns the elements that hit-test at the current x coordinate\n\t\t * @function Chart.Interaction.modes.x\n\t\t * @param {Chart} chart - the chart we are returning items from\n\t\t * @param {Event} e - the event we are find things at\n\t\t * @param {InteractionOptions} options - options to use\n\t\t * @param {boolean} [useFinalPosition] - use final element position (animation target)\n\t\t * @return {InteractionItem[]} - items that are found\n\t\t */\n    x(chart, e, options, useFinalPosition) {\n      const position = getRelativePosition(e, chart);\n      return getAxisItems(chart, position, 'x', options.intersect, useFinalPosition);\n    },\n\n    /**\n\t\t * y mode returns the elements that hit-test at the current y coordinate\n\t\t * @function Chart.Interaction.modes.y\n\t\t * @param {Chart} chart - the chart we are returning items from\n\t\t * @param {Event} e - the event we are find things at\n\t\t * @param {InteractionOptions} options - options to use\n\t\t * @param {boolean} [useFinalPosition] - use final element position (animation target)\n\t\t * @return {InteractionItem[]} - items that are found\n\t\t */\n    y(chart, e, options, useFinalPosition) {\n      const position = getRelativePosition(e, chart);\n      return getAxisItems(chart, position, 'y', options.intersect, useFinalPosition);\n    }\n  }\n};\n"
  },
  {
    "path": "src/core/core.layouts.defaults.js",
    "content": "export function applyLayoutsDefaults(defaults) {\n  defaults.set('layout', {\n    autoPadding: true,\n    padding: {\n      top: 0,\n      right: 0,\n      bottom: 0,\n      left: 0\n    }\n  });\n}\n"
  },
  {
    "path": "src/core/core.layouts.js",
    "content": "import {defined, each, isObject} from '../helpers/helpers.core.js';\nimport {toPadding} from '../helpers/helpers.options.js';\n\n/**\n * @typedef { import('./core.controller.js').default } Chart\n */\n\nconst STATIC_POSITIONS = ['left', 'top', 'right', 'bottom'];\n\nfunction filterByPosition(array, position) {\n  return array.filter(v => v.pos === position);\n}\n\nfunction filterDynamicPositionByAxis(array, axis) {\n  return array.filter(v => STATIC_POSITIONS.indexOf(v.pos) === -1 && v.box.axis === axis);\n}\n\nfunction sortByWeight(array, reverse) {\n  return array.sort((a, b) => {\n    const v0 = reverse ? b : a;\n    const v1 = reverse ? a : b;\n    return v0.weight === v1.weight ?\n      v0.index - v1.index :\n      v0.weight - v1.weight;\n  });\n}\n\nfunction wrapBoxes(boxes) {\n  const layoutBoxes = [];\n  let i, ilen, box, pos, stack, stackWeight;\n\n  for (i = 0, ilen = (boxes || []).length; i < ilen; ++i) {\n    box = boxes[i];\n    ({position: pos, options: {stack, stackWeight = 1}} = box);\n    layoutBoxes.push({\n      index: i,\n      box,\n      pos,\n      horizontal: box.isHorizontal(),\n      weight: box.weight,\n      stack: stack && (pos + stack),\n      stackWeight\n    });\n  }\n  return layoutBoxes;\n}\n\nfunction buildStacks(layouts) {\n  const stacks = {};\n  for (const wrap of layouts) {\n    const {stack, pos, stackWeight} = wrap;\n    if (!stack || !STATIC_POSITIONS.includes(pos)) {\n      continue;\n    }\n    const _stack = stacks[stack] || (stacks[stack] = {count: 0, placed: 0, weight: 0, size: 0});\n    _stack.count++;\n    _stack.weight += stackWeight;\n  }\n  return stacks;\n}\n\n/**\n * store dimensions used instead of available chartArea in fitBoxes\n **/\nfunction setLayoutDims(layouts, params) {\n  const stacks = buildStacks(layouts);\n  const {vBoxMaxWidth, hBoxMaxHeight} = params;\n  let i, ilen, layout;\n  for (i = 0, ilen = layouts.length; i < ilen; ++i) {\n    layout = layouts[i];\n    const {fullSize} = layout.box;\n    const stack = stacks[layout.stack];\n    const factor = stack && layout.stackWeight / stack.weight;\n    if (layout.horizontal) {\n      layout.width = factor ? factor * vBoxMaxWidth : fullSize && params.availableWidth;\n      layout.height = hBoxMaxHeight;\n    } else {\n      layout.width = vBoxMaxWidth;\n      layout.height = factor ? factor * hBoxMaxHeight : fullSize && params.availableHeight;\n    }\n  }\n  return stacks;\n}\n\nfunction buildLayoutBoxes(boxes) {\n  const layoutBoxes = wrapBoxes(boxes);\n  const fullSize = sortByWeight(layoutBoxes.filter(wrap => wrap.box.fullSize), true);\n  const left = sortByWeight(filterByPosition(layoutBoxes, 'left'), true);\n  const right = sortByWeight(filterByPosition(layoutBoxes, 'right'));\n  const top = sortByWeight(filterByPosition(layoutBoxes, 'top'), true);\n  const bottom = sortByWeight(filterByPosition(layoutBoxes, 'bottom'));\n  const centerHorizontal = filterDynamicPositionByAxis(layoutBoxes, 'x');\n  const centerVertical = filterDynamicPositionByAxis(layoutBoxes, 'y');\n\n  return {\n    fullSize,\n    leftAndTop: left.concat(top),\n    rightAndBottom: right.concat(centerVertical).concat(bottom).concat(centerHorizontal),\n    chartArea: filterByPosition(layoutBoxes, 'chartArea'),\n    vertical: left.concat(right).concat(centerVertical),\n    horizontal: top.concat(bottom).concat(centerHorizontal)\n  };\n}\n\nfunction getCombinedMax(maxPadding, chartArea, a, b) {\n  return Math.max(maxPadding[a], chartArea[a]) + Math.max(maxPadding[b], chartArea[b]);\n}\n\nfunction updateMaxPadding(maxPadding, boxPadding) {\n  maxPadding.top = Math.max(maxPadding.top, boxPadding.top);\n  maxPadding.left = Math.max(maxPadding.left, boxPadding.left);\n  maxPadding.bottom = Math.max(maxPadding.bottom, boxPadding.bottom);\n  maxPadding.right = Math.max(maxPadding.right, boxPadding.right);\n}\n\nfunction updateDims(chartArea, params, layout, stacks) {\n  const {pos, box} = layout;\n  const maxPadding = chartArea.maxPadding;\n\n  // dynamically placed boxes size is not considered\n  if (!isObject(pos)) {\n    if (layout.size) {\n      // this layout was already counted for, lets first reduce old size\n      chartArea[pos] -= layout.size;\n    }\n    const stack = stacks[layout.stack] || {size: 0, count: 1};\n    stack.size = Math.max(stack.size, layout.horizontal ? box.height : box.width);\n    layout.size = stack.size / stack.count;\n    chartArea[pos] += layout.size;\n  }\n\n  if (box.getPadding) {\n    updateMaxPadding(maxPadding, box.getPadding());\n  }\n\n  const newWidth = Math.max(0, params.outerWidth - getCombinedMax(maxPadding, chartArea, 'left', 'right'));\n  const newHeight = Math.max(0, params.outerHeight - getCombinedMax(maxPadding, chartArea, 'top', 'bottom'));\n  const widthChanged = newWidth !== chartArea.w;\n  const heightChanged = newHeight !== chartArea.h;\n  chartArea.w = newWidth;\n  chartArea.h = newHeight;\n\n  // return booleans on the changes per direction\n  return layout.horizontal\n    ? {same: widthChanged, other: heightChanged}\n    : {same: heightChanged, other: widthChanged};\n}\n\nfunction handleMaxPadding(chartArea) {\n  const maxPadding = chartArea.maxPadding;\n\n  function updatePos(pos) {\n    const change = Math.max(maxPadding[pos] - chartArea[pos], 0);\n    chartArea[pos] += change;\n    return change;\n  }\n  chartArea.y += updatePos('top');\n  chartArea.x += updatePos('left');\n  updatePos('right');\n  updatePos('bottom');\n}\n\nfunction getMargins(horizontal, chartArea) {\n  const maxPadding = chartArea.maxPadding;\n\n  function marginForPositions(positions) {\n    const margin = {left: 0, top: 0, right: 0, bottom: 0};\n    positions.forEach((pos) => {\n      margin[pos] = Math.max(chartArea[pos], maxPadding[pos]);\n    });\n    return margin;\n  }\n\n  return horizontal\n    ? marginForPositions(['left', 'right'])\n    : marginForPositions(['top', 'bottom']);\n}\n\nfunction fitBoxes(boxes, chartArea, params, stacks) {\n  const refitBoxes = [];\n  let i, ilen, layout, box, refit, changed;\n\n  for (i = 0, ilen = boxes.length, refit = 0; i < ilen; ++i) {\n    layout = boxes[i];\n    box = layout.box;\n\n    box.update(\n      layout.width || chartArea.w,\n      layout.height || chartArea.h,\n      getMargins(layout.horizontal, chartArea)\n    );\n    const {same, other} = updateDims(chartArea, params, layout, stacks);\n\n    // Dimensions changed and there were non full width boxes before this\n    // -> we have to refit those\n    refit |= same && refitBoxes.length;\n\n    // Chart area changed in the opposite direction\n    changed = changed || other;\n\n    if (!box.fullSize) { // fullSize boxes don't need to be re-fitted in any case\n      refitBoxes.push(layout);\n    }\n  }\n\n  return refit && fitBoxes(refitBoxes, chartArea, params, stacks) || changed;\n}\n\nfunction setBoxDims(box, left, top, width, height) {\n  box.top = top;\n  box.left = left;\n  box.right = left + width;\n  box.bottom = top + height;\n  box.width = width;\n  box.height = height;\n}\n\nfunction placeBoxes(boxes, chartArea, params, stacks) {\n  const userPadding = params.padding;\n  let {x, y} = chartArea;\n\n  for (const layout of boxes) {\n    const box = layout.box;\n    const stack = stacks[layout.stack] || {count: 1, placed: 0, weight: 1};\n    const weight = (layout.stackWeight / stack.weight) || 1;\n    if (layout.horizontal) {\n      const width = chartArea.w * weight;\n      const height = stack.size || box.height;\n      if (defined(stack.start)) {\n        y = stack.start;\n      }\n      if (box.fullSize) {\n        setBoxDims(box, userPadding.left, y, params.outerWidth - userPadding.right - userPadding.left, height);\n      } else {\n        setBoxDims(box, chartArea.left + stack.placed, y, width, height);\n      }\n      stack.start = y;\n      stack.placed += width;\n      y = box.bottom;\n    } else {\n      const height = chartArea.h * weight;\n      const width = stack.size || box.width;\n      if (defined(stack.start)) {\n        x = stack.start;\n      }\n      if (box.fullSize) {\n        setBoxDims(box, x, userPadding.top, width, params.outerHeight - userPadding.bottom - userPadding.top);\n      } else {\n        setBoxDims(box, x, chartArea.top + stack.placed, width, height);\n      }\n      stack.start = x;\n      stack.placed += height;\n      x = box.right;\n    }\n  }\n\n  chartArea.x = x;\n  chartArea.y = y;\n}\n\n/**\n * @interface LayoutItem\n * @typedef {object} LayoutItem\n * @prop {string} position - The position of the item in the chart layout. Possible values are\n * 'left', 'top', 'right', 'bottom', and 'chartArea'\n * @prop {number} weight - The weight used to sort the item. Higher weights are further away from the chart area\n * @prop {boolean} fullSize - if true, and the item is horizontal, then push vertical boxes down\n * @prop {function} isHorizontal - returns true if the layout item is horizontal (ie. top or bottom)\n * @prop {function} update - Takes two parameters: width and height. Returns size of item\n * @prop {function} draw - Draws the element\n * @prop {function} [getPadding] -  Returns an object with padding on the edges\n * @prop {number} width - Width of item. Must be valid after update()\n * @prop {number} height - Height of item. Must be valid after update()\n * @prop {number} left - Left edge of the item. Set by layout system and cannot be used in update\n * @prop {number} top - Top edge of the item. Set by layout system and cannot be used in update\n * @prop {number} right - Right edge of the item. Set by layout system and cannot be used in update\n * @prop {number} bottom - Bottom edge of the item. Set by layout system and cannot be used in update\n */\n\n// The layout service is very self explanatory.  It's responsible for the layout within a chart.\n// Scales, Legends and Plugins all rely on the layout service and can easily register to be placed anywhere they need\n// It is this service's responsibility of carrying out that layout.\nexport default {\n\n  /**\n\t * Register a box to a chart.\n\t * A box is simply a reference to an object that requires layout. eg. Scales, Legend, Title.\n\t * @param {Chart} chart - the chart to use\n\t * @param {LayoutItem} item - the item to add to be laid out\n\t */\n  addBox(chart, item) {\n    if (!chart.boxes) {\n      chart.boxes = [];\n    }\n\n    // initialize item with default values\n    item.fullSize = item.fullSize || false;\n    item.position = item.position || 'top';\n    item.weight = item.weight || 0;\n    // @ts-ignore\n    item._layers = item._layers || function() {\n      return [{\n        z: 0,\n        draw(chartArea) {\n          item.draw(chartArea);\n        }\n      }];\n    };\n\n    chart.boxes.push(item);\n  },\n\n  /**\n\t * Remove a layoutItem from a chart\n\t * @param {Chart} chart - the chart to remove the box from\n\t * @param {LayoutItem} layoutItem - the item to remove from the layout\n\t */\n  removeBox(chart, layoutItem) {\n    const index = chart.boxes ? chart.boxes.indexOf(layoutItem) : -1;\n    if (index !== -1) {\n      chart.boxes.splice(index, 1);\n    }\n  },\n\n  /**\n\t * Sets (or updates) options on the given `item`.\n\t * @param {Chart} chart - the chart in which the item lives (or will be added to)\n\t * @param {LayoutItem} item - the item to configure with the given options\n\t * @param {object} options - the new item options.\n\t */\n  configure(chart, item, options) {\n    item.fullSize = options.fullSize;\n    item.position = options.position;\n    item.weight = options.weight;\n  },\n\n  /**\n\t * Fits boxes of the given chart into the given size by having each box measure itself\n\t * then running a fitting algorithm\n\t * @param {Chart} chart - the chart\n\t * @param {number} width - the width to fit into\n\t * @param {number} height - the height to fit into\n   * @param {number} minPadding - minimum padding required for each side of chart area\n\t */\n  update(chart, width, height, minPadding) {\n    if (!chart) {\n      return;\n    }\n\n    const padding = toPadding(chart.options.layout.padding);\n    const availableWidth = Math.max(width - padding.width, 0);\n    const availableHeight = Math.max(height - padding.height, 0);\n    const boxes = buildLayoutBoxes(chart.boxes);\n    const verticalBoxes = boxes.vertical;\n    const horizontalBoxes = boxes.horizontal;\n\n    // Before any changes are made, notify boxes that an update is about to being\n    // This is used to clear any cached data (e.g. scale limits)\n    each(chart.boxes, box => {\n      if (typeof box.beforeLayout === 'function') {\n        box.beforeLayout();\n      }\n    });\n\n    // Essentially we now have any number of boxes on each of the 4 sides.\n    // Our canvas looks like the following.\n    // The areas L1 and L2 are the left axes. R1 is the right axis, T1 is the top axis and\n    // B1 is the bottom axis\n    // There are also 4 quadrant-like locations (left to right instead of clockwise) reserved for chart overlays\n    // These locations are single-box locations only, when trying to register a chartArea location that is already taken,\n    // an error will be thrown.\n    //\n    // |----------------------------------------------------|\n    // |                  T1 (Full Width)                   |\n    // |----------------------------------------------------|\n    // |    |    |                 T2                  |    |\n    // |    |----|-------------------------------------|----|\n    // |    |    | C1 |                           | C2 |    |\n    // |    |    |----|                           |----|    |\n    // |    |    |                                     |    |\n    // | L1 | L2 |           ChartArea (C0)            | R1 |\n    // |    |    |                                     |    |\n    // |    |    |----|                           |----|    |\n    // |    |    | C3 |                           | C4 |    |\n    // |    |----|-------------------------------------|----|\n    // |    |    |                 B1                  |    |\n    // |----------------------------------------------------|\n    // |                  B2 (Full Width)                   |\n    // |----------------------------------------------------|\n    //\n\n    const visibleVerticalBoxCount = verticalBoxes.reduce((total, wrap) =>\n      wrap.box.options && wrap.box.options.display === false ? total : total + 1, 0) || 1;\n\n    const params = Object.freeze({\n      outerWidth: width,\n      outerHeight: height,\n      padding,\n      availableWidth,\n      availableHeight,\n      vBoxMaxWidth: availableWidth / 2 / visibleVerticalBoxCount,\n      hBoxMaxHeight: availableHeight / 2\n    });\n    const maxPadding = Object.assign({}, padding);\n    updateMaxPadding(maxPadding, toPadding(minPadding));\n    const chartArea = Object.assign({\n      maxPadding,\n      w: availableWidth,\n      h: availableHeight,\n      x: padding.left,\n      y: padding.top\n    }, padding);\n\n    const stacks = setLayoutDims(verticalBoxes.concat(horizontalBoxes), params);\n\n    // First fit the fullSize boxes, to reduce probability of re-fitting.\n    fitBoxes(boxes.fullSize, chartArea, params, stacks);\n\n    // Then fit vertical boxes\n    fitBoxes(verticalBoxes, chartArea, params, stacks);\n\n    // Then fit horizontal boxes\n    if (fitBoxes(horizontalBoxes, chartArea, params, stacks)) {\n      // if the area changed, re-fit vertical boxes\n      fitBoxes(verticalBoxes, chartArea, params, stacks);\n    }\n\n    handleMaxPadding(chartArea);\n\n    // Finally place the boxes to correct coordinates\n    placeBoxes(boxes.leftAndTop, chartArea, params, stacks);\n\n    // Move to opposite side of chart\n    chartArea.x += chartArea.w;\n    chartArea.y += chartArea.h;\n\n    placeBoxes(boxes.rightAndBottom, chartArea, params, stacks);\n\n    chart.chartArea = {\n      left: chartArea.left,\n      top: chartArea.top,\n      right: chartArea.left + chartArea.w,\n      bottom: chartArea.top + chartArea.h,\n      height: chartArea.h,\n      width: chartArea.w,\n    };\n\n    // Finally update boxes in chartArea (radial scale for example)\n    each(boxes.chartArea, (layout) => {\n      const box = layout.box;\n      Object.assign(box, chart.chartArea);\n      box.update(chartArea.w, chartArea.h, {left: 0, top: 0, right: 0, bottom: 0});\n    });\n  }\n};\n"
  },
  {
    "path": "src/core/core.plugins.js",
    "content": "import registry from './core.registry.js';\nimport {callback as callCallback, isNullOrUndef, valueOrDefault} from '../helpers/helpers.core.js';\n\n/**\n * @typedef { import('./core.controller.js').default } Chart\n * @typedef { import('../types/index.js').ChartEvent } ChartEvent\n * @typedef { import('../plugins/plugin.tooltip.js').default } Tooltip\n */\n\n/**\n * @callback filterCallback\n * @param {{plugin: object, options: object}} value\n * @param {number} [index]\n * @param {array} [array]\n * @param {object} [thisArg]\n * @return {boolean}\n */\n\n\nexport default class PluginService {\n  constructor() {\n    this._init = undefined;\n  }\n\n  /**\n\t * Calls enabled plugins for `chart` on the specified hook and with the given args.\n\t * This method immediately returns as soon as a plugin explicitly returns false. The\n\t * returned value can be used, for instance, to interrupt the current action.\n\t * @param {Chart} chart - The chart instance for which plugins should be called.\n\t * @param {string} hook - The name of the plugin method to call (e.g. 'beforeUpdate').\n\t * @param {object} [args] - Extra arguments to apply to the hook call.\n   * @param {filterCallback} [filter] - Filtering function for limiting which plugins are notified\n\t * @returns {boolean} false if any of the plugins return false, else returns true.\n\t */\n  notify(chart, hook, args, filter) {\n    if (hook === 'beforeInit') {\n      this._init = this._createDescriptors(chart, true);\n      this._notify(this._init, chart, 'install');\n    }\n\n    if (this._init === undefined) { // Do not trigger events before install\n      return;\n    }\n\n    const descriptors = filter ? this._descriptors(chart).filter(filter) : this._descriptors(chart);\n    const result = this._notify(descriptors, chart, hook, args);\n\n    if (hook === 'afterDestroy') {\n      this._notify(descriptors, chart, 'stop');\n      this._notify(this._init, chart, 'uninstall');\n      this._init = undefined; // Do not trigger events after uninstall\n    }\n    return result;\n  }\n\n  /**\n\t * @private\n\t */\n  _notify(descriptors, chart, hook, args) {\n    args = args || {};\n    for (const descriptor of descriptors) {\n      const plugin = descriptor.plugin;\n      const method = plugin[hook];\n      const params = [chart, args, descriptor.options];\n      if (callCallback(method, params, plugin) === false && args.cancelable) {\n        return false;\n      }\n    }\n\n    return true;\n  }\n\n  invalidate() {\n    // When plugins are registered, there is the possibility of a double\n    // invalidate situation. In this case, we only want to invalidate once.\n    // If we invalidate multiple times, the `_oldCache` is lost and all of the\n    // plugins are restarted without being correctly stopped.\n    // See https://github.com/chartjs/Chart.js/issues/8147\n    if (!isNullOrUndef(this._cache)) {\n      this._oldCache = this._cache;\n      this._cache = undefined;\n    }\n  }\n\n  /**\n\t * @param {Chart} chart\n\t * @private\n\t */\n  _descriptors(chart) {\n    if (this._cache) {\n      return this._cache;\n    }\n\n    const descriptors = this._cache = this._createDescriptors(chart);\n\n    this._notifyStateChanges(chart);\n\n    return descriptors;\n  }\n\n  _createDescriptors(chart, all) {\n    const config = chart && chart.config;\n    const options = valueOrDefault(config.options && config.options.plugins, {});\n    const plugins = allPlugins(config);\n    // options === false => all plugins are disabled\n    return options === false && !all ? [] : createDescriptors(chart, plugins, options, all);\n  }\n\n  /**\n\t * @param {Chart} chart\n\t * @private\n\t */\n  _notifyStateChanges(chart) {\n    const previousDescriptors = this._oldCache || [];\n    const descriptors = this._cache;\n    const diff = (a, b) => a.filter(x => !b.some(y => x.plugin.id === y.plugin.id));\n    this._notify(diff(previousDescriptors, descriptors), chart, 'stop');\n    this._notify(diff(descriptors, previousDescriptors), chart, 'start');\n  }\n}\n\n/**\n * @param {import('./core.config.js').default} config\n */\nfunction allPlugins(config) {\n  const localIds = {};\n  const plugins = [];\n  const keys = Object.keys(registry.plugins.items);\n  for (let i = 0; i < keys.length; i++) {\n    plugins.push(registry.getPlugin(keys[i]));\n  }\n\n  const local = config.plugins || [];\n  for (let i = 0; i < local.length; i++) {\n    const plugin = local[i];\n\n    if (plugins.indexOf(plugin) === -1) {\n      plugins.push(plugin);\n      localIds[plugin.id] = true;\n    }\n  }\n\n  return {plugins, localIds};\n}\n\nfunction getOpts(options, all) {\n  if (!all && options === false) {\n    return null;\n  }\n  if (options === true) {\n    return {};\n  }\n  return options;\n}\n\nfunction createDescriptors(chart, {plugins, localIds}, options, all) {\n  const result = [];\n  const context = chart.getContext();\n\n  for (const plugin of plugins) {\n    const id = plugin.id;\n    const opts = getOpts(options[id], all);\n    if (opts === null) {\n      continue;\n    }\n    result.push({\n      plugin,\n      options: pluginOpts(chart.config, {plugin, local: localIds[id]}, opts, context)\n    });\n  }\n\n  return result;\n}\n\nfunction pluginOpts(config, {plugin, local}, opts, context) {\n  const keys = config.pluginScopeKeys(plugin);\n  const scopes = config.getOptionScopes(opts, keys);\n  if (local && plugin.defaults) {\n    // make sure plugin defaults are in scopes for local (not registered) plugins\n    scopes.push(plugin.defaults);\n  }\n  return config.createResolver(scopes, context, [''], {\n    // These are just defaults that plugins can override\n    scriptable: false,\n    indexable: false,\n    allKeys: true\n  });\n}\n"
  },
  {
    "path": "src/core/core.registry.js",
    "content": "import DatasetController from './core.datasetController.js';\nimport Element from './core.element.js';\nimport Scale from './core.scale.js';\nimport TypedRegistry from './core.typedRegistry.js';\nimport {each, callback as call, _capitalize} from '../helpers/helpers.core.js';\n\n/**\n * Please use the module's default export which provides a singleton instance\n * Note: class is exported for typedoc\n */\nexport class Registry {\n  constructor() {\n    this.controllers = new TypedRegistry(DatasetController, 'datasets', true);\n    this.elements = new TypedRegistry(Element, 'elements');\n    this.plugins = new TypedRegistry(Object, 'plugins');\n    this.scales = new TypedRegistry(Scale, 'scales');\n    // Order is important, Scale has Element in prototype chain,\n    // so Scales must be before Elements. Plugins are a fallback, so not listed here.\n    this._typedRegistries = [this.controllers, this.scales, this.elements];\n  }\n\n  /**\n\t * @param  {...any} args\n\t */\n  add(...args) {\n    this._each('register', args);\n  }\n\n  remove(...args) {\n    this._each('unregister', args);\n  }\n\n  /**\n\t * @param  {...typeof DatasetController} args\n\t */\n  addControllers(...args) {\n    this._each('register', args, this.controllers);\n  }\n\n  /**\n\t * @param  {...typeof Element} args\n\t */\n  addElements(...args) {\n    this._each('register', args, this.elements);\n  }\n\n  /**\n\t * @param  {...any} args\n\t */\n  addPlugins(...args) {\n    this._each('register', args, this.plugins);\n  }\n\n  /**\n\t * @param  {...typeof Scale} args\n\t */\n  addScales(...args) {\n    this._each('register', args, this.scales);\n  }\n\n  /**\n\t * @param {string} id\n\t * @returns {typeof DatasetController}\n\t */\n  getController(id) {\n    return this._get(id, this.controllers, 'controller');\n  }\n\n  /**\n\t * @param {string} id\n\t * @returns {typeof Element}\n\t */\n  getElement(id) {\n    return this._get(id, this.elements, 'element');\n  }\n\n  /**\n\t * @param {string} id\n\t * @returns {object}\n\t */\n  getPlugin(id) {\n    return this._get(id, this.plugins, 'plugin');\n  }\n\n  /**\n\t * @param {string} id\n\t * @returns {typeof Scale}\n\t */\n  getScale(id) {\n    return this._get(id, this.scales, 'scale');\n  }\n\n  /**\n\t * @param  {...typeof DatasetController} args\n\t */\n  removeControllers(...args) {\n    this._each('unregister', args, this.controllers);\n  }\n\n  /**\n\t * @param  {...typeof Element} args\n\t */\n  removeElements(...args) {\n    this._each('unregister', args, this.elements);\n  }\n\n  /**\n\t * @param  {...any} args\n\t */\n  removePlugins(...args) {\n    this._each('unregister', args, this.plugins);\n  }\n\n  /**\n\t * @param  {...typeof Scale} args\n\t */\n  removeScales(...args) {\n    this._each('unregister', args, this.scales);\n  }\n\n  /**\n\t * @private\n\t */\n  _each(method, args, typedRegistry) {\n    [...args].forEach(arg => {\n      const reg = typedRegistry || this._getRegistryForType(arg);\n      if (typedRegistry || reg.isForType(arg) || (reg === this.plugins && arg.id)) {\n        this._exec(method, reg, arg);\n      } else {\n        // Handle loopable args\n        // Use case:\n        //  import * as plugins from './plugins.js';\n        //  Chart.register(plugins);\n        each(arg, item => {\n          // If there are mixed types in the loopable, make sure those are\n          // registered in correct registry\n          // Use case: (treemap exporting controller, elements etc)\n          //  import * as treemap from 'chartjs-chart-treemap.js';\n          //  Chart.register(treemap);\n\n          const itemReg = typedRegistry || this._getRegistryForType(item);\n          this._exec(method, itemReg, item);\n        });\n      }\n    });\n  }\n\n  /**\n\t * @private\n\t */\n  _exec(method, registry, component) {\n    const camelMethod = _capitalize(method);\n    call(component['before' + camelMethod], [], component); // beforeRegister / beforeUnregister\n    registry[method](component);\n    call(component['after' + camelMethod], [], component); // afterRegister / afterUnregister\n  }\n\n  /**\n\t * @private\n\t */\n  _getRegistryForType(type) {\n    for (let i = 0; i < this._typedRegistries.length; i++) {\n      const reg = this._typedRegistries[i];\n      if (reg.isForType(type)) {\n        return reg;\n      }\n    }\n    // plugins is the fallback registry\n    return this.plugins;\n  }\n\n  /**\n\t * @private\n\t */\n  _get(id, typedRegistry, type) {\n    const item = typedRegistry.get(id);\n    if (item === undefined) {\n      throw new Error('\"' + id + '\" is not a registered ' + type + '.');\n    }\n    return item;\n  }\n\n}\n\n// singleton instance\nexport default /* #__PURE__ */ new Registry();\n"
  },
  {
    "path": "src/core/core.scale.autoskip.js",
    "content": "import {isNullOrUndef, valueOrDefault} from '../helpers/helpers.core.js';\nimport {_factorize} from '../helpers/helpers.math.js';\n\n\n/**\n * @typedef { import('./core.controller.js').default } Chart\n * @typedef {{value:number | string, label?:string, major?:boolean, $context?:any}} Tick\n */\n\n/**\n * Returns a subset of ticks to be plotted to avoid overlapping labels.\n * @param {import('./core.scale.js').default} scale\n * @param {Tick[]} ticks\n * @return {Tick[]}\n * @private\n */\nexport function autoSkip(scale, ticks) {\n  const tickOpts = scale.options.ticks;\n  const determinedMaxTicks = determineMaxTicks(scale);\n  const ticksLimit = Math.min(tickOpts.maxTicksLimit || determinedMaxTicks, determinedMaxTicks);\n  const majorIndices = tickOpts.major.enabled ? getMajorIndices(ticks) : [];\n  const numMajorIndices = majorIndices.length;\n  const first = majorIndices[0];\n  const last = majorIndices[numMajorIndices - 1];\n  const newTicks = [];\n\n  // If there are too many major ticks to display them all\n  if (numMajorIndices > ticksLimit) {\n    skipMajors(ticks, newTicks, majorIndices, numMajorIndices / ticksLimit);\n    return newTicks;\n  }\n\n  const spacing = calculateSpacing(majorIndices, ticks, ticksLimit);\n\n  if (numMajorIndices > 0) {\n    let i, ilen;\n    const avgMajorSpacing = numMajorIndices > 1 ? Math.round((last - first) / (numMajorIndices - 1)) : null;\n    skip(ticks, newTicks, spacing, isNullOrUndef(avgMajorSpacing) ? 0 : first - avgMajorSpacing, first);\n    for (i = 0, ilen = numMajorIndices - 1; i < ilen; i++) {\n      skip(ticks, newTicks, spacing, majorIndices[i], majorIndices[i + 1]);\n    }\n    skip(ticks, newTicks, spacing, last, isNullOrUndef(avgMajorSpacing) ? ticks.length : last + avgMajorSpacing);\n    return newTicks;\n  }\n  skip(ticks, newTicks, spacing);\n  return newTicks;\n}\n\nfunction determineMaxTicks(scale) {\n  const offset = scale.options.offset;\n  const tickLength = scale._tickSize();\n  const maxScale = scale._length / tickLength + (offset ? 0 : 1);\n  const maxChart = scale._maxLength / tickLength;\n  return Math.floor(Math.min(maxScale, maxChart));\n}\n\n/**\n * @param {number[]} majorIndices\n * @param {Tick[]} ticks\n * @param {number} ticksLimit\n */\nfunction calculateSpacing(majorIndices, ticks, ticksLimit) {\n  const evenMajorSpacing = getEvenSpacing(majorIndices);\n  const spacing = ticks.length / ticksLimit;\n\n  // If the major ticks are evenly spaced apart, place the minor ticks\n  // so that they divide the major ticks into even chunks\n  if (!evenMajorSpacing) {\n    return Math.max(spacing, 1);\n  }\n\n  const factors = _factorize(evenMajorSpacing);\n  for (let i = 0, ilen = factors.length - 1; i < ilen; i++) {\n    const factor = factors[i];\n    if (factor > spacing) {\n      return factor;\n    }\n  }\n  return Math.max(spacing, 1);\n}\n\n/**\n * @param {Tick[]} ticks\n */\nfunction getMajorIndices(ticks) {\n  const result = [];\n  let i, ilen;\n  for (i = 0, ilen = ticks.length; i < ilen; i++) {\n    if (ticks[i].major) {\n      result.push(i);\n    }\n  }\n  return result;\n}\n\n/**\n * @param {Tick[]} ticks\n * @param {Tick[]} newTicks\n * @param {number[]} majorIndices\n * @param {number} spacing\n */\nfunction skipMajors(ticks, newTicks, majorIndices, spacing) {\n  let count = 0;\n  let next = majorIndices[0];\n  let i;\n\n  spacing = Math.ceil(spacing);\n  for (i = 0; i < ticks.length; i++) {\n    if (i === next) {\n      newTicks.push(ticks[i]);\n      count++;\n      next = majorIndices[count * spacing];\n    }\n  }\n}\n\n/**\n * @param {Tick[]} ticks\n * @param {Tick[]} newTicks\n * @param {number} spacing\n * @param {number} [majorStart]\n * @param {number} [majorEnd]\n */\nfunction skip(ticks, newTicks, spacing, majorStart, majorEnd) {\n  const start = valueOrDefault(majorStart, 0);\n  const end = Math.min(valueOrDefault(majorEnd, ticks.length), ticks.length);\n  let count = 0;\n  let length, i, next;\n\n  spacing = Math.ceil(spacing);\n  if (majorEnd) {\n    length = majorEnd - majorStart;\n    spacing = length / Math.floor(length / spacing);\n  }\n\n  next = start;\n\n  while (next < 0) {\n    count++;\n    next = Math.round(start + count * spacing);\n  }\n\n  for (i = Math.max(start, 0); i < end; i++) {\n    if (i === next) {\n      newTicks.push(ticks[i]);\n      count++;\n      next = Math.round(start + count * spacing);\n    }\n  }\n}\n\n\n/**\n * @param {number[]} arr\n */\nfunction getEvenSpacing(arr) {\n  const len = arr.length;\n  let i, diff;\n\n  if (len < 2) {\n    return false;\n  }\n\n  for (diff = arr[0], i = 1; i < len; ++i) {\n    if (arr[i] - arr[i - 1] !== diff) {\n      return false;\n    }\n  }\n  return diff;\n}\n"
  },
  {
    "path": "src/core/core.scale.defaults.js",
    "content": "import Ticks from './core.ticks.js';\n\nexport function applyScaleDefaults(defaults) {\n  defaults.set('scale', {\n    display: true,\n    offset: false,\n    reverse: false,\n    beginAtZero: false,\n\n    /**\n     * Scale boundary strategy (bypassed by min/max time options)\n     * - `data`: make sure data are fully visible, ticks outside are removed\n     * - `ticks`: make sure ticks are fully visible, data outside are truncated\n     * @see https://github.com/chartjs/Chart.js/pull/4556\n     * @since 3.0.0\n     */\n    bounds: 'ticks',\n\n    clip: true,\n\n    /**\n     * Addition grace added to max and reduced from min data value.\n     * @since 3.0.0\n     */\n    grace: 0,\n\n    // grid line settings\n    grid: {\n      display: true,\n      lineWidth: 1,\n      drawOnChartArea: true,\n      drawTicks: true,\n      tickLength: 8,\n      tickWidth: (_ctx, options) => options.lineWidth,\n      tickColor: (_ctx, options) => options.color,\n      offset: false,\n    },\n\n    border: {\n      display: true,\n      dash: [],\n      dashOffset: 0.0,\n      width: 1\n    },\n\n    // scale title\n    title: {\n      // display property\n      display: false,\n\n      // actual label\n      text: '',\n\n      // top/bottom padding\n      padding: {\n        top: 4,\n        bottom: 4\n      }\n    },\n\n    // label settings\n    ticks: {\n      minRotation: 0,\n      maxRotation: 50,\n      mirror: false,\n      textStrokeWidth: 0,\n      textStrokeColor: '',\n      padding: 3,\n      display: true,\n      autoSkip: true,\n      autoSkipPadding: 3,\n      labelOffset: 0,\n      // We pass through arrays to be rendered as multiline labels, we convert Others to strings here.\n      callback: Ticks.formatters.values,\n      minor: {},\n      major: {},\n      align: 'center',\n      crossAlign: 'near',\n\n      showLabelBackdrop: false,\n      backdropColor: 'rgba(255, 255, 255, 0.75)',\n      backdropPadding: 2,\n    }\n  });\n\n  defaults.route('scale.ticks', 'color', '', 'color');\n  defaults.route('scale.grid', 'color', '', 'borderColor');\n  defaults.route('scale.border', 'color', '', 'borderColor');\n  defaults.route('scale.title', 'color', '', 'color');\n\n  defaults.describe('scale', {\n    _fallback: false,\n    _scriptable: (name) => !name.startsWith('before') && !name.startsWith('after') && name !== 'callback' && name !== 'parser',\n    _indexable: (name) => name !== 'borderDash' && name !== 'tickBorderDash' && name !== 'dash',\n  });\n\n  defaults.describe('scales', {\n    _fallback: 'scale',\n  });\n\n  defaults.describe('scale.ticks', {\n    _scriptable: (name) => name !== 'backdropPadding' && name !== 'callback',\n    _indexable: (name) => name !== 'backdropPadding',\n  });\n}\n"
  },
  {
    "path": "src/core/core.scale.js",
    "content": "import Element from './core.element.js';\nimport {_alignPixel, _measureText, renderText, clipArea, unclipArea} from '../helpers/helpers.canvas.js';\nimport {callback as call, each, finiteOrDefault, isArray, isFinite, isNullOrUndef, isObject, valueOrDefault} from '../helpers/helpers.core.js';\nimport {toDegrees, toRadians, _int16Range, _limitValue, HALF_PI} from '../helpers/helpers.math.js';\nimport {_alignStartEnd, _toLeftRightCenter} from '../helpers/helpers.extras.js';\nimport {createContext, toFont, toPadding, _addGrace} from '../helpers/helpers.options.js';\nimport {autoSkip} from './core.scale.autoskip.js';\n\nconst reverseAlign = (align) => align === 'left' ? 'right' : align === 'right' ? 'left' : align;\nconst offsetFromEdge = (scale, edge, offset) => edge === 'top' || edge === 'left' ? scale[edge] + offset : scale[edge] - offset;\nconst getTicksLimit = (ticksLength, maxTicksLimit) => Math.min(maxTicksLimit || ticksLength, ticksLength);\n\n/**\n * @typedef { import('../types/index.js').Chart } Chart\n * @typedef {{value:number | string, label?:string, major?:boolean, $context?:any}} Tick\n */\n\n/**\n * Returns a new array containing numItems from arr\n * @param {any[]} arr\n * @param {number} numItems\n */\nfunction sample(arr, numItems) {\n  const result = [];\n  const increment = arr.length / numItems;\n  const len = arr.length;\n  let i = 0;\n\n  for (; i < len; i += increment) {\n    result.push(arr[Math.floor(i)]);\n  }\n  return result;\n}\n\n/**\n * @param {Scale} scale\n * @param {number} index\n * @param {boolean} offsetGridLines\n */\nfunction getPixelForGridLine(scale, index, offsetGridLines) {\n  const length = scale.ticks.length;\n  const validIndex = Math.min(index, length - 1);\n  const start = scale._startPixel;\n  const end = scale._endPixel;\n  const epsilon = 1e-6; // 1e-6 is margin in pixels for accumulated error.\n  let lineValue = scale.getPixelForTick(validIndex);\n  let offset;\n\n  if (offsetGridLines) {\n    if (length === 1) {\n      offset = Math.max(lineValue - start, end - lineValue);\n    } else if (index === 0) {\n      offset = (scale.getPixelForTick(1) - lineValue) / 2;\n    } else {\n      offset = (lineValue - scale.getPixelForTick(validIndex - 1)) / 2;\n    }\n    lineValue += validIndex < index ? offset : -offset;\n\n    // Return undefined if the pixel is out of the range\n    if (lineValue < start - epsilon || lineValue > end + epsilon) {\n      return;\n    }\n  }\n  return lineValue;\n}\n\n/**\n * @param {object} caches\n * @param {number} length\n */\nfunction garbageCollect(caches, length) {\n  each(caches, (cache) => {\n    const gc = cache.gc;\n    const gcLen = gc.length / 2;\n    let i;\n    if (gcLen > length) {\n      for (i = 0; i < gcLen; ++i) {\n        delete cache.data[gc[i]];\n      }\n      gc.splice(0, gcLen);\n    }\n  });\n}\n\n/**\n * @param {object} options\n */\nfunction getTickMarkLength(options) {\n  return options.drawTicks ? options.tickLength : 0;\n}\n\n/**\n * @param {object} options\n */\nfunction getTitleHeight(options, fallback) {\n  if (!options.display) {\n    return 0;\n  }\n\n  const font = toFont(options.font, fallback);\n  const padding = toPadding(options.padding);\n  const lines = isArray(options.text) ? options.text.length : 1;\n\n  return (lines * font.lineHeight) + padding.height;\n}\n\nfunction createScaleContext(parent, scale) {\n  return createContext(parent, {\n    scale,\n    type: 'scale'\n  });\n}\n\nfunction createTickContext(parent, index, tick) {\n  return createContext(parent, {\n    tick,\n    index,\n    type: 'tick'\n  });\n}\n\nfunction titleAlign(align, position, reverse) {\n  /** @type {CanvasTextAlign} */\n  let ret = _toLeftRightCenter(align);\n  if ((reverse && position !== 'right') || (!reverse && position === 'right')) {\n    ret = reverseAlign(ret);\n  }\n  return ret;\n}\n\nfunction titleArgs(scale, offset, position, align) {\n  const {top, left, bottom, right, chart} = scale;\n  const {chartArea, scales} = chart;\n  let rotation = 0;\n  let maxWidth, titleX, titleY;\n  const height = bottom - top;\n  const width = right - left;\n\n  if (scale.isHorizontal()) {\n    titleX = _alignStartEnd(align, left, right);\n\n    if (isObject(position)) {\n      const positionAxisID = Object.keys(position)[0];\n      const value = position[positionAxisID];\n      titleY = scales[positionAxisID].getPixelForValue(value) + height - offset;\n    } else if (position === 'center') {\n      titleY = (chartArea.bottom + chartArea.top) / 2 + height - offset;\n    } else {\n      titleY = offsetFromEdge(scale, position, offset);\n    }\n    maxWidth = right - left;\n  } else {\n    if (isObject(position)) {\n      const positionAxisID = Object.keys(position)[0];\n      const value = position[positionAxisID];\n      titleX = scales[positionAxisID].getPixelForValue(value) - width + offset;\n    } else if (position === 'center') {\n      titleX = (chartArea.left + chartArea.right) / 2 - width + offset;\n    } else {\n      titleX = offsetFromEdge(scale, position, offset);\n    }\n    titleY = _alignStartEnd(align, bottom, top);\n    rotation = position === 'left' ? -HALF_PI : HALF_PI;\n  }\n  return {titleX, titleY, maxWidth, rotation};\n}\n\nexport default class Scale extends Element {\n\n  // eslint-disable-next-line max-statements\n  constructor(cfg) {\n    super();\n\n    /** @type {string} */\n    this.id = cfg.id;\n    /** @type {string} */\n    this.type = cfg.type;\n    /** @type {any} */\n    this.options = undefined;\n    /** @type {CanvasRenderingContext2D} */\n    this.ctx = cfg.ctx;\n    /** @type {Chart} */\n    this.chart = cfg.chart;\n\n    // implements box\n    /** @type {number} */\n    this.top = undefined;\n    /** @type {number} */\n    this.bottom = undefined;\n    /** @type {number} */\n    this.left = undefined;\n    /** @type {number} */\n    this.right = undefined;\n    /** @type {number} */\n    this.width = undefined;\n    /** @type {number} */\n    this.height = undefined;\n    this._margins = {\n      left: 0,\n      right: 0,\n      top: 0,\n      bottom: 0\n    };\n    /** @type {number} */\n    this.maxWidth = undefined;\n    /** @type {number} */\n    this.maxHeight = undefined;\n    /** @type {number} */\n    this.paddingTop = undefined;\n    /** @type {number} */\n    this.paddingBottom = undefined;\n    /** @type {number} */\n    this.paddingLeft = undefined;\n    /** @type {number} */\n    this.paddingRight = undefined;\n\n    // scale-specific properties\n    /** @type {string=} */\n    this.axis = undefined;\n    /** @type {number=} */\n    this.labelRotation = undefined;\n    this.min = undefined;\n    this.max = undefined;\n    this._range = undefined;\n    /** @type {Tick[]} */\n    this.ticks = [];\n    /** @type {object[]|null} */\n    this._gridLineItems = null;\n    /** @type {object[]|null} */\n    this._labelItems = null;\n    /** @type {object|null} */\n    this._labelSizes = null;\n    this._length = 0;\n    this._maxLength = 0;\n    this._longestTextCache = {};\n    /** @type {number} */\n    this._startPixel = undefined;\n    /** @type {number} */\n    this._endPixel = undefined;\n    this._reversePixels = false;\n    this._userMax = undefined;\n    this._userMin = undefined;\n    this._suggestedMax = undefined;\n    this._suggestedMin = undefined;\n    this._ticksLength = 0;\n    this._borderValue = 0;\n    this._cache = {};\n    this._dataLimitsCached = false;\n    this.$context = undefined;\n  }\n\n  /**\n\t * @param {any} options\n\t * @since 3.0\n\t */\n  init(options) {\n    this.options = options.setContext(this.getContext());\n\n    this.axis = options.axis;\n\n    // parse min/max value, so we can properly determine min/max for other scales\n    this._userMin = this.parse(options.min);\n    this._userMax = this.parse(options.max);\n    this._suggestedMin = this.parse(options.suggestedMin);\n    this._suggestedMax = this.parse(options.suggestedMax);\n  }\n\n  /**\n\t * Parse a supported input value to internal representation.\n\t * @param {*} raw\n\t * @param {number} [index]\n\t * @since 3.0\n\t */\n  parse(raw, index) { // eslint-disable-line no-unused-vars\n    return raw;\n  }\n\n  /**\n\t * @return {{min: number, max: number, minDefined: boolean, maxDefined: boolean}}\n\t * @protected\n\t * @since 3.0\n\t */\n  getUserBounds() {\n    let {_userMin, _userMax, _suggestedMin, _suggestedMax} = this;\n    _userMin = finiteOrDefault(_userMin, Number.POSITIVE_INFINITY);\n    _userMax = finiteOrDefault(_userMax, Number.NEGATIVE_INFINITY);\n    _suggestedMin = finiteOrDefault(_suggestedMin, Number.POSITIVE_INFINITY);\n    _suggestedMax = finiteOrDefault(_suggestedMax, Number.NEGATIVE_INFINITY);\n    return {\n      min: finiteOrDefault(_userMin, _suggestedMin),\n      max: finiteOrDefault(_userMax, _suggestedMax),\n      minDefined: isFinite(_userMin),\n      maxDefined: isFinite(_userMax)\n    };\n  }\n\n  /**\n\t * @param {boolean} canStack\n\t * @return {{min: number, max: number}}\n\t * @protected\n\t * @since 3.0\n\t */\n  getMinMax(canStack) {\n    let {min, max, minDefined, maxDefined} = this.getUserBounds();\n    let range;\n\n    if (minDefined && maxDefined) {\n      return {min, max};\n    }\n\n    const metas = this.getMatchingVisibleMetas();\n    for (let i = 0, ilen = metas.length; i < ilen; ++i) {\n      range = metas[i].controller.getMinMax(this, canStack);\n      if (!minDefined) {\n        min = Math.min(min, range.min);\n      }\n      if (!maxDefined) {\n        max = Math.max(max, range.max);\n      }\n    }\n\n    // Make sure min <= max when only min or max is defined by user and the data is outside that range\n    min = maxDefined && min > max ? max : min;\n    max = minDefined && min > max ? min : max;\n\n    return {\n      min: finiteOrDefault(min, finiteOrDefault(max, min)),\n      max: finiteOrDefault(max, finiteOrDefault(min, max))\n    };\n  }\n\n  /**\n\t * Get the padding needed for the scale\n\t * @return {{top: number, left: number, bottom: number, right: number}} the necessary padding\n\t * @private\n\t */\n  getPadding() {\n    return {\n      left: this.paddingLeft || 0,\n      top: this.paddingTop || 0,\n      right: this.paddingRight || 0,\n      bottom: this.paddingBottom || 0\n    };\n  }\n\n  /**\n\t * Returns the scale tick objects\n\t * @return {Tick[]}\n\t * @since 2.7\n\t */\n  getTicks() {\n    return this.ticks;\n  }\n\n  /**\n\t * @return {string[]}\n\t */\n  getLabels() {\n    const data = this.chart.data;\n    return this.options.labels || (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels || [];\n  }\n\n  /**\n   * @return {import('../types.js').LabelItem[]}\n   */\n  getLabelItems(chartArea = this.chart.chartArea) {\n    const items = this._labelItems || (this._labelItems = this._computeLabelItems(chartArea));\n    return items;\n  }\n\n  // When a new layout is created, reset the data limits cache\n  beforeLayout() {\n    this._cache = {};\n    this._dataLimitsCached = false;\n  }\n\n  // These methods are ordered by lifecycle. Utilities then follow.\n  // Any function defined here is inherited by all scale types.\n  // Any function can be extended by the scale type\n\n  beforeUpdate() {\n    call(this.options.beforeUpdate, [this]);\n  }\n\n  /**\n\t * @param {number} maxWidth - the max width in pixels\n\t * @param {number} maxHeight - the max height in pixels\n\t * @param {{top: number, left: number, bottom: number, right: number}} margins - the space between the edge of the other scales and edge of the chart\n\t *   This space comes from two sources:\n\t *     - padding - space that's required to show the labels at the edges of the scale\n\t *     - thickness of scales or legends in another orientation\n\t */\n  update(maxWidth, maxHeight, margins) {\n    const {beginAtZero, grace, ticks: tickOpts} = this.options;\n    const sampleSize = tickOpts.sampleSize;\n\n    // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)\n    this.beforeUpdate();\n\n    // Absorb the master measurements\n    this.maxWidth = maxWidth;\n    this.maxHeight = maxHeight;\n    this._margins = margins = Object.assign({\n      left: 0,\n      right: 0,\n      top: 0,\n      bottom: 0\n    }, margins);\n\n    this.ticks = null;\n    this._labelSizes = null;\n    this._gridLineItems = null;\n    this._labelItems = null;\n\n    // Dimensions\n    this.beforeSetDimensions();\n    this.setDimensions();\n    this.afterSetDimensions();\n\n    this._maxLength = this.isHorizontal()\n      ? this.width + margins.left + margins.right\n      : this.height + margins.top + margins.bottom;\n\n    // Data min/max\n    if (!this._dataLimitsCached) {\n      this.beforeDataLimits();\n      this.determineDataLimits();\n      this.afterDataLimits();\n      this._range = _addGrace(this, grace, beginAtZero);\n      this._dataLimitsCached = true;\n    }\n\n    this.beforeBuildTicks();\n\n    this.ticks = this.buildTicks() || [];\n\n    // Allow modification of ticks in callback.\n    this.afterBuildTicks();\n\n    // Compute tick rotation and fit using a sampled subset of labels\n    // We generally don't need to compute the size of every single label for determining scale size\n    const samplingEnabled = sampleSize < this.ticks.length;\n    this._convertTicksToLabels(samplingEnabled ? sample(this.ticks, sampleSize) : this.ticks);\n\n    // configure is called twice, once here, once from core.controller.updateLayout.\n    // Here we haven't been positioned yet, but dimensions are correct.\n    // Variables set in configure are needed for calculateLabelRotation, and\n    // it's ok that coordinates are not correct there, only dimensions matter.\n    this.configure();\n\n    // Tick Rotation\n    this.beforeCalculateLabelRotation();\n    this.calculateLabelRotation(); // Preconditions: number of ticks and sizes of largest labels must be calculated beforehand\n    this.afterCalculateLabelRotation();\n\n    // Auto-skip\n    if (tickOpts.display && (tickOpts.autoSkip || tickOpts.source === 'auto')) {\n      this.ticks = autoSkip(this, this.ticks);\n      this._labelSizes = null;\n      this.afterAutoSkip();\n    }\n\n    if (samplingEnabled) {\n      // Generate labels using all non-skipped ticks\n      this._convertTicksToLabels(this.ticks);\n    }\n\n    this.beforeFit();\n    this.fit(); // Preconditions: label rotation and label sizes must be calculated beforehand\n    this.afterFit();\n\n    // IMPORTANT: after this point, we consider that `this.ticks` will NEVER change!\n\n    this.afterUpdate();\n  }\n\n  /**\n\t * @protected\n\t */\n  configure() {\n    let reversePixels = this.options.reverse;\n    let startPixel, endPixel;\n\n    if (this.isHorizontal()) {\n      startPixel = this.left;\n      endPixel = this.right;\n    } else {\n      startPixel = this.top;\n      endPixel = this.bottom;\n      // by default vertical scales are from bottom to top, so pixels are reversed\n      reversePixels = !reversePixels;\n    }\n    this._startPixel = startPixel;\n    this._endPixel = endPixel;\n    this._reversePixels = reversePixels;\n    this._length = endPixel - startPixel;\n    this._alignToPixels = this.options.alignToPixels;\n  }\n\n  afterUpdate() {\n    call(this.options.afterUpdate, [this]);\n  }\n\n  //\n\n  beforeSetDimensions() {\n    call(this.options.beforeSetDimensions, [this]);\n  }\n  setDimensions() {\n    // Set the unconstrained dimension before label rotation\n    if (this.isHorizontal()) {\n      // Reset position before calculating rotation\n      this.width = this.maxWidth;\n      this.left = 0;\n      this.right = this.width;\n    } else {\n      this.height = this.maxHeight;\n\n      // Reset position before calculating rotation\n      this.top = 0;\n      this.bottom = this.height;\n    }\n\n    // Reset padding\n    this.paddingLeft = 0;\n    this.paddingTop = 0;\n    this.paddingRight = 0;\n    this.paddingBottom = 0;\n  }\n  afterSetDimensions() {\n    call(this.options.afterSetDimensions, [this]);\n  }\n\n  _callHooks(name) {\n    this.chart.notifyPlugins(name, this.getContext());\n    call(this.options[name], [this]);\n  }\n\n  // Data limits\n  beforeDataLimits() {\n    this._callHooks('beforeDataLimits');\n  }\n  determineDataLimits() {}\n  afterDataLimits() {\n    this._callHooks('afterDataLimits');\n  }\n\n  //\n  beforeBuildTicks() {\n    this._callHooks('beforeBuildTicks');\n  }\n  /**\n\t * @return {object[]} the ticks\n\t */\n  buildTicks() {\n    return [];\n  }\n  afterBuildTicks() {\n    this._callHooks('afterBuildTicks');\n  }\n\n  beforeTickToLabelConversion() {\n    call(this.options.beforeTickToLabelConversion, [this]);\n  }\n  /**\n\t * Convert ticks to label strings\n\t * @param {Tick[]} ticks\n\t */\n  generateTickLabels(ticks) {\n    const tickOpts = this.options.ticks;\n    let i, ilen, tick;\n    for (i = 0, ilen = ticks.length; i < ilen; i++) {\n      tick = ticks[i];\n      tick.label = call(tickOpts.callback, [tick.value, i, ticks], this);\n    }\n  }\n  afterTickToLabelConversion() {\n    call(this.options.afterTickToLabelConversion, [this]);\n  }\n\n  //\n\n  beforeCalculateLabelRotation() {\n    call(this.options.beforeCalculateLabelRotation, [this]);\n  }\n  calculateLabelRotation() {\n    const options = this.options;\n    const tickOpts = options.ticks;\n    const numTicks = getTicksLimit(this.ticks.length, options.ticks.maxTicksLimit);\n    const minRotation = tickOpts.minRotation || 0;\n    const maxRotation = tickOpts.maxRotation;\n    let labelRotation = minRotation;\n    let tickWidth, maxHeight, maxLabelDiagonal;\n\n    if (!this._isVisible() || !tickOpts.display || minRotation >= maxRotation || numTicks <= 1 || !this.isHorizontal()) {\n      this.labelRotation = minRotation;\n      return;\n    }\n\n    const labelSizes = this._getLabelSizes();\n    const maxLabelWidth = labelSizes.widest.width;\n    const maxLabelHeight = labelSizes.highest.height;\n\n    // Estimate the width of each grid based on the canvas width, the maximum\n    // label width and the number of tick intervals\n    const maxWidth = _limitValue(this.chart.width - maxLabelWidth, 0, this.maxWidth);\n    tickWidth = options.offset ? this.maxWidth / numTicks : maxWidth / (numTicks - 1);\n\n    // Allow 3 pixels x2 padding either side for label readability\n    if (maxLabelWidth + 6 > tickWidth) {\n      tickWidth = maxWidth / (numTicks - (options.offset ? 0.5 : 1));\n      maxHeight = this.maxHeight - getTickMarkLength(options.grid)\n\t\t\t\t- tickOpts.padding - getTitleHeight(options.title, this.chart.options.font);\n      maxLabelDiagonal = Math.sqrt(maxLabelWidth * maxLabelWidth + maxLabelHeight * maxLabelHeight);\n      labelRotation = toDegrees(Math.min(\n        Math.asin(_limitValue((labelSizes.highest.height + 6) / tickWidth, -1, 1)),\n        Math.asin(_limitValue(maxHeight / maxLabelDiagonal, -1, 1)) - Math.asin(_limitValue(maxLabelHeight / maxLabelDiagonal, -1, 1))\n      ));\n      labelRotation = Math.max(minRotation, Math.min(maxRotation, labelRotation));\n    }\n\n    this.labelRotation = labelRotation;\n  }\n  afterCalculateLabelRotation() {\n    call(this.options.afterCalculateLabelRotation, [this]);\n  }\n  afterAutoSkip() {}\n\n  //\n\n  beforeFit() {\n    call(this.options.beforeFit, [this]);\n  }\n  fit() {\n    // Reset\n    const minSize = {\n      width: 0,\n      height: 0\n    };\n\n    const {chart, options: {ticks: tickOpts, title: titleOpts, grid: gridOpts}} = this;\n    const display = this._isVisible();\n    const isHorizontal = this.isHorizontal();\n\n    if (display) {\n      const titleHeight = getTitleHeight(titleOpts, chart.options.font);\n      if (isHorizontal) {\n        minSize.width = this.maxWidth;\n        minSize.height = getTickMarkLength(gridOpts) + titleHeight;\n      } else {\n        minSize.height = this.maxHeight; // fill all the height\n        minSize.width = getTickMarkLength(gridOpts) + titleHeight;\n      }\n\n      // Don't bother fitting the ticks if we are not showing the labels\n      if (tickOpts.display && this.ticks.length) {\n        const {first, last, widest, highest} = this._getLabelSizes();\n        const tickPadding = tickOpts.padding * 2;\n        const angleRadians = toRadians(this.labelRotation);\n        const cos = Math.cos(angleRadians);\n        const sin = Math.sin(angleRadians);\n\n        if (isHorizontal) {\n        // A horizontal axis is more constrained by the height.\n          const labelHeight = tickOpts.mirror ? 0 : sin * widest.width + cos * highest.height;\n          minSize.height = Math.min(this.maxHeight, minSize.height + labelHeight + tickPadding);\n        } else {\n        // A vertical axis is more constrained by the width. Labels are the\n        // dominant factor here, so get that length first and account for padding\n          const labelWidth = tickOpts.mirror ? 0 : cos * widest.width + sin * highest.height;\n\n          minSize.width = Math.min(this.maxWidth, minSize.width + labelWidth + tickPadding);\n        }\n        this._calculatePadding(first, last, sin, cos);\n      }\n    }\n\n    this._handleMargins();\n\n    if (isHorizontal) {\n      this.width = this._length = chart.width - this._margins.left - this._margins.right;\n      this.height = minSize.height;\n    } else {\n      this.width = minSize.width;\n      this.height = this._length = chart.height - this._margins.top - this._margins.bottom;\n    }\n  }\n\n  _calculatePadding(first, last, sin, cos) {\n    const {ticks: {align, padding}, position} = this.options;\n    const isRotated = this.labelRotation !== 0;\n    const labelsBelowTicks = position !== 'top' && this.axis === 'x';\n\n    if (this.isHorizontal()) {\n      const offsetLeft = this.getPixelForTick(0) - this.left;\n      const offsetRight = this.right - this.getPixelForTick(this.ticks.length - 1);\n      let paddingLeft = 0;\n      let paddingRight = 0;\n\n      // Ensure that our ticks are always inside the canvas. When rotated, ticks are right aligned\n      // which means that the right padding is dominated by the font height\n      if (isRotated) {\n        if (labelsBelowTicks) {\n          paddingLeft = cos * first.width;\n          paddingRight = sin * last.height;\n        } else {\n          paddingLeft = sin * first.height;\n          paddingRight = cos * last.width;\n        }\n      } else if (align === 'start') {\n        paddingRight = last.width;\n      } else if (align === 'end') {\n        paddingLeft = first.width;\n      } else if (align !== 'inner') {\n        paddingLeft = first.width / 2;\n        paddingRight = last.width / 2;\n      }\n\n      // Adjust padding taking into account changes in offsets\n      this.paddingLeft = Math.max((paddingLeft - offsetLeft + padding) * this.width / (this.width - offsetLeft), 0);\n      this.paddingRight = Math.max((paddingRight - offsetRight + padding) * this.width / (this.width - offsetRight), 0);\n    } else {\n      let paddingTop = last.height / 2;\n      let paddingBottom = first.height / 2;\n\n      if (align === 'start') {\n        paddingTop = 0;\n        paddingBottom = first.height;\n      } else if (align === 'end') {\n        paddingTop = last.height;\n        paddingBottom = 0;\n      }\n\n      this.paddingTop = paddingTop + padding;\n      this.paddingBottom = paddingBottom + padding;\n    }\n  }\n\n  /**\n\t * Handle margins and padding interactions\n\t * @private\n\t */\n  _handleMargins() {\n    if (this._margins) {\n      this._margins.left = Math.max(this.paddingLeft, this._margins.left);\n      this._margins.top = Math.max(this.paddingTop, this._margins.top);\n      this._margins.right = Math.max(this.paddingRight, this._margins.right);\n      this._margins.bottom = Math.max(this.paddingBottom, this._margins.bottom);\n    }\n  }\n\n  afterFit() {\n    call(this.options.afterFit, [this]);\n  }\n\n  // Shared Methods\n  /**\n\t * @return {boolean}\n\t */\n  isHorizontal() {\n    const {axis, position} = this.options;\n    return position === 'top' || position === 'bottom' || axis === 'x';\n  }\n  /**\n\t * @return {boolean}\n\t */\n  isFullSize() {\n    return this.options.fullSize;\n  }\n\n  /**\n\t * @param {Tick[]} ticks\n\t * @private\n\t */\n  _convertTicksToLabels(ticks) {\n    this.beforeTickToLabelConversion();\n\n    this.generateTickLabels(ticks);\n\n    // Ticks should be skipped when callback returns null or undef, so lets remove those.\n    let i, ilen;\n    for (i = 0, ilen = ticks.length; i < ilen; i++) {\n      if (isNullOrUndef(ticks[i].label)) {\n        ticks.splice(i, 1);\n        ilen--;\n        i--;\n      }\n    }\n\n    this.afterTickToLabelConversion();\n  }\n\n  /**\n\t * @return {{ first: object, last: object, widest: object, highest: object, widths: Array, heights: array }}\n\t * @private\n\t */\n  _getLabelSizes() {\n    let labelSizes = this._labelSizes;\n\n    if (!labelSizes) {\n      const sampleSize = this.options.ticks.sampleSize;\n      let ticks = this.ticks;\n      if (sampleSize < ticks.length) {\n        ticks = sample(ticks, sampleSize);\n      }\n\n      this._labelSizes = labelSizes = this._computeLabelSizes(ticks, ticks.length, this.options.ticks.maxTicksLimit);\n    }\n\n    return labelSizes;\n  }\n\n  /**\n\t * Returns {width, height, offset} objects for the first, last, widest, highest tick\n\t * labels where offset indicates the anchor point offset from the top in pixels.\n\t * @return {{ first: object, last: object, widest: object, highest: object, widths: Array, heights: array }}\n\t * @private\n\t */\n  _computeLabelSizes(ticks, length, maxTicksLimit) {\n    const {ctx, _longestTextCache: caches} = this;\n    const widths = [];\n    const heights = [];\n    const increment = Math.floor(length / getTicksLimit(length, maxTicksLimit));\n    let widestLabelSize = 0;\n    let highestLabelSize = 0;\n    let i, j, jlen, label, tickFont, fontString, cache, lineHeight, width, height, nestedLabel;\n\n    for (i = 0; i < length; i += increment) {\n      label = ticks[i].label;\n      tickFont = this._resolveTickFontOptions(i);\n      ctx.font = fontString = tickFont.string;\n      cache = caches[fontString] = caches[fontString] || {data: {}, gc: []};\n      lineHeight = tickFont.lineHeight;\n      width = height = 0;\n      // Undefined labels and arrays should not be measured\n      if (!isNullOrUndef(label) && !isArray(label)) {\n        width = _measureText(ctx, cache.data, cache.gc, width, label);\n        height = lineHeight;\n      } else if (isArray(label)) {\n        // if it is an array let's measure each element\n        for (j = 0, jlen = label.length; j < jlen; ++j) {\n          nestedLabel = /** @type {string} */ (label[j]);\n          // Undefined labels and arrays should not be measured\n          if (!isNullOrUndef(nestedLabel) && !isArray(nestedLabel)) {\n            width = _measureText(ctx, cache.data, cache.gc, width, nestedLabel);\n            height += lineHeight;\n          }\n        }\n      }\n      widths.push(width);\n      heights.push(height);\n      widestLabelSize = Math.max(width, widestLabelSize);\n      highestLabelSize = Math.max(height, highestLabelSize);\n    }\n    garbageCollect(caches, length);\n\n    const widest = widths.indexOf(widestLabelSize);\n    const highest = heights.indexOf(highestLabelSize);\n\n    const valueAt = (idx) => ({width: widths[idx] || 0, height: heights[idx] || 0});\n\n    return {\n      first: valueAt(0),\n      last: valueAt(length - 1),\n      widest: valueAt(widest),\n      highest: valueAt(highest),\n      widths,\n      heights,\n    };\n  }\n\n  /**\n\t * Used to get the label to display in the tooltip for the given value\n\t * @param {*} value\n\t * @return {string}\n\t */\n  getLabelForValue(value) {\n    return value;\n  }\n\n  /**\n\t * Returns the location of the given data point. Value can either be an index or a numerical value\n\t * The coordinate (0, 0) is at the upper-left corner of the canvas\n\t * @param {*} value\n\t * @param {number} [index]\n\t * @return {number}\n\t */\n  getPixelForValue(value, index) { // eslint-disable-line no-unused-vars\n    return NaN;\n  }\n\n  /**\n\t * Used to get the data value from a given pixel. This is the inverse of getPixelForValue\n\t * The coordinate (0, 0) is at the upper-left corner of the canvas\n\t * @param {number} pixel\n\t * @return {*}\n\t */\n  getValueForPixel(pixel) {} // eslint-disable-line no-unused-vars\n\n  /**\n\t * Returns the location of the tick at the given index\n\t * The coordinate (0, 0) is at the upper-left corner of the canvas\n\t * @param {number} index\n\t * @return {number}\n\t */\n  getPixelForTick(index) {\n    const ticks = this.ticks;\n    if (index < 0 || index > ticks.length - 1) {\n      return null;\n    }\n    return this.getPixelForValue(ticks[index].value);\n  }\n\n  /**\n\t * Utility for getting the pixel location of a percentage of scale\n\t * The coordinate (0, 0) is at the upper-left corner of the canvas\n\t * @param {number} decimal\n\t * @return {number}\n\t */\n  getPixelForDecimal(decimal) {\n    if (this._reversePixels) {\n      decimal = 1 - decimal;\n    }\n\n    const pixel = this._startPixel + decimal * this._length;\n    return _int16Range(this._alignToPixels ? _alignPixel(this.chart, pixel, 0) : pixel);\n  }\n\n  /**\n\t * @param {number} pixel\n\t * @return {number}\n\t */\n  getDecimalForPixel(pixel) {\n    const decimal = (pixel - this._startPixel) / this._length;\n    return this._reversePixels ? 1 - decimal : decimal;\n  }\n\n  /**\n\t * Returns the pixel for the minimum chart value\n\t * The coordinate (0, 0) is at the upper-left corner of the canvas\n\t * @return {number}\n\t */\n  getBasePixel() {\n    return this.getPixelForValue(this.getBaseValue());\n  }\n\n  /**\n\t * @return {number}\n\t */\n  getBaseValue() {\n    const {min, max} = this;\n\n    return min < 0 && max < 0 ? max :\n      min > 0 && max > 0 ? min :\n      0;\n  }\n\n  /**\n\t * @protected\n\t */\n  getContext(index) {\n    const ticks = this.ticks || [];\n\n    if (index >= 0 && index < ticks.length) {\n      const tick = ticks[index];\n      return tick.$context ||\n\t\t\t\t(tick.$context = createTickContext(this.getContext(), index, tick));\n    }\n    return this.$context ||\n\t\t\t(this.$context = createScaleContext(this.chart.getContext(), this));\n  }\n\n  /**\n\t * @return {number}\n\t * @private\n\t */\n  _tickSize() {\n    const optionTicks = this.options.ticks;\n\n    // Calculate space needed by label in axis direction.\n    const rot = toRadians(this.labelRotation);\n    const cos = Math.abs(Math.cos(rot));\n    const sin = Math.abs(Math.sin(rot));\n\n    const labelSizes = this._getLabelSizes();\n    const padding = optionTicks.autoSkipPadding || 0;\n    const w = labelSizes ? labelSizes.widest.width + padding : 0;\n    const h = labelSizes ? labelSizes.highest.height + padding : 0;\n\n    // Calculate space needed for 1 tick in axis direction.\n    return this.isHorizontal()\n      ? h * cos > w * sin ? w / cos : h / sin\n      : h * sin < w * cos ? h / cos : w / sin;\n  }\n\n  /**\n\t * @return {boolean}\n\t * @private\n\t */\n  _isVisible() {\n    const display = this.options.display;\n\n    if (display !== 'auto') {\n      return !!display;\n    }\n\n    return this.getMatchingVisibleMetas().length > 0;\n  }\n\n  /**\n\t * @private\n\t */\n  _computeGridLineItems(chartArea) {\n    const axis = this.axis;\n    const chart = this.chart;\n    const options = this.options;\n    const {grid, position, border} = options;\n    const offset = grid.offset;\n    const isHorizontal = this.isHorizontal();\n    const ticks = this.ticks;\n    const ticksLength = ticks.length + (offset ? 1 : 0);\n    const tl = getTickMarkLength(grid);\n    const items = [];\n\n    const borderOpts = border.setContext(this.getContext());\n    const axisWidth = borderOpts.display ? borderOpts.width : 0;\n    const axisHalfWidth = axisWidth / 2;\n    const alignBorderValue = function(pixel) {\n      return _alignPixel(chart, pixel, axisWidth);\n    };\n    let borderValue, i, lineValue, alignedLineValue;\n    let tx1, ty1, tx2, ty2, x1, y1, x2, y2;\n\n    if (position === 'top') {\n      borderValue = alignBorderValue(this.bottom);\n      ty1 = this.bottom - tl;\n      ty2 = borderValue - axisHalfWidth;\n      y1 = alignBorderValue(chartArea.top) + axisHalfWidth;\n      y2 = chartArea.bottom;\n    } else if (position === 'bottom') {\n      borderValue = alignBorderValue(this.top);\n      y1 = chartArea.top;\n      y2 = alignBorderValue(chartArea.bottom) - axisHalfWidth;\n      ty1 = borderValue + axisHalfWidth;\n      ty2 = this.top + tl;\n    } else if (position === 'left') {\n      borderValue = alignBorderValue(this.right);\n      tx1 = this.right - tl;\n      tx2 = borderValue - axisHalfWidth;\n      x1 = alignBorderValue(chartArea.left) + axisHalfWidth;\n      x2 = chartArea.right;\n    } else if (position === 'right') {\n      borderValue = alignBorderValue(this.left);\n      x1 = chartArea.left;\n      x2 = alignBorderValue(chartArea.right) - axisHalfWidth;\n      tx1 = borderValue + axisHalfWidth;\n      tx2 = this.left + tl;\n    } else if (axis === 'x') {\n      if (position === 'center') {\n        borderValue = alignBorderValue((chartArea.top + chartArea.bottom) / 2 + 0.5);\n      } else if (isObject(position)) {\n        const positionAxisID = Object.keys(position)[0];\n        const value = position[positionAxisID];\n        borderValue = alignBorderValue(this.chart.scales[positionAxisID].getPixelForValue(value));\n      }\n\n      y1 = chartArea.top;\n      y2 = chartArea.bottom;\n      ty1 = borderValue + axisHalfWidth;\n      ty2 = ty1 + tl;\n    } else if (axis === 'y') {\n      if (position === 'center') {\n        borderValue = alignBorderValue((chartArea.left + chartArea.right) / 2);\n      } else if (isObject(position)) {\n        const positionAxisID = Object.keys(position)[0];\n        const value = position[positionAxisID];\n        borderValue = alignBorderValue(this.chart.scales[positionAxisID].getPixelForValue(value));\n      }\n\n      tx1 = borderValue - axisHalfWidth;\n      tx2 = tx1 - tl;\n      x1 = chartArea.left;\n      x2 = chartArea.right;\n    }\n\n    const limit = valueOrDefault(options.ticks.maxTicksLimit, ticksLength);\n    const step = Math.max(1, Math.ceil(ticksLength / limit));\n    for (i = 0; i < ticksLength; i += step) {\n      const context = this.getContext(i);\n      const optsAtIndex = grid.setContext(context);\n      const optsAtIndexBorder = border.setContext(context);\n\n      const lineWidth = optsAtIndex.lineWidth;\n      const lineColor = optsAtIndex.color;\n      const borderDash = optsAtIndexBorder.dash || [];\n      const borderDashOffset = optsAtIndexBorder.dashOffset;\n\n      const tickWidth = optsAtIndex.tickWidth;\n      const tickColor = optsAtIndex.tickColor;\n      const tickBorderDash = optsAtIndex.tickBorderDash || [];\n      const tickBorderDashOffset = optsAtIndex.tickBorderDashOffset;\n\n      lineValue = getPixelForGridLine(this, i, offset);\n\n      // Skip if the pixel is out of the range\n      if (lineValue === undefined) {\n        continue;\n      }\n\n      alignedLineValue = _alignPixel(chart, lineValue, lineWidth);\n\n      if (isHorizontal) {\n        tx1 = tx2 = x1 = x2 = alignedLineValue;\n      } else {\n        ty1 = ty2 = y1 = y2 = alignedLineValue;\n      }\n\n      items.push({\n        tx1,\n        ty1,\n        tx2,\n        ty2,\n        x1,\n        y1,\n        x2,\n        y2,\n        width: lineWidth,\n        color: lineColor,\n        borderDash,\n        borderDashOffset,\n        tickWidth,\n        tickColor,\n        tickBorderDash,\n        tickBorderDashOffset,\n      });\n    }\n\n    this._ticksLength = ticksLength;\n    this._borderValue = borderValue;\n\n    return items;\n  }\n\n  /**\n\t * @private\n\t */\n  _computeLabelItems(chartArea) {\n    const axis = this.axis;\n    const options = this.options;\n    const {position, ticks: optionTicks} = options;\n    const isHorizontal = this.isHorizontal();\n    const ticks = this.ticks;\n    const {align, crossAlign, padding, mirror} = optionTicks;\n    const tl = getTickMarkLength(options.grid);\n    const tickAndPadding = tl + padding;\n    const hTickAndPadding = mirror ? -padding : tickAndPadding;\n    const rotation = -toRadians(this.labelRotation);\n    const items = [];\n    let i, ilen, tick, label, x, y, textAlign, pixel, font, lineHeight, lineCount, textOffset;\n    let textBaseline = 'middle';\n\n    if (position === 'top') {\n      y = this.bottom - hTickAndPadding;\n      textAlign = this._getXAxisLabelAlignment();\n    } else if (position === 'bottom') {\n      y = this.top + hTickAndPadding;\n      textAlign = this._getXAxisLabelAlignment();\n    } else if (position === 'left') {\n      const ret = this._getYAxisLabelAlignment(tl);\n      textAlign = ret.textAlign;\n      x = ret.x;\n    } else if (position === 'right') {\n      const ret = this._getYAxisLabelAlignment(tl);\n      textAlign = ret.textAlign;\n      x = ret.x;\n    } else if (axis === 'x') {\n      if (position === 'center') {\n        y = ((chartArea.top + chartArea.bottom) / 2) + tickAndPadding;\n      } else if (isObject(position)) {\n        const positionAxisID = Object.keys(position)[0];\n        const value = position[positionAxisID];\n        y = this.chart.scales[positionAxisID].getPixelForValue(value) + tickAndPadding;\n      }\n      textAlign = this._getXAxisLabelAlignment();\n    } else if (axis === 'y') {\n      if (position === 'center') {\n        x = ((chartArea.left + chartArea.right) / 2) - tickAndPadding;\n      } else if (isObject(position)) {\n        const positionAxisID = Object.keys(position)[0];\n        const value = position[positionAxisID];\n        x = this.chart.scales[positionAxisID].getPixelForValue(value);\n      }\n      textAlign = this._getYAxisLabelAlignment(tl).textAlign;\n    }\n\n    if (axis === 'y') {\n      if (align === 'start') {\n        textBaseline = 'top';\n      } else if (align === 'end') {\n        textBaseline = 'bottom';\n      }\n    }\n\n    const labelSizes = this._getLabelSizes();\n    for (i = 0, ilen = ticks.length; i < ilen; ++i) {\n      tick = ticks[i];\n      label = tick.label;\n\n      const optsAtIndex = optionTicks.setContext(this.getContext(i));\n      pixel = this.getPixelForTick(i) + optionTicks.labelOffset;\n      font = this._resolveTickFontOptions(i);\n      lineHeight = font.lineHeight;\n      lineCount = isArray(label) ? label.length : 1;\n      const halfCount = lineCount / 2;\n      const color = optsAtIndex.color;\n      const strokeColor = optsAtIndex.textStrokeColor;\n      const strokeWidth = optsAtIndex.textStrokeWidth;\n      let tickTextAlign = textAlign;\n\n      if (isHorizontal) {\n        x = pixel;\n\n        if (textAlign === 'inner') {\n          if (i === ilen - 1) {\n            tickTextAlign = !this.options.reverse ? 'right' : 'left';\n          } else if (i === 0) {\n            tickTextAlign = !this.options.reverse ? 'left' : 'right';\n          } else {\n            tickTextAlign = 'center';\n          }\n        }\n\n        if (position === 'top') {\n          if (crossAlign === 'near' || rotation !== 0) {\n            textOffset = -lineCount * lineHeight + lineHeight / 2;\n          } else if (crossAlign === 'center') {\n            textOffset = -labelSizes.highest.height / 2 - halfCount * lineHeight + lineHeight;\n          } else {\n            textOffset = -labelSizes.highest.height + lineHeight / 2;\n          }\n        } else {\n          // eslint-disable-next-line no-lonely-if\n          if (crossAlign === 'near' || rotation !== 0) {\n            textOffset = lineHeight / 2;\n          } else if (crossAlign === 'center') {\n            textOffset = labelSizes.highest.height / 2 - halfCount * lineHeight;\n          } else {\n            textOffset = labelSizes.highest.height - lineCount * lineHeight;\n          }\n        }\n        if (mirror) {\n          textOffset *= -1;\n        }\n        if (rotation !== 0 && !optsAtIndex.showLabelBackdrop) {\n          x += (lineHeight / 2) * Math.sin(rotation);\n        }\n      } else {\n        y = pixel;\n        textOffset = (1 - lineCount) * lineHeight / 2;\n      }\n\n      let backdrop;\n\n      if (optsAtIndex.showLabelBackdrop) {\n        const labelPadding = toPadding(optsAtIndex.backdropPadding);\n        const height = labelSizes.heights[i];\n        const width = labelSizes.widths[i];\n\n        let top = textOffset - labelPadding.top;\n        let left = 0 - labelPadding.left;\n\n        switch (textBaseline) {\n        case 'middle':\n          top -= height / 2;\n          break;\n        case 'bottom':\n          top -= height;\n          break;\n        default:\n          break;\n        }\n\n        switch (textAlign) {\n        case 'center':\n          left -= width / 2;\n          break;\n        case 'right':\n          left -= width;\n          break;\n        case 'inner':\n          if (i === ilen - 1) {\n            left -= width;\n          } else if (i > 0) {\n            left -= width / 2;\n          }\n          break;\n        default:\n          break;\n        }\n\n        backdrop = {\n          left,\n          top,\n          width: width + labelPadding.width,\n          height: height + labelPadding.height,\n\n          color: optsAtIndex.backdropColor,\n        };\n      }\n\n      items.push({\n        label,\n        font,\n        textOffset,\n        options: {\n          rotation,\n          color,\n          strokeColor,\n          strokeWidth,\n          textAlign: tickTextAlign,\n          textBaseline,\n          translation: [x, y],\n          backdrop,\n        }\n      });\n    }\n\n    return items;\n  }\n\n  _getXAxisLabelAlignment() {\n    const {position, ticks} = this.options;\n    const rotation = -toRadians(this.labelRotation);\n\n    if (rotation) {\n      return position === 'top' ? 'left' : 'right';\n    }\n\n    let align = 'center';\n\n    if (ticks.align === 'start') {\n      align = 'left';\n    } else if (ticks.align === 'end') {\n      align = 'right';\n    } else if (ticks.align === 'inner') {\n      align = 'inner';\n    }\n\n    return align;\n  }\n\n  _getYAxisLabelAlignment(tl) {\n    const {position, ticks: {crossAlign, mirror, padding}} = this.options;\n    const labelSizes = this._getLabelSizes();\n    const tickAndPadding = tl + padding;\n    const widest = labelSizes.widest.width;\n\n    let textAlign;\n    let x;\n\n    if (position === 'left') {\n      if (mirror) {\n        x = this.right + padding;\n\n        if (crossAlign === 'near') {\n          textAlign = 'left';\n        } else if (crossAlign === 'center') {\n          textAlign = 'center';\n          x += (widest / 2);\n        } else {\n          textAlign = 'right';\n          x += widest;\n        }\n      } else {\n        x = this.right - tickAndPadding;\n\n        if (crossAlign === 'near') {\n          textAlign = 'right';\n        } else if (crossAlign === 'center') {\n          textAlign = 'center';\n          x -= (widest / 2);\n        } else {\n          textAlign = 'left';\n          x = this.left;\n        }\n      }\n    } else if (position === 'right') {\n      if (mirror) {\n        x = this.left + padding;\n\n        if (crossAlign === 'near') {\n          textAlign = 'right';\n        } else if (crossAlign === 'center') {\n          textAlign = 'center';\n          x -= (widest / 2);\n        } else {\n          textAlign = 'left';\n          x -= widest;\n        }\n      } else {\n        x = this.left + tickAndPadding;\n\n        if (crossAlign === 'near') {\n          textAlign = 'left';\n        } else if (crossAlign === 'center') {\n          textAlign = 'center';\n          x += widest / 2;\n        } else {\n          textAlign = 'right';\n          x = this.right;\n        }\n      }\n    } else {\n      textAlign = 'right';\n    }\n\n    return {textAlign, x};\n  }\n\n  /**\n\t * @private\n\t */\n  _computeLabelArea() {\n    if (this.options.ticks.mirror) {\n      return;\n    }\n\n    const chart = this.chart;\n    const position = this.options.position;\n\n    if (position === 'left' || position === 'right') {\n      return {top: 0, left: this.left, bottom: chart.height, right: this.right};\n    } if (position === 'top' || position === 'bottom') {\n      return {top: this.top, left: 0, bottom: this.bottom, right: chart.width};\n    }\n  }\n\n  /**\n   * @protected\n   */\n  drawBackground() {\n    const {ctx, options: {backgroundColor}, left, top, width, height} = this;\n    if (backgroundColor) {\n      ctx.save();\n      ctx.fillStyle = backgroundColor;\n      ctx.fillRect(left, top, width, height);\n      ctx.restore();\n    }\n  }\n\n  getLineWidthForValue(value) {\n    const grid = this.options.grid;\n    if (!this._isVisible() || !grid.display) {\n      return 0;\n    }\n    const ticks = this.ticks;\n    const index = ticks.findIndex(t => t.value === value);\n    if (index >= 0) {\n      const opts = grid.setContext(this.getContext(index));\n      return opts.lineWidth;\n    }\n    return 0;\n  }\n\n  /**\n\t * @protected\n\t */\n  drawGrid(chartArea) {\n    const grid = this.options.grid;\n    const ctx = this.ctx;\n    const items = this._gridLineItems || (this._gridLineItems = this._computeGridLineItems(chartArea));\n    let i, ilen;\n\n    const drawLine = (p1, p2, style) => {\n      if (!style.width || !style.color) {\n        return;\n      }\n      ctx.save();\n      ctx.lineWidth = style.width;\n      ctx.strokeStyle = style.color;\n      ctx.setLineDash(style.borderDash || []);\n      ctx.lineDashOffset = style.borderDashOffset;\n\n      ctx.beginPath();\n      ctx.moveTo(p1.x, p1.y);\n      ctx.lineTo(p2.x, p2.y);\n      ctx.stroke();\n      ctx.restore();\n    };\n\n    if (grid.display) {\n      for (i = 0, ilen = items.length; i < ilen; ++i) {\n        const item = items[i];\n\n        if (grid.drawOnChartArea) {\n          drawLine(\n            {x: item.x1, y: item.y1},\n            {x: item.x2, y: item.y2},\n            item\n          );\n        }\n\n        if (grid.drawTicks) {\n          drawLine(\n            {x: item.tx1, y: item.ty1},\n            {x: item.tx2, y: item.ty2},\n            {\n              color: item.tickColor,\n              width: item.tickWidth,\n              borderDash: item.tickBorderDash,\n              borderDashOffset: item.tickBorderDashOffset\n            }\n          );\n        }\n      }\n    }\n  }\n\n  /**\n\t * @protected\n\t */\n  drawBorder() {\n    const {chart, ctx, options: {border, grid}} = this;\n    const borderOpts = border.setContext(this.getContext());\n    const axisWidth = border.display ? borderOpts.width : 0;\n    if (!axisWidth) {\n      return;\n    }\n    const lastLineWidth = grid.setContext(this.getContext(0)).lineWidth;\n    const borderValue = this._borderValue;\n    let x1, x2, y1, y2;\n\n    if (this.isHorizontal()) {\n      x1 = _alignPixel(chart, this.left, axisWidth) - axisWidth / 2;\n      x2 = _alignPixel(chart, this.right, lastLineWidth) + lastLineWidth / 2;\n      y1 = y2 = borderValue;\n    } else {\n      y1 = _alignPixel(chart, this.top, axisWidth) - axisWidth / 2;\n      y2 = _alignPixel(chart, this.bottom, lastLineWidth) + lastLineWidth / 2;\n      x1 = x2 = borderValue;\n    }\n    ctx.save();\n    ctx.lineWidth = borderOpts.width;\n    ctx.strokeStyle = borderOpts.color;\n\n    ctx.beginPath();\n    ctx.moveTo(x1, y1);\n    ctx.lineTo(x2, y2);\n    ctx.stroke();\n\n    ctx.restore();\n  }\n\n  /**\n\t * @protected\n\t */\n  drawLabels(chartArea) {\n    const optionTicks = this.options.ticks;\n\n    if (!optionTicks.display) {\n      return;\n    }\n\n    const ctx = this.ctx;\n\n    const area = this._computeLabelArea();\n    if (area) {\n      clipArea(ctx, area);\n    }\n\n    const items = this.getLabelItems(chartArea);\n    for (const item of items) {\n      const renderTextOptions = item.options;\n      const tickFont = item.font;\n      const label = item.label;\n      const y = item.textOffset;\n      renderText(ctx, label, 0, y, tickFont, renderTextOptions);\n    }\n\n    if (area) {\n      unclipArea(ctx);\n    }\n  }\n\n  /**\n\t * @protected\n\t */\n  drawTitle() {\n    const {ctx, options: {position, title, reverse}} = this;\n\n    if (!title.display) {\n      return;\n    }\n\n    const font = toFont(title.font);\n    const padding = toPadding(title.padding);\n    const align = title.align;\n    let offset = font.lineHeight / 2;\n\n    if (position === 'bottom' || position === 'center' || isObject(position)) {\n      offset += padding.bottom;\n      if (isArray(title.text)) {\n        offset += font.lineHeight * (title.text.length - 1);\n      }\n    } else {\n      offset += padding.top;\n    }\n\n    const {titleX, titleY, maxWidth, rotation} = titleArgs(this, offset, position, align);\n\n    renderText(ctx, title.text, 0, 0, font, {\n      color: title.color,\n      maxWidth,\n      rotation,\n      textAlign: titleAlign(align, position, reverse),\n      textBaseline: 'middle',\n      translation: [titleX, titleY],\n      strokeColor: title.strokeColor,\n      strokeWidth: title.strokeWidth\n    });\n  }\n\n  draw(chartArea) {\n    if (!this._isVisible()) {\n      return;\n    }\n\n    this.drawBackground();\n    this.drawGrid(chartArea);\n    this.drawBorder();\n    this.drawTitle();\n    this.drawLabels(chartArea);\n  }\n\n  /**\n\t * @return {object[]}\n\t * @private\n\t */\n  _layers() {\n    const opts = this.options;\n    const tz = opts.ticks && opts.ticks.z || 0;\n    const gz = valueOrDefault(opts.grid && opts.grid.z, -1);\n    const bz = valueOrDefault(opts.border && opts.border.z, 0);\n\n    if (!this._isVisible() || this.draw !== Scale.prototype.draw) {\n      // backward compatibility: draw has been overridden by custom scale\n      return [{\n        z: tz,\n        draw: (chartArea) => {\n          this.draw(chartArea);\n        }\n      }];\n    }\n\n    return [{\n      z: gz,\n      draw: (chartArea) => {\n        this.drawBackground();\n        this.drawGrid(chartArea);\n        this.drawTitle();\n      }\n    }, {\n      z: bz,\n      draw: () => {\n        this.drawBorder();\n      }\n    }, {\n      z: tz,\n      draw: (chartArea) => {\n        this.drawLabels(chartArea);\n      }\n    }];\n  }\n\n  /**\n\t * Returns visible dataset metas that are attached to this scale\n\t * @param {string} [type] - if specified, also filter by dataset type\n\t * @return {object[]}\n\t */\n  getMatchingVisibleMetas(type) {\n    const metas = this.chart.getSortedVisibleDatasetMetas();\n    const axisID = this.axis + 'AxisID';\n    const result = [];\n    let i, ilen;\n\n    for (i = 0, ilen = metas.length; i < ilen; ++i) {\n      const meta = metas[i];\n      if (meta[axisID] === this.id && (!type || meta.type === type)) {\n        result.push(meta);\n      }\n    }\n    return result;\n  }\n\n  /**\n\t * @param {number} index\n\t * @return {object}\n\t * @protected\n \t */\n  _resolveTickFontOptions(index) {\n    const opts = this.options.ticks.setContext(this.getContext(index));\n    return toFont(opts.font);\n  }\n\n  /**\n   * @protected\n   */\n  _maxDigits() {\n    const fontSize = this._resolveTickFontOptions(0).lineHeight;\n    return (this.isHorizontal() ? this.width : this.height) / fontSize;\n  }\n}\n"
  },
  {
    "path": "src/core/core.ticks.js",
    "content": "import {isArray} from '../helpers/helpers.core.js';\nimport {formatNumber} from '../helpers/helpers.intl.js';\nimport {log10} from '../helpers/helpers.math.js';\n\n/**\n * Namespace to hold formatters for different types of ticks\n * @namespace Chart.Ticks.formatters\n */\nconst formatters = {\n  /**\n   * Formatter for value labels\n   * @method Chart.Ticks.formatters.values\n   * @param value the value to display\n   * @return {string|string[]} the label to display\n   */\n  values(value) {\n    return isArray(value) ? /** @type {string[]} */ (value) : '' + value;\n  },\n\n  /**\n   * Formatter for numeric ticks\n   * @method Chart.Ticks.formatters.numeric\n   * @param tickValue {number} the value to be formatted\n   * @param index {number} the position of the tickValue parameter in the ticks array\n   * @param ticks {object[]} the list of ticks being converted\n   * @return {string} string representation of the tickValue parameter\n   */\n  numeric(tickValue, index, ticks) {\n    if (tickValue === 0) {\n      return '0'; // never show decimal places for 0\n    }\n\n    const locale = this.chart.options.locale;\n    let notation;\n    let delta = tickValue; // This is used when there are less than 2 ticks as the tick interval.\n\n    if (ticks.length > 1) {\n      // all ticks are small or there huge numbers; use scientific notation\n      const maxTick = Math.max(Math.abs(ticks[0].value), Math.abs(ticks[ticks.length - 1].value));\n      if (maxTick < 1e-4 || maxTick > 1e+15) {\n        notation = 'scientific';\n      }\n\n      delta = calculateDelta(tickValue, ticks);\n    }\n\n    const logDelta = log10(Math.abs(delta));\n\n    // When datasets have values approaching Number.MAX_VALUE, the tick calculations might result in\n    // infinity and eventually NaN. Passing NaN for minimumFractionDigits or maximumFractionDigits\n    // will make the number formatter throw. So instead we check for isNaN and use a fallback value.\n    //\n    // toFixed has a max of 20 decimal places\n    const numDecimal = isNaN(logDelta) ? 1 : Math.max(Math.min(-1 * Math.floor(logDelta), 20), 0);\n\n    const options = {notation, minimumFractionDigits: numDecimal, maximumFractionDigits: numDecimal};\n    Object.assign(options, this.options.ticks.format);\n\n    return formatNumber(tickValue, locale, options);\n  },\n\n\n  /**\n   * Formatter for logarithmic ticks\n   * @method Chart.Ticks.formatters.logarithmic\n   * @param tickValue {number} the value to be formatted\n   * @param index {number} the position of the tickValue parameter in the ticks array\n   * @param ticks {object[]} the list of ticks being converted\n   * @return {string} string representation of the tickValue parameter\n   */\n  logarithmic(tickValue, index, ticks) {\n    if (tickValue === 0) {\n      return '0';\n    }\n    const remain = ticks[index].significand || (tickValue / (Math.pow(10, Math.floor(log10(tickValue)))));\n    if ([1, 2, 3, 5, 10, 15].includes(remain) || index > 0.8 * ticks.length) {\n      return formatters.numeric.call(this, tickValue, index, ticks);\n    }\n    return '';\n  }\n\n};\n\n\nfunction calculateDelta(tickValue, ticks) {\n  // Figure out how many digits to show\n  // The space between the first two ticks might be smaller than normal spacing\n  let delta = ticks.length > 3 ? ticks[2].value - ticks[1].value : ticks[1].value - ticks[0].value;\n\n  // If we have a number like 2.5 as the delta, figure out how many decimal places we need\n  if (Math.abs(delta) >= 1 && tickValue !== Math.floor(tickValue)) {\n    // not an integer\n    delta = tickValue - Math.floor(tickValue);\n  }\n  return delta;\n}\n\n/**\n * Namespace to hold static tick generation functions\n * @namespace Chart.Ticks\n */\nexport default {formatters};\n"
  },
  {
    "path": "src/core/core.typedRegistry.js",
    "content": "import {merge} from '../helpers/index.js';\nimport defaults, {overrides} from './core.defaults.js';\n\n/**\n * @typedef {{id: string, defaults: any, overrides?: any, defaultRoutes: any}} IChartComponent\n */\n\nexport default class TypedRegistry {\n  constructor(type, scope, override) {\n    this.type = type;\n    this.scope = scope;\n    this.override = override;\n    this.items = Object.create(null);\n  }\n\n  isForType(type) {\n    return Object.prototype.isPrototypeOf.call(this.type.prototype, type.prototype);\n  }\n\n  /**\n\t * @param {IChartComponent} item\n\t * @returns {string} The scope where items defaults were registered to.\n\t */\n  register(item) {\n    const proto = Object.getPrototypeOf(item);\n    let parentScope;\n\n    if (isIChartComponent(proto)) {\n      // Make sure the parent is registered and note the scope where its defaults are.\n      parentScope = this.register(proto);\n    }\n\n    const items = this.items;\n    const id = item.id;\n    const scope = this.scope + '.' + id;\n\n    if (!id) {\n      throw new Error('class does not have id: ' + item);\n    }\n\n    if (id in items) {\n      // already registered\n      return scope;\n    }\n\n    items[id] = item;\n    registerDefaults(item, scope, parentScope);\n    if (this.override) {\n      defaults.override(item.id, item.overrides);\n    }\n\n    return scope;\n  }\n\n  /**\n\t * @param {string} id\n\t * @returns {object?}\n\t */\n  get(id) {\n    return this.items[id];\n  }\n\n  /**\n\t * @param {IChartComponent} item\n\t */\n  unregister(item) {\n    const items = this.items;\n    const id = item.id;\n    const scope = this.scope;\n\n    if (id in items) {\n      delete items[id];\n    }\n\n    if (scope && id in defaults[scope]) {\n      delete defaults[scope][id];\n      if (this.override) {\n        delete overrides[id];\n      }\n    }\n  }\n}\n\nfunction registerDefaults(item, scope, parentScope) {\n  // Inherit the parent's defaults and keep existing defaults\n  const itemDefaults = merge(Object.create(null), [\n    parentScope ? defaults.get(parentScope) : {},\n    defaults.get(scope),\n    item.defaults\n  ]);\n\n  defaults.set(scope, itemDefaults);\n\n  if (item.defaultRoutes) {\n    routeDefaults(scope, item.defaultRoutes);\n  }\n\n  if (item.descriptors) {\n    defaults.describe(scope, item.descriptors);\n  }\n}\n\nfunction routeDefaults(scope, routes) {\n  Object.keys(routes).forEach(property => {\n    const propertyParts = property.split('.');\n    const sourceName = propertyParts.pop();\n    const sourceScope = [scope].concat(propertyParts).join('.');\n    const parts = routes[property].split('.');\n    const targetName = parts.pop();\n    const targetScope = parts.join('.');\n    defaults.route(sourceScope, sourceName, targetScope, targetName);\n  });\n}\n\nfunction isIChartComponent(proto) {\n  return 'id' in proto && 'defaults' in proto;\n}\n"
  },
  {
    "path": "src/core/index.ts",
    "content": "export type {DateAdapter, TimeUnit} from './core.adapters.js';\nexport {default as _adapters} from './core.adapters.js';\nexport {default as Animation} from './core.animation.js';\nexport {default as Animations} from './core.animations.js';\nexport {default as animator} from './core.animator.js';\nexport {default as Chart} from './core.controller.js';\nexport {default as DatasetController} from './core.datasetController.js';\nexport {default as defaults} from './core.defaults.js';\nexport {default as Element} from './core.element.js';\nexport {default as Interaction} from './core.interaction.js';\nexport {default as layouts} from './core.layouts.js';\nexport {default as plugins} from './core.plugins.js';\nexport {default as registry} from './core.registry.js';\nexport {default as Scale} from './core.scale.js';\nexport {default as Ticks} from './core.ticks.js';\n"
  },
  {
    "path": "src/elements/element.arc.ts",
    "content": "import Element from '../core/core.element.js';\nimport {_angleBetween, getAngleFromPoint, TAU, HALF_PI, valueOrDefault} from '../helpers/index.js';\nimport {PI, _angleDiff, _normalizeAngle, _isBetween, _limitValue} from '../helpers/helpers.math.js';\nimport {_readValueToProps} from '../helpers/helpers.options.js';\nimport type {ArcOptions, Point} from '../types/index.js';\n\nfunction clipSelf(ctx: CanvasRenderingContext2D, element: ArcElement, endAngle: number) {\n  const {startAngle, x, y, outerRadius, innerRadius, options} = element;\n  const {borderWidth, borderJoinStyle} = options;\n  const outerAngleClip = Math.min(borderWidth / outerRadius, _normalizeAngle(startAngle - endAngle));\n  ctx.beginPath();\n  ctx.arc(x, y, outerRadius - borderWidth / 2, startAngle + outerAngleClip / 2, endAngle - outerAngleClip / 2);\n\n  if (innerRadius > 0) {\n    const innerAngleClip = Math.min(borderWidth / innerRadius, _normalizeAngle(startAngle - endAngle));\n    ctx.arc(x, y, innerRadius + borderWidth / 2, endAngle - innerAngleClip / 2, startAngle + innerAngleClip / 2, true);\n  } else {\n    const clipWidth = Math.min(borderWidth / 2, outerRadius * _normalizeAngle(startAngle - endAngle));\n\n    if (borderJoinStyle === 'round') {\n      ctx.arc(x, y, clipWidth, endAngle - PI / 2, startAngle + PI / 2, true);\n    } else if (borderJoinStyle === 'bevel') {\n      const r = 2 * clipWidth * clipWidth;\n      const endX = -r * Math.cos(endAngle + PI / 2) + x;\n      const endY = -r * Math.sin(endAngle + PI / 2) + y;\n      const startX = r * Math.cos(startAngle + PI / 2) + x;\n      const startY = r * Math.sin(startAngle + PI / 2) + y;\n      ctx.lineTo(endX, endY);\n      ctx.lineTo(startX, startY);\n    }\n  }\n  ctx.closePath();\n\n  ctx.moveTo(0, 0);\n  ctx.rect(0, 0, ctx.canvas.width, ctx.canvas.height);\n\n  ctx.clip('evenodd');\n}\n\n\nfunction clipArc(ctx: CanvasRenderingContext2D, element: ArcElement, endAngle: number) {\n  const {startAngle, pixelMargin, x, y, outerRadius, innerRadius} = element;\n  let angleMargin = pixelMargin / outerRadius;\n\n  // Draw an inner border by clipping the arc and drawing a double-width border\n  // Enlarge the clipping arc by 0.33 pixels to eliminate glitches between borders\n  ctx.beginPath();\n  ctx.arc(x, y, outerRadius, startAngle - angleMargin, endAngle + angleMargin);\n  if (innerRadius > pixelMargin) {\n    angleMargin = pixelMargin / innerRadius;\n    ctx.arc(x, y, innerRadius, endAngle + angleMargin, startAngle - angleMargin, true);\n  } else {\n    ctx.arc(x, y, pixelMargin, endAngle + HALF_PI, startAngle - HALF_PI);\n  }\n  ctx.closePath();\n  ctx.clip();\n}\n\nfunction toRadiusCorners(value) {\n  return _readValueToProps(value, ['outerStart', 'outerEnd', 'innerStart', 'innerEnd']);\n}\n\n/**\n * Parse border radius from the provided options\n */\nfunction parseBorderRadius(arc: ArcElement, innerRadius: number, outerRadius: number, angleDelta: number) {\n  const o = toRadiusCorners(arc.options.borderRadius);\n  const halfThickness = (outerRadius - innerRadius) / 2;\n  const innerLimit = Math.min(halfThickness, angleDelta * innerRadius / 2);\n\n  // Outer limits are complicated. We want to compute the available angular distance at\n  // a radius of outerRadius - borderRadius because for small angular distances, this term limits.\n  // We compute at r = outerRadius - borderRadius because this circle defines the center of the border corners.\n  //\n  // If the borderRadius is large, that value can become negative.\n  // This causes the outer borders to lose their radius entirely, which is rather unexpected. To solve that, if borderRadius > outerRadius\n  // we know that the thickness term will dominate and compute the limits at that point\n  const computeOuterLimit = (val) => {\n    const outerArcLimit = (outerRadius - Math.min(halfThickness, val)) * angleDelta / 2;\n    return _limitValue(val, 0, Math.min(halfThickness, outerArcLimit));\n  };\n\n  return {\n    outerStart: computeOuterLimit(o.outerStart),\n    outerEnd: computeOuterLimit(o.outerEnd),\n    innerStart: _limitValue(o.innerStart, 0, innerLimit),\n    innerEnd: _limitValue(o.innerEnd, 0, innerLimit),\n  };\n}\n\n/**\n * Convert (r, 𝜃) to (x, y)\n */\nfunction rThetaToXY(r: number, theta: number, x: number, y: number) {\n  return {\n    x: x + r * Math.cos(theta),\n    y: y + r * Math.sin(theta),\n  };\n}\n\n\n/**\n * Path the arc, respecting border radius by separating into left and right halves.\n *\n *   Start      End\n *\n *    1--->a--->2    Outer\n *   /           \\\n *   8           3\n *   |           |\n *   |           |\n *   7           4\n *   \\           /\n *    6<---b<---5    Inner\n */\nfunction pathArc(\n  ctx: CanvasRenderingContext2D,\n  element: ArcElement,\n  offset: number,\n  spacing: number,\n  end: number,\n  circular: boolean,\n) {\n  const {x, y, startAngle: start, pixelMargin, innerRadius: innerR} = element;\n\n  const outerRadius = Math.max(element.outerRadius + spacing + offset - pixelMargin, 0);\n  const innerRadius = innerR > 0 ? innerR + spacing + offset + pixelMargin : 0;\n\n  let spacingOffset = 0;\n  const alpha = end - start;\n\n  if (spacing) {\n    // When spacing is present, it is the same for all items\n    // So we adjust the start and end angle of the arc such that\n    // the distance is the same as it would be without the spacing\n    const noSpacingInnerRadius = innerR > 0 ? innerR - spacing : 0;\n    const noSpacingOuterRadius = outerRadius > 0 ? outerRadius - spacing : 0;\n    const avNogSpacingRadius = (noSpacingInnerRadius + noSpacingOuterRadius) / 2;\n    const adjustedAngle = avNogSpacingRadius !== 0 ? (alpha * avNogSpacingRadius) / (avNogSpacingRadius + spacing) : alpha;\n    spacingOffset = (alpha - adjustedAngle) / 2;\n  }\n\n  const beta = Math.max(0.001, alpha * outerRadius - offset / PI) / outerRadius;\n  const angleOffset = (alpha - beta) / 2;\n  const startAngle = start + angleOffset + spacingOffset;\n  const endAngle = end - angleOffset - spacingOffset;\n  const {outerStart, outerEnd, innerStart, innerEnd} = parseBorderRadius(element, innerRadius, outerRadius, endAngle - startAngle);\n\n  const outerStartAdjustedRadius = outerRadius - outerStart;\n  const outerEndAdjustedRadius = outerRadius - outerEnd;\n  const outerStartAdjustedAngle = startAngle + outerStart / outerStartAdjustedRadius;\n  const outerEndAdjustedAngle = endAngle - outerEnd / outerEndAdjustedRadius;\n\n  const innerStartAdjustedRadius = innerRadius + innerStart;\n  const innerEndAdjustedRadius = innerRadius + innerEnd;\n  const innerStartAdjustedAngle = startAngle + innerStart / innerStartAdjustedRadius;\n  const innerEndAdjustedAngle = endAngle - innerEnd / innerEndAdjustedRadius;\n\n  ctx.beginPath();\n\n  if (circular) {\n    // The first arc segments from point 1 to point a to point 2\n    const outerMidAdjustedAngle = (outerStartAdjustedAngle + outerEndAdjustedAngle) / 2;\n    ctx.arc(x, y, outerRadius, outerStartAdjustedAngle, outerMidAdjustedAngle);\n    ctx.arc(x, y, outerRadius, outerMidAdjustedAngle, outerEndAdjustedAngle);\n\n    // The corner segment from point 2 to point 3\n    if (outerEnd > 0) {\n      const pCenter = rThetaToXY(outerEndAdjustedRadius, outerEndAdjustedAngle, x, y);\n      ctx.arc(pCenter.x, pCenter.y, outerEnd, outerEndAdjustedAngle, endAngle + HALF_PI);\n    }\n\n    // The line from point 3 to point 4\n    const p4 = rThetaToXY(innerEndAdjustedRadius, endAngle, x, y);\n    ctx.lineTo(p4.x, p4.y);\n\n    // The corner segment from point 4 to point 5\n    if (innerEnd > 0) {\n      const pCenter = rThetaToXY(innerEndAdjustedRadius, innerEndAdjustedAngle, x, y);\n      ctx.arc(pCenter.x, pCenter.y, innerEnd, endAngle + HALF_PI, innerEndAdjustedAngle + Math.PI);\n    }\n\n    // The inner arc from point 5 to point b to point 6\n    const innerMidAdjustedAngle = ((endAngle - (innerEnd / innerRadius)) + (startAngle + (innerStart / innerRadius))) / 2;\n    ctx.arc(x, y, innerRadius, endAngle - (innerEnd / innerRadius), innerMidAdjustedAngle, true);\n    ctx.arc(x, y, innerRadius, innerMidAdjustedAngle, startAngle + (innerStart / innerRadius), true);\n\n    // The corner segment from point 6 to point 7\n    if (innerStart > 0) {\n      const pCenter = rThetaToXY(innerStartAdjustedRadius, innerStartAdjustedAngle, x, y);\n      ctx.arc(pCenter.x, pCenter.y, innerStart, innerStartAdjustedAngle + Math.PI, startAngle - HALF_PI);\n    }\n\n    // The line from point 7 to point 8\n    const p8 = rThetaToXY(outerStartAdjustedRadius, startAngle, x, y);\n    ctx.lineTo(p8.x, p8.y);\n\n    // The corner segment from point 8 to point 1\n    if (outerStart > 0) {\n      const pCenter = rThetaToXY(outerStartAdjustedRadius, outerStartAdjustedAngle, x, y);\n      ctx.arc(pCenter.x, pCenter.y, outerStart, startAngle - HALF_PI, outerStartAdjustedAngle);\n    }\n  } else {\n    ctx.moveTo(x, y);\n\n    const outerStartX = Math.cos(outerStartAdjustedAngle) * outerRadius + x;\n    const outerStartY = Math.sin(outerStartAdjustedAngle) * outerRadius + y;\n    ctx.lineTo(outerStartX, outerStartY);\n\n    const outerEndX = Math.cos(outerEndAdjustedAngle) * outerRadius + x;\n    const outerEndY = Math.sin(outerEndAdjustedAngle) * outerRadius + y;\n    ctx.lineTo(outerEndX, outerEndY);\n  }\n\n  ctx.closePath();\n}\n\nfunction drawArc(\n  ctx: CanvasRenderingContext2D,\n  element: ArcElement,\n  offset: number,\n  spacing: number,\n  circular: boolean,\n) {\n  const {fullCircles, startAngle, circumference} = element;\n  let endAngle = element.endAngle;\n  if (fullCircles) {\n    pathArc(ctx, element, offset, spacing, endAngle, circular);\n    for (let i = 0; i < fullCircles; ++i) {\n      ctx.fill();\n    }\n    if (!isNaN(circumference)) {\n      endAngle = startAngle + (circumference % TAU || TAU);\n    }\n  }\n  pathArc(ctx, element, offset, spacing, endAngle, circular);\n  ctx.fill();\n  return endAngle;\n}\n\nfunction drawBorder(\n  ctx: CanvasRenderingContext2D,\n  element: ArcElement,\n  offset: number,\n  spacing: number,\n  circular: boolean,\n) {\n  const {fullCircles, startAngle, circumference, options} = element;\n  const {borderWidth, borderJoinStyle, borderDash, borderDashOffset, borderRadius} = options;\n  const inner = options.borderAlign === 'inner';\n\n  if (!borderWidth) {\n    return;\n  }\n\n  ctx.setLineDash(borderDash || []);\n  ctx.lineDashOffset = borderDashOffset;\n\n  if (inner) {\n    ctx.lineWidth = borderWidth * 2;\n    ctx.lineJoin = borderJoinStyle || 'round';\n  } else {\n    ctx.lineWidth = borderWidth;\n    ctx.lineJoin = borderJoinStyle || 'bevel';\n  }\n\n  let endAngle = element.endAngle;\n  if (fullCircles) {\n    pathArc(ctx, element, offset, spacing, endAngle, circular);\n    for (let i = 0; i < fullCircles; ++i) {\n      ctx.stroke();\n    }\n    if (!isNaN(circumference)) {\n      endAngle = startAngle + (circumference % TAU || TAU);\n    }\n  }\n\n  if (inner) {\n    clipArc(ctx, element, endAngle);\n  }\n\n  if (options.selfJoin && endAngle - startAngle >= PI && borderRadius === 0 && borderJoinStyle !== 'miter') {\n    clipSelf(ctx, element, endAngle);\n  }\n\n  if (!fullCircles) {\n    pathArc(ctx, element, offset, spacing, endAngle, circular);\n    ctx.stroke();\n  }\n}\n\nexport interface ArcProps extends Point {\n  startAngle: number;\n  endAngle: number;\n  innerRadius: number;\n  outerRadius: number;\n  circumference: number;\n}\n\nexport default class ArcElement extends Element<ArcProps, ArcOptions> {\n\n  static id = 'arc';\n\n  static defaults = {\n    borderAlign: 'center',\n    borderColor: '#fff',\n    borderDash: [],\n    borderDashOffset: 0,\n    borderJoinStyle: undefined,\n    borderRadius: 0,\n    borderWidth: 2,\n    offset: 0,\n    spacing: 0,\n    angle: undefined,\n    circular: true,\n    selfJoin: false,\n  };\n\n  static defaultRoutes = {\n    backgroundColor: 'backgroundColor'\n  };\n\n  static descriptors = {\n    _scriptable: true,\n    _indexable: (name) => name !== 'borderDash'\n  };\n\n  circumference: number;\n  endAngle: number;\n  fullCircles: number;\n  innerRadius: number;\n  outerRadius: number;\n  pixelMargin: number;\n  startAngle: number;\n\n  constructor(cfg) {\n    super();\n\n    this.options = undefined;\n    this.circumference = undefined;\n    this.startAngle = undefined;\n    this.endAngle = undefined;\n    this.innerRadius = undefined;\n    this.outerRadius = undefined;\n    this.pixelMargin = 0;\n    this.fullCircles = 0;\n\n    if (cfg) {\n      Object.assign(this, cfg);\n    }\n  }\n\n  inRange(chartX: number, chartY: number, useFinalPosition: boolean) {\n    const point = this.getProps(['x', 'y'], useFinalPosition);\n    const {angle, distance} = getAngleFromPoint(point, {x: chartX, y: chartY});\n    const {startAngle, endAngle, innerRadius, outerRadius, circumference} = this.getProps([\n      'startAngle',\n      'endAngle',\n      'innerRadius',\n      'outerRadius',\n      'circumference'\n    ], useFinalPosition);\n    const rAdjust = (this.options.spacing + this.options.borderWidth) / 2;\n    const _circumference = valueOrDefault(circumference, endAngle - startAngle);\n    const nonZeroBetween = _angleBetween(angle, startAngle, endAngle) && startAngle !== endAngle;\n    const betweenAngles = _circumference >= TAU || nonZeroBetween;\n    const withinRadius = _isBetween(distance, innerRadius + rAdjust, outerRadius + rAdjust);\n\n    return (betweenAngles && withinRadius);\n  }\n\n  getCenterPoint(useFinalPosition: boolean) {\n    const {x, y, startAngle, endAngle, innerRadius, outerRadius} = this.getProps([\n      'x',\n      'y',\n      'startAngle',\n      'endAngle',\n      'innerRadius',\n      'outerRadius'\n    ], useFinalPosition);\n    const {offset, spacing} = this.options;\n    const halfAngle = (startAngle + endAngle) / 2;\n    const halfRadius = (innerRadius + outerRadius + spacing + offset) / 2;\n    return {\n      x: x + Math.cos(halfAngle) * halfRadius,\n      y: y + Math.sin(halfAngle) * halfRadius\n    };\n  }\n\n  tooltipPosition(useFinalPosition: boolean) {\n    return this.getCenterPoint(useFinalPosition);\n  }\n\n  draw(ctx: CanvasRenderingContext2D) {\n    const {options, circumference} = this;\n    const offset = (options.offset || 0) / 4;\n    const spacing = (options.spacing || 0) / 2;\n    const circular = options.circular;\n    this.pixelMargin = (options.borderAlign === 'inner') ? 0.33 : 0;\n    this.fullCircles = circumference > TAU ? Math.floor(circumference / TAU) : 0;\n\n    if (circumference === 0 || this.innerRadius < 0 || this.outerRadius < 0) {\n      return;\n    }\n\n    ctx.save();\n\n    const halfAngle = (this.startAngle + this.endAngle) / 2;\n    ctx.translate(Math.cos(halfAngle) * offset, Math.sin(halfAngle) * offset);\n    const fix = 1 - Math.sin(Math.min(PI, circumference || 0));\n    const radiusOffset = offset * fix;\n\n    ctx.fillStyle = options.backgroundColor;\n    ctx.strokeStyle = options.borderColor;\n\n    drawArc(ctx, this, radiusOffset, spacing, circular);\n    drawBorder(ctx, this, radiusOffset, spacing, circular);\n\n    ctx.restore();\n  }\n}\n"
  },
  {
    "path": "src/elements/element.bar.js",
    "content": "import Element from '../core/core.element.js';\nimport {isObject, _isBetween, _limitValue} from '../helpers/index.js';\nimport {addRoundedRectPath} from '../helpers/helpers.canvas.js';\nimport {toTRBL, toTRBLCorners} from '../helpers/helpers.options.js';\n\n/** @typedef {{ x: number, y: number, base: number, horizontal: boolean, width: number, height: number }} BarProps */\n\n/**\n * Helper function to get the bounds of the bar regardless of the orientation\n * @param {BarElement} bar the bar\n * @param {boolean} [useFinalPosition]\n * @return {object} bounds of the bar\n * @private\n */\nfunction getBarBounds(bar, useFinalPosition) {\n  const {x, y, base, width, height} = /** @type {BarProps} */ (bar.getProps(['x', 'y', 'base', 'width', 'height'], useFinalPosition));\n\n  let left, right, top, bottom, half;\n\n  if (bar.horizontal) {\n    half = height / 2;\n    left = Math.min(x, base);\n    right = Math.max(x, base);\n    top = y - half;\n    bottom = y + half;\n  } else {\n    half = width / 2;\n    left = x - half;\n    right = x + half;\n    top = Math.min(y, base);\n    bottom = Math.max(y, base);\n  }\n\n  return {left, top, right, bottom};\n}\n\nfunction skipOrLimit(skip, value, min, max) {\n  return skip ? 0 : _limitValue(value, min, max);\n}\n\nfunction parseBorderWidth(bar, maxW, maxH) {\n  const value = bar.options.borderWidth;\n  const skip = bar.borderSkipped;\n  const o = toTRBL(value);\n\n  return {\n    t: skipOrLimit(skip.top, o.top, 0, maxH),\n    r: skipOrLimit(skip.right, o.right, 0, maxW),\n    b: skipOrLimit(skip.bottom, o.bottom, 0, maxH),\n    l: skipOrLimit(skip.left, o.left, 0, maxW)\n  };\n}\n\nfunction parseBorderRadius(bar, maxW, maxH) {\n  const {enableBorderRadius} = bar.getProps(['enableBorderRadius']);\n  const value = bar.options.borderRadius;\n  const o = toTRBLCorners(value);\n  const maxR = Math.min(maxW, maxH);\n  const skip = bar.borderSkipped;\n\n  // If the value is an object, assume the user knows what they are doing\n  // and apply as directed.\n  const enableBorder = enableBorderRadius || isObject(value);\n\n  return {\n    topLeft: skipOrLimit(!enableBorder || skip.top || skip.left, o.topLeft, 0, maxR),\n    topRight: skipOrLimit(!enableBorder || skip.top || skip.right, o.topRight, 0, maxR),\n    bottomLeft: skipOrLimit(!enableBorder || skip.bottom || skip.left, o.bottomLeft, 0, maxR),\n    bottomRight: skipOrLimit(!enableBorder || skip.bottom || skip.right, o.bottomRight, 0, maxR)\n  };\n}\n\nfunction boundingRects(bar) {\n  const bounds = getBarBounds(bar);\n  const width = bounds.right - bounds.left;\n  const height = bounds.bottom - bounds.top;\n  const border = parseBorderWidth(bar, width / 2, height / 2);\n  const radius = parseBorderRadius(bar, width / 2, height / 2);\n\n  return {\n    outer: {\n      x: bounds.left,\n      y: bounds.top,\n      w: width,\n      h: height,\n      radius\n    },\n    inner: {\n      x: bounds.left + border.l,\n      y: bounds.top + border.t,\n      w: width - border.l - border.r,\n      h: height - border.t - border.b,\n      radius: {\n        topLeft: Math.max(0, radius.topLeft - Math.max(border.t, border.l)),\n        topRight: Math.max(0, radius.topRight - Math.max(border.t, border.r)),\n        bottomLeft: Math.max(0, radius.bottomLeft - Math.max(border.b, border.l)),\n        bottomRight: Math.max(0, radius.bottomRight - Math.max(border.b, border.r)),\n      }\n    }\n  };\n}\n\nfunction inRange(bar, x, y, useFinalPosition) {\n  const skipX = x === null;\n  const skipY = y === null;\n  const skipBoth = skipX && skipY;\n  const bounds = bar && !skipBoth && getBarBounds(bar, useFinalPosition);\n\n  return bounds\n\t\t&& (skipX || _isBetween(x, bounds.left, bounds.right))\n\t\t&& (skipY || _isBetween(y, bounds.top, bounds.bottom));\n}\n\nfunction hasRadius(radius) {\n  return radius.topLeft || radius.topRight || radius.bottomLeft || radius.bottomRight;\n}\n\n/**\n * Add a path of a rectangle to the current sub-path\n * @param {CanvasRenderingContext2D} ctx Context\n * @param {*} rect Bounding rect\n */\nfunction addNormalRectPath(ctx, rect) {\n  ctx.rect(rect.x, rect.y, rect.w, rect.h);\n}\n\nfunction inflateRect(rect, amount, refRect = {}) {\n  const x = rect.x !== refRect.x ? -amount : 0;\n  const y = rect.y !== refRect.y ? -amount : 0;\n  const w = (rect.x + rect.w !== refRect.x + refRect.w ? amount : 0) - x;\n  const h = (rect.y + rect.h !== refRect.y + refRect.h ? amount : 0) - y;\n  return {\n    x: rect.x + x,\n    y: rect.y + y,\n    w: rect.w + w,\n    h: rect.h + h,\n    radius: rect.radius\n  };\n}\n\nexport default class BarElement extends Element {\n\n  static id = 'bar';\n\n  /**\n   * @type {any}\n   */\n  static defaults = {\n    borderSkipped: 'start',\n    borderWidth: 0,\n    borderRadius: 0,\n    inflateAmount: 'auto',\n    pointStyle: undefined\n  };\n\n  /**\n   * @type {any}\n   */\n  static defaultRoutes = {\n    backgroundColor: 'backgroundColor',\n    borderColor: 'borderColor'\n  };\n\n  constructor(cfg) {\n    super();\n\n    this.options = undefined;\n    this.horizontal = undefined;\n    this.base = undefined;\n    this.width = undefined;\n    this.height = undefined;\n    this.inflateAmount = undefined;\n\n    if (cfg) {\n      Object.assign(this, cfg);\n    }\n  }\n\n  draw(ctx) {\n    const {inflateAmount, options: {borderColor, backgroundColor}} = this;\n    const {inner, outer} = boundingRects(this);\n    const addRectPath = hasRadius(outer.radius) ? addRoundedRectPath : addNormalRectPath;\n\n    ctx.save();\n\n    if (outer.w !== inner.w || outer.h !== inner.h) {\n      ctx.beginPath();\n      addRectPath(ctx, inflateRect(outer, inflateAmount, inner));\n      ctx.clip();\n      addRectPath(ctx, inflateRect(inner, -inflateAmount, outer));\n      ctx.fillStyle = borderColor;\n      ctx.fill('evenodd');\n    }\n\n    ctx.beginPath();\n    addRectPath(ctx, inflateRect(inner, inflateAmount));\n    ctx.fillStyle = backgroundColor;\n    ctx.fill();\n\n    ctx.restore();\n  }\n\n  inRange(mouseX, mouseY, useFinalPosition) {\n    return inRange(this, mouseX, mouseY, useFinalPosition);\n  }\n\n  inXRange(mouseX, useFinalPosition) {\n    return inRange(this, mouseX, null, useFinalPosition);\n  }\n\n  inYRange(mouseY, useFinalPosition) {\n    return inRange(this, null, mouseY, useFinalPosition);\n  }\n\n  getCenterPoint(useFinalPosition) {\n    const {x, y, base, horizontal} = /** @type {BarProps} */ (this.getProps(['x', 'y', 'base', 'horizontal'], useFinalPosition));\n    return {\n      x: horizontal ? (x + base) / 2 : x,\n      y: horizontal ? y : (y + base) / 2\n    };\n  }\n\n  getRange(axis) {\n    return axis === 'x' ? this.width / 2 : this.height / 2;\n  }\n}\n"
  },
  {
    "path": "src/elements/element.line.js",
    "content": "import Element from '../core/core.element.js';\nimport {_bezierInterpolation, _pointInLine, _steppedInterpolation} from '../helpers/helpers.interpolation.js';\nimport {_computeSegments, _boundSegments} from '../helpers/helpers.segment.js';\nimport {_steppedLineTo, _bezierCurveTo} from '../helpers/helpers.canvas.js';\nimport {_updateBezierControlPoints} from '../helpers/helpers.curve.js';\nimport {valueOrDefault} from '../helpers/index.js';\n\n/**\n * @typedef { import('./element.point.js').default } PointElement\n */\n\nfunction setStyle(ctx, options, style = options) {\n  ctx.lineCap = valueOrDefault(style.borderCapStyle, options.borderCapStyle);\n  ctx.setLineDash(valueOrDefault(style.borderDash, options.borderDash));\n  ctx.lineDashOffset = valueOrDefault(style.borderDashOffset, options.borderDashOffset);\n  ctx.lineJoin = valueOrDefault(style.borderJoinStyle, options.borderJoinStyle);\n  ctx.lineWidth = valueOrDefault(style.borderWidth, options.borderWidth);\n  ctx.strokeStyle = valueOrDefault(style.borderColor, options.borderColor);\n}\n\nfunction lineTo(ctx, previous, target) {\n  ctx.lineTo(target.x, target.y);\n}\n\n/**\n * @returns {any}\n */\nfunction getLineMethod(options) {\n  if (options.stepped) {\n    return _steppedLineTo;\n  }\n\n  if (options.tension || options.cubicInterpolationMode === 'monotone') {\n    return _bezierCurveTo;\n  }\n\n  return lineTo;\n}\n\nfunction pathVars(points, segment, params = {}) {\n  const count = points.length;\n  const {start: paramsStart = 0, end: paramsEnd = count - 1} = params;\n  const {start: segmentStart, end: segmentEnd} = segment;\n  const start = Math.max(paramsStart, segmentStart);\n  const end = Math.min(paramsEnd, segmentEnd);\n  const outside = paramsStart < segmentStart && paramsEnd < segmentStart || paramsStart > segmentEnd && paramsEnd > segmentEnd;\n\n  return {\n    count,\n    start,\n    loop: segment.loop,\n    ilen: end < start && !outside ? count + end - start : end - start\n  };\n}\n\n/**\n * Create path from points, grouping by truncated x-coordinate\n * Points need to be in order by x-coordinate for this to work efficiently\n * @param {CanvasRenderingContext2D|Path2D} ctx - Context\n * @param {LineElement} line\n * @param {object} segment\n * @param {number} segment.start - start index of the segment, referring the points array\n * @param {number} segment.end - end index of the segment, referring the points array\n * @param {boolean} segment.loop - indicates that the segment is a loop\n * @param {object} params\n * @param {boolean} params.move - move to starting point (vs line to it)\n * @param {boolean} params.reverse - path the segment from end to start\n * @param {number} params.start - limit segment to points starting from `start` index\n * @param {number} params.end - limit segment to points ending at `start` + `count` index\n */\nfunction pathSegment(ctx, line, segment, params) {\n  const {points, options} = line;\n  const {count, start, loop, ilen} = pathVars(points, segment, params);\n  const lineMethod = getLineMethod(options);\n  // eslint-disable-next-line prefer-const\n  let {move = true, reverse} = params || {};\n  let i, point, prev;\n\n  for (i = 0; i <= ilen; ++i) {\n    point = points[(start + (reverse ? ilen - i : i)) % count];\n\n    if (point.skip) {\n      // If there is a skipped point inside a segment, spanGaps must be true\n      continue;\n    } else if (move) {\n      ctx.moveTo(point.x, point.y);\n      move = false;\n    } else {\n      lineMethod(ctx, prev, point, reverse, options.stepped);\n    }\n\n    prev = point;\n  }\n\n  if (loop) {\n    point = points[(start + (reverse ? ilen : 0)) % count];\n    lineMethod(ctx, prev, point, reverse, options.stepped);\n  }\n\n  return !!loop;\n}\n\n/**\n * Create path from points, grouping by truncated x-coordinate\n * Points need to be in order by x-coordinate for this to work efficiently\n * @param {CanvasRenderingContext2D|Path2D} ctx - Context\n * @param {LineElement} line\n * @param {object} segment\n * @param {number} segment.start - start index of the segment, referring the points array\n * @param {number} segment.end - end index of the segment, referring the points array\n * @param {boolean} segment.loop - indicates that the segment is a loop\n * @param {object} params\n * @param {boolean} params.move - move to starting point (vs line to it)\n * @param {boolean} params.reverse - path the segment from end to start\n * @param {number} params.start - limit segment to points starting from `start` index\n * @param {number} params.end - limit segment to points ending at `start` + `count` index\n */\nfunction fastPathSegment(ctx, line, segment, params) {\n  const points = line.points;\n  const {count, start, ilen} = pathVars(points, segment, params);\n  const {move = true, reverse} = params || {};\n  let avgX = 0;\n  let countX = 0;\n  let i, point, prevX, minY, maxY, lastY;\n\n  const pointIndex = (index) => (start + (reverse ? ilen - index : index)) % count;\n  const drawX = () => {\n    if (minY !== maxY) {\n      // Draw line to maxY and minY, using the average x-coordinate\n      ctx.lineTo(avgX, maxY);\n      ctx.lineTo(avgX, minY);\n      // Line to y-value of last point in group. So the line continues\n      // from correct position. Not using move, to have solid path.\n      ctx.lineTo(avgX, lastY);\n    }\n  };\n\n  if (move) {\n    point = points[pointIndex(0)];\n    ctx.moveTo(point.x, point.y);\n  }\n\n  for (i = 0; i <= ilen; ++i) {\n    point = points[pointIndex(i)];\n\n    if (point.skip) {\n      // If there is a skipped point inside a segment, spanGaps must be true\n      continue;\n    }\n\n    const x = point.x;\n    const y = point.y;\n    const truncX = x | 0; // truncated x-coordinate\n\n    if (truncX === prevX) {\n      // Determine `minY` / `maxY` and `avgX` while we stay within same x-position\n      if (y < minY) {\n        minY = y;\n      } else if (y > maxY) {\n        maxY = y;\n      }\n      // For first point in group, countX is `0`, so average will be `x` / 1.\n      avgX = (countX * avgX + x) / ++countX;\n    } else {\n      drawX();\n      // Draw line to next x-position, using the first (or only)\n      // y-value in that group\n      ctx.lineTo(x, y);\n\n      prevX = truncX;\n      countX = 0;\n      minY = maxY = y;\n    }\n    // Keep track of the last y-value in group\n    lastY = y;\n  }\n  drawX();\n}\n\n/**\n * @param {LineElement} line - the line\n * @returns {function}\n * @private\n */\nfunction _getSegmentMethod(line) {\n  const opts = line.options;\n  const borderDash = opts.borderDash && opts.borderDash.length;\n  const useFastPath = !line._decimated && !line._loop && !opts.tension && opts.cubicInterpolationMode !== 'monotone' && !opts.stepped && !borderDash;\n  return useFastPath ? fastPathSegment : pathSegment;\n}\n\n/**\n * @private\n */\nfunction _getInterpolationMethod(options) {\n  if (options.stepped) {\n    return _steppedInterpolation;\n  }\n\n  if (options.tension || options.cubicInterpolationMode === 'monotone') {\n    return _bezierInterpolation;\n  }\n\n  return _pointInLine;\n}\n\nfunction strokePathWithCache(ctx, line, start, count) {\n  let path = line._path;\n  if (!path) {\n    path = line._path = new Path2D();\n    if (line.path(path, start, count)) {\n      path.closePath();\n    }\n  }\n  setStyle(ctx, line.options);\n  ctx.stroke(path);\n}\n\nfunction strokePathDirect(ctx, line, start, count) {\n  const {segments, options} = line;\n  const segmentMethod = _getSegmentMethod(line);\n\n  for (const segment of segments) {\n    setStyle(ctx, options, segment.style);\n    ctx.beginPath();\n    if (segmentMethod(ctx, line, segment, {start, end: start + count - 1})) {\n      ctx.closePath();\n    }\n    ctx.stroke();\n  }\n}\n\nconst usePath2D = typeof Path2D === 'function';\n\nfunction draw(ctx, line, start, count) {\n  if (usePath2D && !line.options.segment) {\n    strokePathWithCache(ctx, line, start, count);\n  } else {\n    strokePathDirect(ctx, line, start, count);\n  }\n}\n\nexport default class LineElement extends Element {\n\n  static id = 'line';\n\n  /**\n   * @type {any}\n   */\n  static defaults = {\n    borderCapStyle: 'butt',\n    borderDash: [],\n    borderDashOffset: 0,\n    borderJoinStyle: 'miter',\n    borderWidth: 3,\n    capBezierPoints: true,\n    cubicInterpolationMode: 'default',\n    fill: false,\n    spanGaps: false,\n    stepped: false,\n    tension: 0,\n  };\n\n  /**\n   * @type {any}\n   */\n  static defaultRoutes = {\n    backgroundColor: 'backgroundColor',\n    borderColor: 'borderColor'\n  };\n\n\n  static descriptors = {\n    _scriptable: true,\n    _indexable: (name) => name !== 'borderDash' && name !== 'fill',\n  };\n\n\n  constructor(cfg) {\n    super();\n\n    this.animated = true;\n    this.options = undefined;\n    this._chart = undefined;\n    this._loop = undefined;\n    this._fullLoop = undefined;\n    this._path = undefined;\n    this._points = undefined;\n    this._segments = undefined;\n    this._decimated = false;\n    this._pointsUpdated = false;\n    this._datasetIndex = undefined;\n\n    if (cfg) {\n      Object.assign(this, cfg);\n    }\n  }\n\n  updateControlPoints(chartArea, indexAxis) {\n    const options = this.options;\n    if ((options.tension || options.cubicInterpolationMode === 'monotone') && !options.stepped && !this._pointsUpdated) {\n      const loop = options.spanGaps ? this._loop : this._fullLoop;\n      _updateBezierControlPoints(this._points, options, chartArea, loop, indexAxis);\n      this._pointsUpdated = true;\n    }\n  }\n\n  set points(points) {\n    this._points = points;\n    delete this._segments;\n    delete this._path;\n    this._pointsUpdated = false;\n  }\n\n  get points() {\n    return this._points;\n  }\n\n  get segments() {\n    return this._segments || (this._segments = _computeSegments(this, this.options.segment));\n  }\n\n  /**\n\t * First non-skipped point on this line\n\t * @returns {PointElement|undefined}\n\t */\n  first() {\n    const segments = this.segments;\n    const points = this.points;\n    return segments.length && points[segments[0].start];\n  }\n\n  /**\n\t * Last non-skipped point on this line\n\t * @returns {PointElement|undefined}\n\t */\n  last() {\n    const segments = this.segments;\n    const points = this.points;\n    const count = segments.length;\n    return count && points[segments[count - 1].end];\n  }\n\n  /**\n\t * Interpolate a point in this line at the same value on `property` as\n\t * the reference `point` provided\n\t * @param {PointElement} point - the reference point\n\t * @param {string} property - the property to match on\n\t * @returns {PointElement|undefined}\n\t */\n  interpolate(point, property) {\n    const options = this.options;\n    const value = point[property];\n    const points = this.points;\n    const segments = _boundSegments(this, {property, start: value, end: value});\n\n    if (!segments.length) {\n      return;\n    }\n\n    const result = [];\n    const _interpolate = _getInterpolationMethod(options);\n    let i, ilen;\n    for (i = 0, ilen = segments.length; i < ilen; ++i) {\n      const {start, end} = segments[i];\n      const p1 = points[start];\n      const p2 = points[end];\n      if (p1 === p2) {\n        result.push(p1);\n        continue;\n      }\n      const t = Math.abs((value - p1[property]) / (p2[property] - p1[property]));\n      const interpolated = _interpolate(p1, p2, t, options.stepped);\n      interpolated[property] = point[property];\n      result.push(interpolated);\n    }\n    return result.length === 1 ? result[0] : result;\n  }\n\n  /**\n\t * Append a segment of this line to current path.\n\t * @param {CanvasRenderingContext2D} ctx\n\t * @param {object} segment\n\t * @param {number} segment.start - start index of the segment, referring the points array\n \t * @param {number} segment.end - end index of the segment, referring the points array\n \t * @param {boolean} segment.loop - indicates that the segment is a loop\n\t * @param {object} params\n\t * @param {boolean} params.move - move to starting point (vs line to it)\n\t * @param {boolean} params.reverse - path the segment from end to start\n\t * @param {number} params.start - limit segment to points starting from `start` index\n\t * @param {number} params.end - limit segment to points ending at `start` + `count` index\n\t * @returns {undefined|boolean} - true if the segment is a full loop (path should be closed)\n\t */\n  pathSegment(ctx, segment, params) {\n    const segmentMethod = _getSegmentMethod(this);\n    return segmentMethod(ctx, this, segment, params);\n  }\n\n  /**\n\t * Append all segments of this line to current path.\n\t * @param {CanvasRenderingContext2D|Path2D} ctx\n\t * @param {number} [start]\n\t * @param {number} [count]\n\t * @returns {undefined|boolean} - true if line is a full loop (path should be closed)\n\t */\n  path(ctx, start, count) {\n    const segments = this.segments;\n    const segmentMethod = _getSegmentMethod(this);\n    let loop = this._loop;\n\n    start = start || 0;\n    count = count || (this.points.length - start);\n\n    for (const segment of segments) {\n      loop &= segmentMethod(ctx, this, segment, {start, end: start + count - 1});\n    }\n    return !!loop;\n  }\n\n  /**\n\t * Draw\n\t * @param {CanvasRenderingContext2D} ctx\n\t * @param {object} chartArea\n\t * @param {number} [start]\n\t * @param {number} [count]\n\t */\n  draw(ctx, chartArea, start, count) {\n    const options = this.options || {};\n    const points = this.points || [];\n\n    if (points.length && options.borderWidth) {\n      ctx.save();\n\n      draw(ctx, this, start, count);\n\n      ctx.restore();\n    }\n\n    if (this.animated) {\n      // When line is animated, the control points and path are not cached.\n      this._pointsUpdated = false;\n      this._path = undefined;\n    }\n  }\n}\n"
  },
  {
    "path": "src/elements/element.point.ts",
    "content": "import Element from '../core/core.element.js';\nimport {drawPoint, _isPointInArea} from '../helpers/helpers.canvas.js';\nimport type {\n  CartesianParsedData,\n  ChartArea,\n  Point,\n  PointHoverOptions,\n  PointOptions,\n} from '../types/index.js';\n\nfunction inRange(el: PointElement, pos: number, axis: 'x' | 'y', useFinalPosition?: boolean) {\n  const options = el.options;\n  const {[axis]: value} = el.getProps([axis], useFinalPosition);\n\n  return (Math.abs(pos - value) < options.radius + options.hitRadius);\n}\n\nexport type PointProps = Point\n\nexport default class PointElement extends Element<PointProps, PointOptions & PointHoverOptions> {\n\n  static id = 'point';\n\n  parsed: CartesianParsedData;\n  skip?: boolean;\n  stop?: boolean;\n\n  /**\n   * @type {any}\n   */\n  static defaults = {\n    borderWidth: 1,\n    hitRadius: 1,\n    hoverBorderWidth: 1,\n    hoverRadius: 4,\n    pointStyle: 'circle',\n    radius: 3,\n    rotation: 0\n  };\n\n  /**\n   * @type {any}\n   */\n  static defaultRoutes = {\n    backgroundColor: 'backgroundColor',\n    borderColor: 'borderColor'\n  };\n\n  constructor(cfg) {\n    super();\n\n    this.options = undefined;\n    this.parsed = undefined;\n    this.skip = undefined;\n    this.stop = undefined;\n\n    if (cfg) {\n      Object.assign(this, cfg);\n    }\n  }\n\n  inRange(mouseX: number, mouseY: number, useFinalPosition?: boolean) {\n    const options = this.options;\n    const {x, y} = this.getProps(['x', 'y'], useFinalPosition);\n    return ((Math.pow(mouseX - x, 2) + Math.pow(mouseY - y, 2)) < Math.pow(options.hitRadius + options.radius, 2));\n  }\n\n  inXRange(mouseX: number, useFinalPosition?: boolean) {\n    return inRange(this, mouseX, 'x', useFinalPosition);\n  }\n\n  inYRange(mouseY: number, useFinalPosition?: boolean) {\n    return inRange(this, mouseY, 'y', useFinalPosition);\n  }\n\n  getCenterPoint(useFinalPosition?: boolean) {\n    const {x, y} = this.getProps(['x', 'y'], useFinalPosition);\n    return {x, y};\n  }\n\n  size(options?: Partial<PointOptions & PointHoverOptions>) {\n    options = options || this.options || {};\n    let radius = options.radius || 0;\n    radius = Math.max(radius, radius && options.hoverRadius || 0);\n    const borderWidth = radius && options.borderWidth || 0;\n    return (radius + borderWidth) * 2;\n  }\n\n  draw(ctx: CanvasRenderingContext2D, area: ChartArea) {\n    const options = this.options;\n\n    if (this.skip || options.radius < 0.1 || !_isPointInArea(this, area, this.size(options) / 2)) {\n      return;\n    }\n\n    ctx.strokeStyle = options.borderColor;\n    ctx.lineWidth = options.borderWidth;\n    ctx.fillStyle = options.backgroundColor;\n    drawPoint(ctx, options, this.x, this.y);\n  }\n\n  getRange() {\n    const options = this.options || {};\n    // @ts-expect-error Fallbacks should never be hit in practice\n    return options.radius + options.hitRadius;\n  }\n}\n"
  },
  {
    "path": "src/elements/index.js",
    "content": "export {default as ArcElement} from './element.arc.js';\nexport {default as LineElement} from './element.line.js';\nexport {default as PointElement} from './element.point.js';\nexport {default as BarElement} from './element.bar.js';\n"
  },
  {
    "path": "src/helpers/helpers.canvas.ts",
    "content": "import type {\n  Chart,\n  Point,\n  FontSpec,\n  CanvasFontSpec,\n  PointStyle,\n  RenderTextOpts,\n  BackdropOptions\n} from '../types/index.js';\nimport type {\n  TRBL,\n  SplinePoint,\n  RoundedRect,\n  TRBLCorners\n} from '../types/geometric.js';\nimport {isArray, isNullOrUndef} from './helpers.core.js';\nimport {PI, TAU, HALF_PI, QUARTER_PI, TWO_THIRDS_PI, RAD_PER_DEG} from './helpers.math.js';\n\n/**\n * Converts the given font object into a CSS font string.\n * @param font - A font object.\n * @return The CSS font string. See https://developer.mozilla.org/en-US/docs/Web/CSS/font\n * @private\n */\nexport function toFontString(font: FontSpec) {\n  if (!font || isNullOrUndef(font.size) || isNullOrUndef(font.family)) {\n    return null;\n  }\n\n  return (font.style ? font.style + ' ' : '')\n\t\t+ (font.weight ? font.weight + ' ' : '')\n\t\t+ font.size + 'px '\n\t\t+ font.family;\n}\n\n/**\n * @private\n */\nexport function _measureText(\n  ctx: CanvasRenderingContext2D,\n  data: Record<string, number>,\n  gc: string[],\n  longest: number,\n  string: string\n) {\n  let textWidth = data[string];\n  if (!textWidth) {\n    textWidth = data[string] = ctx.measureText(string).width;\n    gc.push(string);\n  }\n  if (textWidth > longest) {\n    longest = textWidth;\n  }\n  return longest;\n}\n\ntype Thing = string | undefined | null\ntype Things = (Thing | Thing[])[]\n\n/**\n * @private\n */\n// eslint-disable-next-line complexity\nexport function _longestText(\n  ctx: CanvasRenderingContext2D,\n  font: string,\n  arrayOfThings: Things,\n  cache?: {data?: Record<string, number>, garbageCollect?: string[], font?: string}\n) {\n  cache = cache || {};\n  let data = cache.data = cache.data || {};\n  let gc = cache.garbageCollect = cache.garbageCollect || [];\n\n  if (cache.font !== font) {\n    data = cache.data = {};\n    gc = cache.garbageCollect = [];\n    cache.font = font;\n  }\n\n  ctx.save();\n\n  ctx.font = font;\n  let longest = 0;\n  const ilen = arrayOfThings.length;\n  let i: number, j: number, jlen: number, thing: Thing | Thing[], nestedThing: Thing | Thing[];\n  for (i = 0; i < ilen; i++) {\n    thing = arrayOfThings[i];\n\n    // Undefined strings and arrays should not be measured\n    if (thing !== undefined && thing !== null && !isArray(thing)) {\n      longest = _measureText(ctx, data, gc, longest, thing);\n    } else if (isArray(thing)) {\n      // if it is an array lets measure each element\n      // to do maybe simplify this function a bit so we can do this more recursively?\n      for (j = 0, jlen = thing.length; j < jlen; j++) {\n        nestedThing = thing[j];\n        // Undefined strings and arrays should not be measured\n        if (nestedThing !== undefined && nestedThing !== null && !isArray(nestedThing)) {\n          longest = _measureText(ctx, data, gc, longest, nestedThing);\n        }\n      }\n    }\n  }\n\n  ctx.restore();\n\n  const gcLen = gc.length / 2;\n  if (gcLen > arrayOfThings.length) {\n    for (i = 0; i < gcLen; i++) {\n      delete data[gc[i]];\n    }\n    gc.splice(0, gcLen);\n  }\n  return longest;\n}\n\n/**\n * Returns the aligned pixel value to avoid anti-aliasing blur\n * @param chart - The chart instance.\n * @param pixel - A pixel value.\n * @param width - The width of the element.\n * @returns The aligned pixel value.\n * @private\n */\nexport function _alignPixel(chart: Chart, pixel: number, width: number) {\n  const devicePixelRatio = chart.currentDevicePixelRatio;\n  const halfWidth = width !== 0 ? Math.max(width / 2, 0.5) : 0;\n  return Math.round((pixel - halfWidth) * devicePixelRatio) / devicePixelRatio + halfWidth;\n}\n\n/**\n * Clears the entire canvas.\n */\nexport function clearCanvas(canvas?: HTMLCanvasElement, ctx?: CanvasRenderingContext2D) {\n  if (!ctx && !canvas) {\n    return;\n  }\n\n  ctx = ctx || canvas.getContext('2d');\n\n  ctx.save();\n  // canvas.width and canvas.height do not consider the canvas transform,\n  // while clearRect does\n  ctx.resetTransform();\n  ctx.clearRect(0, 0, canvas.width, canvas.height);\n  ctx.restore();\n}\n\nexport interface DrawPointOptions {\n  pointStyle: PointStyle;\n  rotation?: number;\n  radius: number;\n  borderWidth: number;\n}\n\nexport function drawPoint(\n  ctx: CanvasRenderingContext2D,\n  options: DrawPointOptions,\n  x: number,\n  y: number\n) {\n  // eslint-disable-next-line @typescript-eslint/no-use-before-define\n  drawPointLegend(ctx, options, x, y, null);\n}\n\n// eslint-disable-next-line complexity\nexport function drawPointLegend(\n  ctx: CanvasRenderingContext2D,\n  options: DrawPointOptions,\n  x: number,\n  y: number,\n  w: number\n) {\n  let type: string, xOffset: number, yOffset: number, size: number, cornerRadius: number, width: number, xOffsetW: number, yOffsetW: number;\n  const style = options.pointStyle;\n  const rotation = options.rotation;\n  const radius = options.radius;\n  let rad = (rotation || 0) * RAD_PER_DEG;\n\n  if (style && typeof style === 'object') {\n    type = style.toString();\n    if (type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]') {\n      ctx.save();\n      ctx.translate(x, y);\n      ctx.rotate(rad);\n      ctx.drawImage(style, -style.width / 2, -style.height / 2, style.width, style.height);\n      ctx.restore();\n      return;\n    }\n  }\n\n  if (isNaN(radius) || radius <= 0) {\n    return;\n  }\n\n  ctx.beginPath();\n\n  switch (style) {\n  // Default includes circle\n    default:\n      if (w) {\n        ctx.ellipse(x, y, w / 2, radius, 0, 0, TAU);\n      } else {\n        ctx.arc(x, y, radius, 0, TAU);\n      }\n      ctx.closePath();\n      break;\n    case 'triangle':\n      width = w ? w / 2 : radius;\n      ctx.moveTo(x + Math.sin(rad) * width, y - Math.cos(rad) * radius);\n      rad += TWO_THIRDS_PI;\n      ctx.lineTo(x + Math.sin(rad) * width, y - Math.cos(rad) * radius);\n      rad += TWO_THIRDS_PI;\n      ctx.lineTo(x + Math.sin(rad) * width, y - Math.cos(rad) * radius);\n      ctx.closePath();\n      break;\n    case 'rectRounded':\n    // NOTE: the rounded rect implementation changed to use `arc` instead of\n    // `quadraticCurveTo` since it generates better results when rect is\n    // almost a circle. 0.516 (instead of 0.5) produces results with visually\n    // closer proportion to the previous impl and it is inscribed in the\n    // circle with `radius`. For more details, see the following PRs:\n    // https://github.com/chartjs/Chart.js/issues/5597\n    // https://github.com/chartjs/Chart.js/issues/5858\n      cornerRadius = radius * 0.516;\n      size = radius - cornerRadius;\n      xOffset = Math.cos(rad + QUARTER_PI) * size;\n      xOffsetW = Math.cos(rad + QUARTER_PI) * (w ? w / 2 - cornerRadius : size);\n      yOffset = Math.sin(rad + QUARTER_PI) * size;\n      yOffsetW = Math.sin(rad + QUARTER_PI) * (w ? w / 2 - cornerRadius : size);\n      ctx.arc(x - xOffsetW, y - yOffset, cornerRadius, rad - PI, rad - HALF_PI);\n      ctx.arc(x + yOffsetW, y - xOffset, cornerRadius, rad - HALF_PI, rad);\n      ctx.arc(x + xOffsetW, y + yOffset, cornerRadius, rad, rad + HALF_PI);\n      ctx.arc(x - yOffsetW, y + xOffset, cornerRadius, rad + HALF_PI, rad + PI);\n      ctx.closePath();\n      break;\n    case 'rect':\n      if (!rotation) {\n        size = Math.SQRT1_2 * radius;\n        width = w ? w / 2 : size;\n        ctx.rect(x - width, y - size, 2 * width, 2 * size);\n        break;\n      }\n      rad += QUARTER_PI;\n    /* falls through */\n    case 'rectRot':\n      xOffsetW = Math.cos(rad) * (w ? w / 2 : radius);\n      xOffset = Math.cos(rad) * radius;\n      yOffset = Math.sin(rad) * radius;\n      yOffsetW = Math.sin(rad) * (w ? w / 2 : radius);\n      ctx.moveTo(x - xOffsetW, y - yOffset);\n      ctx.lineTo(x + yOffsetW, y - xOffset);\n      ctx.lineTo(x + xOffsetW, y + yOffset);\n      ctx.lineTo(x - yOffsetW, y + xOffset);\n      ctx.closePath();\n      break;\n    case 'crossRot':\n      rad += QUARTER_PI;\n    /* falls through */\n    case 'cross':\n      xOffsetW = Math.cos(rad) * (w ? w / 2 : radius);\n      xOffset = Math.cos(rad) * radius;\n      yOffset = Math.sin(rad) * radius;\n      yOffsetW = Math.sin(rad) * (w ? w / 2 : radius);\n      ctx.moveTo(x - xOffsetW, y - yOffset);\n      ctx.lineTo(x + xOffsetW, y + yOffset);\n      ctx.moveTo(x + yOffsetW, y - xOffset);\n      ctx.lineTo(x - yOffsetW, y + xOffset);\n      break;\n    case 'star':\n      xOffsetW = Math.cos(rad) * (w ? w / 2 : radius);\n      xOffset = Math.cos(rad) * radius;\n      yOffset = Math.sin(rad) * radius;\n      yOffsetW = Math.sin(rad) * (w ? w / 2 : radius);\n      ctx.moveTo(x - xOffsetW, y - yOffset);\n      ctx.lineTo(x + xOffsetW, y + yOffset);\n      ctx.moveTo(x + yOffsetW, y - xOffset);\n      ctx.lineTo(x - yOffsetW, y + xOffset);\n      rad += QUARTER_PI;\n      xOffsetW = Math.cos(rad) * (w ? w / 2 : radius);\n      xOffset = Math.cos(rad) * radius;\n      yOffset = Math.sin(rad) * radius;\n      yOffsetW = Math.sin(rad) * (w ? w / 2 : radius);\n      ctx.moveTo(x - xOffsetW, y - yOffset);\n      ctx.lineTo(x + xOffsetW, y + yOffset);\n      ctx.moveTo(x + yOffsetW, y - xOffset);\n      ctx.lineTo(x - yOffsetW, y + xOffset);\n      break;\n    case 'line':\n      xOffset = w ? w / 2 : Math.cos(rad) * radius;\n      yOffset = Math.sin(rad) * radius;\n      ctx.moveTo(x - xOffset, y - yOffset);\n      ctx.lineTo(x + xOffset, y + yOffset);\n      break;\n    case 'dash':\n      ctx.moveTo(x, y);\n      ctx.lineTo(x + Math.cos(rad) * (w ? w / 2 : radius), y + Math.sin(rad) * radius);\n      break;\n    case false:\n      ctx.closePath();\n      break;\n  }\n\n  ctx.fill();\n  if (options.borderWidth > 0) {\n    ctx.stroke();\n  }\n}\n\n/**\n * Returns true if the point is inside the rectangle\n * @param point - The point to test\n * @param area - The rectangle\n * @param margin - allowed margin\n * @private\n */\nexport function _isPointInArea(\n  point: Point,\n  area: TRBL,\n  margin?: number\n) {\n  margin = margin || 0.5; // margin - default is to match rounded decimals\n\n  return !area || (point && point.x > area.left - margin && point.x < area.right + margin &&\n\t\tpoint.y > area.top - margin && point.y < area.bottom + margin);\n}\n\nexport function clipArea(ctx: CanvasRenderingContext2D, area: TRBL) {\n  ctx.save();\n  ctx.beginPath();\n  ctx.rect(area.left, area.top, area.right - area.left, area.bottom - area.top);\n  ctx.clip();\n}\n\nexport function unclipArea(ctx: CanvasRenderingContext2D) {\n  ctx.restore();\n}\n\n/**\n * @private\n */\nexport function _steppedLineTo(\n  ctx: CanvasRenderingContext2D,\n  previous: Point,\n  target: Point,\n  flip?: boolean,\n  mode?: string\n) {\n  if (!previous) {\n    return ctx.lineTo(target.x, target.y);\n  }\n  if (mode === 'middle') {\n    const midpoint = (previous.x + target.x) / 2.0;\n    ctx.lineTo(midpoint, previous.y);\n    ctx.lineTo(midpoint, target.y);\n  } else if (mode === 'after' !== !!flip) {\n    ctx.lineTo(previous.x, target.y);\n  } else {\n    ctx.lineTo(target.x, previous.y);\n  }\n  ctx.lineTo(target.x, target.y);\n}\n\n/**\n * @private\n */\nexport function _bezierCurveTo(\n  ctx: CanvasRenderingContext2D,\n  previous: SplinePoint,\n  target: SplinePoint,\n  flip?: boolean\n) {\n  if (!previous) {\n    return ctx.lineTo(target.x, target.y);\n  }\n  ctx.bezierCurveTo(\n    flip ? previous.cp1x : previous.cp2x,\n    flip ? previous.cp1y : previous.cp2y,\n    flip ? target.cp2x : target.cp1x,\n    flip ? target.cp2y : target.cp1y,\n    target.x,\n    target.y);\n}\n\nfunction setRenderOpts(ctx: CanvasRenderingContext2D, opts: RenderTextOpts) {\n  if (opts.translation) {\n    ctx.translate(opts.translation[0], opts.translation[1]);\n  }\n\n  if (!isNullOrUndef(opts.rotation)) {\n    ctx.rotate(opts.rotation);\n  }\n\n  if (opts.color) {\n    ctx.fillStyle = opts.color;\n  }\n\n  if (opts.textAlign) {\n    ctx.textAlign = opts.textAlign;\n  }\n\n  if (opts.textBaseline) {\n    ctx.textBaseline = opts.textBaseline;\n  }\n}\n\nfunction decorateText(\n  ctx: CanvasRenderingContext2D,\n  x: number,\n  y: number,\n  line: string,\n  opts: RenderTextOpts\n) {\n  if (opts.strikethrough || opts.underline) {\n    /**\n     * Now that IE11 support has been dropped, we can use more\n     * of the TextMetrics object. The actual bounding boxes\n     * are unflagged in Chrome, Firefox, Edge, and Safari so they\n     * can be safely used.\n     * See https://developer.mozilla.org/en-US/docs/Web/API/TextMetrics#Browser_compatibility\n     */\n    const metrics = ctx.measureText(line);\n    const left = x - metrics.actualBoundingBoxLeft;\n    const right = x + metrics.actualBoundingBoxRight;\n    const top = y - metrics.actualBoundingBoxAscent;\n    const bottom = y + metrics.actualBoundingBoxDescent;\n    const yDecoration = opts.strikethrough ? (top + bottom) / 2 : bottom;\n\n    ctx.strokeStyle = ctx.fillStyle;\n    ctx.beginPath();\n    ctx.lineWidth = opts.decorationWidth || 2;\n    ctx.moveTo(left, yDecoration);\n    ctx.lineTo(right, yDecoration);\n    ctx.stroke();\n  }\n}\n\nfunction drawBackdrop(ctx: CanvasRenderingContext2D, opts: BackdropOptions) {\n  const oldColor = ctx.fillStyle;\n\n  ctx.fillStyle = opts.color as string;\n  ctx.fillRect(opts.left, opts.top, opts.width, opts.height);\n  ctx.fillStyle = oldColor;\n}\n\n/**\n * Render text onto the canvas\n */\nexport function renderText(\n  ctx: CanvasRenderingContext2D,\n  text: string | string[],\n  x: number,\n  y: number,\n  font: CanvasFontSpec,\n  opts: RenderTextOpts = {}\n) {\n  const lines = isArray(text) ? text : [text];\n  const stroke = opts.strokeWidth > 0 && opts.strokeColor !== '';\n  let i: number, line: string;\n\n  ctx.save();\n  ctx.font = font.string;\n  setRenderOpts(ctx, opts);\n\n  for (i = 0; i < lines.length; ++i) {\n    line = lines[i];\n\n    if (opts.backdrop) {\n      drawBackdrop(ctx, opts.backdrop);\n    }\n\n    if (stroke) {\n      if (opts.strokeColor) {\n        ctx.strokeStyle = opts.strokeColor;\n      }\n\n      if (!isNullOrUndef(opts.strokeWidth)) {\n        ctx.lineWidth = opts.strokeWidth;\n      }\n\n      ctx.strokeText(line, x, y, opts.maxWidth);\n    }\n\n    ctx.fillText(line, x, y, opts.maxWidth);\n    decorateText(ctx, x, y, line, opts);\n\n    y += Number(font.lineHeight);\n  }\n\n  ctx.restore();\n}\n\n/**\n * Add a path of a rectangle with rounded corners to the current sub-path\n * @param ctx - Context\n * @param rect - Bounding rect\n */\nexport function addRoundedRectPath(\n  ctx: CanvasRenderingContext2D,\n  rect: RoundedRect & { radius: TRBLCorners }\n) {\n  const {x, y, w, h, radius} = rect;\n\n  // top left arc\n  ctx.arc(x + radius.topLeft, y + radius.topLeft, radius.topLeft, 1.5 * PI, PI, true);\n\n  // line from top left to bottom left\n  ctx.lineTo(x, y + h - radius.bottomLeft);\n\n  // bottom left arc\n  ctx.arc(x + radius.bottomLeft, y + h - radius.bottomLeft, radius.bottomLeft, PI, HALF_PI, true);\n\n  // line from bottom left to bottom right\n  ctx.lineTo(x + w - radius.bottomRight, y + h);\n\n  // bottom right arc\n  ctx.arc(x + w - radius.bottomRight, y + h - radius.bottomRight, radius.bottomRight, HALF_PI, 0, true);\n\n  // line from bottom right to top right\n  ctx.lineTo(x + w, y + radius.topRight);\n\n  // top right arc\n  ctx.arc(x + w - radius.topRight, y + radius.topRight, radius.topRight, 0, -HALF_PI, true);\n\n  // line from top right to top left\n  ctx.lineTo(x + radius.topLeft, y);\n}\n"
  },
  {
    "path": "src/helpers/helpers.collection.ts",
    "content": "import {_capitalize} from './helpers.core.js';\n\n/**\n * Binary search\n * @param table - the table search. must be sorted!\n * @param value - value to find\n * @param cmp\n * @private\n */\nexport function _lookup(\n  table: number[],\n  value: number,\n  cmp?: (value: number) => boolean\n): {lo: number, hi: number};\nexport function _lookup<T>(\n  table: T[],\n  value: number,\n  cmp: (value: number) => boolean\n): {lo: number, hi: number};\nexport function _lookup(\n  table: unknown[],\n  value: number,\n  cmp?: (value: number) => boolean\n) {\n  cmp = cmp || ((index) => table[index] < value);\n  let hi = table.length - 1;\n  let lo = 0;\n  let mid: number;\n\n  while (hi - lo > 1) {\n    mid = (lo + hi) >> 1;\n    if (cmp(mid)) {\n      lo = mid;\n    } else {\n      hi = mid;\n    }\n  }\n\n  return {lo, hi};\n}\n\n/**\n * Binary search\n * @param table - the table search. must be sorted!\n * @param key - property name for the value in each entry\n * @param value - value to find\n * @param last - lookup last index\n * @private\n */\nexport const _lookupByKey = (\n  table: Record<string, number>[],\n  key: string,\n  value: number,\n  last?: boolean\n) =>\n  _lookup(table, value, last\n    ? index => {\n      const ti = table[index][key];\n      return ti < value || ti === value && table[index + 1][key] === value;\n    }\n    : index => table[index][key] < value);\n\n/**\n * Reverse binary search\n * @param table - the table search. must be sorted!\n * @param key - property name for the value in each entry\n * @param value - value to find\n * @private\n */\nexport const _rlookupByKey = (\n  table: Record<string, number>[],\n  key: string,\n  value: number\n) =>\n  _lookup(table, value, index => table[index][key] >= value);\n\n/**\n * Return subset of `values` between `min` and `max` inclusive.\n * Values are assumed to be in sorted order.\n * @param values - sorted array of values\n * @param min - min value\n * @param max - max value\n */\nexport function _filterBetween(values: number[], min: number, max: number) {\n  let start = 0;\n  let end = values.length;\n\n  while (start < end && values[start] < min) {\n    start++;\n  }\n  while (end > start && values[end - 1] > max) {\n    end--;\n  }\n\n  return start > 0 || end < values.length\n    ? values.slice(start, end)\n    : values;\n}\n\nconst arrayEvents = ['push', 'pop', 'shift', 'splice', 'unshift'] as const;\n\nexport interface ArrayListener<T> {\n  _onDataPush?(...item: T[]): void;\n  _onDataPop?(): void;\n  _onDataShift?(): void;\n  _onDataSplice?(index: number, deleteCount: number, ...items: T[]): void;\n  _onDataUnshift?(...item: T[]): void;\n}\n\n/**\n * Hooks the array methods that add or remove values ('push', pop', 'shift', 'splice',\n * 'unshift') and notify the listener AFTER the array has been altered. Listeners are\n * called on the '_onData*' callbacks (e.g. _onDataPush, etc.) with same arguments.\n */\nexport function listenArrayEvents<T>(array: T[], listener: ArrayListener<T>): void;\nexport function listenArrayEvents(array, listener) {\n  if (array._chartjs) {\n    array._chartjs.listeners.push(listener);\n    return;\n  }\n\n  Object.defineProperty(array, '_chartjs', {\n    configurable: true,\n    enumerable: false,\n    value: {\n      listeners: [listener]\n    }\n  });\n\n  arrayEvents.forEach((key) => {\n    const method = '_onData' + _capitalize(key);\n    const base = array[key];\n\n    Object.defineProperty(array, key, {\n      configurable: true,\n      enumerable: false,\n      value(...args) {\n        const res = base.apply(this, args);\n\n        array._chartjs.listeners.forEach((object) => {\n          if (typeof object[method] === 'function') {\n            object[method](...args);\n          }\n        });\n\n        return res;\n      }\n    });\n  });\n}\n\n\n/**\n * Removes the given array event listener and cleanup extra attached properties (such as\n * the _chartjs stub and overridden methods) if array doesn't have any more listeners.\n */\nexport function unlistenArrayEvents<T>(array: T[], listener: ArrayListener<T>): void;\nexport function unlistenArrayEvents(array, listener) {\n  const stub = array._chartjs;\n  if (!stub) {\n    return;\n  }\n\n  const listeners = stub.listeners;\n  const index = listeners.indexOf(listener);\n  if (index !== -1) {\n    listeners.splice(index, 1);\n  }\n\n  if (listeners.length > 0) {\n    return;\n  }\n\n  arrayEvents.forEach((key) => {\n    delete array[key];\n  });\n\n  delete array._chartjs;\n}\n\n/**\n * @param items\n */\nexport function _arrayUnique<T>(items: T[]) {\n  const set = new Set<T>(items);\n\n  if (set.size === items.length) {\n    return items;\n  }\n\n  return Array.from(set);\n}\n"
  },
  {
    "path": "src/helpers/helpers.color.ts",
    "content": "import {Color} from '@kurkle/color';\n\nexport function isPatternOrGradient(value: unknown): value is CanvasPattern | CanvasGradient {\n  if (value && typeof value === 'object') {\n    const type = value.toString();\n    return type === '[object CanvasPattern]' || type === '[object CanvasGradient]';\n  }\n\n  return false;\n}\n\nexport function color(value: CanvasGradient): CanvasGradient;\nexport function color(value: CanvasPattern): CanvasPattern;\nexport function color(\n  value:\n  | string\n  | { r: number; g: number; b: number; a: number }\n  | [number, number, number]\n  | [number, number, number, number]\n): Color;\nexport function color(value) {\n  return isPatternOrGradient(value) ? value : new Color(value);\n}\n\nexport function getHoverColor(value: CanvasGradient): CanvasGradient;\nexport function getHoverColor(value: CanvasPattern): CanvasPattern;\nexport function getHoverColor(value: string): string;\nexport function getHoverColor(value) {\n  return isPatternOrGradient(value)\n    ? value\n    : new Color(value).saturate(0.5).darken(0.1).hexString();\n}\n"
  },
  {
    "path": "src/helpers/helpers.config.ts",
    "content": "/* eslint-disable @typescript-eslint/no-use-before-define */\nimport type {AnyObject} from '../types/basic.js';\nimport type {ChartMeta} from '../types/index.js';\nimport type {\n  ResolverObjectKey,\n  ResolverCache,\n  ResolverProxy,\n  DescriptorDefaults,\n  Descriptor,\n  ContextCache,\n  ContextProxy\n} from './helpers.config.types.js';\nimport {isArray, isFunction, isObject, resolveObjectKey, _capitalize} from './helpers.core.js';\n\nexport * from './helpers.config.types.js';\n\n/**\n * Creates a Proxy for resolving raw values for options.\n * @param scopes - The option scopes to look for values, in resolution order\n * @param prefixes - The prefixes for values, in resolution order.\n * @param rootScopes - The root option scopes\n * @param fallback - Parent scopes fallback\n * @param getTarget - callback for getting the target for changed values\n * @returns Proxy\n * @private\n */\nexport function _createResolver<\n  T extends AnyObject[] = AnyObject[],\n  R extends AnyObject[] = T\n>(\n  scopes: T,\n  prefixes = [''],\n  rootScopes?: R,\n  fallback?: ResolverObjectKey,\n  getTarget = () => scopes[0]\n) {\n  const finalRootScopes = rootScopes || scopes;\n  if (typeof fallback === 'undefined') {\n    fallback = _resolve('_fallback', scopes);\n  }\n  const cache: ResolverCache<T, R> = {\n    [Symbol.toStringTag]: 'Object',\n    _cacheable: true,\n    _scopes: scopes,\n    _rootScopes: finalRootScopes,\n    _fallback: fallback,\n    _getTarget: getTarget,\n    override: (scope: AnyObject) => _createResolver([scope, ...scopes], prefixes, finalRootScopes, fallback),\n  };\n  return new Proxy(cache, {\n    /**\n     * A trap for the delete operator.\n     */\n    deleteProperty(target, prop: string) {\n      delete target[prop]; // remove from cache\n      delete target._keys; // remove cached keys\n      delete scopes[0][prop]; // remove from top level scope\n      return true;\n    },\n\n    /**\n     * A trap for getting property values.\n     */\n    get(target, prop: string) {\n      return _cached(target, prop,\n        () => _resolveWithPrefixes(prop, prefixes, scopes, target));\n    },\n\n    /**\n     * A trap for Object.getOwnPropertyDescriptor.\n     * Also used by Object.hasOwnProperty.\n     */\n    getOwnPropertyDescriptor(target, prop) {\n      return Reflect.getOwnPropertyDescriptor(target._scopes[0], prop);\n    },\n\n    /**\n     * A trap for Object.getPrototypeOf.\n     */\n    getPrototypeOf() {\n      return Reflect.getPrototypeOf(scopes[0]);\n    },\n\n    /**\n     * A trap for the in operator.\n     */\n    has(target, prop: string) {\n      return getKeysFromAllScopes(target).includes(prop);\n    },\n\n    /**\n     * A trap for Object.getOwnPropertyNames and Object.getOwnPropertySymbols.\n     */\n    ownKeys(target) {\n      return getKeysFromAllScopes(target);\n    },\n\n    /**\n     * A trap for setting property values.\n     */\n    set(target, prop: string, value) {\n      const storage = target._storage || (target._storage = getTarget());\n      target[prop] = storage[prop] = value; // set to top level scope + cache\n      delete target._keys; // remove cached keys\n      return true;\n    }\n  }) as ResolverProxy<T, R>;\n}\n\n/**\n * Returns an Proxy for resolving option values with context.\n * @param proxy - The Proxy returned by `_createResolver`\n * @param context - Context object for scriptable/indexable options\n * @param subProxy - The proxy provided for scriptable options\n * @param descriptorDefaults - Defaults for descriptors\n * @private\n */\nexport function _attachContext<\n  T extends AnyObject[] = AnyObject[],\n  R extends AnyObject[] = T\n>(\n  proxy: ResolverProxy<T, R>,\n  context: AnyObject,\n  subProxy?: ResolverProxy<T, R>,\n  descriptorDefaults?: DescriptorDefaults\n) {\n  const cache: ContextCache<T, R> = {\n    _cacheable: false,\n    _proxy: proxy,\n    _context: context,\n    _subProxy: subProxy,\n    _stack: new Set(),\n    _descriptors: _descriptors(proxy, descriptorDefaults),\n    setContext: (ctx: AnyObject) => _attachContext(proxy, ctx, subProxy, descriptorDefaults),\n    override: (scope: AnyObject) => _attachContext(proxy.override(scope), context, subProxy, descriptorDefaults)\n  };\n  return new Proxy(cache, {\n    /**\n     * A trap for the delete operator.\n     */\n    deleteProperty(target, prop) {\n      delete target[prop]; // remove from cache\n      delete proxy[prop]; // remove from proxy\n      return true;\n    },\n\n    /**\n     * A trap for getting property values.\n     */\n    get(target, prop: string, receiver) {\n      return _cached(target, prop,\n        () => _resolveWithContext(target, prop, receiver));\n    },\n\n    /**\n     * A trap for Object.getOwnPropertyDescriptor.\n     * Also used by Object.hasOwnProperty.\n     */\n    getOwnPropertyDescriptor(target, prop) {\n      return target._descriptors.allKeys\n        ? Reflect.has(proxy, prop) ? {enumerable: true, configurable: true} : undefined\n        : Reflect.getOwnPropertyDescriptor(proxy, prop);\n    },\n\n    /**\n     * A trap for Object.getPrototypeOf.\n     */\n    getPrototypeOf() {\n      return Reflect.getPrototypeOf(proxy);\n    },\n\n    /**\n     * A trap for the in operator.\n     */\n    has(target, prop) {\n      return Reflect.has(proxy, prop);\n    },\n\n    /**\n     * A trap for Object.getOwnPropertyNames and Object.getOwnPropertySymbols.\n     */\n    ownKeys() {\n      return Reflect.ownKeys(proxy);\n    },\n\n    /**\n     * A trap for setting property values.\n     */\n    set(target, prop, value) {\n      proxy[prop] = value; // set to proxy\n      delete target[prop]; // remove from cache\n      return true;\n    }\n  }) as ContextProxy<T, R>;\n}\n\n/**\n * @private\n */\nexport function _descriptors(\n  proxy: ResolverCache,\n  defaults: DescriptorDefaults = {scriptable: true, indexable: true}\n): Descriptor {\n  const {_scriptable = defaults.scriptable, _indexable = defaults.indexable, _allKeys = defaults.allKeys} = proxy;\n  return {\n    allKeys: _allKeys,\n    scriptable: _scriptable,\n    indexable: _indexable,\n    isScriptable: isFunction(_scriptable) ? _scriptable : () => _scriptable,\n    isIndexable: isFunction(_indexable) ? _indexable : () => _indexable\n  };\n}\n\nconst readKey = (prefix: string, name: string) => prefix ? prefix + _capitalize(name) : name;\nconst needsSubResolver = (prop: string, value: unknown) => isObject(value) && prop !== 'adapters' &&\n  (Object.getPrototypeOf(value) === null || value.constructor === Object);\n\nfunction _cached(\n  target: AnyObject,\n  prop: string,\n  resolve: () => unknown\n) {\n  if (Object.prototype.hasOwnProperty.call(target, prop) || prop === 'constructor') {\n    return target[prop];\n  }\n\n  const value = resolve();\n  // cache the resolved value\n  target[prop] = value;\n  return value;\n}\n\nfunction _resolveWithContext(\n  target: ContextCache,\n  prop: string,\n  receiver: AnyObject\n) {\n  const {_proxy, _context, _subProxy, _descriptors: descriptors} = target;\n  let value = _proxy[prop]; // resolve from proxy\n\n  // resolve with context\n  if (isFunction(value) && descriptors.isScriptable(prop)) {\n    value = _resolveScriptable(prop, value, target, receiver);\n  }\n  if (isArray(value) && value.length) {\n    value = _resolveArray(prop, value, target, descriptors.isIndexable);\n  }\n  if (needsSubResolver(prop, value)) {\n    // if the resolved value is an object, create a sub resolver for it\n    value = _attachContext(value, _context, _subProxy && _subProxy[prop], descriptors);\n  }\n  return value;\n}\n\nfunction _resolveScriptable(\n  prop: string,\n  getValue: (ctx: AnyObject, sub: AnyObject) => unknown,\n  target: ContextCache,\n  receiver: AnyObject\n) {\n  const {_proxy, _context, _subProxy, _stack} = target;\n  if (_stack.has(prop)) {\n    throw new Error('Recursion detected: ' + Array.from(_stack).join('->') + '->' + prop);\n  }\n  _stack.add(prop);\n  let value = getValue(_context, _subProxy || receiver);\n  _stack.delete(prop);\n  if (needsSubResolver(prop, value)) {\n    // When scriptable option returns an object, create a resolver on that.\n    value = createSubResolver(_proxy._scopes, _proxy, prop, value);\n  }\n  return value;\n}\n\nfunction _resolveArray(\n  prop: string,\n  value: unknown[],\n  target: ContextCache,\n  isIndexable: (key: string) => boolean\n) {\n  const {_proxy, _context, _subProxy, _descriptors: descriptors} = target;\n\n  if (typeof _context.index !== 'undefined' && isIndexable(prop)) {\n    return value[_context.index % value.length];\n  } else if (isObject(value[0])) {\n    // Array of objects, return array or resolvers\n    const arr = value;\n    const scopes = _proxy._scopes.filter(s => s !== arr);\n    value = [];\n    for (const item of arr) {\n      const resolver = createSubResolver(scopes, _proxy, prop, item);\n      value.push(_attachContext(resolver, _context, _subProxy && _subProxy[prop], descriptors));\n    }\n  }\n  return value;\n}\n\nfunction resolveFallback(\n  fallback: ResolverObjectKey | ((prop: ResolverObjectKey, value: unknown) => ResolverObjectKey),\n  prop: ResolverObjectKey,\n  value: unknown\n) {\n  return isFunction(fallback) ? fallback(prop, value) : fallback;\n}\n\nconst getScope = (key: ResolverObjectKey, parent: AnyObject) => key === true ? parent\n  : typeof key === 'string' ? resolveObjectKey(parent, key) : undefined;\n\nfunction addScopes(\n  set: Set<AnyObject>,\n  parentScopes: AnyObject[],\n  key: ResolverObjectKey,\n  parentFallback: ResolverObjectKey,\n  value: unknown\n) {\n  for (const parent of parentScopes) {\n    const scope = getScope(key, parent);\n    if (scope) {\n      set.add(scope);\n      const fallback = resolveFallback(scope._fallback, key, value);\n      if (typeof fallback !== 'undefined' && fallback !== key && fallback !== parentFallback) {\n        // When we reach the descriptor that defines a new _fallback, return that.\n        // The fallback will resume to that new scope.\n        return fallback;\n      }\n    } else if (scope === false && typeof parentFallback !== 'undefined' && key !== parentFallback) {\n      // Fallback to `false` results to `false`, when falling back to different key.\n      // For example `interaction` from `hover` or `plugins.tooltip` and `animation` from `animations`\n      return null;\n    }\n  }\n  return false;\n}\n\nfunction createSubResolver(\n  parentScopes: AnyObject[],\n  resolver: ResolverCache,\n  prop: ResolverObjectKey,\n  value: unknown\n) {\n  const rootScopes = resolver._rootScopes;\n  const fallback = resolveFallback(resolver._fallback, prop, value);\n  const allScopes = [...parentScopes, ...rootScopes];\n  const set = new Set<AnyObject>();\n  set.add(value);\n  let key = addScopesFromKey(set, allScopes, prop, fallback || prop, value);\n  if (key === null) {\n    return false;\n  }\n  if (typeof fallback !== 'undefined' && fallback !== prop) {\n    key = addScopesFromKey(set, allScopes, fallback, key, value);\n    if (key === null) {\n      return false;\n    }\n  }\n  return _createResolver(Array.from(set), [''], rootScopes, fallback,\n    () => subGetTarget(resolver, prop as string, value));\n}\n\nfunction addScopesFromKey(\n  set: Set<AnyObject>,\n  allScopes: AnyObject[],\n  key: ResolverObjectKey,\n  fallback: ResolverObjectKey,\n  item: unknown\n) {\n  while (key) {\n    key = addScopes(set, allScopes, key, fallback, item);\n  }\n  return key;\n}\n\nfunction subGetTarget(\n  resolver: ResolverCache,\n  prop: string,\n  value: unknown\n) {\n  const parent = resolver._getTarget();\n  if (!(prop in parent)) {\n    parent[prop] = {};\n  }\n  const target = parent[prop];\n  if (isArray(target) && isObject(value)) {\n    // For array of objects, the object is used to store updated values\n    return value;\n  }\n  return target || {};\n}\n\nfunction _resolveWithPrefixes(\n  prop: string,\n  prefixes: string[],\n  scopes: AnyObject[],\n  proxy: ResolverProxy\n) {\n  let value: unknown;\n  for (const prefix of prefixes) {\n    value = _resolve(readKey(prefix, prop), scopes);\n    if (typeof value !== 'undefined') {\n      return needsSubResolver(prop, value)\n        ? createSubResolver(scopes, proxy, prop, value)\n        : value;\n    }\n  }\n}\n\nfunction _resolve(key: string, scopes: AnyObject[]) {\n  for (const scope of scopes) {\n    if (!scope) {\n      continue;\n    }\n    const value = scope[key];\n    if (typeof value !== 'undefined') {\n      return value;\n    }\n  }\n}\n\nfunction getKeysFromAllScopes(target: ResolverCache) {\n  let keys = target._keys;\n  if (!keys) {\n    keys = target._keys = resolveKeysFromAllScopes(target._scopes);\n  }\n  return keys;\n}\n\nfunction resolveKeysFromAllScopes(scopes: AnyObject[]) {\n  const set = new Set<string>();\n  for (const scope of scopes) {\n    for (const key of Object.keys(scope).filter(k => !k.startsWith('_'))) {\n      set.add(key);\n    }\n  }\n  return Array.from(set);\n}\n\nexport function _parseObjectDataRadialScale(\n  meta: ChartMeta<'line' | 'scatter'>,\n  data: AnyObject[],\n  start: number,\n  count: number\n) {\n  const {iScale} = meta;\n  const {key = 'r'} = this._parsing;\n  const parsed = new Array<{r: unknown}>(count);\n  let i: number, ilen: number, index: number, item: AnyObject;\n\n  for (i = 0, ilen = count; i < ilen; ++i) {\n    index = i + start;\n    item = data[index];\n    parsed[i] = {\n      r: iScale.parse(resolveObjectKey(item, key), index)\n    };\n  }\n  return parsed;\n}\n"
  },
  {
    "path": "src/helpers/helpers.config.types.ts",
    "content": "import type {AnyObject} from '../types/basic.js';\nimport type {Merge} from '../types/utils.js';\n\nexport type ResolverObjectKey = string | boolean;\n\nexport interface ResolverCache<\n  T extends AnyObject[] = AnyObject[],\n  R extends AnyObject[] = T\n> {\n  [Symbol.toStringTag]: 'Object';\n  _cacheable: boolean;\n  _scopes: T;\n  _rootScopes: T | R;\n  _fallback: ResolverObjectKey;\n  _keys?: string[];\n  _scriptable?: boolean;\n  _indexable?: boolean;\n  _allKeys?: boolean;\n  _storage?: T[number];\n  _getTarget(): T[number];\n  override<S extends AnyObject>(scope: S): ResolverProxy<(T[number] | S)[], T | R>\n}\n\nexport type ResolverProxy<\n  T extends AnyObject[] = AnyObject[],\n  R extends AnyObject[] = T\n> = Merge<T[number]> & ResolverCache<T, R>\n\nexport interface DescriptorDefaults {\n  scriptable: boolean;\n  indexable: boolean;\n  allKeys?: boolean\n}\n\nexport interface Descriptor {\n  allKeys: boolean;\n  scriptable: boolean;\n  indexable: boolean;\n  isScriptable(key: string): boolean;\n  isIndexable(key: string): boolean;\n}\n\nexport interface ContextCache<\n  T extends AnyObject[] = AnyObject[],\n  R extends AnyObject[] = T\n> {\n  _cacheable: boolean;\n  _proxy: ResolverProxy<T, R>;\n  _context: AnyObject;\n  _subProxy: ResolverProxy<T, R>;\n  _stack: Set<string>;\n  _descriptors: Descriptor\n  setContext(ctx: AnyObject): ContextProxy<T, R>\n  override<S extends AnyObject>(scope: S): ContextProxy<(T[number] | S)[], T | R>\n}\n\nexport type ContextProxy<\n  T extends AnyObject[] = AnyObject[],\n  R extends AnyObject[] = T\n> = Merge<T[number]> & ContextCache<T, R>;\n"
  },
  {
    "path": "src/helpers/helpers.core.ts",
    "content": "/**\n * @namespace Chart.helpers\n */\n\nimport type {AnyObject} from '../types/basic.js';\nimport type {ActiveDataPoint, ChartEvent} from '../types/index.js';\n\n/**\n * An empty function that can be used, for example, for optional callback.\n */\nexport function noop() {\n  /* noop */\n}\n\n/**\n * Returns a unique id, sequentially generated from a global variable.\n */\nexport const uid = (() => {\n  let id = 0;\n  return () => id++;\n})();\n\n/**\n * Returns true if `value` is neither null nor undefined, else returns false.\n * @param value - The value to test.\n * @since 2.7.0\n */\nexport function isNullOrUndef(value: unknown): value is null | undefined {\n  return value === null || value === undefined;\n}\n\n/**\n * Returns true if `value` is an array (including typed arrays), else returns false.\n * @param value - The value to test.\n * @function\n */\nexport function isArray<T = unknown>(value: unknown): value is T[] {\n  if (Array.isArray && Array.isArray(value)) {\n    return true;\n  }\n  const type = Object.prototype.toString.call(value);\n  if (type.slice(0, 7) === '[object' && type.slice(-6) === 'Array]') {\n    return true;\n  }\n  return false;\n}\n\n/**\n * Returns true if `value` is an object (excluding null), else returns false.\n * @param value - The value to test.\n * @since 2.7.0\n */\nexport function isObject(value: unknown): value is AnyObject {\n  return value !== null && Object.prototype.toString.call(value) === '[object Object]';\n}\n\n/**\n * Returns true if `value` is a finite number, else returns false\n * @param value  - The value to test.\n */\nfunction isNumberFinite(value: unknown): value is number {\n  return (typeof value === 'number' || value instanceof Number) && isFinite(+value);\n}\nexport {\n  isNumberFinite as isFinite,\n};\n\n/**\n * Returns `value` if finite, else returns `defaultValue`.\n * @param value - The value to return if defined.\n * @param defaultValue - The value to return if `value` is not finite.\n */\nexport function finiteOrDefault(value: unknown, defaultValue: number) {\n  return isNumberFinite(value) ? value : defaultValue;\n}\n\n/**\n * Returns `value` if defined, else returns `defaultValue`.\n * @param value - The value to return if defined.\n * @param defaultValue - The value to return if `value` is undefined.\n */\nexport function valueOrDefault<T>(value: T | undefined, defaultValue: T) {\n  return typeof value === 'undefined' ? defaultValue : value;\n}\n\nexport const toPercentage = (value: number | string, dimension: number) =>\n  typeof value === 'string' && value.endsWith('%') ?\n    parseFloat(value) / 100\n    : +value / dimension;\n\nexport const toDimension = (value: number | string, dimension: number) =>\n  typeof value === 'string' && value.endsWith('%') ?\n    parseFloat(value) / 100 * dimension\n    : +value;\n\n/**\n * Calls `fn` with the given `args` in the scope defined by `thisArg` and returns the\n * value returned by `fn`. If `fn` is not a function, this method returns undefined.\n * @param fn - The function to call.\n * @param args - The arguments with which `fn` should be called.\n * @param [thisArg] - The value of `this` provided for the call to `fn`.\n */\nexport function callback<T extends (this: TA, ...restArgs: unknown[]) => R, TA, R>(\n  fn: T | undefined,\n  args: unknown[],\n  thisArg?: TA\n): R | undefined {\n  if (fn && typeof fn.call === 'function') {\n    return fn.apply(thisArg, args);\n  }\n}\n\n/**\n * Note(SB) for performance sake, this method should only be used when loopable type\n * is unknown or in none intensive code (not called often and small loopable). Else\n * it's preferable to use a regular for() loop and save extra function calls.\n * @param loopable - The object or array to be iterated.\n * @param fn - The function to call for each item.\n * @param [thisArg] - The value of `this` provided for the call to `fn`.\n * @param [reverse] - If true, iterates backward on the loopable.\n */\nexport function each<T, TA>(\n  loopable: Record<string, T>,\n  fn: (this: TA, v: T, i: string) => void,\n  thisArg?: TA,\n  reverse?: boolean\n): void;\nexport function each<T, TA>(\n  loopable: T[],\n  fn: (this: TA, v: T, i: number) => void,\n  thisArg?: TA,\n  reverse?: boolean\n): void;\nexport function each<T, TA>(\n  loopable: T[] | Record<string, T>,\n  fn: (this: TA, v: T, i: any) => void,\n  thisArg?: TA,\n  reverse?: boolean\n) {\n  let i: number, len: number, keys: string[];\n  if (isArray(loopable)) {\n    len = loopable.length;\n    if (reverse) {\n      for (i = len - 1; i >= 0; i--) {\n        fn.call(thisArg, loopable[i], i);\n      }\n    } else {\n      for (i = 0; i < len; i++) {\n        fn.call(thisArg, loopable[i], i);\n      }\n    }\n  } else if (isObject(loopable)) {\n    keys = Object.keys(loopable);\n    len = keys.length;\n    for (i = 0; i < len; i++) {\n      fn.call(thisArg, loopable[keys[i]], keys[i]);\n    }\n  }\n}\n\n/**\n * Returns true if the `a0` and `a1` arrays have the same content, else returns false.\n * @param a0 - The array to compare\n * @param a1 - The array to compare\n * @private\n */\nexport function _elementsEqual(a0: ActiveDataPoint[], a1: ActiveDataPoint[]) {\n  let i: number, ilen: number, v0: ActiveDataPoint, v1: ActiveDataPoint;\n\n  if (!a0 || !a1 || a0.length !== a1.length) {\n    return false;\n  }\n\n  for (i = 0, ilen = a0.length; i < ilen; ++i) {\n    v0 = a0[i];\n    v1 = a1[i];\n\n    if (v0.datasetIndex !== v1.datasetIndex || v0.index !== v1.index) {\n      return false;\n    }\n  }\n\n  return true;\n}\n\n/**\n * Returns a deep copy of `source` without keeping references on objects and arrays.\n * @param source - The value to clone.\n */\nexport function clone<T>(source: T): T {\n  if (isArray(source)) {\n    return source.map(clone) as unknown as T;\n  }\n\n  if (isObject(source)) {\n    const target = Object.create(null);\n    const keys = Object.keys(source);\n    const klen = keys.length;\n    let k = 0;\n\n    for (; k < klen; ++k) {\n      target[keys[k]] = clone(source[keys[k]]);\n    }\n\n    return target;\n  }\n\n  return source;\n}\n\nfunction isValidKey(key: string) {\n  return ['__proto__', 'prototype', 'constructor'].indexOf(key) === -1;\n}\n\n/**\n * The default merger when Chart.helpers.merge is called without merger option.\n * Note(SB): also used by mergeConfig and mergeScaleConfig as fallback.\n * @private\n */\nexport function _merger(key: string, target: AnyObject, source: AnyObject, options: AnyObject) {\n  if (!isValidKey(key)) {\n    return;\n  }\n\n  const tval = target[key];\n  const sval = source[key];\n\n  if (isObject(tval) && isObject(sval)) {\n    // eslint-disable-next-line @typescript-eslint/no-use-before-define\n    merge(tval, sval, options);\n  } else {\n    target[key] = clone(sval);\n  }\n}\n\nexport interface MergeOptions {\n  merger?: (key: string, target: AnyObject, source: AnyObject, options?: AnyObject) => void;\n}\n\n/**\n * Recursively deep copies `source` properties into `target` with the given `options`.\n * IMPORTANT: `target` is not cloned and will be updated with `source` properties.\n * @param target - The target object in which all sources are merged into.\n * @param source - Object(s) to merge into `target`.\n * @param [options] - Merging options:\n * @param [options.merger] - The merge method (key, target, source, options)\n * @returns The `target` object.\n */\nexport function merge<T>(target: T, source: [], options?: MergeOptions): T;\nexport function merge<T, S1>(target: T, source: S1, options?: MergeOptions): T & S1;\nexport function merge<T, S1>(target: T, source: [S1], options?: MergeOptions): T & S1;\nexport function merge<T, S1, S2>(target: T, source: [S1, S2], options?: MergeOptions): T & S1 & S2;\nexport function merge<T, S1, S2, S3>(target: T, source: [S1, S2, S3], options?: MergeOptions): T & S1 & S2 & S3;\nexport function merge<T, S1, S2, S3, S4>(\n  target: T,\n  source: [S1, S2, S3, S4],\n  options?: MergeOptions\n): T & S1 & S2 & S3 & S4;\nexport function merge<T>(target: T, source: AnyObject[], options?: MergeOptions): AnyObject;\nexport function merge<T>(target: T, source: AnyObject[], options?: MergeOptions): AnyObject {\n  const sources = isArray(source) ? source : [source];\n  const ilen = sources.length;\n\n  if (!isObject(target)) {\n    return target as AnyObject;\n  }\n\n  options = options || {};\n  const merger = options.merger || _merger;\n  let current: AnyObject;\n\n  for (let i = 0; i < ilen; ++i) {\n    current = sources[i];\n    if (!isObject(current)) {\n      continue;\n    }\n\n    const keys = Object.keys(current);\n    for (let k = 0, klen = keys.length; k < klen; ++k) {\n      merger(keys[k], target, current, options as AnyObject);\n    }\n  }\n\n  return target;\n}\n\n/**\n * Recursively deep copies `source` properties into `target` *only* if not defined in target.\n * IMPORTANT: `target` is not cloned and will be updated with `source` properties.\n * @param target - The target object in which all sources are merged into.\n * @param source - Object(s) to merge into `target`.\n * @returns The `target` object.\n */\nexport function mergeIf<T>(target: T, source: []): T;\nexport function mergeIf<T, S1>(target: T, source: S1): T & S1;\nexport function mergeIf<T, S1>(target: T, source: [S1]): T & S1;\nexport function mergeIf<T, S1, S2>(target: T, source: [S1, S2]): T & S1 & S2;\nexport function mergeIf<T, S1, S2, S3>(target: T, source: [S1, S2, S3]): T & S1 & S2 & S3;\nexport function mergeIf<T, S1, S2, S3, S4>(target: T, source: [S1, S2, S3, S4]): T & S1 & S2 & S3 & S4;\nexport function mergeIf<T>(target: T, source: AnyObject[]): AnyObject;\nexport function mergeIf<T>(target: T, source: AnyObject[]): AnyObject {\n  // eslint-disable-next-line @typescript-eslint/no-use-before-define\n  return merge<T>(target, source, {merger: _mergerIf});\n}\n\n/**\n * Merges source[key] in target[key] only if target[key] is undefined.\n * @private\n */\nexport function _mergerIf(key: string, target: AnyObject, source: AnyObject) {\n  if (!isValidKey(key)) {\n    return;\n  }\n\n  const tval = target[key];\n  const sval = source[key];\n\n  if (isObject(tval) && isObject(sval)) {\n    mergeIf(tval, sval);\n  } else if (!Object.prototype.hasOwnProperty.call(target, key)) {\n    target[key] = clone(sval);\n  }\n}\n\n/**\n * @private\n */\nexport function _deprecated(scope: string, value: unknown, previous: string, current: string) {\n  if (value !== undefined) {\n    console.warn(scope + ': \"' + previous +\n      '\" is deprecated. Please use \"' + current + '\" instead');\n  }\n}\n\n// resolveObjectKey resolver cache\nconst keyResolvers = {\n  // Chart.helpers.core resolveObjectKey should resolve empty key to root object\n  '': v => v,\n  // default resolvers\n  x: o => o.x,\n  y: o => o.y\n};\n\n/**\n * @private\n */\nexport function _splitKey(key: string) {\n  const parts = key.split('.');\n  const keys: string[] = [];\n  let tmp = '';\n  for (const part of parts) {\n    tmp += part;\n    if (tmp.endsWith('\\\\')) {\n      tmp = tmp.slice(0, -1) + '.';\n    } else {\n      keys.push(tmp);\n      tmp = '';\n    }\n  }\n  return keys;\n}\n\nfunction _getKeyResolver(key: string) {\n  const keys = _splitKey(key);\n  return obj => {\n    for (const k of keys) {\n      if (k === '') {\n        // For backward compatibility:\n        // Chart.helpers.core resolveObjectKey should break at empty key\n        break;\n      }\n      obj = obj && obj[k];\n    }\n    return obj;\n  };\n}\n\nexport function resolveObjectKey(obj: AnyObject, key: string): any {\n  const resolver = keyResolvers[key] || (keyResolvers[key] = _getKeyResolver(key));\n  return resolver(obj);\n}\n\n/**\n * @private\n */\nexport function _capitalize(str: string) {\n  return str.charAt(0).toUpperCase() + str.slice(1);\n}\n\n\nexport const defined = (value: unknown) => typeof value !== 'undefined';\n\nexport const isFunction = (value: unknown): value is (...args: any[]) => any => typeof value === 'function';\n\n// Adapted from https://stackoverflow.com/questions/31128855/comparing-ecma6-sets-for-equality#31129384\nexport const setsEqual = <T>(a: Set<T>, b: Set<T>) => {\n  if (a.size !== b.size) {\n    return false;\n  }\n\n  for (const item of a) {\n    if (!b.has(item)) {\n      return false;\n    }\n  }\n\n  return true;\n};\n\n/**\n * @param e - The event\n * @private\n */\nexport function _isClickEvent(e: ChartEvent) {\n  return e.type === 'mouseup' || e.type === 'click' || e.type === 'contextmenu';\n}\n"
  },
  {
    "path": "src/helpers/helpers.curve.ts",
    "content": "import {almostEquals, distanceBetweenPoints, sign} from './helpers.math.js';\nimport {_isPointInArea} from './helpers.canvas.js';\nimport type {ChartArea} from '../types/index.js';\nimport type {SplinePoint} from '../types/geometric.js';\n\nconst EPSILON = Number.EPSILON || 1e-14;\n\ntype OptionalSplinePoint = SplinePoint | false\nconst getPoint = (points: SplinePoint[], i: number): OptionalSplinePoint => i < points.length && !points[i].skip && points[i];\nconst getValueAxis = (indexAxis: 'x' | 'y') => indexAxis === 'x' ? 'y' : 'x';\n\nexport function splineCurve(\n  firstPoint: SplinePoint,\n  middlePoint: SplinePoint,\n  afterPoint: SplinePoint,\n  t: number\n): {\n    previous: SplinePoint\n    next: SplinePoint\n  } {\n  // Props to Rob Spencer at scaled innovation for his post on splining between points\n  // http://scaledinnovation.com/analytics/splines/aboutSplines.html\n\n  // This function must also respect \"skipped\" points\n\n  const previous = firstPoint.skip ? middlePoint : firstPoint;\n  const current = middlePoint;\n  const next = afterPoint.skip ? middlePoint : afterPoint;\n  const d01 = distanceBetweenPoints(current, previous);\n  const d12 = distanceBetweenPoints(next, current);\n\n  let s01 = d01 / (d01 + d12);\n  let s12 = d12 / (d01 + d12);\n\n  // If all points are the same, s01 & s02 will be inf\n  s01 = isNaN(s01) ? 0 : s01;\n  s12 = isNaN(s12) ? 0 : s12;\n\n  const fa = t * s01; // scaling factor for triangle Ta\n  const fb = t * s12;\n\n  return {\n    previous: {\n      x: current.x - fa * (next.x - previous.x),\n      y: current.y - fa * (next.y - previous.y)\n    },\n    next: {\n      x: current.x + fb * (next.x - previous.x),\n      y: current.y + fb * (next.y - previous.y)\n    }\n  };\n}\n\n/**\n * Adjust tangents to ensure monotonic properties\n */\nfunction monotoneAdjust(points: SplinePoint[], deltaK: number[], mK: number[]) {\n  const pointsLen = points.length;\n\n  let alphaK: number, betaK: number, tauK: number, squaredMagnitude: number, pointCurrent: OptionalSplinePoint;\n  let pointAfter = getPoint(points, 0);\n  for (let i = 0; i < pointsLen - 1; ++i) {\n    pointCurrent = pointAfter;\n    pointAfter = getPoint(points, i + 1);\n    if (!pointCurrent || !pointAfter) {\n      continue;\n    }\n\n    if (almostEquals(deltaK[i], 0, EPSILON)) {\n      mK[i] = mK[i + 1] = 0;\n      continue;\n    }\n\n    alphaK = mK[i] / deltaK[i];\n    betaK = mK[i + 1] / deltaK[i];\n    squaredMagnitude = Math.pow(alphaK, 2) + Math.pow(betaK, 2);\n    if (squaredMagnitude <= 9) {\n      continue;\n    }\n\n    tauK = 3 / Math.sqrt(squaredMagnitude);\n    mK[i] = alphaK * tauK * deltaK[i];\n    mK[i + 1] = betaK * tauK * deltaK[i];\n  }\n}\n\nfunction monotoneCompute(points: SplinePoint[], mK: number[], indexAxis: 'x' | 'y' = 'x') {\n  const valueAxis = getValueAxis(indexAxis);\n  const pointsLen = points.length;\n  let delta: number, pointBefore: OptionalSplinePoint, pointCurrent: OptionalSplinePoint;\n  let pointAfter = getPoint(points, 0);\n\n  for (let i = 0; i < pointsLen; ++i) {\n    pointBefore = pointCurrent;\n    pointCurrent = pointAfter;\n    pointAfter = getPoint(points, i + 1);\n    if (!pointCurrent) {\n      continue;\n    }\n\n    const iPixel = pointCurrent[indexAxis];\n    const vPixel = pointCurrent[valueAxis];\n    if (pointBefore) {\n      delta = (iPixel - pointBefore[indexAxis]) / 3;\n      pointCurrent[`cp1${indexAxis}`] = iPixel - delta;\n      pointCurrent[`cp1${valueAxis}`] = vPixel - delta * mK[i];\n    }\n    if (pointAfter) {\n      delta = (pointAfter[indexAxis] - iPixel) / 3;\n      pointCurrent[`cp2${indexAxis}`] = iPixel + delta;\n      pointCurrent[`cp2${valueAxis}`] = vPixel + delta * mK[i];\n    }\n  }\n}\n\n/**\n * This function calculates Bézier control points in a similar way than |splineCurve|,\n * but preserves monotonicity of the provided data and ensures no local extremums are added\n * between the dataset discrete points due to the interpolation.\n * See : https://en.wikipedia.org/wiki/Monotone_cubic_interpolation\n */\nexport function splineCurveMonotone(points: SplinePoint[], indexAxis: 'x' | 'y' = 'x') {\n  const valueAxis = getValueAxis(indexAxis);\n  const pointsLen = points.length;\n  const deltaK: number[] = Array(pointsLen).fill(0);\n  const mK: number[] = Array(pointsLen);\n\n  // Calculate slopes (deltaK) and initialize tangents (mK)\n  let i, pointBefore: OptionalSplinePoint, pointCurrent: OptionalSplinePoint;\n  let pointAfter = getPoint(points, 0);\n\n  for (i = 0; i < pointsLen; ++i) {\n    pointBefore = pointCurrent;\n    pointCurrent = pointAfter;\n    pointAfter = getPoint(points, i + 1);\n    if (!pointCurrent) {\n      continue;\n    }\n\n    if (pointAfter) {\n      const slopeDelta = pointAfter[indexAxis] - pointCurrent[indexAxis];\n\n      // In the case of two points that appear at the same x pixel, slopeDeltaX is 0\n      deltaK[i] = slopeDelta !== 0 ? (pointAfter[valueAxis] - pointCurrent[valueAxis]) / slopeDelta : 0;\n    }\n    mK[i] = !pointBefore ? deltaK[i]\n      : !pointAfter ? deltaK[i - 1]\n        : (sign(deltaK[i - 1]) !== sign(deltaK[i])) ? 0\n          : (deltaK[i - 1] + deltaK[i]) / 2;\n  }\n\n  monotoneAdjust(points, deltaK, mK);\n\n  monotoneCompute(points, mK, indexAxis);\n}\n\nfunction capControlPoint(pt: number, min: number, max: number) {\n  return Math.max(Math.min(pt, max), min);\n}\n\nfunction capBezierPoints(points: SplinePoint[], area: ChartArea) {\n  let i, ilen, point, inArea, inAreaPrev;\n  let inAreaNext = _isPointInArea(points[0], area);\n  for (i = 0, ilen = points.length; i < ilen; ++i) {\n    inAreaPrev = inArea;\n    inArea = inAreaNext;\n    inAreaNext = i < ilen - 1 && _isPointInArea(points[i + 1], area);\n    if (!inArea) {\n      continue;\n    }\n    point = points[i];\n    if (inAreaPrev) {\n      point.cp1x = capControlPoint(point.cp1x, area.left, area.right);\n      point.cp1y = capControlPoint(point.cp1y, area.top, area.bottom);\n    }\n    if (inAreaNext) {\n      point.cp2x = capControlPoint(point.cp2x, area.left, area.right);\n      point.cp2y = capControlPoint(point.cp2y, area.top, area.bottom);\n    }\n  }\n}\n\n/**\n * @private\n */\nexport function _updateBezierControlPoints(\n  points: SplinePoint[],\n  options,\n  area: ChartArea,\n  loop: boolean,\n  indexAxis: 'x' | 'y'\n) {\n  let i: number, ilen: number, point: SplinePoint, controlPoints: ReturnType<typeof splineCurve>;\n\n  // Only consider points that are drawn in case the spanGaps option is used\n  if (options.spanGaps) {\n    points = points.filter((pt) => !pt.skip);\n  }\n\n  if (options.cubicInterpolationMode === 'monotone') {\n    splineCurveMonotone(points, indexAxis);\n  } else {\n    let prev = loop ? points[points.length - 1] : points[0];\n    for (i = 0, ilen = points.length; i < ilen; ++i) {\n      point = points[i];\n      controlPoints = splineCurve(\n        prev,\n        point,\n        points[Math.min(i + 1, ilen - (loop ? 0 : 1)) % ilen],\n        options.tension\n      );\n      point.cp1x = controlPoints.previous.x;\n      point.cp1y = controlPoints.previous.y;\n      point.cp2x = controlPoints.next.x;\n      point.cp2y = controlPoints.next.y;\n      prev = point;\n    }\n  }\n\n  if (options.capBezierPoints) {\n    capBezierPoints(points, area);\n  }\n}\n"
  },
  {
    "path": "src/helpers/helpers.dataset.ts",
    "content": "import type {Chart, ChartArea, ChartMeta, Scale, TRBL} from '../types/index.js';\n\nfunction getSizeForArea(scale: Scale, chartArea: ChartArea, field: keyof ChartArea) {\n  return scale.options.clip ? scale[field] : chartArea[field];\n}\n\nfunction getDatasetArea(meta: ChartMeta, chartArea: ChartArea): TRBL {\n  const {xScale, yScale} = meta;\n  if (xScale && yScale) {\n    return {\n      left: getSizeForArea(xScale, chartArea, 'left'),\n      right: getSizeForArea(xScale, chartArea, 'right'),\n      top: getSizeForArea(yScale, chartArea, 'top'),\n      bottom: getSizeForArea(yScale, chartArea, 'bottom')\n    };\n  }\n  return chartArea;\n}\n\nexport function getDatasetClipArea(chart: Chart, meta: ChartMeta): TRBL | false {\n  const clip = meta._clip;\n  if (clip.disabled) {\n    return false;\n  }\n  const area = getDatasetArea(meta, chart.chartArea);\n\n  return {\n    left: clip.left === false ? 0 : area.left - (clip.left === true ? 0 : clip.left),\n    right: clip.right === false ? chart.width : area.right + (clip.right === true ? 0 : clip.right),\n    top: clip.top === false ? 0 : area.top - (clip.top === true ? 0 : clip.top),\n    bottom: clip.bottom === false ? chart.height : area.bottom + (clip.bottom === true ? 0 : clip.bottom)\n  };\n}\n"
  },
  {
    "path": "src/helpers/helpers.dom.ts",
    "content": "import type {ChartArea, Scale} from '../types/index.js';\nimport type PrivateChart from '../core/core.controller.js';\nimport type {Chart, ChartEvent} from '../types.js';\nimport {INFINITY} from './helpers.math.js';\n\n/**\n * @private\n */\nexport function _isDomSupported(): boolean {\n  return typeof window !== 'undefined' && typeof document !== 'undefined';\n}\n\n/**\n * @private\n */\nexport function _getParentNode(domNode: HTMLCanvasElement): HTMLCanvasElement {\n  let parent = domNode.parentNode;\n  if (parent && parent.toString() === '[object ShadowRoot]') {\n    parent = (parent as ShadowRoot).host;\n  }\n  return parent as HTMLCanvasElement;\n}\n\n/**\n * convert max-width/max-height values that may be percentages into a number\n * @private\n */\n\nfunction parseMaxStyle(styleValue: string | number, node: HTMLElement, parentProperty: string) {\n  let valueInPixels: number;\n  if (typeof styleValue === 'string') {\n    valueInPixels = parseInt(styleValue, 10);\n\n    if (styleValue.indexOf('%') !== -1) {\n      // percentage * size in dimension\n      valueInPixels = (valueInPixels / 100) * node.parentNode[parentProperty];\n    }\n  } else {\n    valueInPixels = styleValue;\n  }\n\n  return valueInPixels;\n}\n\nconst getComputedStyle = (element: HTMLElement): CSSStyleDeclaration =>\n  element.ownerDocument.defaultView.getComputedStyle(element, null);\n\nexport function getStyle(el: HTMLElement, property: string): string {\n  return getComputedStyle(el).getPropertyValue(property);\n}\n\nconst positions = ['top', 'right', 'bottom', 'left'];\nfunction getPositionedStyle(styles: CSSStyleDeclaration, style: string, suffix?: string): ChartArea {\n  const result = {} as ChartArea;\n  suffix = suffix ? '-' + suffix : '';\n  for (let i = 0; i < 4; i++) {\n    const pos = positions[i];\n    result[pos] = parseFloat(styles[style + '-' + pos + suffix]) || 0;\n  }\n  result.width = result.left + result.right;\n  result.height = result.top + result.bottom;\n  return result;\n}\n\nconst useOffsetPos = (x: number, y: number, target: HTMLElement | EventTarget) =>\n  (x > 0 || y > 0) && (!target || !(target as HTMLElement).shadowRoot);\n\n/**\n * @param e\n * @param canvas\n * @returns Canvas position\n */\nfunction getCanvasPosition(\n  e: Event | TouchEvent | MouseEvent,\n  canvas: HTMLCanvasElement\n): {\n    x: number;\n    y: number;\n    box: boolean;\n  } {\n  const touches = (e as TouchEvent).touches;\n  const source = (touches && touches.length ? touches[0] : e) as MouseEvent;\n  const {offsetX, offsetY} = source as MouseEvent;\n  let box = false;\n  let x, y;\n  if (useOffsetPos(offsetX, offsetY, e.target)) {\n    x = offsetX;\n    y = offsetY;\n  } else {\n    const rect = canvas.getBoundingClientRect();\n    x = source.clientX - rect.left;\n    y = source.clientY - rect.top;\n    box = true;\n  }\n  return {x, y, box};\n}\n\n/**\n * Gets an event's x, y coordinates, relative to the chart area\n * @param event\n * @param chart\n * @returns x and y coordinates of the event\n */\n\nexport function getRelativePosition(\n  event: Event | ChartEvent | TouchEvent | MouseEvent,\n  chart: Chart | PrivateChart\n): { x: number; y: number } {\n  if ('native' in event) {\n    return event;\n  }\n\n  const {canvas, currentDevicePixelRatio} = chart;\n  const style = getComputedStyle(canvas);\n  const borderBox = style.boxSizing === 'border-box';\n  const paddings = getPositionedStyle(style, 'padding');\n  const borders = getPositionedStyle(style, 'border', 'width');\n  const {x, y, box} = getCanvasPosition(event, canvas);\n  const xOffset = paddings.left + (box && borders.left);\n  const yOffset = paddings.top + (box && borders.top);\n\n  let {width, height} = chart;\n  if (borderBox) {\n    width -= paddings.width + borders.width;\n    height -= paddings.height + borders.height;\n  }\n  return {\n    x: Math.round((x - xOffset) / width * canvas.width / currentDevicePixelRatio),\n    y: Math.round((y - yOffset) / height * canvas.height / currentDevicePixelRatio)\n  };\n}\n\nfunction getContainerSize(canvas: HTMLCanvasElement, width: number, height: number): Partial<Scale> {\n  let maxWidth: number, maxHeight: number;\n\n  if (width === undefined || height === undefined) {\n    const container = canvas && _getParentNode(canvas);\n    if (!container) {\n      width = canvas.clientWidth;\n      height = canvas.clientHeight;\n    } else {\n      const rect = container.getBoundingClientRect(); // this is the border box of the container\n      const containerStyle = getComputedStyle(container);\n      const containerBorder = getPositionedStyle(containerStyle, 'border', 'width');\n      const containerPadding = getPositionedStyle(containerStyle, 'padding');\n      width = rect.width - containerPadding.width - containerBorder.width;\n      height = rect.height - containerPadding.height - containerBorder.height;\n      maxWidth = parseMaxStyle(containerStyle.maxWidth, container, 'clientWidth');\n      maxHeight = parseMaxStyle(containerStyle.maxHeight, container, 'clientHeight');\n    }\n  }\n  return {\n    width,\n    height,\n    maxWidth: maxWidth || INFINITY,\n    maxHeight: maxHeight || INFINITY\n  };\n}\n\nconst round1 = (v: number) => Math.round(v * 10) / 10;\n\n// eslint-disable-next-line complexity\nexport function getMaximumSize(\n  canvas: HTMLCanvasElement,\n  bbWidth?: number,\n  bbHeight?: number,\n  aspectRatio?: number\n): { width: number; height: number } {\n  const style = getComputedStyle(canvas);\n  const margins = getPositionedStyle(style, 'margin');\n  const maxWidth = parseMaxStyle(style.maxWidth, canvas, 'clientWidth') || INFINITY;\n  const maxHeight = parseMaxStyle(style.maxHeight, canvas, 'clientHeight') || INFINITY;\n  const containerSize = getContainerSize(canvas, bbWidth, bbHeight);\n  let {width, height} = containerSize;\n\n  if (style.boxSizing === 'content-box') {\n    const borders = getPositionedStyle(style, 'border', 'width');\n    const paddings = getPositionedStyle(style, 'padding');\n    width -= paddings.width + borders.width;\n    height -= paddings.height + borders.height;\n  }\n  width = Math.max(0, width - margins.width);\n  height = Math.max(0, aspectRatio ? width / aspectRatio : height - margins.height);\n  width = round1(Math.min(width, maxWidth, containerSize.maxWidth));\n  height = round1(Math.min(height, maxHeight, containerSize.maxHeight));\n  if (width && !height) {\n    // https://github.com/chartjs/Chart.js/issues/4659\n    // If the canvas has width, but no height, default to aspectRatio of 2 (canvas default)\n    height = round1(width / 2);\n  }\n\n  const maintainHeight = bbWidth !== undefined || bbHeight !== undefined;\n\n  if (maintainHeight && aspectRatio && containerSize.height && height > containerSize.height) {\n    height = containerSize.height;\n    width = round1(Math.floor(height * aspectRatio));\n  }\n\n  return {width, height};\n}\n\n/**\n * @param chart\n * @param forceRatio\n * @param forceStyle\n * @returns True if the canvas context size or transformation has changed.\n */\nexport function retinaScale(\n  chart: Chart | PrivateChart,\n  forceRatio: number,\n  forceStyle?: boolean\n): boolean | void {\n  const pixelRatio = forceRatio || 1;\n  const deviceHeight = round1(chart.height * pixelRatio);\n  const deviceWidth = round1(chart.width * pixelRatio);\n\n  (chart as PrivateChart).height = round1(chart.height);\n  (chart as PrivateChart).width = round1(chart.width);\n\n  const canvas = chart.canvas;\n\n  // If no style has been set on the canvas, the render size is used as display size,\n  // making the chart visually bigger, so let's enforce it to the \"correct\" values.\n  // See https://github.com/chartjs/Chart.js/issues/3575\n  if (canvas.style && (forceStyle || (!canvas.style.height && !canvas.style.width))) {\n    canvas.style.height = `${chart.height}px`;\n    canvas.style.width = `${chart.width}px`;\n  }\n\n  const canvasHeight = Math.floor(deviceHeight);\n  const canvasWidth = Math.floor(deviceWidth);\n  if (chart.currentDevicePixelRatio !== pixelRatio\n      || canvas.height !== canvasHeight\n      || canvas.width !== canvasWidth) {\n    (chart as PrivateChart).currentDevicePixelRatio = pixelRatio;\n    canvas.height = canvasHeight;\n    canvas.width = canvasWidth;\n    chart.ctx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);\n    return true;\n  }\n  return false;\n}\n\n/**\n * Detects support for options object argument in addEventListener.\n * https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Safely_detecting_option_support\n * @private\n */\nexport const supportsEventListenerOptions = (function() {\n  let passiveSupported = false;\n  try {\n    const options = {\n      get passive() { // This function will be called when the browser attempts to access the passive property.\n        passiveSupported = true;\n        return false;\n      }\n    } as EventListenerOptions;\n\n    if (_isDomSupported()) {\n      window.addEventListener('test', null, options);\n      window.removeEventListener('test', null, options);\n    }\n  } catch (e) {\n    // continue regardless of error\n  }\n  return passiveSupported;\n}());\n\n/**\n * The \"used\" size is the final value of a dimension property after all calculations have\n * been performed. This method uses the computed style of `element` but returns undefined\n * if the computed style is not expressed in pixels. That can happen in some cases where\n * `element` has a size relative to its parent and this last one is not yet displayed,\n * for example because of `display: none` on a parent node.\n * @see https://developer.mozilla.org/en-US/docs/Web/CSS/used_value\n * @returns Size in pixels or undefined if unknown.\n */\n\nexport function readUsedSize(\n  element: HTMLElement,\n  property: 'width' | 'height'\n): number | undefined {\n  const value = getStyle(element, property);\n  const matches = value && value.match(/^(\\d+)(\\.\\d+)?px$/);\n  return matches ? +matches[1] : undefined;\n}\n"
  },
  {
    "path": "src/helpers/helpers.easing.ts",
    "content": "import {PI, TAU, HALF_PI} from './helpers.math.js';\n\nconst atEdge = (t: number) => t === 0 || t === 1;\nconst elasticIn = (t: number, s: number, p: number) => -(Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * TAU / p));\nconst elasticOut = (t: number, s: number, p: number) => Math.pow(2, -10 * t) * Math.sin((t - s) * TAU / p) + 1;\n\n/**\n * Easing functions adapted from Robert Penner's easing equations.\n * @namespace Chart.helpers.easing.effects\n * @see http://www.robertpenner.com/easing/\n */\nconst effects = {\n  linear: (t: number) => t,\n\n  easeInQuad: (t: number) => t * t,\n\n  easeOutQuad: (t: number) => -t * (t - 2),\n\n  easeInOutQuad: (t: number) => ((t /= 0.5) < 1)\n    ? 0.5 * t * t\n    : -0.5 * ((--t) * (t - 2) - 1),\n\n  easeInCubic: (t: number) => t * t * t,\n\n  easeOutCubic: (t: number) => (t -= 1) * t * t + 1,\n\n  easeInOutCubic: (t: number) => ((t /= 0.5) < 1)\n    ? 0.5 * t * t * t\n    : 0.5 * ((t -= 2) * t * t + 2),\n\n  easeInQuart: (t: number) => t * t * t * t,\n\n  easeOutQuart: (t: number) => -((t -= 1) * t * t * t - 1),\n\n  easeInOutQuart: (t: number) => ((t /= 0.5) < 1)\n    ? 0.5 * t * t * t * t\n    : -0.5 * ((t -= 2) * t * t * t - 2),\n\n  easeInQuint: (t: number) => t * t * t * t * t,\n\n  easeOutQuint: (t: number) => (t -= 1) * t * t * t * t + 1,\n\n  easeInOutQuint: (t: number) => ((t /= 0.5) < 1)\n    ? 0.5 * t * t * t * t * t\n    : 0.5 * ((t -= 2) * t * t * t * t + 2),\n\n  easeInSine: (t: number) => -Math.cos(t * HALF_PI) + 1,\n\n  easeOutSine: (t: number) => Math.sin(t * HALF_PI),\n\n  easeInOutSine: (t: number) => -0.5 * (Math.cos(PI * t) - 1),\n\n  easeInExpo: (t: number) => (t === 0) ? 0 : Math.pow(2, 10 * (t - 1)),\n\n  easeOutExpo: (t: number) => (t === 1) ? 1 : -Math.pow(2, -10 * t) + 1,\n\n  easeInOutExpo: (t: number) => atEdge(t) ? t : t < 0.5\n    ? 0.5 * Math.pow(2, 10 * (t * 2 - 1))\n    : 0.5 * (-Math.pow(2, -10 * (t * 2 - 1)) + 2),\n\n  easeInCirc: (t: number) => (t >= 1) ? t : -(Math.sqrt(1 - t * t) - 1),\n\n  easeOutCirc: (t: number) => Math.sqrt(1 - (t -= 1) * t),\n\n  easeInOutCirc: (t: number) => ((t /= 0.5) < 1)\n    ? -0.5 * (Math.sqrt(1 - t * t) - 1)\n    : 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1),\n\n  easeInElastic: (t: number) => atEdge(t) ? t : elasticIn(t, 0.075, 0.3),\n\n  easeOutElastic: (t: number) => atEdge(t) ? t : elasticOut(t, 0.075, 0.3),\n\n  easeInOutElastic(t: number) {\n    const s = 0.1125;\n    const p = 0.45;\n    return atEdge(t) ? t :\n      t < 0.5\n        ? 0.5 * elasticIn(t * 2, s, p)\n        : 0.5 + 0.5 * elasticOut(t * 2 - 1, s, p);\n  },\n\n  easeInBack(t: number) {\n    const s = 1.70158;\n    return t * t * ((s + 1) * t - s);\n  },\n\n  easeOutBack(t: number) {\n    const s = 1.70158;\n    return (t -= 1) * t * ((s + 1) * t + s) + 1;\n  },\n\n  easeInOutBack(t: number) {\n    let s = 1.70158;\n    if ((t /= 0.5) < 1) {\n      return 0.5 * (t * t * (((s *= (1.525)) + 1) * t - s));\n    }\n    return 0.5 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2);\n  },\n\n  easeInBounce: (t: number) => 1 - effects.easeOutBounce(1 - t),\n\n  easeOutBounce(t: number) {\n    const m = 7.5625;\n    const d = 2.75;\n    if (t < (1 / d)) {\n      return m * t * t;\n    }\n    if (t < (2 / d)) {\n      return m * (t -= (1.5 / d)) * t + 0.75;\n    }\n    if (t < (2.5 / d)) {\n      return m * (t -= (2.25 / d)) * t + 0.9375;\n    }\n    return m * (t -= (2.625 / d)) * t + 0.984375;\n  },\n\n  easeInOutBounce: (t: number) => (t < 0.5)\n    ? effects.easeInBounce(t * 2) * 0.5\n    : effects.easeOutBounce(t * 2 - 1) * 0.5 + 0.5,\n} as const;\n\nexport type EasingFunction = keyof typeof effects\n\nexport default effects;\n"
  },
  {
    "path": "src/helpers/helpers.extras.ts",
    "content": "import type {ChartMeta, PointElement} from '../types/index.js';\n\nimport {_limitValue} from './helpers.math.js';\nimport {_lookupByKey} from './helpers.collection.js';\nimport {isNullOrUndef} from './helpers.core.js';\n\nexport function fontString(pixelSize: number, fontStyle: string, fontFamily: string) {\n  return fontStyle + ' ' + pixelSize + 'px ' + fontFamily;\n}\n\n/**\n* Request animation polyfill\n*/\nexport const requestAnimFrame = (function() {\n  if (typeof window === 'undefined') {\n    return function(callback) {\n      return callback();\n    };\n  }\n  return window.requestAnimationFrame;\n}());\n\n/**\n * Throttles calling `fn` once per animation frame\n * Latest arguments are used on the actual call\n */\nexport function throttled<TArgs extends Array<any>>(\n  fn: (...args: TArgs) => void,\n  thisArg: any,\n) {\n  let argsToUse = [] as TArgs;\n  let ticking = false;\n\n  return function(...args: TArgs) {\n    // Save the args for use later\n    argsToUse = args;\n    if (!ticking) {\n      ticking = true;\n      requestAnimFrame.call(window, () => {\n        ticking = false;\n        fn.apply(thisArg, argsToUse);\n      });\n    }\n  };\n}\n\n/**\n * Debounces calling `fn` for `delay` ms\n */\nexport function debounce<TArgs extends Array<any>>(fn: (...args: TArgs) => void, delay: number) {\n  let timeout;\n  return function(...args: TArgs) {\n    if (delay) {\n      clearTimeout(timeout);\n      timeout = setTimeout(fn, delay, args);\n    } else {\n      fn.apply(this, args);\n    }\n    return delay;\n  };\n}\n\n/**\n * Converts 'start' to 'left', 'end' to 'right' and others to 'center'\n * @private\n */\nexport const _toLeftRightCenter = (align: 'start' | 'end' | 'center') => align === 'start' ? 'left' : align === 'end' ? 'right' : 'center';\n\n/**\n * Returns `start`, `end` or `(start + end) / 2` depending on `align`. Defaults to `center`\n * @private\n */\nexport const _alignStartEnd = (align: 'start' | 'end' | 'center', start: number, end: number) => align === 'start' ? start : align === 'end' ? end : (start + end) / 2;\n\n/**\n * Returns `left`, `right` or `(left + right) / 2` depending on `align`. Defaults to `left`\n * @private\n */\nexport const _textX = (align: 'left' | 'right' | 'center', left: number, right: number, rtl: boolean) => {\n  const check = rtl ? 'left' : 'right';\n  return align === check ? right : align === 'center' ? (left + right) / 2 : left;\n};\n\n/**\n * Return start and count of visible points.\n * @private\n */\nexport function _getStartAndCountOfVisiblePoints(meta: ChartMeta<'line' | 'scatter'>, points: PointElement[], animationsDisabled: boolean) {\n  const pointCount = points.length;\n\n  let start = 0;\n  let count = pointCount;\n\n  if (meta._sorted) {\n    const {iScale, vScale, _parsed} = meta;\n    const spanGaps = meta.dataset ? meta.dataset.options ? meta.dataset.options.spanGaps : null : null;\n    const axis = iScale.axis;\n    const {min, max, minDefined, maxDefined} = iScale.getUserBounds();\n\n    if (minDefined) {\n      start = Math.min(\n        // @ts-expect-error Need to type _parsed\n        _lookupByKey(_parsed, axis, min).lo,\n        // @ts-expect-error Need to fix types on _lookupByKey\n        animationsDisabled ? pointCount : _lookupByKey(points, axis, iScale.getPixelForValue(min)).lo);\n      if (spanGaps) {\n        const distanceToDefinedLo = (_parsed\n          .slice(0, start + 1)\n          .reverse()\n          .findIndex(\n            point => !isNullOrUndef(point[vScale.axis])));\n        start -= Math.max(0, distanceToDefinedLo);\n      }\n      start = _limitValue(start, 0, pointCount - 1);\n    }\n    if (maxDefined) {\n      let end = Math.max(\n        // @ts-expect-error Need to type _parsed\n        _lookupByKey(_parsed, iScale.axis, max, true).hi + 1,\n        // @ts-expect-error Need to fix types on _lookupByKey\n        animationsDisabled ? 0 : _lookupByKey(points, axis, iScale.getPixelForValue(max), true).hi + 1);\n      if (spanGaps) {\n        const distanceToDefinedHi = (_parsed\n          .slice(end - 1)\n          .findIndex(\n            point => !isNullOrUndef(point[vScale.axis])));\n        end += Math.max(0, distanceToDefinedHi);\n      }\n      count = _limitValue(end, start, pointCount) - start;\n    } else {\n      count = pointCount - start;\n    }\n  }\n\n  return {start, count};\n}\n\n/**\n * Checks if the scale ranges have changed.\n * @param {object} meta - dataset meta.\n * @returns {boolean}\n * @private\n */\nexport function _scaleRangesChanged(meta) {\n  const {xScale, yScale, _scaleRanges} = meta;\n  const newRanges = {\n    xmin: xScale.min,\n    xmax: xScale.max,\n    ymin: yScale.min,\n    ymax: yScale.max\n  };\n  if (!_scaleRanges) {\n    meta._scaleRanges = newRanges;\n    return true;\n  }\n  const changed = _scaleRanges.xmin !== xScale.min\n\t\t|| _scaleRanges.xmax !== xScale.max\n\t\t|| _scaleRanges.ymin !== yScale.min\n\t\t|| _scaleRanges.ymax !== yScale.max;\n\n  Object.assign(_scaleRanges, newRanges);\n  return changed;\n}\n"
  },
  {
    "path": "src/helpers/helpers.interpolation.ts",
    "content": "import type {Point, SplinePoint} from '../types/geometric.js';\n\n/**\n * @private\n */\nexport function _pointInLine(p1: Point, p2: Point, t: number, mode?) { // eslint-disable-line @typescript-eslint/no-unused-vars\n  return {\n    x: p1.x + t * (p2.x - p1.x),\n    y: p1.y + t * (p2.y - p1.y)\n  };\n}\n\n/**\n * @private\n */\nexport function _steppedInterpolation(\n  p1: Point,\n  p2: Point,\n  t: number, mode: 'middle' | 'after' | unknown\n) {\n  return {\n    x: p1.x + t * (p2.x - p1.x),\n    y: mode === 'middle' ? t < 0.5 ? p1.y : p2.y\n      : mode === 'after' ? t < 1 ? p1.y : p2.y\n        : t > 0 ? p2.y : p1.y\n  };\n}\n\n/**\n * @private\n */\nexport function _bezierInterpolation(p1: SplinePoint, p2: SplinePoint, t: number, mode?) { // eslint-disable-line @typescript-eslint/no-unused-vars\n  const cp1 = {x: p1.cp2x, y: p1.cp2y};\n  const cp2 = {x: p2.cp1x, y: p2.cp1y};\n  const a = _pointInLine(p1, cp1, t);\n  const b = _pointInLine(cp1, cp2, t);\n  const c = _pointInLine(cp2, p2, t);\n  const d = _pointInLine(a, b, t);\n  const e = _pointInLine(b, c, t);\n  return _pointInLine(d, e, t);\n}\n"
  },
  {
    "path": "src/helpers/helpers.intl.ts",
    "content": "\nconst intlCache = new Map<string, Intl.NumberFormat>();\n\nfunction getNumberFormat(locale: string, options?: Intl.NumberFormatOptions) {\n  options = options || {};\n  const cacheKey = locale + JSON.stringify(options);\n  let formatter = intlCache.get(cacheKey);\n  if (!formatter) {\n    formatter = new Intl.NumberFormat(locale, options);\n    intlCache.set(cacheKey, formatter);\n  }\n  return formatter;\n}\n\nexport function formatNumber(num: number, locale: string, options?: Intl.NumberFormatOptions) {\n  return getNumberFormat(locale, options).format(num);\n}\n"
  },
  {
    "path": "src/helpers/helpers.math.ts",
    "content": "import type {Point} from '../types/geometric.js';\nimport {isFinite as isFiniteNumber} from './helpers.core.js';\n\n/**\n * @alias Chart.helpers.math\n * @namespace\n */\n\nexport const PI = Math.PI;\nexport const TAU = 2 * PI;\nexport const PITAU = TAU + PI;\nexport const INFINITY = Number.POSITIVE_INFINITY;\nexport const RAD_PER_DEG = PI / 180;\nexport const HALF_PI = PI / 2;\nexport const QUARTER_PI = PI / 4;\nexport const TWO_THIRDS_PI = PI * 2 / 3;\n\nexport const log10 = Math.log10;\nexport const sign = Math.sign;\n\nexport function almostEquals(x: number, y: number, epsilon: number) {\n  return Math.abs(x - y) < epsilon;\n}\n\n/**\n * Implementation of the nice number algorithm used in determining where axis labels will go\n */\nexport function niceNum(range: number) {\n  const roundedRange = Math.round(range);\n  range = almostEquals(range, roundedRange, range / 1000) ? roundedRange : range;\n  const niceRange = Math.pow(10, Math.floor(log10(range)));\n  const fraction = range / niceRange;\n  const niceFraction = fraction <= 1 ? 1 : fraction <= 2 ? 2 : fraction <= 5 ? 5 : 10;\n  return niceFraction * niceRange;\n}\n\n/**\n * Returns an array of factors sorted from 1 to sqrt(value)\n * @private\n */\nexport function _factorize(value: number) {\n  const result: number[] = [];\n  const sqrt = Math.sqrt(value);\n  let i: number;\n\n  for (i = 1; i < sqrt; i++) {\n    if (value % i === 0) {\n      result.push(i);\n      result.push(value / i);\n    }\n  }\n  if (sqrt === (sqrt | 0)) { // if value is a square number\n    result.push(sqrt);\n  }\n\n  result.sort((a, b) => a - b).pop();\n  return result;\n}\n\n/**\n * Verifies that attempting to coerce n to string or number won't throw a TypeError.\n */\nfunction isNonPrimitive(n: unknown) {\n  return typeof n === 'symbol' || (typeof n === 'object' && n !== null && !(Symbol.toPrimitive in n || 'toString' in n || 'valueOf' in n));\n}\n\nexport function isNumber(n: unknown): n is number {\n  return !isNonPrimitive(n) && !isNaN(parseFloat(n as string)) && isFinite(n as number);\n}\n\nexport function almostWhole(x: number, epsilon: number) {\n  const rounded = Math.round(x);\n  return ((rounded - epsilon) <= x) && ((rounded + epsilon) >= x);\n}\n\n/**\n * @private\n */\nexport function _setMinAndMaxByKey(\n  array: Record<string, number>[],\n  target: { min: number, max: number },\n  property: string\n) {\n  let i: number, ilen: number, value: number;\n\n  for (i = 0, ilen = array.length; i < ilen; i++) {\n    value = array[i][property];\n    if (!isNaN(value)) {\n      target.min = Math.min(target.min, value);\n      target.max = Math.max(target.max, value);\n    }\n  }\n}\n\nexport function toRadians(degrees: number) {\n  return degrees * (PI / 180);\n}\n\nexport function toDegrees(radians: number) {\n  return radians * (180 / PI);\n}\n\n/**\n * Returns the number of decimal places\n * i.e. the number of digits after the decimal point, of the value of this Number.\n * @param x - A number.\n * @returns The number of decimal places.\n * @private\n */\nexport function _decimalPlaces(x: number) {\n  if (!isFiniteNumber(x)) {\n    return;\n  }\n  let e = 1;\n  let p = 0;\n  while (Math.round(x * e) / e !== x) {\n    e *= 10;\n    p++;\n  }\n  return p;\n}\n\n// Gets the angle from vertical upright to the point about a centre.\nexport function getAngleFromPoint(\n  centrePoint: Point,\n  anglePoint: Point\n) {\n  const distanceFromXCenter = anglePoint.x - centrePoint.x;\n  const distanceFromYCenter = anglePoint.y - centrePoint.y;\n  const radialDistanceFromCenter = Math.sqrt(distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter);\n\n  let angle = Math.atan2(distanceFromYCenter, distanceFromXCenter);\n\n  if (angle < (-0.5 * PI)) {\n    angle += TAU; // make sure the returned angle is in the range of (-PI/2, 3PI/2]\n  }\n\n  return {\n    angle,\n    distance: radialDistanceFromCenter\n  };\n}\n\nexport function distanceBetweenPoints(pt1: Point, pt2: Point) {\n  return Math.sqrt(Math.pow(pt2.x - pt1.x, 2) + Math.pow(pt2.y - pt1.y, 2));\n}\n\n/**\n * Shortest distance between angles, in either direction.\n * @private\n */\nexport function _angleDiff(a: number, b: number) {\n  return (a - b + PITAU) % TAU - PI;\n}\n\n/**\n * Normalize angle to be between 0 and 2*PI\n * @private\n */\nexport function _normalizeAngle(a: number) {\n  return (a % TAU + TAU) % TAU;\n}\n\n/**\n * @private\n */\nexport function _angleBetween(angle: number, start: number, end: number, sameAngleIsFullCircle?: boolean) {\n  const a = _normalizeAngle(angle);\n  const s = _normalizeAngle(start);\n  const e = _normalizeAngle(end);\n  const angleToStart = _normalizeAngle(s - a);\n  const angleToEnd = _normalizeAngle(e - a);\n  const startToAngle = _normalizeAngle(a - s);\n  const endToAngle = _normalizeAngle(a - e);\n  return a === s || a === e || (sameAngleIsFullCircle && s === e)\n    || (angleToStart > angleToEnd && startToAngle < endToAngle);\n}\n\n/**\n * Limit `value` between `min` and `max`\n * @param value\n * @param min\n * @param max\n * @private\n */\nexport function _limitValue(value: number, min: number, max: number) {\n  return Math.max(min, Math.min(max, value));\n}\n\n/**\n * @param {number} value\n * @private\n */\nexport function _int16Range(value: number) {\n  return _limitValue(value, -32768, 32767);\n}\n\n/**\n * @param value\n * @param start\n * @param end\n * @param [epsilon]\n * @private\n */\nexport function _isBetween(value: number, start: number, end: number, epsilon = 1e-6) {\n  return value >= Math.min(start, end) - epsilon && value <= Math.max(start, end) + epsilon;\n}\n"
  },
  {
    "path": "src/helpers/helpers.options.ts",
    "content": "import defaults from '../core/core.defaults.js';\nimport {isArray, isObject, toDimension, valueOrDefault} from './helpers.core.js';\nimport {toFontString} from './helpers.canvas.js';\nimport type {ChartArea, FontSpec, Point} from '../types/index.js';\nimport type {TRBL, TRBLCorners} from '../types/geometric.js';\n\nconst LINE_HEIGHT = /^(normal|(\\d+(?:\\.\\d+)?)(px|em|%)?)$/;\nconst FONT_STYLE = /^(normal|italic|initial|inherit|unset|(oblique( -?[0-9]?[0-9]deg)?))$/;\n\n/**\n * @alias Chart.helpers.options\n * @namespace\n */\n/**\n * Converts the given line height `value` in pixels for a specific font `size`.\n * @param value - The lineHeight to parse (eg. 1.6, '14px', '75%', '1.6em').\n * @param size - The font size (in pixels) used to resolve relative `value`.\n * @returns The effective line height in pixels (size * 1.2 if value is invalid).\n * @see https://developer.mozilla.org/en-US/docs/Web/CSS/line-height\n * @since 2.7.0\n */\nexport function toLineHeight(value: number | string, size: number): number {\n  const matches = ('' + value).match(LINE_HEIGHT);\n  if (!matches || matches[1] === 'normal') {\n    return size * 1.2;\n  }\n\n  value = +matches[2];\n\n  switch (matches[3]) {\n    case 'px':\n      return value;\n    case '%':\n      value /= 100;\n      break;\n    default:\n      break;\n  }\n\n  return size * value;\n}\n\nconst numberOrZero = (v: unknown) => +v || 0;\n\n/**\n * @param value\n * @param props\n */\nexport function _readValueToProps<K extends string>(value: number | Record<K, number>, props: K[]): Record<K, number>;\nexport function _readValueToProps<K extends string, T extends string>(value: number | Record<K & T, number>, props: Record<T, K>): Record<T, number>;\nexport function _readValueToProps(value: number | Record<string, number>, props: string[] | Record<string, string>) {\n  const ret = {};\n  const objProps = isObject(props);\n  const keys = objProps ? Object.keys(props) : props;\n  const read = isObject(value)\n    ? objProps\n      ? prop => valueOrDefault(value[prop], value[props[prop]])\n      : prop => value[prop]\n    : () => value;\n\n  for (const prop of keys) {\n    ret[prop] = numberOrZero(read(prop));\n  }\n  return ret;\n}\n\n/**\n * Converts the given value into a TRBL object.\n * @param value - If a number, set the value to all TRBL component,\n *  else, if an object, use defined properties and sets undefined ones to 0.\n *  x / y are shorthands for same value for left/right and top/bottom.\n * @returns The padding values (top, right, bottom, left)\n * @since 3.0.0\n */\nexport function toTRBL(value: number | TRBL | Point) {\n  return _readValueToProps(value, {top: 'y', right: 'x', bottom: 'y', left: 'x'});\n}\n\n/**\n * Converts the given value into a TRBL corners object (similar with css border-radius).\n * @param value - If a number, set the value to all TRBL corner components,\n *  else, if an object, use defined properties and sets undefined ones to 0.\n * @returns The TRBL corner values (topLeft, topRight, bottomLeft, bottomRight)\n * @since 3.0.0\n */\nexport function toTRBLCorners(value: number | TRBLCorners) {\n  return _readValueToProps(value, ['topLeft', 'topRight', 'bottomLeft', 'bottomRight']);\n}\n\n/**\n * Converts the given value into a padding object with pre-computed width/height.\n * @param value - If a number, set the value to all TRBL component,\n *  else, if an object, use defined properties and sets undefined ones to 0.\n *  x / y are shorthands for same value for left/right and top/bottom.\n * @returns The padding values (top, right, bottom, left, width, height)\n * @since 2.7.0\n */\nexport function toPadding(value?: number | TRBL): ChartArea {\n  const obj = toTRBL(value) as ChartArea;\n\n  obj.width = obj.left + obj.right;\n  obj.height = obj.top + obj.bottom;\n\n  return obj;\n}\n\n/**\n * Parses font options and returns the font object.\n * @param options - A object that contains font options to be parsed.\n * @param fallback - A object that contains fallback font options.\n * @return The font object.\n * @private\n */\n\nexport function toFont(options: Partial<FontSpec>, fallback?: Partial<FontSpec>) {\n  options = options || {};\n  fallback = fallback || defaults.font as FontSpec;\n\n  let size = valueOrDefault(options.size, fallback.size);\n\n  if (typeof size === 'string') {\n    size = parseInt(size, 10);\n  }\n  let style = valueOrDefault(options.style, fallback.style);\n  if (style && !('' + style).match(FONT_STYLE)) {\n    console.warn('Invalid font style specified: \"' + style + '\"');\n    style = undefined;\n  }\n\n  const font = {\n    family: valueOrDefault(options.family, fallback.family),\n    lineHeight: toLineHeight(valueOrDefault(options.lineHeight, fallback.lineHeight), size),\n    size,\n    style,\n    weight: valueOrDefault(options.weight, fallback.weight),\n    string: ''\n  };\n\n  font.string = toFontString(font);\n  return font;\n}\n\n/**\n * Evaluates the given `inputs` sequentially and returns the first defined value.\n * @param inputs - An array of values, falling back to the last value.\n * @param context - If defined and the current value is a function, the value\n * is called with `context` as first argument and the result becomes the new input.\n * @param index - If defined and the current value is an array, the value\n * at `index` become the new input.\n * @param info - object to return information about resolution in\n * @param info.cacheable - Will be set to `false` if option is not cacheable.\n * @since 2.7.0\n */\nexport function resolve(inputs: Array<unknown>, context?: object, index?: number, info?: { cacheable: boolean }) {\n  let cacheable = true;\n  let i: number, ilen: number, value: unknown;\n\n  for (i = 0, ilen = inputs.length; i < ilen; ++i) {\n    value = inputs[i];\n    if (value === undefined) {\n      continue;\n    }\n    if (context !== undefined && typeof value === 'function') {\n      value = value(context);\n      cacheable = false;\n    }\n    if (index !== undefined && isArray(value)) {\n      value = value[index % value.length];\n      cacheable = false;\n    }\n    if (value !== undefined) {\n      if (info && !cacheable) {\n        info.cacheable = false;\n      }\n      return value;\n    }\n  }\n}\n\n/**\n * @param minmax\n * @param grace\n * @param beginAtZero\n * @private\n */\nexport function _addGrace(minmax: { min: number; max: number; }, grace: number | string, beginAtZero: boolean) {\n  const {min, max} = minmax;\n  const change = toDimension(grace, (max - min) / 2);\n  const keepZero = (value: number, add: number) => beginAtZero && value === 0 ? 0 : value + add;\n  return {\n    min: keepZero(min, -Math.abs(change)),\n    max: keepZero(max, change)\n  };\n}\n\n/**\n * Create a context inheriting parentContext\n * @param parentContext\n * @param context\n * @returns\n */\nexport function createContext<T extends object>(parentContext: null, context: T): T;\nexport function createContext<T extends object, P extends T>(parentContext: P, context: T): P & T;\nexport function createContext(parentContext: object, context: object) {\n  return Object.assign(Object.create(parentContext), context);\n}\n"
  },
  {
    "path": "src/helpers/helpers.rtl.ts",
    "content": "export interface RTLAdapter {\n  x(x: number): number;\n  setWidth(w: number): void;\n  textAlign(align: 'center' | 'left' | 'right'): 'center' | 'left' | 'right';\n  xPlus(x: number, value: number): number;\n  leftForLtr(x: number, itemWidth: number): number;\n}\n\nconst getRightToLeftAdapter = function(rectX: number, width: number): RTLAdapter {\n  return {\n    x(x) {\n      return rectX + rectX + width - x;\n    },\n    setWidth(w) {\n      width = w;\n    },\n    textAlign(align) {\n      if (align === 'center') {\n        return align;\n      }\n      return align === 'right' ? 'left' : 'right';\n    },\n    xPlus(x, value) {\n      return x - value;\n    },\n    leftForLtr(x, itemWidth) {\n      return x - itemWidth;\n    },\n  };\n};\n\nconst getLeftToRightAdapter = function(): RTLAdapter {\n  return {\n    x(x) {\n      return x;\n    },\n    setWidth(w) { // eslint-disable-line no-unused-vars\n    },\n    textAlign(align) {\n      return align;\n    },\n    xPlus(x, value) {\n      return x + value;\n    },\n    leftForLtr(x, _itemWidth) { // eslint-disable-line @typescript-eslint/no-unused-vars\n      return x;\n    },\n  };\n};\n\nexport function getRtlAdapter(rtl: boolean, rectX: number, width: number) {\n  return rtl ? getRightToLeftAdapter(rectX, width) : getLeftToRightAdapter();\n}\n\nexport function overrideTextDirection(ctx: CanvasRenderingContext2D, direction: 'ltr' | 'rtl') {\n  let style: CSSStyleDeclaration, original: [string, string];\n  if (direction === 'ltr' || direction === 'rtl') {\n    style = ctx.canvas.style;\n    original = [\n      style.getPropertyValue('direction'),\n      style.getPropertyPriority('direction'),\n    ];\n\n    style.setProperty('direction', direction, 'important');\n    (ctx as { prevTextDirection?: [string, string] }).prevTextDirection = original;\n  }\n}\n\nexport function restoreTextDirection(ctx: CanvasRenderingContext2D, original?: [string, string]) {\n  if (original !== undefined) {\n    delete (ctx as { prevTextDirection?: [string, string] }).prevTextDirection;\n    ctx.canvas.style.setProperty('direction', original[0], original[1]);\n  }\n}\n"
  },
  {
    "path": "src/helpers/helpers.segment.js",
    "content": "import {_angleBetween, _angleDiff, _isBetween, _normalizeAngle} from './helpers.math.js';\nimport {createContext} from './helpers.options.js';\nimport {isPatternOrGradient} from './helpers.color.js';\n\n/**\n * @typedef { import('../elements/element.line.js').default } LineElement\n * @typedef { import('../elements/element.point.js').default } PointElement\n * @typedef {{start: number, end: number, loop: boolean, style?: any}} Segment\n */\n\nfunction propertyFn(property) {\n  if (property === 'angle') {\n    return {\n      between: _angleBetween,\n      compare: _angleDiff,\n      normalize: _normalizeAngle,\n    };\n  }\n  return {\n    between: _isBetween,\n    compare: (a, b) => a - b,\n    normalize: x => x\n  };\n}\n\nfunction normalizeSegment({start, end, count, loop, style}) {\n  return {\n    start: start % count,\n    end: end % count,\n    loop: loop && (end - start + 1) % count === 0,\n    style\n  };\n}\n\nfunction getSegment(segment, points, bounds) {\n  const {property, start: startBound, end: endBound} = bounds;\n  const {between, normalize} = propertyFn(property);\n  const count = points.length;\n  // eslint-disable-next-line prefer-const\n  let {start, end, loop} = segment;\n  let i, ilen;\n\n  if (loop) {\n    start += count;\n    end += count;\n    for (i = 0, ilen = count; i < ilen; ++i) {\n      if (!between(normalize(points[start % count][property]), startBound, endBound)) {\n        break;\n      }\n      start--;\n      end--;\n    }\n    start %= count;\n    end %= count;\n  }\n\n  if (end < start) {\n    end += count;\n  }\n  return {start, end, loop, style: segment.style};\n}\n\n/**\n * Returns the sub-segment(s) of a line segment that fall in the given bounds\n * @param {object} segment\n * @param {number} segment.start - start index of the segment, referring the points array\n * @param {number} segment.end - end index of the segment, referring the points array\n * @param {boolean} segment.loop - indicates that the segment is a loop\n * @param {object} [segment.style] - segment style\n * @param {PointElement[]} points - the points that this segment refers to\n * @param {object} [bounds]\n * @param {string} bounds.property - the property of a `PointElement` we are bounding. `x`, `y` or `angle`.\n * @param {number} bounds.start - start value of the property\n * @param {number} bounds.end - end value of the property\n * @private\n **/\nexport function _boundSegment(segment, points, bounds) {\n  if (!bounds) {\n    return [segment];\n  }\n\n  const {property, start: startBound, end: endBound} = bounds;\n  const count = points.length;\n  const {compare, between, normalize} = propertyFn(property);\n  const {start, end, loop, style} = getSegment(segment, points, bounds);\n\n  const result = [];\n  let inside = false;\n  let subStart = null;\n  let value, point, prevValue;\n\n  const startIsBefore = () => between(startBound, prevValue, value) && compare(startBound, prevValue) !== 0;\n  const endIsBefore = () => compare(endBound, value) === 0 || between(endBound, prevValue, value);\n  const shouldStart = () => inside || startIsBefore();\n  const shouldStop = () => !inside || endIsBefore();\n\n  for (let i = start, prev = start; i <= end; ++i) {\n    point = points[i % count];\n\n    if (point.skip) {\n      continue;\n    }\n\n    value = normalize(point[property]);\n\n    if (value === prevValue) {\n      continue;\n    }\n\n    inside = between(value, startBound, endBound);\n\n    if (subStart === null && shouldStart()) {\n      subStart = compare(value, startBound) === 0 ? i : prev;\n    }\n\n    if (subStart !== null && shouldStop()) {\n      result.push(normalizeSegment({start: subStart, end: i, loop, count, style}));\n      subStart = null;\n    }\n    prev = i;\n    prevValue = value;\n  }\n\n  if (subStart !== null) {\n    result.push(normalizeSegment({start: subStart, end, loop, count, style}));\n  }\n\n  return result;\n}\n\n\n/**\n * Returns the segments of the line that are inside given bounds\n * @param {LineElement} line\n * @param {object} [bounds]\n * @param {string} bounds.property - the property we are bounding with. `x`, `y` or `angle`.\n * @param {number} bounds.start - start value of the `property`\n * @param {number} bounds.end - end value of the `property`\n * @private\n */\nexport function _boundSegments(line, bounds) {\n  const result = [];\n  const segments = line.segments;\n\n  for (let i = 0; i < segments.length; i++) {\n    const sub = _boundSegment(segments[i], line.points, bounds);\n    if (sub.length) {\n      result.push(...sub);\n    }\n  }\n  return result;\n}\n\n/**\n * Find start and end index of a line.\n */\nfunction findStartAndEnd(points, count, loop, spanGaps) {\n  let start = 0;\n  let end = count - 1;\n\n  if (loop && !spanGaps) {\n    // loop and not spanning gaps, first find a gap to start from\n    while (start < count && !points[start].skip) {\n      start++;\n    }\n  }\n\n  // find first non skipped point (after the first gap possibly)\n  while (start < count && points[start].skip) {\n    start++;\n  }\n\n  // if we looped to count, start needs to be 0\n  start %= count;\n\n  if (loop) {\n    // loop will go past count, if start > 0\n    end += start;\n  }\n\n  while (end > start && points[end % count].skip) {\n    end--;\n  }\n\n  // end could be more than count, normalize\n  end %= count;\n\n  return {start, end};\n}\n\n/**\n * Compute solid segments from Points, when spanGaps === false\n * @param {PointElement[]} points - the points\n * @param {number} start - start index\n * @param {number} max - max index (can go past count on a loop)\n * @param {boolean} loop - boolean indicating that this would be a loop if no gaps are found\n */\nfunction solidSegments(points, start, max, loop) {\n  const count = points.length;\n  const result = [];\n  let last = start;\n  let prev = points[start];\n  let end;\n\n  for (end = start + 1; end <= max; ++end) {\n    const cur = points[end % count];\n    if (cur.skip || cur.stop) {\n      if (!prev.skip) {\n        loop = false;\n        result.push({start: start % count, end: (end - 1) % count, loop});\n        // @ts-ignore\n        start = last = cur.stop ? end : null;\n      }\n    } else {\n      last = end;\n      if (prev.skip) {\n        start = end;\n      }\n    }\n    prev = cur;\n  }\n\n  if (last !== null) {\n    result.push({start: start % count, end: last % count, loop});\n  }\n\n  return result;\n}\n\n/**\n * Compute the continuous segments that define the whole line\n * There can be skipped points within a segment, if spanGaps is true.\n * @param {LineElement} line\n * @param {object} [segmentOptions]\n * @return {Segment[]}\n * @private\n */\nexport function _computeSegments(line, segmentOptions) {\n  const points = line.points;\n  const spanGaps = line.options.spanGaps;\n  const count = points.length;\n\n  if (!count) {\n    return [];\n  }\n\n  const loop = !!line._loop;\n  const {start, end} = findStartAndEnd(points, count, loop, spanGaps);\n\n  if (spanGaps === true) {\n    return splitByStyles(line, [{start, end, loop}], points, segmentOptions);\n  }\n\n  const max = end < start ? end + count : end;\n  const completeLoop = !!line._fullLoop && start === 0 && end === count - 1;\n  return splitByStyles(line, solidSegments(points, start, max, completeLoop), points, segmentOptions);\n}\n\n/**\n * @param {Segment[]} segments\n * @param {PointElement[]} points\n * @param {object} [segmentOptions]\n * @return {Segment[]}\n */\nfunction splitByStyles(line, segments, points, segmentOptions) {\n  if (!segmentOptions || !segmentOptions.setContext || !points) {\n    return segments;\n  }\n  return doSplitByStyles(line, segments, points, segmentOptions);\n}\n\n/**\n * @param {LineElement} line\n * @param {Segment[]} segments\n * @param {PointElement[]} points\n * @param {object} [segmentOptions]\n * @return {Segment[]}\n */\nfunction doSplitByStyles(line, segments, points, segmentOptions) {\n  const chartContext = line._chart.getContext();\n  const baseStyle = readStyle(line.options);\n  const {_datasetIndex: datasetIndex, options: {spanGaps}} = line;\n  const count = points.length;\n  const result = [];\n  let prevStyle = baseStyle;\n  let start = segments[0].start;\n  let i = start;\n\n  function addStyle(s, e, l, st) {\n    const dir = spanGaps ? -1 : 1;\n    if (s === e) {\n      return;\n    }\n    // Style can not start/end on a skipped point, adjust indices accordingly\n    s += count;\n    while (points[s % count].skip) {\n      s -= dir;\n    }\n    while (points[e % count].skip) {\n      e += dir;\n    }\n    if (s % count !== e % count) {\n      result.push({start: s % count, end: e % count, loop: l, style: st});\n      prevStyle = st;\n      start = e % count;\n    }\n  }\n\n  for (const segment of segments) {\n    start = spanGaps ? start : segment.start;\n    let prev = points[start % count];\n    let style;\n    for (i = start + 1; i <= segment.end; i++) {\n      const pt = points[i % count];\n      style = readStyle(segmentOptions.setContext(createContext(chartContext, {\n        type: 'segment',\n        p0: prev,\n        p1: pt,\n        p0DataIndex: (i - 1) % count,\n        p1DataIndex: i % count,\n        datasetIndex\n      })));\n      if (styleChanged(style, prevStyle)) {\n        addStyle(start, i - 1, segment.loop, prevStyle);\n      }\n      prev = pt;\n      prevStyle = style;\n    }\n    if (start < i - 1) {\n      addStyle(start, i - 1, segment.loop, prevStyle);\n    }\n  }\n\n  return result;\n}\n\nfunction readStyle(options) {\n  return {\n    backgroundColor: options.backgroundColor,\n    borderCapStyle: options.borderCapStyle,\n    borderDash: options.borderDash,\n    borderDashOffset: options.borderDashOffset,\n    borderJoinStyle: options.borderJoinStyle,\n    borderWidth: options.borderWidth,\n    borderColor: options.borderColor\n  };\n}\n\nfunction styleChanged(style, prevStyle) {\n  if (!prevStyle) {\n    return false;\n  }\n  const cache = [];\n  const replacer = function(key, value) {\n    if (!isPatternOrGradient(value)) {\n      return value;\n    }\n    if (!cache.includes(value)) {\n      cache.push(value);\n    }\n    return cache.indexOf(value);\n  };\n  return JSON.stringify(style, replacer) !== JSON.stringify(prevStyle, replacer);\n}\n"
  },
  {
    "path": "src/helpers/index.ts",
    "content": "export * from './helpers.color.js';\nexport * from './helpers.core.js';\nexport * from './helpers.canvas.js';\nexport * from './helpers.collection.js';\nexport * from './helpers.config.js';\nexport * from './helpers.curve.js';\nexport * from './helpers.dom.js';\nexport {default as easingEffects} from './helpers.easing.js';\nexport * from './helpers.extras.js';\nexport * from './helpers.interpolation.js';\nexport * from './helpers.intl.js';\nexport * from './helpers.options.js';\nexport * from './helpers.math.js';\nexport * from './helpers.rtl.js';\nexport * from './helpers.segment.js';\nexport * from './helpers.dataset.js';\n"
  },
  {
    "path": "src/index.ts",
    "content": "export * from './controllers/index.js';\nexport * from './core/index.js';\nexport * from './elements/index.js';\nexport * from './platform/index.js';\nexport * from './plugins/index.js';\nexport * from './scales/index.js';\n\nimport * as controllers from './controllers/index.js';\nimport * as elements from './elements/index.js';\nimport * as plugins from './plugins/index.js';\nimport * as scales from './scales/index.js';\n\nexport {\n  controllers,\n  elements,\n  plugins,\n  scales,\n};\n\nexport const registerables = [\n  controllers,\n  elements,\n  plugins,\n  scales,\n];\n"
  },
  {
    "path": "src/index.umd.ts",
    "content": "// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-nocheck\n\n/**\n * @namespace Chart\n */\nimport Chart from './core/core.controller.js';\n\nimport * as helpers from './helpers/index.js';\nimport _adapters from './core/core.adapters.js';\nimport Animation from './core/core.animation.js';\nimport animator from './core/core.animator.js';\nimport Animations from './core/core.animations.js';\nimport * as controllers from './controllers/index.js';\nimport DatasetController from './core/core.datasetController.js';\nimport Element from './core/core.element.js';\nimport * as elements from './elements/index.js';\nimport Interaction from './core/core.interaction.js';\nimport layouts from './core/core.layouts.js';\nimport * as platforms from './platform/index.js';\nimport * as plugins from './plugins/index.js';\nimport registry from './core/core.registry.js';\nimport Scale from './core/core.scale.js';\nimport * as scales from './scales/index.js';\nimport Ticks from './core/core.ticks.js';\n\n// Register built-ins\nChart.register(controllers, scales, elements, plugins);\n\nChart.helpers = {...helpers};\nChart._adapters = _adapters;\nChart.Animation = Animation;\nChart.Animations = Animations;\nChart.animator = animator;\nChart.controllers = registry.controllers.items;\nChart.DatasetController = DatasetController;\nChart.Element = Element;\nChart.elements = elements;\nChart.Interaction = Interaction;\nChart.layouts = layouts;\nChart.platforms = platforms;\nChart.Scale = Scale;\nChart.Ticks = Ticks;\n\n// Compatibility with ESM extensions\nObject.assign(Chart, controllers, scales, elements, plugins, platforms);\nChart.Chart = Chart;\n\nif (typeof window !== 'undefined') {\n  window.Chart = Chart;\n}\n\nexport default Chart;\n\n"
  },
  {
    "path": "src/platform/index.js",
    "content": "import {_isDomSupported} from '../helpers/index.js';\nimport BasePlatform from './platform.base.js';\nimport BasicPlatform from './platform.basic.js';\nimport DomPlatform from './platform.dom.js';\n\nexport function _detectPlatform(canvas) {\n  if (!_isDomSupported() || (typeof OffscreenCanvas !== 'undefined' && canvas instanceof OffscreenCanvas)) {\n    return BasicPlatform;\n  }\n  return DomPlatform;\n}\n\nexport {BasePlatform, BasicPlatform, DomPlatform};\n"
  },
  {
    "path": "src/platform/platform.base.js",
    "content": "\n/**\n * @typedef { import('../core/core.controller.js').default } Chart\n */\n\n/**\n * Abstract class that allows abstracting platform dependencies away from the chart.\n */\nexport default class BasePlatform {\n  /**\n\t * Called at chart construction time, returns a context2d instance implementing\n\t * the [W3C Canvas 2D Context API standard]{@link https://www.w3.org/TR/2dcontext/}.\n\t * @param {HTMLCanvasElement} canvas - The canvas from which to acquire context (platform specific)\n\t * @param {number} [aspectRatio] - The chart options\n\t */\n  acquireContext(canvas, aspectRatio) {} // eslint-disable-line no-unused-vars\n\n  /**\n\t * Called at chart destruction time, releases any resources associated to the context\n\t * previously returned by the acquireContext() method.\n\t * @param {CanvasRenderingContext2D} context - The context2d instance\n\t * @returns {boolean} true if the method succeeded, else false\n\t */\n  releaseContext(context) { // eslint-disable-line no-unused-vars\n    return false;\n  }\n\n  /**\n\t * Registers the specified listener on the given chart.\n\t * @param {Chart} chart - Chart from which to listen for event\n\t * @param {string} type - The ({@link ChartEvent}) type to listen for\n\t * @param {function} listener - Receives a notification (an object that implements\n\t * the {@link ChartEvent} interface) when an event of the specified type occurs.\n\t */\n  addEventListener(chart, type, listener) {} // eslint-disable-line no-unused-vars\n\n  /**\n\t * Removes the specified listener previously registered with addEventListener.\n\t * @param {Chart} chart - Chart from which to remove the listener\n\t * @param {string} type - The ({@link ChartEvent}) type to remove\n\t * @param {function} listener - The listener function to remove from the event target.\n\t */\n  removeEventListener(chart, type, listener) {} // eslint-disable-line no-unused-vars\n\n  /**\n\t * @returns {number} the current devicePixelRatio of the device this platform is connected to.\n\t */\n  getDevicePixelRatio() {\n    return 1;\n  }\n\n  /**\n\t * Returns the maximum size in pixels of given canvas element.\n\t * @param {HTMLCanvasElement} element\n\t * @param {number} [width] - content width of parent element\n\t * @param {number} [height] - content height of parent element\n\t * @param {number} [aspectRatio] - aspect ratio to maintain\n\t */\n  getMaximumSize(element, width, height, aspectRatio) {\n    width = Math.max(0, width || element.width);\n    height = height || element.height;\n    return {\n      width,\n      height: Math.max(0, aspectRatio ? Math.floor(width / aspectRatio) : height)\n    };\n  }\n\n  /**\n\t * @param {HTMLCanvasElement} canvas\n\t * @returns {boolean} true if the canvas is attached to the platform, false if not.\n\t */\n  isAttached(canvas) { // eslint-disable-line no-unused-vars\n    return true;\n  }\n\n  /**\n   * Updates config with platform specific requirements\n   * @param {import('../core/core.config.js').default} config\n   */\n  updateConfig(config) { // eslint-disable-line no-unused-vars\n    // no-op\n  }\n}\n"
  },
  {
    "path": "src/platform/platform.basic.js",
    "content": "/**\n * Platform fallback implementation (minimal).\n * @see https://github.com/chartjs/Chart.js/pull/4591#issuecomment-319575939\n */\n\nimport BasePlatform from './platform.base.js';\n\n/**\n * Platform class for charts without access to the DOM or to many element properties\n * This platform is used by default for any chart passed an OffscreenCanvas.\n * @extends BasePlatform\n */\nexport default class BasicPlatform extends BasePlatform {\n  acquireContext(item) {\n    // To prevent canvas fingerprinting, some add-ons undefine the getContext\n    // method, for example: https://github.com/kkapsner/CanvasBlocker\n    // https://github.com/chartjs/Chart.js/issues/2807\n    return item && item.getContext && item.getContext('2d') || null;\n  }\n  updateConfig(config) {\n    config.options.animation = false;\n  }\n}\n"
  },
  {
    "path": "src/platform/platform.dom.js",
    "content": "/**\n * Chart.Platform implementation for targeting a web browser\n */\n\nimport BasePlatform from './platform.base.js';\nimport {_getParentNode, getRelativePosition, supportsEventListenerOptions, readUsedSize, getMaximumSize} from '../helpers/helpers.dom.js';\nimport {throttled} from '../helpers/helpers.extras.js';\nimport {isNullOrUndef} from '../helpers/helpers.core.js';\n\n/**\n * @typedef { import('../core/core.controller.js').default } Chart\n */\n\nconst EXPANDO_KEY = '$chartjs';\n\n/**\n * DOM event types -> Chart.js event types.\n * Note: only events with different types are mapped.\n * @see https://developer.mozilla.org/en-US/docs/Web/Events\n */\nconst EVENT_TYPES = {\n  touchstart: 'mousedown',\n  touchmove: 'mousemove',\n  touchend: 'mouseup',\n  pointerenter: 'mouseenter',\n  pointerdown: 'mousedown',\n  pointermove: 'mousemove',\n  pointerup: 'mouseup',\n  pointerleave: 'mouseout',\n  pointerout: 'mouseout'\n};\n\nconst isNullOrEmpty = value => value === null || value === '';\n/**\n * Initializes the canvas style and render size without modifying the canvas display size,\n * since responsiveness is handled by the controller.resize() method. The config is used\n * to determine the aspect ratio to apply in case no explicit height has been specified.\n * @param {HTMLCanvasElement} canvas\n * @param {number} [aspectRatio]\n */\nfunction initCanvas(canvas, aspectRatio) {\n  const style = canvas.style;\n\n  // NOTE(SB) canvas.getAttribute('width') !== canvas.width: in the first case it\n  // returns null or '' if no explicit value has been set to the canvas attribute.\n  const renderHeight = canvas.getAttribute('height');\n  const renderWidth = canvas.getAttribute('width');\n\n  // Chart.js modifies some canvas values that we want to restore on destroy\n  canvas[EXPANDO_KEY] = {\n    initial: {\n      height: renderHeight,\n      width: renderWidth,\n      style: {\n        display: style.display,\n        height: style.height,\n        width: style.width\n      }\n    }\n  };\n\n  // Force canvas to display as block to avoid extra space caused by inline\n  // elements, which would interfere with the responsive resize process.\n  // https://github.com/chartjs/Chart.js/issues/2538\n  style.display = style.display || 'block';\n  // Include possible borders in the size\n  style.boxSizing = style.boxSizing || 'border-box';\n\n  if (isNullOrEmpty(renderWidth)) {\n    const displayWidth = readUsedSize(canvas, 'width');\n    if (displayWidth !== undefined) {\n      canvas.width = displayWidth;\n    }\n  }\n\n  if (isNullOrEmpty(renderHeight)) {\n    if (canvas.style.height === '') {\n      // If no explicit render height and style height, let's apply the aspect ratio,\n      // which one can be specified by the user but also by charts as default option\n      // (i.e. options.aspectRatio). If not specified, use canvas aspect ratio of 2.\n      canvas.height = canvas.width / (aspectRatio || 2);\n    } else {\n      const displayHeight = readUsedSize(canvas, 'height');\n      if (displayHeight !== undefined) {\n        canvas.height = displayHeight;\n      }\n    }\n  }\n\n  return canvas;\n}\n\n// Default passive to true as expected by Chrome for 'touchstart' and 'touchend' events.\n// https://github.com/chartjs/Chart.js/issues/4287\nconst eventListenerOptions = supportsEventListenerOptions ? {passive: true} : false;\n\nfunction addListener(node, type, listener) {\n  if (node) {\n    node.addEventListener(type, listener, eventListenerOptions);\n  }\n}\n\nfunction removeListener(chart, type, listener) {\n  if (chart && chart.canvas) {\n    chart.canvas.removeEventListener(type, listener, eventListenerOptions);\n  }\n}\n\nfunction fromNativeEvent(event, chart) {\n  const type = EVENT_TYPES[event.type] || event.type;\n  const {x, y} = getRelativePosition(event, chart);\n  return {\n    type,\n    chart,\n    native: event,\n    x: x !== undefined ? x : null,\n    y: y !== undefined ? y : null,\n  };\n}\n\nfunction nodeListContains(nodeList, canvas) {\n  for (const node of nodeList) {\n    if (node === canvas || node.contains(canvas)) {\n      return true;\n    }\n  }\n}\n\nfunction createAttachObserver(chart, type, listener) {\n  const canvas = chart.canvas;\n  const observer = new MutationObserver(entries => {\n    let trigger = false;\n    for (const entry of entries) {\n      trigger = trigger || nodeListContains(entry.addedNodes, canvas);\n      trigger = trigger && !nodeListContains(entry.removedNodes, canvas);\n    }\n    if (trigger) {\n      listener();\n    }\n  });\n  observer.observe(document, {childList: true, subtree: true});\n  return observer;\n}\n\nfunction createDetachObserver(chart, type, listener) {\n  const canvas = chart.canvas;\n  const observer = new MutationObserver(entries => {\n    let trigger = false;\n    for (const entry of entries) {\n      trigger = trigger || nodeListContains(entry.removedNodes, canvas);\n      trigger = trigger && !nodeListContains(entry.addedNodes, canvas);\n    }\n    if (trigger) {\n      listener();\n    }\n  });\n  observer.observe(document, {childList: true, subtree: true});\n  return observer;\n}\n\nconst drpListeningCharts = new Map();\nlet oldDevicePixelRatio = 0;\n\nfunction onWindowResize() {\n  const dpr = window.devicePixelRatio;\n  if (dpr === oldDevicePixelRatio) {\n    return;\n  }\n  oldDevicePixelRatio = dpr;\n  drpListeningCharts.forEach((resize, chart) => {\n    if (chart.currentDevicePixelRatio !== dpr) {\n      resize();\n    }\n  });\n}\n\nfunction listenDevicePixelRatioChanges(chart, resize) {\n  if (!drpListeningCharts.size) {\n    window.addEventListener('resize', onWindowResize);\n  }\n  drpListeningCharts.set(chart, resize);\n}\n\nfunction unlistenDevicePixelRatioChanges(chart) {\n  drpListeningCharts.delete(chart);\n  if (!drpListeningCharts.size) {\n    window.removeEventListener('resize', onWindowResize);\n  }\n}\n\nfunction createResizeObserver(chart, type, listener) {\n  const canvas = chart.canvas;\n  const container = canvas && _getParentNode(canvas);\n  if (!container) {\n    return;\n  }\n  const resize = throttled((width, height) => {\n    const w = container.clientWidth;\n    listener(width, height);\n    if (w < container.clientWidth) {\n      // If the container size shrank during chart resize, let's assume\n      // scrollbar appeared. So we resize again with the scrollbar visible -\n      // effectively making chart smaller and the scrollbar hidden again.\n      // Because we are inside `throttled`, and currently `ticking`, scroll\n      // events are ignored during this whole 2 resize process.\n      // If we assumed wrong and something else happened, we are resizing\n      // twice in a frame (potential performance issue)\n      listener();\n    }\n  }, window);\n\n  // @ts-ignore until https://github.com/microsoft/TypeScript/issues/37861 implemented\n  const observer = new ResizeObserver(entries => {\n    const entry = entries[0];\n    const width = entry.contentRect.width;\n    const height = entry.contentRect.height;\n    // When its container's display is set to 'none' the callback will be called with a\n    // size of (0, 0), which will cause the chart to lose its original height, so skip\n    // resizing in such case.\n    if (width === 0 && height === 0) {\n      return;\n    }\n    resize(width, height);\n  });\n  observer.observe(container);\n  listenDevicePixelRatioChanges(chart, resize);\n\n  return observer;\n}\n\nfunction releaseObserver(chart, type, observer) {\n  if (observer) {\n    observer.disconnect();\n  }\n  if (type === 'resize') {\n    unlistenDevicePixelRatioChanges(chart);\n  }\n}\n\nfunction createProxyAndListen(chart, type, listener) {\n  const canvas = chart.canvas;\n  const proxy = throttled((event) => {\n    // This case can occur if the chart is destroyed while waiting\n    // for the throttled function to occur. We prevent crashes by checking\n    // for a destroyed chart\n    if (chart.ctx !== null) {\n      listener(fromNativeEvent(event, chart));\n    }\n  }, chart);\n\n  addListener(canvas, type, proxy);\n\n  return proxy;\n}\n\n/**\n * Platform class for charts that can access the DOM and global window/document properties\n * @extends BasePlatform\n */\nexport default class DomPlatform extends BasePlatform {\n\n  /**\n\t * @param {HTMLCanvasElement} canvas\n\t * @param {number} [aspectRatio]\n\t * @return {CanvasRenderingContext2D|null}\n\t */\n  acquireContext(canvas, aspectRatio) {\n    // To prevent canvas fingerprinting, some add-ons undefine the getContext\n    // method, for example: https://github.com/kkapsner/CanvasBlocker\n    // https://github.com/chartjs/Chart.js/issues/2807\n    const context = canvas && canvas.getContext && canvas.getContext('2d');\n\n    // `instanceof HTMLCanvasElement/CanvasRenderingContext2D` fails when the canvas is\n    // inside an iframe or when running in a protected environment. We could guess the\n    // types from their toString() value but let's keep things flexible and assume it's\n    // a sufficient condition if the canvas has a context2D which has canvas as `canvas`.\n    // https://github.com/chartjs/Chart.js/issues/3887\n    // https://github.com/chartjs/Chart.js/issues/4102\n    // https://github.com/chartjs/Chart.js/issues/4152\n    if (context && context.canvas === canvas) {\n      // Load platform resources on first chart creation, to make it possible to\n      // import the library before setting platform options.\n      initCanvas(canvas, aspectRatio);\n      return context;\n    }\n\n    return null;\n  }\n\n  /**\n\t * @param {CanvasRenderingContext2D} context\n\t */\n  releaseContext(context) {\n    const canvas = context.canvas;\n    if (!canvas[EXPANDO_KEY]) {\n      return false;\n    }\n\n    const initial = canvas[EXPANDO_KEY].initial;\n    ['height', 'width'].forEach((prop) => {\n      const value = initial[prop];\n      if (isNullOrUndef(value)) {\n        canvas.removeAttribute(prop);\n      } else {\n        canvas.setAttribute(prop, value);\n      }\n    });\n\n    const style = initial.style || {};\n    Object.keys(style).forEach((key) => {\n      canvas.style[key] = style[key];\n    });\n\n    // The canvas render size might have been changed (and thus the state stack discarded),\n    // we can't use save() and restore() to restore the initial state. So make sure that at\n    // least the canvas context is reset to the default state by setting the canvas width.\n    // https://www.w3.org/TR/2011/WD-html5-20110525/the-canvas-element.html\n    // eslint-disable-next-line no-self-assign\n    canvas.width = canvas.width;\n\n    delete canvas[EXPANDO_KEY];\n    return true;\n  }\n\n  /**\n\t *\n\t * @param {Chart} chart\n\t * @param {string} type\n\t * @param {function} listener\n\t */\n  addEventListener(chart, type, listener) {\n    // Can have only one listener per type, so make sure previous is removed\n    this.removeEventListener(chart, type);\n\n    const proxies = chart.$proxies || (chart.$proxies = {});\n    const handlers = {\n      attach: createAttachObserver,\n      detach: createDetachObserver,\n      resize: createResizeObserver\n    };\n    const handler = handlers[type] || createProxyAndListen;\n    proxies[type] = handler(chart, type, listener);\n  }\n\n\n  /**\n\t * @param {Chart} chart\n\t * @param {string} type\n\t */\n  removeEventListener(chart, type) {\n    const proxies = chart.$proxies || (chart.$proxies = {});\n    const proxy = proxies[type];\n\n    if (!proxy) {\n      return;\n    }\n\n    const handlers = {\n      attach: releaseObserver,\n      detach: releaseObserver,\n      resize: releaseObserver\n    };\n    const handler = handlers[type] || removeListener;\n    handler(chart, type, proxy);\n    proxies[type] = undefined;\n  }\n\n  getDevicePixelRatio() {\n    return window.devicePixelRatio;\n  }\n\n  /**\n\t * @param {HTMLCanvasElement} canvas\n\t * @param {number} [width] - content width of parent element\n\t * @param {number} [height] - content height of parent element\n\t * @param {number} [aspectRatio] - aspect ratio to maintain\n\t */\n  getMaximumSize(canvas, width, height, aspectRatio) {\n    return getMaximumSize(canvas, width, height, aspectRatio);\n  }\n\n  /**\n\t * @param {HTMLCanvasElement} canvas\n\t */\n  isAttached(canvas) {\n    const container = canvas && _getParentNode(canvas);\n    return !!(container && container.isConnected);\n  }\n}\n"
  },
  {
    "path": "src/plugins/index.js",
    "content": "export {default as Colors} from './plugin.colors.js';\nexport {default as Decimation} from './plugin.decimation.js';\nexport {default as Filler} from './plugin.filler/index.js';\nexport {default as Legend} from './plugin.legend.js';\nexport {default as SubTitle} from './plugin.subtitle.js';\nexport {default as Title} from './plugin.title.js';\nexport {default as Tooltip} from './plugin.tooltip.js';\n"
  },
  {
    "path": "src/plugins/plugin.colors.ts",
    "content": "import {DoughnutController, PolarAreaController, defaults} from '../index.js';\nimport type {Chart, ChartDataset} from '../types.js';\n\nexport interface ColorsPluginOptions {\n  enabled?: boolean;\n  forceOverride?: boolean;\n}\n\ninterface ColorsDescriptor {\n  backgroundColor?: unknown;\n  borderColor?: unknown;\n}\n\nconst BORDER_COLORS = [\n  'rgb(54, 162, 235)', // blue\n  'rgb(255, 99, 132)', // red\n  'rgb(255, 159, 64)', // orange\n  'rgb(255, 205, 86)', // yellow\n  'rgb(75, 192, 192)', // green\n  'rgb(153, 102, 255)', // purple\n  'rgb(201, 203, 207)' // grey\n];\n\n// Border colors with 50% transparency\nconst BACKGROUND_COLORS = /* #__PURE__ */ BORDER_COLORS.map(color => color.replace('rgb(', 'rgba(').replace(')', ', 0.5)'));\n\nfunction getBorderColor(i: number) {\n  return BORDER_COLORS[i % BORDER_COLORS.length];\n}\n\nfunction getBackgroundColor(i: number) {\n  return BACKGROUND_COLORS[i % BACKGROUND_COLORS.length];\n}\n\nfunction colorizeDefaultDataset(dataset: ChartDataset, i: number) {\n  dataset.borderColor = getBorderColor(i);\n  dataset.backgroundColor = getBackgroundColor(i);\n\n  return ++i;\n}\n\nfunction colorizeDoughnutDataset(dataset: ChartDataset, i: number) {\n  dataset.backgroundColor = dataset.data.map(() => getBorderColor(i++));\n\n  return i;\n}\n\nfunction colorizePolarAreaDataset(dataset: ChartDataset, i: number) {\n  dataset.backgroundColor = dataset.data.map(() => getBackgroundColor(i++));\n\n  return i;\n}\n\nfunction getColorizer(chart: Chart) {\n  let i = 0;\n\n  return (dataset: ChartDataset, datasetIndex: number) => {\n    const controller = chart.getDatasetMeta(datasetIndex).controller;\n\n    if (controller instanceof DoughnutController) {\n      i = colorizeDoughnutDataset(dataset, i);\n    } else if (controller instanceof PolarAreaController) {\n      i = colorizePolarAreaDataset(dataset, i);\n    } else if (controller) {\n      i = colorizeDefaultDataset(dataset, i);\n    }\n  };\n}\n\nfunction containsColorsDefinitions(\n  descriptors: ColorsDescriptor[] | Record<string, ColorsDescriptor>\n) {\n  let k: number | string;\n\n  for (k in descriptors) {\n    if (descriptors[k].borderColor || descriptors[k].backgroundColor) {\n      return true;\n    }\n  }\n\n  return false;\n}\n\nfunction containsColorsDefinition(\n  descriptor: ColorsDescriptor\n) {\n  return descriptor && (descriptor.borderColor || descriptor.backgroundColor);\n}\n\nfunction containsDefaultColorsDefenitions() {\n  return defaults.borderColor !== 'rgba(0,0,0,0.1)' || defaults.backgroundColor !== 'rgba(0,0,0,0.1)';\n}\n\nexport default {\n  id: 'colors',\n\n  defaults: {\n    enabled: true,\n    forceOverride: false\n  } as ColorsPluginOptions,\n\n  beforeLayout(chart: Chart, _args, options: ColorsPluginOptions) {\n    if (!options.enabled) {\n      return;\n    }\n\n    const {\n      data: {datasets},\n      options: chartOptions\n    } = chart.config;\n    const {elements} = chartOptions;\n\n    const containsColorDefenition = (\n      containsColorsDefinitions(datasets) ||\n      containsColorsDefinition(chartOptions) ||\n      (elements && containsColorsDefinitions(elements)) ||\n      containsDefaultColorsDefenitions());\n\n    if (!options.forceOverride && containsColorDefenition) {\n      return;\n    }\n\n    const colorizer = getColorizer(chart);\n\n    datasets.forEach(colorizer);\n  }\n};\n"
  },
  {
    "path": "src/plugins/plugin.decimation.js",
    "content": "import {_limitValue, _lookupByKey, isNullOrUndef, resolve} from '../helpers/index.js';\n\nfunction lttbDecimation(data, start, count, availableWidth, options) {\n  /**\n   * Implementation of the Largest Triangle Three Buckets algorithm.\n   *\n   * This implementation is based on the original implementation by Sveinn Steinarsson\n   * in https://github.com/sveinn-steinarsson/flot-downsample/blob/master/jquery.flot.downsample.js\n   *\n   * The original implementation is MIT licensed.\n   */\n  const samples = options.samples || availableWidth;\n  // There are less points than the threshold, returning the whole array\n  if (samples >= count) {\n    return data.slice(start, start + count);\n  }\n\n  const decimated = [];\n\n  const bucketWidth = (count - 2) / (samples - 2);\n  let sampledIndex = 0;\n  const endIndex = start + count - 1;\n  // Starting from offset\n  let a = start;\n  let i, maxAreaPoint, maxArea, area, nextA;\n\n  decimated[sampledIndex++] = data[a];\n\n  for (i = 0; i < samples - 2; i++) {\n    let avgX = 0;\n    let avgY = 0;\n    let j;\n\n    // Adding offset\n    const avgRangeStart = Math.floor((i + 1) * bucketWidth) + 1 + start;\n    const avgRangeEnd = Math.min(Math.floor((i + 2) * bucketWidth) + 1, count) + start;\n    const avgRangeLength = avgRangeEnd - avgRangeStart;\n\n    for (j = avgRangeStart; j < avgRangeEnd; j++) {\n      avgX += data[j].x;\n      avgY += data[j].y;\n    }\n\n    avgX /= avgRangeLength;\n    avgY /= avgRangeLength;\n\n    // Adding offset\n    const rangeOffs = Math.floor(i * bucketWidth) + 1 + start;\n    const rangeTo = Math.min(Math.floor((i + 1) * bucketWidth) + 1, count) + start;\n    const {x: pointAx, y: pointAy} = data[a];\n\n    // Note that this is changed from the original algorithm which initializes these\n    // values to 1. The reason for this change is that if the area is small, nextA\n    // would never be set and thus a crash would occur in the next loop as `a` would become\n    // `undefined`. Since the area is always positive, but could be 0 in the case of a flat trace,\n    // initializing with a negative number is the correct solution.\n    maxArea = area = -1;\n\n    for (j = rangeOffs; j < rangeTo; j++) {\n      area = 0.5 * Math.abs(\n        (pointAx - avgX) * (data[j].y - pointAy) -\n        (pointAx - data[j].x) * (avgY - pointAy)\n      );\n\n      if (area > maxArea) {\n        maxArea = area;\n        maxAreaPoint = data[j];\n        nextA = j;\n      }\n    }\n\n    decimated[sampledIndex++] = maxAreaPoint;\n    a = nextA;\n  }\n\n  // Include the last point\n  decimated[sampledIndex++] = data[endIndex];\n\n  return decimated;\n}\n\nfunction minMaxDecimation(data, start, count, availableWidth) {\n  let avgX = 0;\n  let countX = 0;\n  let i, point, x, y, prevX, minIndex, maxIndex, startIndex, minY, maxY;\n  const decimated = [];\n  const endIndex = start + count - 1;\n\n  const xMin = data[start].x;\n  const xMax = data[endIndex].x;\n  const dx = xMax - xMin;\n\n  for (i = start; i < start + count; ++i) {\n    point = data[i];\n    x = (point.x - xMin) / dx * availableWidth;\n    y = point.y;\n    const truncX = x | 0;\n\n    if (truncX === prevX) {\n      // Determine `minY` / `maxY` and `avgX` while we stay within same x-position\n      if (y < minY) {\n        minY = y;\n        minIndex = i;\n      } else if (y > maxY) {\n        maxY = y;\n        maxIndex = i;\n      }\n      // For first point in group, countX is `0`, so average will be `x` / 1.\n      // Use point.x here because we're computing the average data `x` value\n      avgX = (countX * avgX + point.x) / ++countX;\n    } else {\n      // Push up to 4 points, 3 for the last interval and the first point for this interval\n      const lastIndex = i - 1;\n\n      if (!isNullOrUndef(minIndex) && !isNullOrUndef(maxIndex)) {\n        // The interval is defined by 4 points: start, min, max, end.\n        // The starting point is already considered at this point, so we need to determine which\n        // of the other points to add. We need to sort these points to ensure the decimated data\n        // is still sorted and then ensure there are no duplicates.\n        const intermediateIndex1 = Math.min(minIndex, maxIndex);\n        const intermediateIndex2 = Math.max(minIndex, maxIndex);\n\n        if (intermediateIndex1 !== startIndex && intermediateIndex1 !== lastIndex) {\n          decimated.push({\n            ...data[intermediateIndex1],\n            x: avgX,\n          });\n        }\n        if (intermediateIndex2 !== startIndex && intermediateIndex2 !== lastIndex) {\n          decimated.push({\n            ...data[intermediateIndex2],\n            x: avgX\n          });\n        }\n      }\n\n      // lastIndex === startIndex will occur when a range has only 1 point which could\n      // happen with very uneven data\n      if (i > 0 && lastIndex !== startIndex) {\n        // Last point in the previous interval\n        decimated.push(data[lastIndex]);\n      }\n\n      // Start of the new interval\n      decimated.push(point);\n      prevX = truncX;\n      countX = 0;\n      minY = maxY = y;\n      minIndex = maxIndex = startIndex = i;\n    }\n  }\n\n  return decimated;\n}\n\nfunction cleanDecimatedDataset(dataset) {\n  if (dataset._decimated) {\n    const data = dataset._data;\n    delete dataset._decimated;\n    delete dataset._data;\n    Object.defineProperty(dataset, 'data', {\n      configurable: true,\n      enumerable: true,\n      writable: true,\n      value: data,\n    });\n  }\n}\n\nfunction cleanDecimatedData(chart) {\n  chart.data.datasets.forEach((dataset) => {\n    cleanDecimatedDataset(dataset);\n  });\n}\n\nfunction getStartAndCountOfVisiblePointsSimplified(meta, points) {\n  const pointCount = points.length;\n\n  let start = 0;\n  let count;\n\n  const {iScale} = meta;\n  const {min, max, minDefined, maxDefined} = iScale.getUserBounds();\n\n  if (minDefined) {\n    start = _limitValue(_lookupByKey(points, iScale.axis, min).lo, 0, pointCount - 1);\n  }\n  if (maxDefined) {\n    count = _limitValue(_lookupByKey(points, iScale.axis, max).hi + 1, start, pointCount) - start;\n  } else {\n    count = pointCount - start;\n  }\n\n  return {start, count};\n}\n\nexport default {\n  id: 'decimation',\n\n  defaults: {\n    algorithm: 'min-max',\n    enabled: false,\n  },\n\n  beforeElementsUpdate: (chart, args, options) => {\n    if (!options.enabled) {\n      // The decimation plugin may have been previously enabled. Need to remove old `dataset._data` handlers\n      cleanDecimatedData(chart);\n      return;\n    }\n\n    // Assume the entire chart is available to show a few more points than needed\n    const availableWidth = chart.width;\n\n    chart.data.datasets.forEach((dataset, datasetIndex) => {\n      const {_data, indexAxis} = dataset;\n      const meta = chart.getDatasetMeta(datasetIndex);\n      const data = _data || dataset.data;\n\n      if (resolve([indexAxis, chart.options.indexAxis]) === 'y') {\n        // Decimation is only supported for lines that have an X indexAxis\n        return;\n      }\n\n      if (!meta.controller.supportsDecimation) {\n        // Only line datasets are supported\n        return;\n      }\n\n      const xAxis = chart.scales[meta.xAxisID];\n      if (xAxis.type !== 'linear' && xAxis.type !== 'time') {\n        // Only linear interpolation is supported\n        return;\n      }\n\n      if (chart.options.parsing) {\n        // Plugin only supports data that does not need parsing\n        return;\n      }\n\n      let {start, count} = getStartAndCountOfVisiblePointsSimplified(meta, data);\n      const threshold = options.threshold || 4 * availableWidth;\n      if (count <= threshold) {\n        // No decimation is required until we are above this threshold\n        cleanDecimatedDataset(dataset);\n        return;\n      }\n\n      if (isNullOrUndef(_data)) {\n        // First time we are seeing this dataset\n        // We override the 'data' property with a setter that stores the\n        // raw data in _data, but reads the decimated data from _decimated\n        dataset._data = data;\n        delete dataset.data;\n        Object.defineProperty(dataset, 'data', {\n          configurable: true,\n          enumerable: true,\n          get: function() {\n            return this._decimated;\n          },\n          set: function(d) {\n            this._data = d;\n          }\n        });\n      }\n\n      // Point the chart to the decimated data\n      let decimated;\n      switch (options.algorithm) {\n      case 'lttb':\n        decimated = lttbDecimation(data, start, count, availableWidth, options);\n        break;\n      case 'min-max':\n        decimated = minMaxDecimation(data, start, count, availableWidth);\n        break;\n      default:\n        throw new Error(`Unsupported decimation algorithm '${options.algorithm}'`);\n      }\n\n      dataset._decimated = decimated;\n    });\n  },\n\n  destroy(chart) {\n    cleanDecimatedData(chart);\n  }\n};\n"
  },
  {
    "path": "src/plugins/plugin.filler/filler.drawing.js",
    "content": "import {clipArea, unclipArea, getDatasetClipArea} from '../../helpers/index.js';\nimport {_findSegmentEnd, _getBounds, _segments} from './filler.segment.js';\nimport {_getTarget} from './filler.target.js';\n\nexport function _drawfill(ctx, source, area) {\n  const target = _getTarget(source);\n  const {chart, index, line, scale, axis} = source;\n  const lineOpts = line.options;\n  const fillOption = lineOpts.fill;\n  const color = lineOpts.backgroundColor;\n  const {above = color, below = color} = fillOption || {};\n  const meta = chart.getDatasetMeta(index);\n  const clip = getDatasetClipArea(chart, meta);\n  if (target && line.points.length) {\n    clipArea(ctx, area);\n    doFill(ctx, {line, target, above, below, area, scale, axis, clip});\n    unclipArea(ctx);\n  }\n}\n\nfunction doFill(ctx, cfg) {\n  const {line, target, above, below, area, scale, clip} = cfg;\n  const property = line._loop ? 'angle' : cfg.axis;\n\n  ctx.save();\n\n  let fillColor = below;\n  if (below !== above) {\n    if (property === 'x') {\n      clipVertical(ctx, target, area.top);\n      fill(ctx, {line, target, color: above, scale, property, clip});\n      ctx.restore();\n      ctx.save();\n      clipVertical(ctx, target, area.bottom);\n    } else if (property === 'y') {\n      clipHorizontal(ctx, target, area.left);\n      fill(ctx, {line, target, color: below, scale, property, clip});\n      ctx.restore();\n      ctx.save();\n      clipHorizontal(ctx, target, area.right);\n      fillColor = above;\n    }\n  }\n  fill(ctx, {line, target, color: fillColor, scale, property, clip});\n\n  ctx.restore();\n}\n\nfunction clipVertical(ctx, target, clipY) {\n  const {segments, points} = target;\n  let first = true;\n  let lineLoop = false;\n\n  ctx.beginPath();\n  for (const segment of segments) {\n    const {start, end} = segment;\n    const firstPoint = points[start];\n    const lastPoint = points[_findSegmentEnd(start, end, points)];\n    if (first) {\n      ctx.moveTo(firstPoint.x, firstPoint.y);\n      first = false;\n    } else {\n      ctx.lineTo(firstPoint.x, clipY);\n      ctx.lineTo(firstPoint.x, firstPoint.y);\n    }\n    lineLoop = !!target.pathSegment(ctx, segment, {move: lineLoop});\n    if (lineLoop) {\n      ctx.closePath();\n    } else {\n      ctx.lineTo(lastPoint.x, clipY);\n    }\n  }\n\n  ctx.lineTo(target.first().x, clipY);\n  ctx.closePath();\n  ctx.clip();\n}\n\nfunction clipHorizontal(ctx, target, clipX) {\n  const {segments, points} = target;\n  let first = true;\n  let lineLoop = false;\n\n  ctx.beginPath();\n  for (const segment of segments) {\n    const {start, end} = segment;\n    const firstPoint = points[start];\n    const lastPoint = points[_findSegmentEnd(start, end, points)];\n    if (first) {\n      ctx.moveTo(firstPoint.x, firstPoint.y);\n      first = false;\n    } else {\n      ctx.lineTo(clipX, firstPoint.y);\n      ctx.lineTo(firstPoint.x, firstPoint.y);\n    }\n    lineLoop = !!target.pathSegment(ctx, segment, {move: lineLoop});\n    if (lineLoop) {\n      ctx.closePath();\n    } else {\n      ctx.lineTo(clipX, lastPoint.y);\n    }\n  }\n\n  ctx.lineTo(clipX, target.first().y);\n  ctx.closePath();\n  ctx.clip();\n}\n\nfunction fill(ctx, cfg) {\n  const {line, target, property, color, scale, clip} = cfg;\n  const segments = _segments(line, target, property);\n\n  for (const {source: src, target: tgt, start, end} of segments) {\n    const {style: {backgroundColor = color} = {}} = src;\n    const notShape = target !== true;\n\n    ctx.save();\n    ctx.fillStyle = backgroundColor;\n\n    clipBounds(ctx, scale, clip, notShape && _getBounds(property, start, end));\n\n    ctx.beginPath();\n\n    const lineLoop = !!line.pathSegment(ctx, src);\n\n    let loop;\n    if (notShape) {\n      if (lineLoop) {\n        ctx.closePath();\n      } else {\n        interpolatedLineTo(ctx, target, end, property);\n      }\n\n      const targetLoop = !!target.pathSegment(ctx, tgt, {move: lineLoop, reverse: true});\n      loop = lineLoop && targetLoop;\n      if (!loop) {\n        interpolatedLineTo(ctx, target, start, property);\n      }\n    }\n\n    ctx.closePath();\n    ctx.fill(loop ? 'evenodd' : 'nonzero');\n\n    ctx.restore();\n  }\n}\n\nfunction clipBounds(ctx, scale, clip, bounds) {\n  const chartArea = scale.chart.chartArea;\n  const {property, start, end} = bounds || {};\n\n  if (property === 'x' || property === 'y') {\n    let left, top, right, bottom;\n\n    if (property === 'x') {\n      left = start;\n      top = chartArea.top;\n      right = end;\n      bottom = chartArea.bottom;\n    } else {\n      left = chartArea.left;\n      top = start;\n      right = chartArea.right;\n      bottom = end;\n    }\n\n    ctx.beginPath();\n\n    if (clip) {\n      left = Math.max(left, clip.left);\n      right = Math.min(right, clip.right);\n      top = Math.max(top, clip.top);\n      bottom = Math.min(bottom, clip.bottom);\n    }\n\n    ctx.rect(left, top, right - left, bottom - top);\n    ctx.clip();\n  }\n}\n\nfunction interpolatedLineTo(ctx, target, point, property) {\n  const interpolatedPoint = target.interpolate(point, property);\n  if (interpolatedPoint) {\n    ctx.lineTo(interpolatedPoint.x, interpolatedPoint.y);\n  }\n}\n\n"
  },
  {
    "path": "src/plugins/plugin.filler/filler.helper.js",
    "content": "/**\n * @typedef { import('../../core/core.controller.js').default } Chart\n * @typedef { import('../../core/core.scale.js').default } Scale\n * @typedef { import('../../elements/element.point.js').default } PointElement\n */\n\nimport {LineElement} from '../../elements/index.js';\nimport {isArray} from '../../helpers/index.js';\nimport {_pointsFromSegments} from './filler.segment.js';\n\n/**\n * @param {PointElement[] | { x: number; y: number; }} boundary\n * @param {LineElement} line\n * @return {LineElement?}\n */\nexport function _createBoundaryLine(boundary, line) {\n  let points = [];\n  let _loop = false;\n\n  if (isArray(boundary)) {\n    _loop = true;\n    // @ts-ignore\n    points = boundary;\n  } else {\n    points = _pointsFromSegments(boundary, line);\n  }\n\n  return points.length ? new LineElement({\n    points,\n    options: {tension: 0},\n    _loop,\n    _fullLoop: _loop\n  }) : null;\n}\n\nexport function _shouldApplyFill(source) {\n  return source && source.fill !== false;\n}\n"
  },
  {
    "path": "src/plugins/plugin.filler/filler.options.js",
    "content": "import {isObject, isFinite, valueOrDefault} from '../../helpers/helpers.core.js';\n\n/**\n * @typedef { import('../../core/core.scale.js').default } Scale\n * @typedef { import('../../elements/element.line.js').default } LineElement\n * @typedef { import('../../types/index.js').FillTarget } FillTarget\n * @typedef { import('../../types/index.js').ComplexFillTarget } ComplexFillTarget\n */\n\nexport function _resolveTarget(sources, index, propagate) {\n  const source = sources[index];\n  let fill = source.fill;\n  const visited = [index];\n  let target;\n\n  if (!propagate) {\n    return fill;\n  }\n\n  while (fill !== false && visited.indexOf(fill) === -1) {\n    if (!isFinite(fill)) {\n      return fill;\n    }\n\n    target = sources[fill];\n    if (!target) {\n      return false;\n    }\n\n    if (target.visible) {\n      return fill;\n    }\n\n    visited.push(fill);\n    fill = target.fill;\n  }\n\n  return false;\n}\n\n/**\n * @param {LineElement} line\n * @param {number} index\n * @param {number} count\n */\nexport function _decodeFill(line, index, count) {\n  /** @type {string | {value: number}} */\n  const fill = parseFillOption(line);\n\n  if (isObject(fill)) {\n    return isNaN(fill.value) ? false : fill;\n  }\n\n  let target = parseFloat(fill);\n\n  if (isFinite(target) && Math.floor(target) === target) {\n    return decodeTargetIndex(fill[0], index, target, count);\n  }\n\n  return ['origin', 'start', 'end', 'stack', 'shape'].indexOf(fill) >= 0 && fill;\n}\n\nfunction decodeTargetIndex(firstCh, index, target, count) {\n  if (firstCh === '-' || firstCh === '+') {\n    target = index + target;\n  }\n\n  if (target === index || target < 0 || target >= count) {\n    return false;\n  }\n\n  return target;\n}\n\n/**\n * @param {FillTarget | ComplexFillTarget} fill\n * @param {Scale} scale\n * @returns {number | null}\n */\nexport function _getTargetPixel(fill, scale) {\n  let pixel = null;\n  if (fill === 'start') {\n    pixel = scale.bottom;\n  } else if (fill === 'end') {\n    pixel = scale.top;\n  } else if (isObject(fill)) {\n    // @ts-ignore\n    pixel = scale.getPixelForValue(fill.value);\n  } else if (scale.getBasePixel) {\n    pixel = scale.getBasePixel();\n  }\n  return pixel;\n}\n\n/**\n * @param {FillTarget | ComplexFillTarget} fill\n * @param {Scale} scale\n * @param {number} startValue\n * @returns {number | undefined}\n */\nexport function _getTargetValue(fill, scale, startValue) {\n  let value;\n\n  if (fill === 'start') {\n    value = startValue;\n  } else if (fill === 'end') {\n    value = scale.options.reverse ? scale.min : scale.max;\n  } else if (isObject(fill)) {\n    // @ts-ignore\n    value = fill.value;\n  } else {\n    value = scale.getBaseValue();\n  }\n  return value;\n}\n\n/**\n * @param {LineElement} line\n */\nfunction parseFillOption(line) {\n  const options = line.options;\n  const fillOption = options.fill;\n  let fill = valueOrDefault(fillOption && fillOption.target, fillOption);\n\n  if (fill === undefined) {\n    fill = !!options.backgroundColor;\n  }\n\n  if (fill === false || fill === null) {\n    return false;\n  }\n\n  if (fill === true) {\n    return 'origin';\n  }\n  return fill;\n}\n"
  },
  {
    "path": "src/plugins/plugin.filler/filler.segment.js",
    "content": "import {_boundSegment, _boundSegments, _normalizeAngle} from '../../helpers/index.js';\n\nexport function _segments(line, target, property) {\n  const segments = line.segments;\n  const points = line.points;\n  const tpoints = target.points;\n  const parts = [];\n\n  for (const segment of segments) {\n    let {start, end} = segment;\n    end = _findSegmentEnd(start, end, points);\n\n    const bounds = _getBounds(property, points[start], points[end], segment.loop);\n\n    if (!target.segments) {\n      // Special case for boundary not supporting `segments` (simpleArc)\n      // Bounds are provided as `target` for partial circle, or undefined for full circle\n      parts.push({\n        source: segment,\n        target: bounds,\n        start: points[start],\n        end: points[end]\n      });\n      continue;\n    }\n\n    // Get all segments from `target` that intersect the bounds of current segment of `line`\n    const targetSegments = _boundSegments(target, bounds);\n\n    for (const tgt of targetSegments) {\n      const subBounds = _getBounds(property, tpoints[tgt.start], tpoints[tgt.end], tgt.loop);\n      const fillSources = _boundSegment(segment, points, subBounds);\n\n      for (const fillSource of fillSources) {\n        parts.push({\n          source: fillSource,\n          target: tgt,\n          start: {\n            [property]: _getEdge(bounds, subBounds, 'start', Math.max)\n          },\n          end: {\n            [property]: _getEdge(bounds, subBounds, 'end', Math.min)\n          }\n        });\n      }\n    }\n  }\n  return parts;\n}\n\nexport function _getBounds(property, first, last, loop) {\n  if (loop) {\n    return;\n  }\n  let start = first[property];\n  let end = last[property];\n\n  if (property === 'angle') {\n    start = _normalizeAngle(start);\n    end = _normalizeAngle(end);\n  }\n  return {property, start, end};\n}\n\nexport function _pointsFromSegments(boundary, line) {\n  const {x = null, y = null} = boundary || {};\n  const linePoints = line.points;\n  const points = [];\n  line.segments.forEach(({start, end}) => {\n    end = _findSegmentEnd(start, end, linePoints);\n    const first = linePoints[start];\n    const last = linePoints[end];\n    if (y !== null) {\n      points.push({x: first.x, y});\n      points.push({x: last.x, y});\n    } else if (x !== null) {\n      points.push({x, y: first.y});\n      points.push({x, y: last.y});\n    }\n  });\n  return points;\n}\n\nexport function _findSegmentEnd(start, end, points) {\n  for (;end > start; end--) {\n    const point = points[end];\n    if (!isNaN(point.x) && !isNaN(point.y)) {\n      break;\n    }\n  }\n  return end;\n}\n\nfunction _getEdge(a, b, prop, fn) {\n  if (a && b) {\n    return fn(a[prop], b[prop]);\n  }\n  return a ? a[prop] : b ? b[prop] : 0;\n}\n"
  },
  {
    "path": "src/plugins/plugin.filler/filler.target.js",
    "content": "import {isFinite} from '../../helpers/index.js';\nimport {_createBoundaryLine} from './filler.helper.js';\nimport {_getTargetPixel, _getTargetValue} from './filler.options.js';\nimport {_buildStackLine} from './filler.target.stack.js';\nimport {simpleArc} from './simpleArc.js';\n\n/**\n * @typedef { import('../../core/core.controller.js').default } Chart\n * @typedef { import('../../core/core.scale.js').default } Scale\n * @typedef { import('../../elements/element.point.js').default } PointElement\n */\n\nexport function _getTarget(source) {\n  const {chart, fill, line} = source;\n\n  if (isFinite(fill)) {\n    return getLineByIndex(chart, fill);\n  }\n\n  if (fill === 'stack') {\n    return _buildStackLine(source);\n  }\n\n  if (fill === 'shape') {\n    return true;\n  }\n\n  const boundary = computeBoundary(source);\n\n  if (boundary instanceof simpleArc) {\n    return boundary;\n  }\n\n  return _createBoundaryLine(boundary, line);\n}\n\n/**\n * @param {Chart} chart\n * @param {number} index\n */\nfunction getLineByIndex(chart, index) {\n  const meta = chart.getDatasetMeta(index);\n  const visible = meta && chart.isDatasetVisible(index);\n  return visible ? meta.dataset : null;\n}\n\nfunction computeBoundary(source) {\n  const scale = source.scale || {};\n\n  if (scale.getPointPositionForValue) {\n    return computeCircularBoundary(source);\n  }\n  return computeLinearBoundary(source);\n}\n\n\nfunction computeLinearBoundary(source) {\n  const {scale = {}, fill} = source;\n  const pixel = _getTargetPixel(fill, scale);\n\n  if (isFinite(pixel)) {\n    const horizontal = scale.isHorizontal();\n\n    return {\n      x: horizontal ? pixel : null,\n      y: horizontal ? null : pixel\n    };\n  }\n\n  return null;\n}\n\nfunction computeCircularBoundary(source) {\n  const {scale, fill} = source;\n  const options = scale.options;\n  const length = scale.getLabels().length;\n  const start = options.reverse ? scale.max : scale.min;\n  const value = _getTargetValue(fill, scale, start);\n  const target = [];\n\n  if (options.grid.circular) {\n    const center = scale.getPointPositionForValue(0, start);\n    return new simpleArc({\n      x: center.x,\n      y: center.y,\n      radius: scale.getDistanceFromCenterForValue(value)\n    });\n  }\n\n  for (let i = 0; i < length; ++i) {\n    target.push(scale.getPointPositionForValue(i, value));\n  }\n  return target;\n}\n\n"
  },
  {
    "path": "src/plugins/plugin.filler/filler.target.stack.js",
    "content": "/**\n * @typedef { import('../../core/core.controller.js').default } Chart\n * @typedef { import('../../core/core.scale.js').default } Scale\n * @typedef { import('../../elements/element.point.js').default } PointElement\n */\n\nimport {LineElement} from '../../elements/index.js';\nimport {_isBetween} from '../../helpers/index.js';\nimport {_createBoundaryLine} from './filler.helper.js';\n\n/**\n * @param {{ chart: Chart; scale: Scale; index: number; line: LineElement; }} source\n * @return {LineElement}\n */\nexport function _buildStackLine(source) {\n  const {scale, index, line} = source;\n  const points = [];\n  const segments = line.segments;\n  const sourcePoints = line.points;\n  const linesBelow = getLinesBelow(scale, index);\n  linesBelow.push(_createBoundaryLine({x: null, y: scale.bottom}, line));\n\n  for (let i = 0; i < segments.length; i++) {\n    const segment = segments[i];\n    for (let j = segment.start; j <= segment.end; j++) {\n      addPointsBelow(points, sourcePoints[j], linesBelow);\n    }\n  }\n  return new LineElement({points, options: {}});\n}\n\n/**\n * @param {Scale} scale\n * @param {number} index\n * @return {LineElement[]}\n */\nfunction getLinesBelow(scale, index) {\n  const below = [];\n  const metas = scale.getMatchingVisibleMetas('line');\n\n  for (let i = 0; i < metas.length; i++) {\n    const meta = metas[i];\n    if (meta.index === index) {\n      break;\n    }\n    if (!meta.hidden) {\n      below.unshift(meta.dataset);\n    }\n  }\n  return below;\n}\n\n/**\n * @param {PointElement[]} points\n * @param {PointElement} sourcePoint\n * @param {LineElement[]} linesBelow\n */\nfunction addPointsBelow(points, sourcePoint, linesBelow) {\n  const postponed = [];\n  for (let j = 0; j < linesBelow.length; j++) {\n    const line = linesBelow[j];\n    const {first, last, point} = findPoint(line, sourcePoint, 'x');\n\n    if (!point || (first && last)) {\n      continue;\n    }\n    if (first) {\n      // First point of a segment -> need to add another point before this,\n      postponed.unshift(point);\n    } else {\n      points.push(point);\n      if (!last) {\n        // In the middle of a segment, no need to add more points.\n        break;\n      }\n    }\n  }\n  points.push(...postponed);\n}\n\n/**\n * @param {LineElement} line\n * @param {PointElement} sourcePoint\n * @param {string} property\n * @returns {{point?: PointElement, first?: boolean, last?: boolean}}\n */\nfunction findPoint(line, sourcePoint, property) {\n  const point = line.interpolate(sourcePoint, property);\n  if (!point) {\n    return {};\n  }\n\n  const pointValue = point[property];\n  const segments = line.segments;\n  const linePoints = line.points;\n  let first = false;\n  let last = false;\n  for (let i = 0; i < segments.length; i++) {\n    const segment = segments[i];\n    const firstValue = linePoints[segment.start][property];\n    const lastValue = linePoints[segment.end][property];\n    if (_isBetween(pointValue, firstValue, lastValue)) {\n      first = pointValue === firstValue;\n      last = pointValue === lastValue;\n      break;\n    }\n  }\n  return {first, last, point};\n}\n"
  },
  {
    "path": "src/plugins/plugin.filler/index.js",
    "content": "/**\n * Plugin based on discussion from the following Chart.js issues:\n * @see https://github.com/chartjs/Chart.js/issues/2380#issuecomment-279961569\n * @see https://github.com/chartjs/Chart.js/issues/2440#issuecomment-256461897\n */\n\nimport LineElement from '../../elements/element.line.js';\nimport {_drawfill} from './filler.drawing.js';\nimport {_shouldApplyFill} from './filler.helper.js';\nimport {_decodeFill, _resolveTarget} from './filler.options.js';\n\nexport default {\n  id: 'filler',\n\n  afterDatasetsUpdate(chart, _args, options) {\n    const count = (chart.data.datasets || []).length;\n    const sources = [];\n    let meta, i, line, source;\n\n    for (i = 0; i < count; ++i) {\n      meta = chart.getDatasetMeta(i);\n      line = meta.dataset;\n      source = null;\n\n      if (line && line.options && line instanceof LineElement) {\n        source = {\n          visible: chart.isDatasetVisible(i),\n          index: i,\n          fill: _decodeFill(line, i, count),\n          chart,\n          axis: meta.controller.options.indexAxis,\n          scale: meta.vScale,\n          line,\n        };\n      }\n\n      meta.$filler = source;\n      sources.push(source);\n    }\n\n    for (i = 0; i < count; ++i) {\n      source = sources[i];\n      if (!source || source.fill === false) {\n        continue;\n      }\n\n      source.fill = _resolveTarget(sources, i, options.propagate);\n    }\n  },\n\n  beforeDraw(chart, _args, options) {\n    const draw = options.drawTime === 'beforeDraw';\n    const metasets = chart.getSortedVisibleDatasetMetas();\n    const area = chart.chartArea;\n    for (let i = metasets.length - 1; i >= 0; --i) {\n      const source = metasets[i].$filler;\n      if (!source) {\n        continue;\n      }\n\n      source.line.updateControlPoints(area, source.axis);\n      if (draw && source.fill) {\n        _drawfill(chart.ctx, source, area);\n      }\n    }\n  },\n\n  beforeDatasetsDraw(chart, _args, options) {\n    if (options.drawTime !== 'beforeDatasetsDraw') {\n      return;\n    }\n\n    const metasets = chart.getSortedVisibleDatasetMetas();\n    for (let i = metasets.length - 1; i >= 0; --i) {\n      const source = metasets[i].$filler;\n\n      if (_shouldApplyFill(source)) {\n        _drawfill(chart.ctx, source, chart.chartArea);\n      }\n    }\n  },\n\n  beforeDatasetDraw(chart, args, options) {\n    const source = args.meta.$filler;\n\n    if (!_shouldApplyFill(source) || options.drawTime !== 'beforeDatasetDraw') {\n      return;\n    }\n\n    _drawfill(chart.ctx, source, chart.chartArea);\n  },\n\n  defaults: {\n    propagate: true,\n    drawTime: 'beforeDatasetDraw'\n  }\n};\n"
  },
  {
    "path": "src/plugins/plugin.filler/simpleArc.js",
    "content": "import {TAU} from '../../helpers/index.js';\n\n// TODO: use elements.ArcElement instead\nexport class simpleArc {\n  constructor(opts) {\n    this.x = opts.x;\n    this.y = opts.y;\n    this.radius = opts.radius;\n  }\n\n  pathSegment(ctx, bounds, opts) {\n    const {x, y, radius} = this;\n    bounds = bounds || {start: 0, end: TAU};\n    ctx.arc(x, y, radius, bounds.end, bounds.start, true);\n    return !opts.bounds;\n  }\n\n  interpolate(point) {\n    const {x, y, radius} = this;\n    const angle = point.angle;\n    return {\n      x: x + Math.cos(angle) * radius,\n      y: y + Math.sin(angle) * radius,\n      angle\n    };\n  }\n}\n"
  },
  {
    "path": "src/plugins/plugin.legend.js",
    "content": "import defaults from '../core/core.defaults.js';\nimport Element from '../core/core.element.js';\nimport layouts from '../core/core.layouts.js';\nimport {addRoundedRectPath, drawPointLegend, renderText} from '../helpers/helpers.canvas.js';\nimport {\n  _isBetween,\n  callback as call,\n  clipArea,\n  getRtlAdapter,\n  overrideTextDirection,\n  restoreTextDirection,\n  toFont,\n  toPadding,\n  unclipArea,\n  valueOrDefault,\n} from '../helpers/index.js';\nimport {_alignStartEnd, _textX, _toLeftRightCenter} from '../helpers/helpers.extras.js';\nimport {toTRBLCorners} from '../helpers/helpers.options.js';\n\n/**\n * @typedef { import('../types/index.js').ChartEvent } ChartEvent\n */\n\nconst getBoxSize = (labelOpts, fontSize) => {\n  let {boxHeight = fontSize, boxWidth = fontSize} = labelOpts;\n\n  if (labelOpts.usePointStyle) {\n    boxHeight = Math.min(boxHeight, fontSize);\n    boxWidth = labelOpts.pointStyleWidth || Math.min(boxWidth, fontSize);\n  }\n\n  return {\n    boxWidth,\n    boxHeight,\n    itemHeight: Math.max(fontSize, boxHeight)\n  };\n};\n\nconst itemsEqual = (a, b) => a !== null && b !== null && a.datasetIndex === b.datasetIndex && a.index === b.index;\n\nexport class Legend extends Element {\n\n  /**\n\t * @param {{ ctx: any; options: any; chart: any; }} config\n\t */\n  constructor(config) {\n    super();\n\n    this._added = false;\n\n    // Contains hit boxes for each dataset (in dataset order)\n    this.legendHitBoxes = [];\n\n    /**\n \t\t * @private\n \t\t */\n    this._hoveredItem = null;\n\n    // Are we in doughnut mode which has a different data type\n    this.doughnutMode = false;\n\n    this.chart = config.chart;\n    this.options = config.options;\n    this.ctx = config.ctx;\n    this.legendItems = undefined;\n    this.columnSizes = undefined;\n    this.lineWidths = undefined;\n    this.maxHeight = undefined;\n    this.maxWidth = undefined;\n    this.top = undefined;\n    this.bottom = undefined;\n    this.left = undefined;\n    this.right = undefined;\n    this.height = undefined;\n    this.width = undefined;\n    this._margins = undefined;\n    this.position = undefined;\n    this.weight = undefined;\n    this.fullSize = undefined;\n  }\n\n  update(maxWidth, maxHeight, margins) {\n    this.maxWidth = maxWidth;\n    this.maxHeight = maxHeight;\n    this._margins = margins;\n\n    this.setDimensions();\n    this.buildLabels();\n    this.fit();\n  }\n\n  setDimensions() {\n    if (this.isHorizontal()) {\n      this.width = this.maxWidth;\n      this.left = this._margins.left;\n      this.right = this.width;\n    } else {\n      this.height = this.maxHeight;\n      this.top = this._margins.top;\n      this.bottom = this.height;\n    }\n  }\n\n  buildLabels() {\n    const labelOpts = this.options.labels || {};\n    let legendItems = call(labelOpts.generateLabels, [this.chart], this) || [];\n\n    if (labelOpts.filter) {\n      legendItems = legendItems.filter((item) => labelOpts.filter(item, this.chart.data));\n    }\n\n    if (labelOpts.sort) {\n      legendItems = legendItems.sort((a, b) => labelOpts.sort(a, b, this.chart.data));\n    }\n\n    if (this.options.reverse) {\n      legendItems.reverse();\n    }\n\n    this.legendItems = legendItems;\n  }\n\n  fit() {\n    const {options, ctx} = this;\n\n    // The legend may not be displayed for a variety of reasons including\n    // the fact that the defaults got set to `false`.\n    // When the legend is not displayed, there are no guarantees that the options\n    // are correctly formatted so we need to bail out as early as possible.\n    if (!options.display) {\n      this.width = this.height = 0;\n      return;\n    }\n\n    const labelOpts = options.labels;\n    const labelFont = toFont(labelOpts.font);\n    const fontSize = labelFont.size;\n    const titleHeight = this._computeTitleHeight();\n    const {boxWidth, itemHeight} = getBoxSize(labelOpts, fontSize);\n\n    let width, height;\n\n    ctx.font = labelFont.string;\n\n    if (this.isHorizontal()) {\n      width = this.maxWidth; // fill all the width\n      height = this._fitRows(titleHeight, fontSize, boxWidth, itemHeight) + 10;\n    } else {\n      height = this.maxHeight; // fill all the height\n      width = this._fitCols(titleHeight, labelFont, boxWidth, itemHeight) + 10;\n    }\n\n    this.width = Math.min(width, options.maxWidth || this.maxWidth);\n    this.height = Math.min(height, options.maxHeight || this.maxHeight);\n  }\n\n  /**\n\t * @private\n\t */\n  _fitRows(titleHeight, fontSize, boxWidth, itemHeight) {\n    const {ctx, maxWidth, options: {labels: {padding}}} = this;\n    const hitboxes = this.legendHitBoxes = [];\n    // Width of each line of legend boxes. Labels wrap onto multiple lines when there are too many to fit on one\n    const lineWidths = this.lineWidths = [0];\n    const lineHeight = itemHeight + padding;\n    let totalHeight = titleHeight;\n\n    ctx.textAlign = 'left';\n    ctx.textBaseline = 'middle';\n\n    let row = -1;\n    let top = -lineHeight;\n    this.legendItems.forEach((legendItem, i) => {\n      const itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;\n\n      if (i === 0 || lineWidths[lineWidths.length - 1] + itemWidth + 2 * padding > maxWidth) {\n        totalHeight += lineHeight;\n        lineWidths[lineWidths.length - (i > 0 ? 0 : 1)] = 0;\n        top += lineHeight;\n        row++;\n      }\n\n      hitboxes[i] = {left: 0, top, row, width: itemWidth, height: itemHeight};\n\n      lineWidths[lineWidths.length - 1] += itemWidth + padding;\n    });\n\n    return totalHeight;\n  }\n\n  _fitCols(titleHeight, labelFont, boxWidth, _itemHeight) {\n    const {ctx, maxHeight, options: {labels: {padding}}} = this;\n    const hitboxes = this.legendHitBoxes = [];\n    const columnSizes = this.columnSizes = [];\n    const heightLimit = maxHeight - titleHeight;\n\n    let totalWidth = padding;\n    let currentColWidth = 0;\n    let currentColHeight = 0;\n\n    let left = 0;\n    let col = 0;\n\n    this.legendItems.forEach((legendItem, i) => {\n      const {itemWidth, itemHeight} = calculateItemSize(boxWidth, labelFont, ctx, legendItem, _itemHeight);\n\n      // If too tall, go to new column\n      if (i > 0 && currentColHeight + itemHeight + 2 * padding > heightLimit) {\n        totalWidth += currentColWidth + padding;\n        columnSizes.push({width: currentColWidth, height: currentColHeight}); // previous column size\n        left += currentColWidth + padding;\n        col++;\n        currentColWidth = currentColHeight = 0;\n      }\n\n      // Store the hitbox width and height here. Final position will be updated in `draw`\n      hitboxes[i] = {left, top: currentColHeight, col, width: itemWidth, height: itemHeight};\n\n      // Get max width\n      currentColWidth = Math.max(currentColWidth, itemWidth);\n      currentColHeight += itemHeight + padding;\n    });\n\n    totalWidth += currentColWidth;\n    columnSizes.push({width: currentColWidth, height: currentColHeight}); // previous column size\n\n    return totalWidth;\n  }\n\n  adjustHitBoxes() {\n    if (!this.options.display) {\n      return;\n    }\n    const titleHeight = this._computeTitleHeight();\n    const {legendHitBoxes: hitboxes, options: {align, labels: {padding}, rtl}} = this;\n    const rtlHelper = getRtlAdapter(rtl, this.left, this.width);\n    if (this.isHorizontal()) {\n      let row = 0;\n      let left = _alignStartEnd(align, this.left + padding, this.right - this.lineWidths[row]);\n      for (const hitbox of hitboxes) {\n        if (row !== hitbox.row) {\n          row = hitbox.row;\n          left = _alignStartEnd(align, this.left + padding, this.right - this.lineWidths[row]);\n        }\n        hitbox.top += this.top + titleHeight + padding;\n        hitbox.left = rtlHelper.leftForLtr(rtlHelper.x(left), hitbox.width);\n        left += hitbox.width + padding;\n      }\n    } else {\n      let col = 0;\n      let top = _alignStartEnd(align, this.top + titleHeight + padding, this.bottom - this.columnSizes[col].height);\n      for (const hitbox of hitboxes) {\n        if (hitbox.col !== col) {\n          col = hitbox.col;\n          top = _alignStartEnd(align, this.top + titleHeight + padding, this.bottom - this.columnSizes[col].height);\n        }\n        hitbox.top = top;\n        hitbox.left += this.left + padding;\n        hitbox.left = rtlHelper.leftForLtr(rtlHelper.x(hitbox.left), hitbox.width);\n        top += hitbox.height + padding;\n      }\n    }\n  }\n\n  isHorizontal() {\n    return this.options.position === 'top' || this.options.position === 'bottom';\n  }\n\n  draw() {\n    if (this.options.display) {\n      const ctx = this.ctx;\n      clipArea(ctx, this);\n\n      this._draw();\n\n      unclipArea(ctx);\n    }\n  }\n\n  /**\n\t * @private\n\t */\n  _draw() {\n    const {options: opts, columnSizes, lineWidths, ctx} = this;\n    const {align, labels: labelOpts} = opts;\n    const defaultColor = defaults.color;\n    const rtlHelper = getRtlAdapter(opts.rtl, this.left, this.width);\n    const labelFont = toFont(labelOpts.font);\n    const {padding} = labelOpts;\n    const fontSize = labelFont.size;\n    const halfFontSize = fontSize / 2;\n    let cursor;\n\n    this.drawTitle();\n\n    // Canvas setup\n    ctx.textAlign = rtlHelper.textAlign('left');\n    ctx.textBaseline = 'middle';\n    ctx.lineWidth = 0.5;\n    ctx.font = labelFont.string;\n\n    const {boxWidth, boxHeight, itemHeight} = getBoxSize(labelOpts, fontSize);\n\n    // current position\n    const drawLegendBox = function(x, y, legendItem) {\n      if (isNaN(boxWidth) || boxWidth <= 0 || isNaN(boxHeight) || boxHeight < 0) {\n        return;\n      }\n\n      // Set the ctx for the box\n      ctx.save();\n\n      const lineWidth = valueOrDefault(legendItem.lineWidth, 1);\n      ctx.fillStyle = valueOrDefault(legendItem.fillStyle, defaultColor);\n      ctx.lineCap = valueOrDefault(legendItem.lineCap, 'butt');\n      ctx.lineDashOffset = valueOrDefault(legendItem.lineDashOffset, 0);\n      ctx.lineJoin = valueOrDefault(legendItem.lineJoin, 'miter');\n      ctx.lineWidth = lineWidth;\n      ctx.strokeStyle = valueOrDefault(legendItem.strokeStyle, defaultColor);\n\n      ctx.setLineDash(valueOrDefault(legendItem.lineDash, []));\n\n      if (labelOpts.usePointStyle) {\n        // Recalculate x and y for drawPoint() because its expecting\n        // x and y to be center of figure (instead of top left)\n        const drawOptions = {\n          radius: boxHeight * Math.SQRT2 / 2,\n          pointStyle: legendItem.pointStyle,\n          rotation: legendItem.rotation,\n          borderWidth: lineWidth\n        };\n        const centerX = rtlHelper.xPlus(x, boxWidth / 2);\n        const centerY = y + halfFontSize;\n\n        // Draw pointStyle as legend symbol\n        drawPointLegend(ctx, drawOptions, centerX, centerY, labelOpts.pointStyleWidth && boxWidth);\n      } else {\n        // Draw box as legend symbol\n        // Adjust position when boxHeight < fontSize (want it centered)\n        const yBoxTop = y + Math.max((fontSize - boxHeight) / 2, 0);\n        const xBoxLeft = rtlHelper.leftForLtr(x, boxWidth);\n        const borderRadius = toTRBLCorners(legendItem.borderRadius);\n\n        ctx.beginPath();\n\n        if (Object.values(borderRadius).some(v => v !== 0)) {\n          addRoundedRectPath(ctx, {\n            x: xBoxLeft,\n            y: yBoxTop,\n            w: boxWidth,\n            h: boxHeight,\n            radius: borderRadius,\n          });\n        } else {\n          ctx.rect(xBoxLeft, yBoxTop, boxWidth, boxHeight);\n        }\n\n        ctx.fill();\n        if (lineWidth !== 0) {\n          ctx.stroke();\n        }\n      }\n\n      ctx.restore();\n    };\n\n    const fillText = function(x, y, legendItem) {\n      renderText(ctx, legendItem.text, x, y + (itemHeight / 2), labelFont, {\n        strikethrough: legendItem.hidden,\n        textAlign: rtlHelper.textAlign(legendItem.textAlign)\n      });\n    };\n\n    // Horizontal\n    const isHorizontal = this.isHorizontal();\n    const titleHeight = this._computeTitleHeight();\n    if (isHorizontal) {\n      cursor = {\n        x: _alignStartEnd(align, this.left + padding, this.right - lineWidths[0]),\n        y: this.top + padding + titleHeight,\n        line: 0\n      };\n    } else {\n      cursor = {\n        x: this.left + padding,\n        y: _alignStartEnd(align, this.top + titleHeight + padding, this.bottom - columnSizes[0].height),\n        line: 0\n      };\n    }\n\n    overrideTextDirection(this.ctx, opts.textDirection);\n\n    const lineHeight = itemHeight + padding;\n    this.legendItems.forEach((legendItem, i) => {\n      ctx.strokeStyle = legendItem.fontColor; // for strikethrough effect\n      ctx.fillStyle = legendItem.fontColor; // render in correct colour\n\n      const textWidth = ctx.measureText(legendItem.text).width;\n      const textAlign = rtlHelper.textAlign(legendItem.textAlign || (legendItem.textAlign = labelOpts.textAlign));\n      const width = boxWidth + halfFontSize + textWidth;\n      let x = cursor.x;\n      let y = cursor.y;\n\n      rtlHelper.setWidth(this.width);\n\n      if (isHorizontal) {\n        if (i > 0 && x + width + padding > this.right) {\n          y = cursor.y += lineHeight;\n          cursor.line++;\n          x = cursor.x = _alignStartEnd(align, this.left + padding, this.right - lineWidths[cursor.line]);\n        }\n      } else if (i > 0 && y + lineHeight > this.bottom) {\n        x = cursor.x = x + columnSizes[cursor.line].width + padding;\n        cursor.line++;\n        y = cursor.y = _alignStartEnd(align, this.top + titleHeight + padding, this.bottom - columnSizes[cursor.line].height);\n      }\n\n      const realX = rtlHelper.x(x);\n\n      drawLegendBox(realX, y, legendItem);\n\n      x = _textX(textAlign, x + boxWidth + halfFontSize, isHorizontal ? x + width : this.right, opts.rtl);\n\n      // Fill the actual label\n      fillText(rtlHelper.x(x), y, legendItem);\n\n      if (isHorizontal) {\n        cursor.x += width + padding;\n      } else if (typeof legendItem.text !== 'string') {\n        const fontLineHeight = labelFont.lineHeight;\n        cursor.y += calculateLegendItemHeight(legendItem, fontLineHeight) + padding;\n      } else {\n        cursor.y += lineHeight;\n      }\n    });\n\n    restoreTextDirection(this.ctx, opts.textDirection);\n  }\n\n  /**\n\t * @protected\n\t */\n  drawTitle() {\n    const opts = this.options;\n    const titleOpts = opts.title;\n    const titleFont = toFont(titleOpts.font);\n    const titlePadding = toPadding(titleOpts.padding);\n\n    if (!titleOpts.display) {\n      return;\n    }\n\n    const rtlHelper = getRtlAdapter(opts.rtl, this.left, this.width);\n    const ctx = this.ctx;\n    const position = titleOpts.position;\n    const halfFontSize = titleFont.size / 2;\n    const topPaddingPlusHalfFontSize = titlePadding.top + halfFontSize;\n    let y;\n\n    // These defaults are used when the legend is vertical.\n    // When horizontal, they are computed below.\n    let left = this.left;\n    let maxWidth = this.width;\n\n    if (this.isHorizontal()) {\n      // Move left / right so that the title is above the legend lines\n      maxWidth = Math.max(...this.lineWidths);\n      y = this.top + topPaddingPlusHalfFontSize;\n      left = _alignStartEnd(opts.align, left, this.right - maxWidth);\n    } else {\n      // Move down so that the title is above the legend stack in every alignment\n      const maxHeight = this.columnSizes.reduce((acc, size) => Math.max(acc, size.height), 0);\n      y = topPaddingPlusHalfFontSize + _alignStartEnd(opts.align, this.top, this.bottom - maxHeight - opts.labels.padding - this._computeTitleHeight());\n    }\n\n    // Now that we know the left edge of the inner legend box, compute the correct\n    // X coordinate from the title alignment\n    const x = _alignStartEnd(position, left, left + maxWidth);\n\n    // Canvas setup\n    ctx.textAlign = rtlHelper.textAlign(_toLeftRightCenter(position));\n    ctx.textBaseline = 'middle';\n    ctx.strokeStyle = titleOpts.color;\n    ctx.fillStyle = titleOpts.color;\n    ctx.font = titleFont.string;\n\n    renderText(ctx, titleOpts.text, x, y, titleFont);\n  }\n\n  /**\n\t * @private\n\t */\n  _computeTitleHeight() {\n    const titleOpts = this.options.title;\n    const titleFont = toFont(titleOpts.font);\n    const titlePadding = toPadding(titleOpts.padding);\n    return titleOpts.display ? titleFont.lineHeight + titlePadding.height : 0;\n  }\n\n  /**\n\t * @private\n\t */\n  _getLegendItemAt(x, y) {\n    let i, hitBox, lh;\n\n    if (_isBetween(x, this.left, this.right)\n      && _isBetween(y, this.top, this.bottom)) {\n      // See if we are touching one of the dataset boxes\n      lh = this.legendHitBoxes;\n      for (i = 0; i < lh.length; ++i) {\n        hitBox = lh[i];\n\n        if (_isBetween(x, hitBox.left, hitBox.left + hitBox.width)\n          && _isBetween(y, hitBox.top, hitBox.top + hitBox.height)) {\n          // Touching an element\n          return this.legendItems[i];\n        }\n      }\n    }\n\n    return null;\n  }\n\n  /**\n\t * Handle an event\n\t * @param {ChartEvent} e - The event to handle\n\t */\n  handleEvent(e) {\n    const opts = this.options;\n    if (!isListened(e.type, opts)) {\n      return;\n    }\n\n    // Chart event already has relative position in it\n    const hoveredItem = this._getLegendItemAt(e.x, e.y);\n\n    if (e.type === 'mousemove' || e.type === 'mouseout') {\n      const previous = this._hoveredItem;\n      const sameItem = itemsEqual(previous, hoveredItem);\n      if (previous && !sameItem) {\n        call(opts.onLeave, [e, previous, this], this);\n      }\n\n      this._hoveredItem = hoveredItem;\n\n      if (hoveredItem && !sameItem) {\n        call(opts.onHover, [e, hoveredItem, this], this);\n      }\n    } else if (hoveredItem) {\n      call(opts.onClick, [e, hoveredItem, this], this);\n    }\n  }\n}\n\nfunction calculateItemSize(boxWidth, labelFont, ctx, legendItem, _itemHeight) {\n  const itemWidth = calculateItemWidth(legendItem, boxWidth, labelFont, ctx);\n  const itemHeight = calculateItemHeight(_itemHeight, legendItem, labelFont.lineHeight);\n  return {itemWidth, itemHeight};\n}\n\nfunction calculateItemWidth(legendItem, boxWidth, labelFont, ctx) {\n  let legendItemText = legendItem.text;\n  if (legendItemText && typeof legendItemText !== 'string') {\n    legendItemText = legendItemText.reduce((a, b) => a.length > b.length ? a : b);\n  }\n  return boxWidth + (labelFont.size / 2) + ctx.measureText(legendItemText).width;\n}\n\nfunction calculateItemHeight(_itemHeight, legendItem, fontLineHeight) {\n  let itemHeight = _itemHeight;\n  if (typeof legendItem.text !== 'string') {\n    itemHeight = calculateLegendItemHeight(legendItem, fontLineHeight);\n  }\n  return itemHeight;\n}\n\nfunction calculateLegendItemHeight(legendItem, fontLineHeight) {\n  const labelHeight = legendItem.text ? legendItem.text.length : 0;\n  return fontLineHeight * labelHeight;\n}\n\nfunction isListened(type, opts) {\n  if ((type === 'mousemove' || type === 'mouseout') && (opts.onHover || opts.onLeave)) {\n    return true;\n  }\n  if (opts.onClick && (type === 'click' || type === 'mouseup')) {\n    return true;\n  }\n  return false;\n}\n\nexport default {\n  id: 'legend',\n\n  /**\n\t * For tests\n\t * @private\n\t */\n  _element: Legend,\n\n  start(chart, _args, options) {\n    const legend = chart.legend = new Legend({ctx: chart.ctx, options, chart});\n    layouts.configure(chart, legend, options);\n    layouts.addBox(chart, legend);\n  },\n\n  stop(chart) {\n    layouts.removeBox(chart, chart.legend);\n    delete chart.legend;\n  },\n\n  // During the beforeUpdate step, the layout configuration needs to run\n  // This ensures that if the legend position changes (via an option update)\n  // the layout system respects the change. See https://github.com/chartjs/Chart.js/issues/7527\n  beforeUpdate(chart, _args, options) {\n    const legend = chart.legend;\n    layouts.configure(chart, legend, options);\n    legend.options = options;\n  },\n\n  // The labels need to be built after datasets are updated to ensure that colors\n  // and other styling are correct. See https://github.com/chartjs/Chart.js/issues/6968\n  afterUpdate(chart) {\n    const legend = chart.legend;\n    legend.buildLabels();\n    legend.adjustHitBoxes();\n  },\n\n\n  afterEvent(chart, args) {\n    if (!args.replay) {\n      chart.legend.handleEvent(args.event);\n    }\n  },\n\n  defaults: {\n    display: true,\n    position: 'top',\n    align: 'center',\n    fullSize: true,\n    reverse: false,\n    weight: 1000,\n\n    // a callback that will handle\n    onClick(e, legendItem, legend) {\n      const index = legendItem.datasetIndex;\n      const ci = legend.chart;\n      if (ci.isDatasetVisible(index)) {\n        ci.hide(index);\n        legendItem.hidden = true;\n      } else {\n        ci.show(index);\n        legendItem.hidden = false;\n      }\n    },\n\n    onHover: null,\n    onLeave: null,\n\n    labels: {\n      color: (ctx) => ctx.chart.options.color,\n      boxWidth: 40,\n      padding: 10,\n      // Generates labels shown in the legend\n      // Valid properties to return:\n      // text : text to display\n      // fillStyle : fill of coloured box\n      // strokeStyle: stroke of coloured box\n      // hidden : if this legend item refers to a hidden item\n      // lineCap : cap style for line\n      // lineDash\n      // lineDashOffset :\n      // lineJoin :\n      // lineWidth :\n      generateLabels(chart) {\n        const datasets = chart.data.datasets;\n        const {labels: {usePointStyle, pointStyle, textAlign, color, useBorderRadius, borderRadius}} = chart.legend.options;\n\n        return chart._getSortedDatasetMetas().map((meta) => {\n          const style = meta.controller.getStyle(usePointStyle ? 0 : undefined);\n          const borderWidth = toPadding(style.borderWidth);\n\n          return {\n            text: datasets[meta.index].label,\n            fillStyle: style.backgroundColor,\n            fontColor: color,\n            hidden: !meta.visible,\n            lineCap: style.borderCapStyle,\n            lineDash: style.borderDash,\n            lineDashOffset: style.borderDashOffset,\n            lineJoin: style.borderJoinStyle,\n            lineWidth: (borderWidth.width + borderWidth.height) / 4,\n            strokeStyle: style.borderColor,\n            pointStyle: pointStyle || style.pointStyle,\n            rotation: style.rotation,\n            textAlign: textAlign || style.textAlign,\n            borderRadius: useBorderRadius && (borderRadius || style.borderRadius),\n\n            // Below is extra data used for toggling the datasets\n            datasetIndex: meta.index\n          };\n        }, this);\n      }\n    },\n\n    title: {\n      color: (ctx) => ctx.chart.options.color,\n      display: false,\n      position: 'center',\n      text: '',\n    }\n  },\n\n  descriptors: {\n    _scriptable: (name) => !name.startsWith('on'),\n    labels: {\n      _scriptable: (name) => !['generateLabels', 'filter', 'sort'].includes(name),\n    }\n  },\n};\n"
  },
  {
    "path": "src/plugins/plugin.subtitle.js",
    "content": "import {Title} from './plugin.title.js';\nimport layouts from '../core/core.layouts.js';\n\nconst map = new WeakMap();\n\nexport default {\n  id: 'subtitle',\n\n  start(chart, _args, options) {\n    const title = new Title({\n      ctx: chart.ctx,\n      options,\n      chart\n    });\n\n    layouts.configure(chart, title, options);\n    layouts.addBox(chart, title);\n    map.set(chart, title);\n  },\n\n  stop(chart) {\n    layouts.removeBox(chart, map.get(chart));\n    map.delete(chart);\n  },\n\n  beforeUpdate(chart, _args, options) {\n    const title = map.get(chart);\n    layouts.configure(chart, title, options);\n    title.options = options;\n  },\n\n  defaults: {\n    align: 'center',\n    display: false,\n    font: {\n      weight: 'normal',\n    },\n    fullSize: true,\n    padding: 0,\n    position: 'top',\n    text: '',\n    weight: 1500         // by default greater than legend (1000) and smaller than title (2000)\n  },\n\n  defaultRoutes: {\n    color: 'color'\n  },\n\n  descriptors: {\n    _scriptable: true,\n    _indexable: false,\n  },\n};\n"
  },
  {
    "path": "src/plugins/plugin.title.js",
    "content": "import Element from '../core/core.element.js';\nimport layouts from '../core/core.layouts.js';\nimport {PI, isArray, toPadding, toFont} from '../helpers/index.js';\nimport {_toLeftRightCenter, _alignStartEnd} from '../helpers/helpers.extras.js';\nimport {renderText} from '../helpers/helpers.canvas.js';\n\nexport class Title extends Element {\n  /**\n\t * @param {{ ctx: any; options: any; chart: any; }} config\n\t */\n  constructor(config) {\n    super();\n\n    this.chart = config.chart;\n    this.options = config.options;\n    this.ctx = config.ctx;\n    this._padding = undefined;\n    this.top = undefined;\n    this.bottom = undefined;\n    this.left = undefined;\n    this.right = undefined;\n    this.width = undefined;\n    this.height = undefined;\n    this.position = undefined;\n    this.weight = undefined;\n    this.fullSize = undefined;\n  }\n\n  update(maxWidth, maxHeight) {\n    const opts = this.options;\n\n    this.left = 0;\n    this.top = 0;\n\n    if (!opts.display) {\n      this.width = this.height = this.right = this.bottom = 0;\n      return;\n    }\n\n    this.width = this.right = maxWidth;\n    this.height = this.bottom = maxHeight;\n\n    const lineCount = isArray(opts.text) ? opts.text.length : 1;\n    this._padding = toPadding(opts.padding);\n    const textSize = lineCount * toFont(opts.font).lineHeight + this._padding.height;\n\n    if (this.isHorizontal()) {\n      this.height = textSize;\n    } else {\n      this.width = textSize;\n    }\n  }\n\n  isHorizontal() {\n    const pos = this.options.position;\n    return pos === 'top' || pos === 'bottom';\n  }\n\n  _drawArgs(offset) {\n    const {top, left, bottom, right, options} = this;\n    const align = options.align;\n    let rotation = 0;\n    let maxWidth, titleX, titleY;\n\n    if (this.isHorizontal()) {\n      titleX = _alignStartEnd(align, left, right);\n      titleY = top + offset;\n      maxWidth = right - left;\n    } else {\n      if (options.position === 'left') {\n        titleX = left + offset;\n        titleY = _alignStartEnd(align, bottom, top);\n        rotation = PI * -0.5;\n      } else {\n        titleX = right - offset;\n        titleY = _alignStartEnd(align, top, bottom);\n        rotation = PI * 0.5;\n      }\n      maxWidth = bottom - top;\n    }\n    return {titleX, titleY, maxWidth, rotation};\n  }\n\n  draw() {\n    const ctx = this.ctx;\n    const opts = this.options;\n\n    if (!opts.display) {\n      return;\n    }\n\n    const fontOpts = toFont(opts.font);\n    const lineHeight = fontOpts.lineHeight;\n    const offset = lineHeight / 2 + this._padding.top;\n    const {titleX, titleY, maxWidth, rotation} = this._drawArgs(offset);\n\n    renderText(ctx, opts.text, 0, 0, fontOpts, {\n      color: opts.color,\n      maxWidth,\n      rotation,\n      textAlign: _toLeftRightCenter(opts.align),\n      textBaseline: 'middle',\n      translation: [titleX, titleY],\n    });\n  }\n}\n\nfunction createTitle(chart, titleOpts) {\n  const title = new Title({\n    ctx: chart.ctx,\n    options: titleOpts,\n    chart\n  });\n\n  layouts.configure(chart, title, titleOpts);\n  layouts.addBox(chart, title);\n  chart.titleBlock = title;\n}\n\nexport default {\n  id: 'title',\n\n  /**\n\t * For tests\n\t * @private\n\t */\n  _element: Title,\n\n  start(chart, _args, options) {\n    createTitle(chart, options);\n  },\n\n  stop(chart) {\n    const titleBlock = chart.titleBlock;\n    layouts.removeBox(chart, titleBlock);\n    delete chart.titleBlock;\n  },\n\n  beforeUpdate(chart, _args, options) {\n    const title = chart.titleBlock;\n    layouts.configure(chart, title, options);\n    title.options = options;\n  },\n\n  defaults: {\n    align: 'center',\n    display: false,\n    font: {\n      weight: 'bold',\n    },\n    fullSize: true,\n    padding: 10,\n    position: 'top',\n    text: '',\n    weight: 2000         // by default greater than legend (1000) to be above\n  },\n\n  defaultRoutes: {\n    color: 'color'\n  },\n\n  descriptors: {\n    _scriptable: true,\n    _indexable: false,\n  },\n};\n"
  },
  {
    "path": "src/plugins/plugin.tooltip.js",
    "content": "import Animations from '../core/core.animations.js';\nimport Element from '../core/core.element.js';\nimport {addRoundedRectPath} from '../helpers/helpers.canvas.js';\nimport {each, noop, isNullOrUndef, isArray, _elementsEqual, isObject} from '../helpers/helpers.core.js';\nimport {toFont, toPadding, toTRBLCorners} from '../helpers/helpers.options.js';\nimport {getRtlAdapter, overrideTextDirection, restoreTextDirection} from '../helpers/helpers.rtl.js';\nimport {distanceBetweenPoints, _limitValue} from '../helpers/helpers.math.js';\nimport {createContext, drawPoint} from '../helpers/index.js';\n\n/**\n * @typedef { import('../platform/platform.base.js').Chart } Chart\n * @typedef { import('../types/index.js').ChartEvent } ChartEvent\n * @typedef { import('../types/index.js').ActiveElement } ActiveElement\n * @typedef { import('../core/core.interaction.js').InteractionItem } InteractionItem\n */\n\nconst positioners = {\n  /**\n\t * Average mode places the tooltip at the average position of the elements shown\n\t */\n  average(items) {\n    if (!items.length) {\n      return false;\n    }\n\n    let i, len;\n    let xSet = new Set();\n    let y = 0;\n    let count = 0;\n\n    for (i = 0, len = items.length; i < len; ++i) {\n      const el = items[i].element;\n      if (el && el.hasValue()) {\n        const pos = el.tooltipPosition();\n        xSet.add(pos.x);\n        y += pos.y;\n        ++count;\n      }\n    }\n\n    // No visible items where found, return false so we don't have to divide by 0 which reduces in NaN\n    if (count === 0 || xSet.size === 0) {\n      return false;\n    }\n\n    const xAverage = [...xSet].reduce((a, b) => a + b) / xSet.size;\n\n    return {\n      x: xAverage,\n      y: y / count\n    };\n  },\n\n  /**\n\t * Gets the tooltip position nearest of the item nearest to the event position\n\t */\n  nearest(items, eventPosition) {\n    if (!items.length) {\n      return false;\n    }\n\n    let x = eventPosition.x;\n    let y = eventPosition.y;\n    let minDistance = Number.POSITIVE_INFINITY;\n    let i, len, nearestElement;\n\n    for (i = 0, len = items.length; i < len; ++i) {\n      const el = items[i].element;\n      if (el && el.hasValue()) {\n        const center = el.getCenterPoint();\n        const d = distanceBetweenPoints(eventPosition, center);\n\n        if (d < minDistance) {\n          minDistance = d;\n          nearestElement = el;\n        }\n      }\n    }\n\n    if (nearestElement) {\n      const tp = nearestElement.tooltipPosition();\n      x = tp.x;\n      y = tp.y;\n    }\n\n    return {\n      x,\n      y\n    };\n  }\n};\n\n// Helper to push or concat based on if the 2nd parameter is an array or not\nfunction pushOrConcat(base, toPush) {\n  if (toPush) {\n    if (isArray(toPush)) {\n      // base = base.concat(toPush);\n      Array.prototype.push.apply(base, toPush);\n    } else {\n      base.push(toPush);\n    }\n  }\n\n  return base;\n}\n\n/**\n * Returns array of strings split by newline\n * @param {*} str - The value to split by newline.\n * @returns {string|string[]} value if newline present - Returned from String split() method\n * @function\n */\nfunction splitNewlines(str) {\n  if ((typeof str === 'string' || str instanceof String) && str.indexOf('\\n') > -1) {\n    return str.split('\\n');\n  }\n  return str;\n}\n\n\n/**\n * Private helper to create a tooltip item model\n * @param {Chart} chart\n * @param {ActiveElement} item - {element, index, datasetIndex} to create the tooltip item for\n * @return new tooltip item\n */\nfunction createTooltipItem(chart, item) {\n  const {element, datasetIndex, index} = item;\n  const controller = chart.getDatasetMeta(datasetIndex).controller;\n  const {label, value} = controller.getLabelAndValue(index);\n\n  return {\n    chart,\n    label,\n    parsed: controller.getParsed(index),\n    raw: chart.data.datasets[datasetIndex].data[index],\n    formattedValue: value,\n    dataset: controller.getDataset(),\n    dataIndex: index,\n    datasetIndex,\n    element\n  };\n}\n\n/**\n * Get the size of the tooltip\n */\nfunction getTooltipSize(tooltip, options) {\n  const ctx = tooltip.chart.ctx;\n  const {body, footer, title} = tooltip;\n  const {boxWidth, boxHeight} = options;\n  const bodyFont = toFont(options.bodyFont);\n  const titleFont = toFont(options.titleFont);\n  const footerFont = toFont(options.footerFont);\n  const titleLineCount = title.length;\n  const footerLineCount = footer.length;\n  const bodyLineItemCount = body.length;\n\n  const padding = toPadding(options.padding);\n  let height = padding.height;\n  let width = 0;\n\n  // Count of all lines in the body\n  let combinedBodyLength = body.reduce((count, bodyItem) => count + bodyItem.before.length + bodyItem.lines.length + bodyItem.after.length, 0);\n  combinedBodyLength += tooltip.beforeBody.length + tooltip.afterBody.length;\n\n  if (titleLineCount) {\n    height += titleLineCount * titleFont.lineHeight\n\t\t\t+ (titleLineCount - 1) * options.titleSpacing\n\t\t\t+ options.titleMarginBottom;\n  }\n  if (combinedBodyLength) {\n    // Body lines may include some extra height depending on boxHeight\n    const bodyLineHeight = options.displayColors ? Math.max(boxHeight, bodyFont.lineHeight) : bodyFont.lineHeight;\n    height += bodyLineItemCount * bodyLineHeight\n\t\t\t+ (combinedBodyLength - bodyLineItemCount) * bodyFont.lineHeight\n\t\t\t+ (combinedBodyLength - 1) * options.bodySpacing;\n  }\n  if (footerLineCount) {\n    height += options.footerMarginTop\n\t\t\t+ footerLineCount * footerFont.lineHeight\n\t\t\t+ (footerLineCount - 1) * options.footerSpacing;\n  }\n\n  // Title width\n  let widthPadding = 0;\n  const maxLineWidth = function(line) {\n    width = Math.max(width, ctx.measureText(line).width + widthPadding);\n  };\n\n  ctx.save();\n\n  ctx.font = titleFont.string;\n  each(tooltip.title, maxLineWidth);\n\n  // Body width\n  ctx.font = bodyFont.string;\n  each(tooltip.beforeBody.concat(tooltip.afterBody), maxLineWidth);\n\n  // Body lines may include some extra width due to the color box\n  widthPadding = options.displayColors ? (boxWidth + 2 + options.boxPadding) : 0;\n  each(body, (bodyItem) => {\n    each(bodyItem.before, maxLineWidth);\n    each(bodyItem.lines, maxLineWidth);\n    each(bodyItem.after, maxLineWidth);\n  });\n\n  // Reset back to 0\n  widthPadding = 0;\n\n  // Footer width\n  ctx.font = footerFont.string;\n  each(tooltip.footer, maxLineWidth);\n\n  ctx.restore();\n\n  // Add padding\n  width += padding.width;\n\n  return {width, height};\n}\n\nfunction determineYAlign(chart, size) {\n  const {y, height} = size;\n\n  if (y < height / 2) {\n    return 'top';\n  } else if (y > (chart.height - height / 2)) {\n    return 'bottom';\n  }\n  return 'center';\n}\n\nfunction doesNotFitWithAlign(xAlign, chart, options, size) {\n  const {x, width} = size;\n  const caret = options.caretSize + options.caretPadding;\n  if (xAlign === 'left' && x + width + caret > chart.width) {\n    return true;\n  }\n\n  if (xAlign === 'right' && x - width - caret < 0) {\n    return true;\n  }\n}\n\nfunction determineXAlign(chart, options, size, yAlign) {\n  const {x, width} = size;\n  const {width: chartWidth, chartArea: {left, right}} = chart;\n  let xAlign = 'center';\n\n  if (yAlign === 'center') {\n    xAlign = x <= (left + right) / 2 ? 'left' : 'right';\n  } else if (x <= width / 2) {\n    xAlign = 'left';\n  } else if (x >= chartWidth - width / 2) {\n    xAlign = 'right';\n  }\n\n  if (doesNotFitWithAlign(xAlign, chart, options, size)) {\n    xAlign = 'center';\n  }\n\n  return xAlign;\n}\n\n/**\n * Helper to get the alignment of a tooltip given the size\n */\nfunction determineAlignment(chart, options, size) {\n  const yAlign = size.yAlign || options.yAlign || determineYAlign(chart, size);\n\n  return {\n    xAlign: size.xAlign || options.xAlign || determineXAlign(chart, options, size, yAlign),\n    yAlign\n  };\n}\n\nfunction alignX(size, xAlign) {\n  let {x, width} = size;\n  if (xAlign === 'right') {\n    x -= width;\n  } else if (xAlign === 'center') {\n    x -= (width / 2);\n  }\n  return x;\n}\n\nfunction alignY(size, yAlign, paddingAndSize) {\n  // eslint-disable-next-line prefer-const\n  let {y, height} = size;\n  if (yAlign === 'top') {\n    y += paddingAndSize;\n  } else if (yAlign === 'bottom') {\n    y -= height + paddingAndSize;\n  } else {\n    y -= (height / 2);\n  }\n  return y;\n}\n\n/**\n * Helper to get the location a tooltip needs to be placed at given the initial position (via the vm) and the size and alignment\n */\nfunction getBackgroundPoint(options, size, alignment, chart) {\n  const {caretSize, caretPadding, cornerRadius} = options;\n  const {xAlign, yAlign} = alignment;\n  const paddingAndSize = caretSize + caretPadding;\n  const {topLeft, topRight, bottomLeft, bottomRight} = toTRBLCorners(cornerRadius);\n\n  let x = alignX(size, xAlign);\n  const y = alignY(size, yAlign, paddingAndSize);\n\n  if (yAlign === 'center') {\n    if (xAlign === 'left') {\n      x += paddingAndSize;\n    } else if (xAlign === 'right') {\n      x -= paddingAndSize;\n    }\n  } else if (xAlign === 'left') {\n    x -= Math.max(topLeft, bottomLeft) + caretSize;\n  } else if (xAlign === 'right') {\n    x += Math.max(topRight, bottomRight) + caretSize;\n  }\n\n  return {\n    x: _limitValue(x, 0, chart.width - size.width),\n    y: _limitValue(y, 0, chart.height - size.height)\n  };\n}\n\nfunction getAlignedX(tooltip, align, options) {\n  const padding = toPadding(options.padding);\n\n  return align === 'center'\n    ? tooltip.x + tooltip.width / 2\n    : align === 'right'\n      ? tooltip.x + tooltip.width - padding.right\n      : tooltip.x + padding.left;\n}\n\n/**\n * Helper to build before and after body lines\n */\nfunction getBeforeAfterBodyLines(callback) {\n  return pushOrConcat([], splitNewlines(callback));\n}\n\nfunction createTooltipContext(parent, tooltip, tooltipItems) {\n  return createContext(parent, {\n    tooltip,\n    tooltipItems,\n    type: 'tooltip'\n  });\n}\n\nfunction overrideCallbacks(callbacks, context) {\n  const override = context && context.dataset && context.dataset.tooltip && context.dataset.tooltip.callbacks;\n  return override ? callbacks.override(override) : callbacks;\n}\n\nconst defaultCallbacks = {\n  // Args are: (tooltipItems, data)\n  beforeTitle: noop,\n  title(tooltipItems) {\n    if (tooltipItems.length > 0) {\n      const item = tooltipItems[0];\n      const labels = item.chart.data.labels;\n      const labelCount = labels ? labels.length : 0;\n\n      if (this && this.options && this.options.mode === 'dataset') {\n        return item.dataset.label || '';\n      } else if (item.label) {\n        return item.label;\n      } else if (labelCount > 0 && item.dataIndex < labelCount) {\n        return labels[item.dataIndex];\n      }\n    }\n\n    return '';\n  },\n  afterTitle: noop,\n\n  // Args are: (tooltipItems, data)\n  beforeBody: noop,\n\n  // Args are: (tooltipItem, data)\n  beforeLabel: noop,\n  label(tooltipItem) {\n    if (this && this.options && this.options.mode === 'dataset') {\n      return tooltipItem.label + ': ' + tooltipItem.formattedValue || tooltipItem.formattedValue;\n    }\n\n    let label = tooltipItem.dataset.label || '';\n\n    if (label) {\n      label += ': ';\n    }\n    const value = tooltipItem.formattedValue;\n    if (!isNullOrUndef(value)) {\n      label += value;\n    }\n    return label;\n  },\n  labelColor(tooltipItem) {\n    const meta = tooltipItem.chart.getDatasetMeta(tooltipItem.datasetIndex);\n    const options = meta.controller.getStyle(tooltipItem.dataIndex);\n    return {\n      borderColor: options.borderColor,\n      backgroundColor: options.backgroundColor,\n      borderWidth: options.borderWidth,\n      borderDash: options.borderDash,\n      borderDashOffset: options.borderDashOffset,\n      borderRadius: 0,\n    };\n  },\n  labelTextColor() {\n    return this.options.bodyColor;\n  },\n  labelPointStyle(tooltipItem) {\n    const meta = tooltipItem.chart.getDatasetMeta(tooltipItem.datasetIndex);\n    const options = meta.controller.getStyle(tooltipItem.dataIndex);\n    return {\n      pointStyle: options.pointStyle,\n      rotation: options.rotation,\n    };\n  },\n  afterLabel: noop,\n\n  // Args are: (tooltipItems, data)\n  afterBody: noop,\n\n  // Args are: (tooltipItems, data)\n  beforeFooter: noop,\n  footer: noop,\n  afterFooter: noop\n};\n\n/**\n * Invoke callback from object with context and arguments.\n * If callback returns `undefined`, then will be invoked default callback.\n * @param {Record<keyof typeof defaultCallbacks, Function>} callbacks\n * @param {keyof typeof defaultCallbacks} name\n * @param {*} ctx\n * @param {*} arg\n * @returns {any}\n */\nfunction invokeCallbackWithFallback(callbacks, name, ctx, arg) {\n  const result = callbacks[name].call(ctx, arg);\n\n  if (typeof result === 'undefined') {\n    return defaultCallbacks[name].call(ctx, arg);\n  }\n\n  return result;\n}\n\nexport class Tooltip extends Element {\n\n  /**\n   * @namespace Chart.Tooltip.positioners\n   */\n  static positioners = positioners;\n\n  constructor(config) {\n    super();\n\n    this.opacity = 0;\n    this._active = [];\n    this._eventPosition = undefined;\n    this._size = undefined;\n    this._cachedAnimations = undefined;\n    this._tooltipItems = [];\n    this.$animations = undefined;\n    this.$context = undefined;\n    this.chart = config.chart;\n    this.options = config.options;\n    this.dataPoints = undefined;\n    this.title = undefined;\n    this.beforeBody = undefined;\n    this.body = undefined;\n    this.afterBody = undefined;\n    this.footer = undefined;\n    this.xAlign = undefined;\n    this.yAlign = undefined;\n    this.x = undefined;\n    this.y = undefined;\n    this.height = undefined;\n    this.width = undefined;\n    this.caretX = undefined;\n    this.caretY = undefined;\n    // TODO: V4, make this private, rename to `_labelStyles`, and combine with `labelPointStyles`\n    // and `labelTextColors` to create a single variable\n    this.labelColors = undefined;\n    this.labelPointStyles = undefined;\n    this.labelTextColors = undefined;\n  }\n\n  initialize(options) {\n    this.options = options;\n    this._cachedAnimations = undefined;\n    this.$context = undefined;\n  }\n\n  /**\n\t * @private\n\t */\n  _resolveAnimations() {\n    const cached = this._cachedAnimations;\n\n    if (cached) {\n      return cached;\n    }\n\n    const chart = this.chart;\n    const options = this.options.setContext(this.getContext());\n    const opts = options.enabled && chart.options.animation && options.animations;\n    const animations = new Animations(this.chart, opts);\n    if (opts._cacheable) {\n      this._cachedAnimations = Object.freeze(animations);\n    }\n\n    return animations;\n  }\n\n  /**\n\t * @protected\n\t */\n  getContext() {\n    return this.$context ||\n\t\t\t(this.$context = createTooltipContext(this.chart.getContext(), this, this._tooltipItems));\n  }\n\n  getTitle(context, options) {\n    const {callbacks} = options;\n\n    const beforeTitle = invokeCallbackWithFallback(callbacks, 'beforeTitle', this, context);\n    const title = invokeCallbackWithFallback(callbacks, 'title', this, context);\n    const afterTitle = invokeCallbackWithFallback(callbacks, 'afterTitle', this, context);\n\n    let lines = [];\n    lines = pushOrConcat(lines, splitNewlines(beforeTitle));\n    lines = pushOrConcat(lines, splitNewlines(title));\n    lines = pushOrConcat(lines, splitNewlines(afterTitle));\n\n    return lines;\n  }\n\n  getBeforeBody(tooltipItems, options) {\n    return getBeforeAfterBodyLines(\n      invokeCallbackWithFallback(options.callbacks, 'beforeBody', this, tooltipItems)\n    );\n  }\n\n  getBody(tooltipItems, options) {\n    const {callbacks} = options;\n    const bodyItems = [];\n\n    each(tooltipItems, (context) => {\n      const bodyItem = {\n        before: [],\n        lines: [],\n        after: []\n      };\n      const scoped = overrideCallbacks(callbacks, context);\n      pushOrConcat(bodyItem.before, splitNewlines(invokeCallbackWithFallback(scoped, 'beforeLabel', this, context)));\n      pushOrConcat(bodyItem.lines, invokeCallbackWithFallback(scoped, 'label', this, context));\n      pushOrConcat(bodyItem.after, splitNewlines(invokeCallbackWithFallback(scoped, 'afterLabel', this, context)));\n\n      bodyItems.push(bodyItem);\n    });\n\n    return bodyItems;\n  }\n\n  getAfterBody(tooltipItems, options) {\n    return getBeforeAfterBodyLines(\n      invokeCallbackWithFallback(options.callbacks, 'afterBody', this, tooltipItems)\n    );\n  }\n\n  // Get the footer and beforeFooter and afterFooter lines\n  getFooter(tooltipItems, options) {\n    const {callbacks} = options;\n\n    const beforeFooter = invokeCallbackWithFallback(callbacks, 'beforeFooter', this, tooltipItems);\n    const footer = invokeCallbackWithFallback(callbacks, 'footer', this, tooltipItems);\n    const afterFooter = invokeCallbackWithFallback(callbacks, 'afterFooter', this, tooltipItems);\n\n    let lines = [];\n    lines = pushOrConcat(lines, splitNewlines(beforeFooter));\n    lines = pushOrConcat(lines, splitNewlines(footer));\n    lines = pushOrConcat(lines, splitNewlines(afterFooter));\n\n    return lines;\n  }\n\n  /**\n\t * @private\n\t */\n  _createItems(options) {\n    const active = this._active;\n    const data = this.chart.data;\n    const labelColors = [];\n    const labelPointStyles = [];\n    const labelTextColors = [];\n    let tooltipItems = [];\n    let i, len;\n\n    for (i = 0, len = active.length; i < len; ++i) {\n      tooltipItems.push(createTooltipItem(this.chart, active[i]));\n    }\n\n    // If the user provided a filter function, use it to modify the tooltip items\n    if (options.filter) {\n      tooltipItems = tooltipItems.filter((element, index, array) => options.filter(element, index, array, data));\n    }\n\n    // If the user provided a sorting function, use it to modify the tooltip items\n    if (options.itemSort) {\n      tooltipItems = tooltipItems.sort((a, b) => options.itemSort(a, b, data));\n    }\n\n    // Determine colors for boxes\n    each(tooltipItems, (context) => {\n      const scoped = overrideCallbacks(options.callbacks, context);\n      labelColors.push(invokeCallbackWithFallback(scoped, 'labelColor', this, context));\n      labelPointStyles.push(invokeCallbackWithFallback(scoped, 'labelPointStyle', this, context));\n      labelTextColors.push(invokeCallbackWithFallback(scoped, 'labelTextColor', this, context));\n    });\n\n    this.labelColors = labelColors;\n    this.labelPointStyles = labelPointStyles;\n    this.labelTextColors = labelTextColors;\n    this.dataPoints = tooltipItems;\n    return tooltipItems;\n  }\n\n  update(changed, replay) {\n    const options = this.options.setContext(this.getContext());\n    const active = this._active;\n    let properties;\n    let tooltipItems = [];\n\n    if (!active.length) {\n      if (this.opacity !== 0) {\n        properties = {\n          opacity: 0\n        };\n      }\n    } else {\n      const position = positioners[options.position].call(this, active, this._eventPosition);\n      tooltipItems = this._createItems(options);\n\n      this.title = this.getTitle(tooltipItems, options);\n      this.beforeBody = this.getBeforeBody(tooltipItems, options);\n      this.body = this.getBody(tooltipItems, options);\n      this.afterBody = this.getAfterBody(tooltipItems, options);\n      this.footer = this.getFooter(tooltipItems, options);\n\n      const size = this._size = getTooltipSize(this, options);\n      const positionAndSize = Object.assign({}, position, size);\n      const alignment = determineAlignment(this.chart, options, positionAndSize);\n      const backgroundPoint = getBackgroundPoint(options, positionAndSize, alignment, this.chart);\n\n      this.xAlign = alignment.xAlign;\n      this.yAlign = alignment.yAlign;\n\n      properties = {\n        opacity: 1,\n        x: backgroundPoint.x,\n        y: backgroundPoint.y,\n        width: size.width,\n        height: size.height,\n        caretX: position.x,\n        caretY: position.y\n      };\n    }\n\n    this._tooltipItems = tooltipItems;\n    this.$context = undefined;\n\n    if (properties) {\n      this._resolveAnimations().update(this, properties);\n    }\n\n    if (changed && options.external) {\n      options.external.call(this, {chart: this.chart, tooltip: this, replay});\n    }\n  }\n\n  drawCaret(tooltipPoint, ctx, size, options) {\n    const caretPosition = this.getCaretPosition(tooltipPoint, size, options);\n\n    ctx.lineTo(caretPosition.x1, caretPosition.y1);\n    ctx.lineTo(caretPosition.x2, caretPosition.y2);\n    ctx.lineTo(caretPosition.x3, caretPosition.y3);\n  }\n\n  getCaretPosition(tooltipPoint, size, options) {\n    const {xAlign, yAlign} = this;\n    const {caretSize, cornerRadius} = options;\n    const {topLeft, topRight, bottomLeft, bottomRight} = toTRBLCorners(cornerRadius);\n    const {x: ptX, y: ptY} = tooltipPoint;\n    const {width, height} = size;\n    let x1, x2, x3, y1, y2, y3;\n\n    if (yAlign === 'center') {\n      y2 = ptY + (height / 2);\n\n      if (xAlign === 'left') {\n        x1 = ptX;\n        x2 = x1 - caretSize;\n\n        // Left draws bottom -> top, this y1 is on the bottom\n        y1 = y2 + caretSize;\n        y3 = y2 - caretSize;\n      } else {\n        x1 = ptX + width;\n        x2 = x1 + caretSize;\n\n        // Right draws top -> bottom, thus y1 is on the top\n        y1 = y2 - caretSize;\n        y3 = y2 + caretSize;\n      }\n\n      x3 = x1;\n    } else {\n      if (xAlign === 'left') {\n        x2 = ptX + Math.max(topLeft, bottomLeft) + (caretSize);\n      } else if (xAlign === 'right') {\n        x2 = ptX + width - Math.max(topRight, bottomRight) - caretSize;\n      } else {\n        x2 = this.caretX;\n      }\n\n      if (yAlign === 'top') {\n        y1 = ptY;\n        y2 = y1 - caretSize;\n\n        // Top draws left -> right, thus x1 is on the left\n        x1 = x2 - caretSize;\n        x3 = x2 + caretSize;\n      } else {\n        y1 = ptY + height;\n        y2 = y1 + caretSize;\n\n        // Bottom draws right -> left, thus x1 is on the right\n        x1 = x2 + caretSize;\n        x3 = x2 - caretSize;\n      }\n      y3 = y1;\n    }\n    return {x1, x2, x3, y1, y2, y3};\n  }\n\n  drawTitle(pt, ctx, options) {\n    const title = this.title;\n    const length = title.length;\n    let titleFont, titleSpacing, i;\n\n    if (length) {\n      const rtlHelper = getRtlAdapter(options.rtl, this.x, this.width);\n\n      pt.x = getAlignedX(this, options.titleAlign, options);\n\n      ctx.textAlign = rtlHelper.textAlign(options.titleAlign);\n      ctx.textBaseline = 'middle';\n\n      titleFont = toFont(options.titleFont);\n      titleSpacing = options.titleSpacing;\n\n      ctx.fillStyle = options.titleColor;\n      ctx.font = titleFont.string;\n\n      for (i = 0; i < length; ++i) {\n        ctx.fillText(title[i], rtlHelper.x(pt.x), pt.y + titleFont.lineHeight / 2);\n        pt.y += titleFont.lineHeight + titleSpacing; // Line Height and spacing\n\n        if (i + 1 === length) {\n          pt.y += options.titleMarginBottom - titleSpacing; // If Last, add margin, remove spacing\n        }\n      }\n    }\n  }\n\n  /**\n\t * @private\n\t */\n  _drawColorBox(ctx, pt, i, rtlHelper, options) {\n    const labelColor = this.labelColors[i];\n    const labelPointStyle = this.labelPointStyles[i];\n    const {boxHeight, boxWidth} = options;\n    const bodyFont = toFont(options.bodyFont);\n    const colorX = getAlignedX(this, 'left', options);\n    const rtlColorX = rtlHelper.x(colorX);\n    const yOffSet = boxHeight < bodyFont.lineHeight ? (bodyFont.lineHeight - boxHeight) / 2 : 0;\n    const colorY = pt.y + yOffSet;\n\n    if (options.usePointStyle) {\n      const drawOptions = {\n        radius: Math.min(boxWidth, boxHeight) / 2, // fit the circle in the box\n        pointStyle: labelPointStyle.pointStyle,\n        rotation: labelPointStyle.rotation,\n        borderWidth: 1\n      };\n      // Recalculate x and y for drawPoint() because its expecting\n      // x and y to be center of figure (instead of top left)\n      const centerX = rtlHelper.leftForLtr(rtlColorX, boxWidth) + boxWidth / 2;\n      const centerY = colorY + boxHeight / 2;\n\n      // Fill the point with white so that colours merge nicely if the opacity is < 1\n      ctx.strokeStyle = options.multiKeyBackground;\n      ctx.fillStyle = options.multiKeyBackground;\n      drawPoint(ctx, drawOptions, centerX, centerY);\n\n      // Draw the point\n      ctx.strokeStyle = labelColor.borderColor;\n      ctx.fillStyle = labelColor.backgroundColor;\n      drawPoint(ctx, drawOptions, centerX, centerY);\n    } else {\n      // Border\n      ctx.lineWidth = isObject(labelColor.borderWidth) ? Math.max(...Object.values(labelColor.borderWidth)) : (labelColor.borderWidth || 1); // TODO, v4 remove fallback\n      ctx.strokeStyle = labelColor.borderColor;\n      ctx.setLineDash(labelColor.borderDash || []);\n      ctx.lineDashOffset = labelColor.borderDashOffset || 0;\n\n      // Fill a white rect so that colours merge nicely if the opacity is < 1\n      const outerX = rtlHelper.leftForLtr(rtlColorX, boxWidth);\n      const innerX = rtlHelper.leftForLtr(rtlHelper.xPlus(rtlColorX, 1), boxWidth - 2);\n      const borderRadius = toTRBLCorners(labelColor.borderRadius);\n\n      if (Object.values(borderRadius).some(v => v !== 0)) {\n        ctx.beginPath();\n        ctx.fillStyle = options.multiKeyBackground;\n        addRoundedRectPath(ctx, {\n          x: outerX,\n          y: colorY,\n          w: boxWidth,\n          h: boxHeight,\n          radius: borderRadius,\n        });\n        ctx.fill();\n        ctx.stroke();\n\n        // Inner square\n        ctx.fillStyle = labelColor.backgroundColor;\n        ctx.beginPath();\n        addRoundedRectPath(ctx, {\n          x: innerX,\n          y: colorY + 1,\n          w: boxWidth - 2,\n          h: boxHeight - 2,\n          radius: borderRadius,\n        });\n        ctx.fill();\n      } else {\n        // Normal rect\n        ctx.fillStyle = options.multiKeyBackground;\n        ctx.fillRect(outerX, colorY, boxWidth, boxHeight);\n        ctx.strokeRect(outerX, colorY, boxWidth, boxHeight);\n        // Inner square\n        ctx.fillStyle = labelColor.backgroundColor;\n        ctx.fillRect(innerX, colorY + 1, boxWidth - 2, boxHeight - 2);\n      }\n    }\n\n    // restore fillStyle\n    ctx.fillStyle = this.labelTextColors[i];\n  }\n\n  drawBody(pt, ctx, options) {\n    const {body} = this;\n    const {bodySpacing, bodyAlign, displayColors, boxHeight, boxWidth, boxPadding} = options;\n    const bodyFont = toFont(options.bodyFont);\n    let bodyLineHeight = bodyFont.lineHeight;\n    let xLinePadding = 0;\n\n    const rtlHelper = getRtlAdapter(options.rtl, this.x, this.width);\n\n    const fillLineOfText = function(line) {\n      ctx.fillText(line, rtlHelper.x(pt.x + xLinePadding), pt.y + bodyLineHeight / 2);\n      pt.y += bodyLineHeight + bodySpacing;\n    };\n\n    const bodyAlignForCalculation = rtlHelper.textAlign(bodyAlign);\n    let bodyItem, textColor, lines, i, j, ilen, jlen;\n\n    ctx.textAlign = bodyAlign;\n    ctx.textBaseline = 'middle';\n    ctx.font = bodyFont.string;\n\n    pt.x = getAlignedX(this, bodyAlignForCalculation, options);\n\n    // Before body lines\n    ctx.fillStyle = options.bodyColor;\n    each(this.beforeBody, fillLineOfText);\n\n    xLinePadding = displayColors && bodyAlignForCalculation !== 'right'\n      ? bodyAlign === 'center' ? (boxWidth / 2 + boxPadding) : (boxWidth + 2 + boxPadding)\n      : 0;\n\n    // Draw body lines now\n    for (i = 0, ilen = body.length; i < ilen; ++i) {\n      bodyItem = body[i];\n      textColor = this.labelTextColors[i];\n\n      ctx.fillStyle = textColor;\n      each(bodyItem.before, fillLineOfText);\n\n      lines = bodyItem.lines;\n      // Draw Legend-like boxes if needed\n      if (displayColors && lines.length) {\n        this._drawColorBox(ctx, pt, i, rtlHelper, options);\n        bodyLineHeight = Math.max(bodyFont.lineHeight, boxHeight);\n      }\n\n      for (j = 0, jlen = lines.length; j < jlen; ++j) {\n        fillLineOfText(lines[j]);\n        // Reset for any lines that don't include colorbox\n        bodyLineHeight = bodyFont.lineHeight;\n      }\n\n      each(bodyItem.after, fillLineOfText);\n    }\n\n    // Reset back to 0 for after body\n    xLinePadding = 0;\n    bodyLineHeight = bodyFont.lineHeight;\n\n    // After body lines\n    each(this.afterBody, fillLineOfText);\n    pt.y -= bodySpacing; // Remove last body spacing\n  }\n\n  drawFooter(pt, ctx, options) {\n    const footer = this.footer;\n    const length = footer.length;\n    let footerFont, i;\n\n    if (length) {\n      const rtlHelper = getRtlAdapter(options.rtl, this.x, this.width);\n\n      pt.x = getAlignedX(this, options.footerAlign, options);\n      pt.y += options.footerMarginTop;\n\n      ctx.textAlign = rtlHelper.textAlign(options.footerAlign);\n      ctx.textBaseline = 'middle';\n\n      footerFont = toFont(options.footerFont);\n\n      ctx.fillStyle = options.footerColor;\n      ctx.font = footerFont.string;\n\n      for (i = 0; i < length; ++i) {\n        ctx.fillText(footer[i], rtlHelper.x(pt.x), pt.y + footerFont.lineHeight / 2);\n        pt.y += footerFont.lineHeight + options.footerSpacing;\n      }\n    }\n  }\n\n  drawBackground(pt, ctx, tooltipSize, options) {\n    const {xAlign, yAlign} = this;\n    const {x, y} = pt;\n    const {width, height} = tooltipSize;\n    const {topLeft, topRight, bottomLeft, bottomRight} = toTRBLCorners(options.cornerRadius);\n\n    ctx.fillStyle = options.backgroundColor;\n    ctx.strokeStyle = options.borderColor;\n    ctx.lineWidth = options.borderWidth;\n\n    ctx.beginPath();\n    ctx.moveTo(x + topLeft, y);\n    if (yAlign === 'top') {\n      this.drawCaret(pt, ctx, tooltipSize, options);\n    }\n    ctx.lineTo(x + width - topRight, y);\n    ctx.quadraticCurveTo(x + width, y, x + width, y + topRight);\n    if (yAlign === 'center' && xAlign === 'right') {\n      this.drawCaret(pt, ctx, tooltipSize, options);\n    }\n    ctx.lineTo(x + width, y + height - bottomRight);\n    ctx.quadraticCurveTo(x + width, y + height, x + width - bottomRight, y + height);\n    if (yAlign === 'bottom') {\n      this.drawCaret(pt, ctx, tooltipSize, options);\n    }\n    ctx.lineTo(x + bottomLeft, y + height);\n    ctx.quadraticCurveTo(x, y + height, x, y + height - bottomLeft);\n    if (yAlign === 'center' && xAlign === 'left') {\n      this.drawCaret(pt, ctx, tooltipSize, options);\n    }\n    ctx.lineTo(x, y + topLeft);\n    ctx.quadraticCurveTo(x, y, x + topLeft, y);\n    ctx.closePath();\n\n    ctx.fill();\n\n    if (options.borderWidth > 0) {\n      ctx.stroke();\n    }\n  }\n\n  /**\n\t * Update x/y animation targets when _active elements are animating too\n\t * @private\n\t */\n  _updateAnimationTarget(options) {\n    const chart = this.chart;\n    const anims = this.$animations;\n    const animX = anims && anims.x;\n    const animY = anims && anims.y;\n    if (animX || animY) {\n      const position = positioners[options.position].call(this, this._active, this._eventPosition);\n      if (!position) {\n        return;\n      }\n      const size = this._size = getTooltipSize(this, options);\n      const positionAndSize = Object.assign({}, position, this._size);\n      const alignment = determineAlignment(chart, options, positionAndSize);\n      const point = getBackgroundPoint(options, positionAndSize, alignment, chart);\n      if (animX._to !== point.x || animY._to !== point.y) {\n        this.xAlign = alignment.xAlign;\n        this.yAlign = alignment.yAlign;\n        this.width = size.width;\n        this.height = size.height;\n        this.caretX = position.x;\n        this.caretY = position.y;\n        this._resolveAnimations().update(this, point);\n      }\n    }\n  }\n\n  /**\n   * Determine if the tooltip will draw anything\n   * @returns {boolean} True if the tooltip will render\n   */\n  _willRender() {\n    return !!this.opacity;\n  }\n\n  draw(ctx) {\n    const options = this.options.setContext(this.getContext());\n    let opacity = this.opacity;\n\n    if (!opacity) {\n      return;\n    }\n\n    this._updateAnimationTarget(options);\n\n    const tooltipSize = {\n      width: this.width,\n      height: this.height\n    };\n    const pt = {\n      x: this.x,\n      y: this.y\n    };\n\n    // IE11/Edge does not like very small opacities, so snap to 0\n    opacity = Math.abs(opacity) < 1e-3 ? 0 : opacity;\n\n    const padding = toPadding(options.padding);\n\n    // Truthy/falsey value for empty tooltip\n    const hasTooltipContent = this.title.length || this.beforeBody.length || this.body.length || this.afterBody.length || this.footer.length;\n\n    if (options.enabled && hasTooltipContent) {\n      ctx.save();\n      ctx.globalAlpha = opacity;\n\n      // Draw Background\n      this.drawBackground(pt, ctx, tooltipSize, options);\n\n      overrideTextDirection(ctx, options.textDirection);\n\n      pt.y += padding.top;\n\n      // Titles\n      this.drawTitle(pt, ctx, options);\n\n      // Body\n      this.drawBody(pt, ctx, options);\n\n      // Footer\n      this.drawFooter(pt, ctx, options);\n\n      restoreTextDirection(ctx, options.textDirection);\n\n      ctx.restore();\n    }\n  }\n\n  /**\n\t * Get active elements in the tooltip\n\t * @returns {Array} Array of elements that are active in the tooltip\n\t */\n  getActiveElements() {\n    return this._active || [];\n  }\n\n  /**\n\t * Set active elements in the tooltip\n\t * @param {array} activeElements Array of active datasetIndex/index pairs.\n\t * @param {object} eventPosition Synthetic event position used in positioning\n\t */\n  setActiveElements(activeElements, eventPosition) {\n    const lastActive = this._active;\n    const active = activeElements.map(({datasetIndex, index}) => {\n      const meta = this.chart.getDatasetMeta(datasetIndex);\n\n      if (!meta) {\n        throw new Error('Cannot find a dataset at index ' + datasetIndex);\n      }\n\n      return {\n        datasetIndex,\n        element: meta.data[index],\n        index,\n      };\n    });\n    const changed = !_elementsEqual(lastActive, active);\n    const positionChanged = this._positionChanged(active, eventPosition);\n\n    if (changed || positionChanged) {\n      this._active = active;\n      this._eventPosition = eventPosition;\n      this._ignoreReplayEvents = true;\n      this.update(true);\n    }\n  }\n\n  /**\n\t * Handle an event\n\t * @param {ChartEvent} e - The event to handle\n\t * @param {boolean} [replay] - This is a replayed event (from update)\n\t * @param {boolean} [inChartArea] - The event is inside chartArea\n\t * @returns {boolean} true if the tooltip changed\n\t */\n  handleEvent(e, replay, inChartArea = true) {\n    if (replay && this._ignoreReplayEvents) {\n      return false;\n    }\n    this._ignoreReplayEvents = false;\n\n    const options = this.options;\n    const lastActive = this._active || [];\n    const active = this._getActiveElements(e, lastActive, replay, inChartArea);\n\n    // When there are multiple items shown, but the tooltip position is nearest mode\n    // an update may need to be made because our position may have changed even though\n    // the items are the same as before.\n    const positionChanged = this._positionChanged(active, e);\n\n    // Remember Last Actives\n    const changed = replay || !_elementsEqual(active, lastActive) || positionChanged;\n\n    // Only handle target event on tooltip change\n    if (changed) {\n      this._active = active;\n\n      if (options.enabled || options.external) {\n        this._eventPosition = {\n          x: e.x,\n          y: e.y\n        };\n\n        this.update(true, replay);\n      }\n    }\n\n    return changed;\n  }\n\n  /**\n\t * Helper for determining the active elements for event\n\t * @param {ChartEvent} e - The event to handle\n\t * @param {InteractionItem[]} lastActive - Previously active elements\n\t * @param {boolean} [replay] - This is a replayed event (from update)\n\t * @param {boolean} [inChartArea] - The event is inside chartArea\n\t * @returns {InteractionItem[]} - Active elements\n\t * @private\n\t */\n  _getActiveElements(e, lastActive, replay, inChartArea) {\n    const options = this.options;\n\n    if (e.type === 'mouseout') {\n      return [];\n    }\n\n    if (!inChartArea) {\n      // Let user control the active elements outside chartArea. Eg. using Legend.\n      // But make sure that active elements are still valid.\n      return lastActive.filter(i =>\n        this.chart.data.datasets[i.datasetIndex] &&\n        this.chart.getDatasetMeta(i.datasetIndex).controller.getParsed(i.index) !== undefined\n      );\n    }\n\n    // Find Active Elements for tooltips\n    const active = this.chart.getElementsAtEventForMode(e, options.mode, options, replay);\n\n    if (options.reverse) {\n      active.reverse();\n    }\n\n    return active;\n  }\n\n  /**\n\t * Determine if the active elements + event combination changes the\n\t * tooltip position\n\t * @param {array} active - Active elements\n\t * @param {ChartEvent} e - Event that triggered the position change\n\t * @returns {boolean} True if the position has changed\n\t */\n  _positionChanged(active, e) {\n    const {caretX, caretY, options} = this;\n    const position = positioners[options.position].call(this, active, e);\n    return position !== false && (caretX !== position.x || caretY !== position.y);\n  }\n}\n\nexport default {\n  id: 'tooltip',\n  _element: Tooltip,\n  positioners,\n\n  afterInit(chart, _args, options) {\n    if (options) {\n      chart.tooltip = new Tooltip({chart, options});\n    }\n  },\n\n  beforeUpdate(chart, _args, options) {\n    if (chart.tooltip) {\n      chart.tooltip.initialize(options);\n    }\n  },\n\n  reset(chart, _args, options) {\n    if (chart.tooltip) {\n      chart.tooltip.initialize(options);\n    }\n  },\n\n  afterDraw(chart) {\n    const tooltip = chart.tooltip;\n\n    if (tooltip && tooltip._willRender()) {\n      const args = {\n        tooltip\n      };\n\n      if (chart.notifyPlugins('beforeTooltipDraw', {...args, cancelable: true}) === false) {\n        return;\n      }\n\n      tooltip.draw(chart.ctx);\n\n      chart.notifyPlugins('afterTooltipDraw', args);\n    }\n  },\n\n  afterEvent(chart, args) {\n    if (chart.tooltip) {\n      // If the event is replayed from `update`, we should evaluate with the final positions.\n      const useFinalPosition = args.replay;\n      if (chart.tooltip.handleEvent(args.event, useFinalPosition, args.inChartArea)) {\n        // notify chart about the change, so it will render\n        args.changed = true;\n      }\n    }\n  },\n\n  defaults: {\n    enabled: true,\n    external: null,\n    position: 'average',\n    backgroundColor: 'rgba(0,0,0,0.8)',\n    titleColor: '#fff',\n    titleFont: {\n      weight: 'bold',\n    },\n    titleSpacing: 2,\n    titleMarginBottom: 6,\n    titleAlign: 'left',\n    bodyColor: '#fff',\n    bodySpacing: 2,\n    bodyFont: {\n    },\n    bodyAlign: 'left',\n    footerColor: '#fff',\n    footerSpacing: 2,\n    footerMarginTop: 6,\n    footerFont: {\n      weight: 'bold',\n    },\n    footerAlign: 'left',\n    padding: 6,\n    caretPadding: 2,\n    caretSize: 5,\n    cornerRadius: 6,\n    boxHeight: (ctx, opts) => opts.bodyFont.size,\n    boxWidth: (ctx, opts) => opts.bodyFont.size,\n    multiKeyBackground: '#fff',\n    displayColors: true,\n    boxPadding: 0,\n    borderColor: 'rgba(0,0,0,0)',\n    borderWidth: 0,\n    animation: {\n      duration: 400,\n      easing: 'easeOutQuart',\n    },\n    animations: {\n      numbers: {\n        type: 'number',\n        properties: ['x', 'y', 'width', 'height', 'caretX', 'caretY'],\n      },\n      opacity: {\n        easing: 'linear',\n        duration: 200\n      }\n    },\n    callbacks: defaultCallbacks\n  },\n\n  defaultRoutes: {\n    bodyFont: 'font',\n    footerFont: 'font',\n    titleFont: 'font'\n  },\n\n  descriptors: {\n    _scriptable: (name) => name !== 'filter' && name !== 'itemSort' && name !== 'external',\n    _indexable: false,\n    callbacks: {\n      _scriptable: false,\n      _indexable: false,\n    },\n    animation: {\n      _fallback: false\n    },\n    animations: {\n      _fallback: 'animation'\n    }\n  },\n\n  // Resolve additionally from `interaction` options and defaults.\n  additionalOptionScopes: ['interaction']\n};\n"
  },
  {
    "path": "src/scales/index.js",
    "content": "export {default as CategoryScale} from './scale.category.js';\nexport {default as LinearScale} from './scale.linear.js';\nexport {default as LogarithmicScale} from './scale.logarithmic.js';\nexport {default as RadialLinearScale} from './scale.radialLinear.js';\nexport {default as TimeScale} from './scale.time.js';\nexport {default as TimeSeriesScale} from './scale.timeseries.js';\n"
  },
  {
    "path": "src/scales/scale.category.js",
    "content": "import Scale from '../core/core.scale.js';\nimport {isNullOrUndef, valueOrDefault, _limitValue} from '../helpers/index.js';\n\nconst addIfString = (labels, raw, index, addedLabels) => {\n  if (typeof raw === 'string') {\n    index = labels.push(raw) - 1;\n    addedLabels.unshift({index, label: raw});\n  } else if (isNaN(raw)) {\n    index = null;\n  }\n  return index;\n};\n\nfunction findOrAddLabel(labels, raw, index, addedLabels) {\n  const first = labels.indexOf(raw);\n  if (first === -1) {\n    return addIfString(labels, raw, index, addedLabels);\n  }\n  const last = labels.lastIndexOf(raw);\n  return first !== last ? index : first;\n}\n\nconst validIndex = (index, max) => index === null ? null : _limitValue(Math.round(index), 0, max);\n\nfunction _getLabelForValue(value) {\n  const labels = this.getLabels();\n\n  if (value >= 0 && value < labels.length) {\n    return labels[value];\n  }\n  return value;\n}\n\nexport default class CategoryScale extends Scale {\n\n  static id = 'category';\n\n  /**\n   * @type {any}\n   */\n  static defaults = {\n    ticks: {\n      callback: _getLabelForValue\n    }\n  };\n\n  constructor(cfg) {\n    super(cfg);\n\n    /** @type {number} */\n    this._startValue = undefined;\n    this._valueRange = 0;\n    this._addedLabels = [];\n  }\n\n  init(scaleOptions) {\n    const added = this._addedLabels;\n    if (added.length) {\n      const labels = this.getLabels();\n      for (const {index, label} of added) {\n        if (labels[index] === label) {\n          labels.splice(index, 1);\n        }\n      }\n      this._addedLabels = [];\n    }\n    super.init(scaleOptions);\n  }\n\n  parse(raw, index) {\n    if (isNullOrUndef(raw)) {\n      return null;\n    }\n    const labels = this.getLabels();\n    index = isFinite(index) && labels[index] === raw ? index\n      : findOrAddLabel(labels, raw, valueOrDefault(index, raw), this._addedLabels);\n    return validIndex(index, labels.length - 1);\n  }\n\n  determineDataLimits() {\n    const {minDefined, maxDefined} = this.getUserBounds();\n    let {min, max} = this.getMinMax(true);\n\n    if (this.options.bounds === 'ticks') {\n      if (!minDefined) {\n        min = 0;\n      }\n      if (!maxDefined) {\n        max = this.getLabels().length - 1;\n      }\n    }\n\n    this.min = min;\n    this.max = max;\n  }\n\n  buildTicks() {\n    const min = this.min;\n    const max = this.max;\n    const offset = this.options.offset;\n    const ticks = [];\n    let labels = this.getLabels();\n\n    // If we are viewing some subset of labels, slice the original array\n    labels = (min === 0 && max === labels.length - 1) ? labels : labels.slice(min, max + 1);\n\n    this._valueRange = Math.max(labels.length - (offset ? 0 : 1), 1);\n    this._startValue = this.min - (offset ? 0.5 : 0);\n\n    for (let value = min; value <= max; value++) {\n      ticks.push({value});\n    }\n    return ticks;\n  }\n\n  getLabelForValue(value) {\n    return _getLabelForValue.call(this, value);\n  }\n\n  /**\n\t * @protected\n\t */\n  configure() {\n    super.configure();\n\n    if (!this.isHorizontal()) {\n      // For backward compatibility, vertical category scale reverse is inverted.\n      this._reversePixels = !this._reversePixels;\n    }\n  }\n\n  // Used to get data value locations. Value can either be an index or a numerical value\n  getPixelForValue(value) {\n    if (typeof value !== 'number') {\n      value = this.parse(value);\n    }\n\n    return value === null ? NaN : this.getPixelForDecimal((value - this._startValue) / this._valueRange);\n  }\n\n  // Must override base implementation because it calls getPixelForValue\n  // and category scale can have duplicate values\n  getPixelForTick(index) {\n    const ticks = this.ticks;\n    if (index < 0 || index > ticks.length - 1) {\n      return null;\n    }\n    return this.getPixelForValue(ticks[index].value);\n  }\n\n  getValueForPixel(pixel) {\n    return Math.round(this._startValue + this.getDecimalForPixel(pixel) * this._valueRange);\n  }\n\n  getBasePixel() {\n    return this.bottom;\n  }\n}\n"
  },
  {
    "path": "src/scales/scale.linear.js",
    "content": "import {isFinite} from '../helpers/helpers.core.js';\nimport LinearScaleBase from './scale.linearbase.js';\nimport Ticks from '../core/core.ticks.js';\nimport {toRadians} from '../helpers/index.js';\n\nexport default class LinearScale extends LinearScaleBase {\n\n  static id = 'linear';\n\n  /**\n   * @type {any}\n   */\n  static defaults = {\n    ticks: {\n      callback: Ticks.formatters.numeric\n    }\n  };\n\n\n  determineDataLimits() {\n    const {min, max} = this.getMinMax(true);\n\n    this.min = isFinite(min) ? min : 0;\n    this.max = isFinite(max) ? max : 1;\n\n    // Common base implementation to handle min, max, beginAtZero\n    this.handleTickRangeOptions();\n  }\n\n  /**\n\t * Returns the maximum number of ticks based on the scale dimension\n\t * @protected\n \t */\n  computeTickLimit() {\n    const horizontal = this.isHorizontal();\n    const length = horizontal ? this.width : this.height;\n    const minRotation = toRadians(this.options.ticks.minRotation);\n    const ratio = (horizontal ? Math.sin(minRotation) : Math.cos(minRotation)) || 0.001;\n    const tickFont = this._resolveTickFontOptions(0);\n    return Math.ceil(length / Math.min(40, tickFont.lineHeight / ratio));\n  }\n\n  // Utils\n  getPixelForValue(value) {\n    return value === null ? NaN : this.getPixelForDecimal((value - this._startValue) / this._valueRange);\n  }\n\n  getValueForPixel(pixel) {\n    return this._startValue + this.getDecimalForPixel(pixel) * this._valueRange;\n  }\n}\n"
  },
  {
    "path": "src/scales/scale.linearbase.js",
    "content": "import {isNullOrUndef} from '../helpers/helpers.core.js';\nimport {almostEquals, almostWhole, niceNum, _decimalPlaces, _setMinAndMaxByKey, sign, toRadians} from '../helpers/helpers.math.js';\nimport Scale from '../core/core.scale.js';\nimport {formatNumber} from '../helpers/helpers.intl.js';\n\n/**\n * Generate a set of linear ticks for an axis\n * 1. If generationOptions.min, generationOptions.max, and generationOptions.step are defined:\n *    if (max - min) / step is an integer, ticks are generated as [min, min + step, ..., max]\n *    Note that the generationOptions.maxCount setting is respected in this scenario\n *\n * 2. If generationOptions.min, generationOptions.max, and generationOptions.count is defined\n *    spacing = (max - min) / count\n *    Ticks are generated as [min, min + spacing, ..., max]\n *\n * 3. If generationOptions.count is defined\n *    spacing = (niceMax - niceMin) / count\n *\n * 4. Compute optimal spacing of ticks using niceNum algorithm\n *\n * @param generationOptions the options used to generate the ticks\n * @param dataRange the range of the data\n * @returns {object[]} array of tick objects\n */\nfunction generateTicks(generationOptions, dataRange) {\n  const ticks = [];\n  // To get a \"nice\" value for the tick spacing, we will use the appropriately named\n  // \"nice number\" algorithm. See https://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks\n  // for details.\n\n  const MIN_SPACING = 1e-14;\n  const {bounds, step, min, max, precision, count, maxTicks, maxDigits, includeBounds} = generationOptions;\n  const unit = step || 1;\n  const maxSpaces = maxTicks - 1;\n  const {min: rmin, max: rmax} = dataRange;\n  const minDefined = !isNullOrUndef(min);\n  const maxDefined = !isNullOrUndef(max);\n  const countDefined = !isNullOrUndef(count);\n  const minSpacing = (rmax - rmin) / (maxDigits + 1);\n  let spacing = niceNum((rmax - rmin) / maxSpaces / unit) * unit;\n  let factor, niceMin, niceMax, numSpaces;\n\n  // Beyond MIN_SPACING floating point numbers being to lose precision\n  // such that we can't do the math necessary to generate ticks\n  if (spacing < MIN_SPACING && !minDefined && !maxDefined) {\n    return [{value: rmin}, {value: rmax}];\n  }\n\n  numSpaces = Math.ceil(rmax / spacing) - Math.floor(rmin / spacing);\n  if (numSpaces > maxSpaces) {\n    // If the calculated num of spaces exceeds maxNumSpaces, recalculate it\n    spacing = niceNum(numSpaces * spacing / maxSpaces / unit) * unit;\n  }\n\n  if (!isNullOrUndef(precision)) {\n    // If the user specified a precision, round to that number of decimal places\n    factor = Math.pow(10, precision);\n    spacing = Math.ceil(spacing * factor) / factor;\n  }\n\n  if (bounds === 'ticks') {\n    niceMin = Math.floor(rmin / spacing) * spacing;\n    niceMax = Math.ceil(rmax / spacing) * spacing;\n  } else {\n    niceMin = rmin;\n    niceMax = rmax;\n  }\n\n  if (minDefined && maxDefined && step && almostWhole((max - min) / step, spacing / 1000)) {\n    // Case 1: If min, max and stepSize are set and they make an evenly spaced scale use it.\n    // spacing = step;\n    // numSpaces = (max - min) / spacing;\n    // Note that we round here to handle the case where almostWhole translated an FP error\n    numSpaces = Math.round(Math.min((max - min) / spacing, maxTicks));\n    spacing = (max - min) / numSpaces;\n    niceMin = min;\n    niceMax = max;\n  } else if (countDefined) {\n    // Cases 2 & 3, we have a count specified. Handle optional user defined edges to the range.\n    // Sometimes these are no-ops, but it makes the code a lot clearer\n    // and when a user defined range is specified, we want the correct ticks\n    niceMin = minDefined ? min : niceMin;\n    niceMax = maxDefined ? max : niceMax;\n    numSpaces = count - 1;\n    spacing = (niceMax - niceMin) / numSpaces;\n  } else {\n    // Case 4\n    numSpaces = (niceMax - niceMin) / spacing;\n\n    // If very close to our rounded value, use it.\n    if (almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) {\n      numSpaces = Math.round(numSpaces);\n    } else {\n      numSpaces = Math.ceil(numSpaces);\n    }\n  }\n\n  // The spacing will have changed in cases 1, 2, and 3 so the factor cannot be computed\n  // until this point\n  const decimalPlaces = Math.max(\n    _decimalPlaces(spacing),\n    _decimalPlaces(niceMin)\n  );\n  factor = Math.pow(10, isNullOrUndef(precision) ? decimalPlaces : precision);\n  niceMin = Math.round(niceMin * factor) / factor;\n  niceMax = Math.round(niceMax * factor) / factor;\n\n  let j = 0;\n  if (minDefined) {\n    if (includeBounds && niceMin !== min) {\n      ticks.push({value: min});\n\n      if (niceMin < min) {\n        j++; // Skip niceMin\n      }\n      // If the next nice tick is close to min, skip it\n      if (almostEquals(Math.round((niceMin + j * spacing) * factor) / factor, min, relativeLabelSize(min, minSpacing, generationOptions))) {\n        j++;\n      }\n    } else if (niceMin < min) {\n      j++;\n    }\n  }\n\n  for (; j < numSpaces; ++j) {\n    const tickValue = Math.round((niceMin + j * spacing) * factor) / factor;\n    if (maxDefined && tickValue > max) {\n      break;\n    }\n    ticks.push({value: tickValue});\n  }\n\n  if (maxDefined && includeBounds && niceMax !== max) {\n    // If the previous tick is too close to max, replace it with max, else add max\n    if (ticks.length && almostEquals(ticks[ticks.length - 1].value, max, relativeLabelSize(max, minSpacing, generationOptions))) {\n      ticks[ticks.length - 1].value = max;\n    } else {\n      ticks.push({value: max});\n    }\n  } else if (!maxDefined || niceMax === max) {\n    ticks.push({value: niceMax});\n  }\n\n  return ticks;\n}\n\nfunction relativeLabelSize(value, minSpacing, {horizontal, minRotation}) {\n  const rad = toRadians(minRotation);\n  const ratio = (horizontal ? Math.sin(rad) : Math.cos(rad)) || 0.001;\n  const length = 0.75 * minSpacing * ('' + value).length;\n  return Math.min(minSpacing / ratio, length);\n}\n\nexport default class LinearScaleBase extends Scale {\n\n  constructor(cfg) {\n    super(cfg);\n\n    /** @type {number} */\n    this.start = undefined;\n    /** @type {number} */\n    this.end = undefined;\n    /** @type {number} */\n    this._startValue = undefined;\n    /** @type {number} */\n    this._endValue = undefined;\n    this._valueRange = 0;\n  }\n\n  parse(raw, index) { // eslint-disable-line no-unused-vars\n    if (isNullOrUndef(raw)) {\n      return null;\n    }\n    if ((typeof raw === 'number' || raw instanceof Number) && !isFinite(+raw)) {\n      return null;\n    }\n\n    return +raw;\n  }\n\n  handleTickRangeOptions() {\n    const {beginAtZero} = this.options;\n    const {minDefined, maxDefined} = this.getUserBounds();\n    let {min, max} = this;\n\n    const setMin = v => (min = minDefined ? min : v);\n    const setMax = v => (max = maxDefined ? max : v);\n\n    if (beginAtZero) {\n      const minSign = sign(min);\n      const maxSign = sign(max);\n\n      if (minSign < 0 && maxSign < 0) {\n        setMax(0);\n      } else if (minSign > 0 && maxSign > 0) {\n        setMin(0);\n      }\n    }\n\n    if (min === max) {\n      let offset = max === 0 ? 1 : Math.abs(max * 0.05);\n\n      setMax(max + offset);\n\n      if (!beginAtZero) {\n        setMin(min - offset);\n      }\n    }\n    this.min = min;\n    this.max = max;\n  }\n\n  getTickLimit() {\n    const tickOpts = this.options.ticks;\n    // eslint-disable-next-line prefer-const\n    let {maxTicksLimit, stepSize} = tickOpts;\n    let maxTicks;\n\n    if (stepSize) {\n      maxTicks = Math.ceil(this.max / stepSize) - Math.floor(this.min / stepSize) + 1;\n      if (maxTicks > 1000) {\n        console.warn(`scales.${this.id}.ticks.stepSize: ${stepSize} would result generating up to ${maxTicks} ticks. Limiting to 1000.`);\n        maxTicks = 1000;\n      }\n    } else {\n      maxTicks = this.computeTickLimit();\n      maxTicksLimit = maxTicksLimit || 11;\n    }\n\n    if (maxTicksLimit) {\n      maxTicks = Math.min(maxTicksLimit, maxTicks);\n    }\n\n    return maxTicks;\n  }\n\n  /**\n\t * @protected\n\t */\n  computeTickLimit() {\n    return Number.POSITIVE_INFINITY;\n  }\n\n  buildTicks() {\n    const opts = this.options;\n    const tickOpts = opts.ticks;\n\n    // Figure out what the max number of ticks we can support it is based on the size of\n    // the axis area. For now, we say that the minimum tick spacing in pixels must be 40\n    // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on\n    // the graph. Make sure we always have at least 2 ticks\n    let maxTicks = this.getTickLimit();\n    maxTicks = Math.max(2, maxTicks);\n\n    const numericGeneratorOptions = {\n      maxTicks,\n      bounds: opts.bounds,\n      min: opts.min,\n      max: opts.max,\n      precision: tickOpts.precision,\n      step: tickOpts.stepSize,\n      count: tickOpts.count,\n      maxDigits: this._maxDigits(),\n      horizontal: this.isHorizontal(),\n      minRotation: tickOpts.minRotation || 0,\n      includeBounds: tickOpts.includeBounds !== false\n    };\n    const dataRange = this._range || this;\n    const ticks = generateTicks(numericGeneratorOptions, dataRange);\n\n    // At this point, we need to update our max and min given the tick values,\n    // since we probably have expanded the range of the scale\n    if (opts.bounds === 'ticks') {\n      _setMinAndMaxByKey(ticks, this, 'value');\n    }\n\n    if (opts.reverse) {\n      ticks.reverse();\n\n      this.start = this.max;\n      this.end = this.min;\n    } else {\n      this.start = this.min;\n      this.end = this.max;\n    }\n\n    return ticks;\n  }\n\n  /**\n\t * @protected\n\t */\n  configure() {\n    const ticks = this.ticks;\n    let start = this.min;\n    let end = this.max;\n\n    super.configure();\n\n    if (this.options.offset && ticks.length) {\n      const offset = (end - start) / Math.max(ticks.length - 1, 1) / 2;\n      start -= offset;\n      end += offset;\n    }\n    this._startValue = start;\n    this._endValue = end;\n    this._valueRange = end - start;\n  }\n\n  getLabelForValue(value) {\n    return formatNumber(value, this.chart.options.locale, this.options.ticks.format);\n  }\n}\n"
  },
  {
    "path": "src/scales/scale.logarithmic.js",
    "content": "import {finiteOrDefault, isFinite} from '../helpers/helpers.core.js';\nimport {formatNumber} from '../helpers/helpers.intl.js';\nimport {_setMinAndMaxByKey, log10} from '../helpers/helpers.math.js';\nimport Scale from '../core/core.scale.js';\nimport LinearScaleBase from './scale.linearbase.js';\nimport Ticks from '../core/core.ticks.js';\n\nconst log10Floor = v => Math.floor(log10(v));\nconst changeExponent = (v, m) => Math.pow(10, log10Floor(v) + m);\n\nfunction isMajor(tickVal) {\n  const remain = tickVal / (Math.pow(10, log10Floor(tickVal)));\n  return remain === 1;\n}\n\nfunction steps(min, max, rangeExp) {\n  const rangeStep = Math.pow(10, rangeExp);\n  const start = Math.floor(min / rangeStep);\n  const end = Math.ceil(max / rangeStep);\n  return end - start;\n}\n\nfunction startExp(min, max) {\n  const range = max - min;\n  let rangeExp = log10Floor(range);\n  while (steps(min, max, rangeExp) > 10) {\n    rangeExp++;\n  }\n  while (steps(min, max, rangeExp) < 10) {\n    rangeExp--;\n  }\n  return Math.min(rangeExp, log10Floor(min));\n}\n\n\n/**\n * Generate a set of logarithmic ticks\n * @param generationOptions the options used to generate the ticks\n * @param dataRange the range of the data\n * @returns {object[]} array of tick objects\n */\nfunction generateTicks(generationOptions, {min, max}) {\n  min = finiteOrDefault(generationOptions.min, min);\n  const ticks = [];\n  const minExp = log10Floor(min);\n  let exp = startExp(min, max);\n  let precision = exp < 0 ? Math.pow(10, Math.abs(exp)) : 1;\n  const stepSize = Math.pow(10, exp);\n  const base = minExp > exp ? Math.pow(10, minExp) : 0;\n  const start = Math.round((min - base) * precision) / precision;\n  const offset = Math.floor((min - base) / stepSize / 10) * stepSize * 10;\n  let significand = Math.floor((start - offset) / Math.pow(10, exp));\n  let value = finiteOrDefault(generationOptions.min, Math.round((base + offset + significand * Math.pow(10, exp)) * precision) / precision);\n  while (value < max) {\n    ticks.push({value, major: isMajor(value), significand});\n    if (significand >= 10) {\n      significand = significand < 15 ? 15 : 20;\n    } else {\n      significand++;\n    }\n    if (significand >= 20) {\n      exp++;\n      significand = 2;\n      precision = exp >= 0 ? 1 : precision;\n    }\n    value = Math.round((base + offset + significand * Math.pow(10, exp)) * precision) / precision;\n  }\n  const lastTick = finiteOrDefault(generationOptions.max, value);\n  ticks.push({value: lastTick, major: isMajor(lastTick), significand});\n\n  return ticks;\n}\n\nexport default class LogarithmicScale extends Scale {\n\n  static id = 'logarithmic';\n\n  /**\n   * @type {any}\n   */\n  static defaults = {\n    ticks: {\n      callback: Ticks.formatters.logarithmic,\n      major: {\n        enabled: true\n      }\n    }\n  };\n\n\n  constructor(cfg) {\n    super(cfg);\n\n    /** @type {number} */\n    this.start = undefined;\n    /** @type {number} */\n    this.end = undefined;\n    /** @type {number} */\n    this._startValue = undefined;\n    this._valueRange = 0;\n  }\n\n  parse(raw, index) {\n    const value = LinearScaleBase.prototype.parse.apply(this, [raw, index]);\n    if (value === 0) {\n      this._zero = true;\n      return undefined;\n    }\n    return isFinite(value) && value > 0 ? value : null;\n  }\n\n  determineDataLimits() {\n    const {min, max} = this.getMinMax(true);\n\n    this.min = isFinite(min) ? Math.max(0, min) : null;\n    this.max = isFinite(max) ? Math.max(0, max) : null;\n\n    if (this.options.beginAtZero) {\n      this._zero = true;\n    }\n\n    // if data has `0` in it or `beginAtZero` is true, min (non zero) value is at bottom\n    // of scale, and it does not equal suggestedMin, lower the min bound by one exp.\n    if (this._zero && this.min !== this._suggestedMin && !isFinite(this._userMin)) {\n      this.min = min === changeExponent(this.min, 0) ? changeExponent(this.min, -1) : changeExponent(this.min, 0);\n    }\n\n    this.handleTickRangeOptions();\n  }\n\n  handleTickRangeOptions() {\n    const {minDefined, maxDefined} = this.getUserBounds();\n    let min = this.min;\n    let max = this.max;\n\n    const setMin = v => (min = minDefined ? min : v);\n    const setMax = v => (max = maxDefined ? max : v);\n\n    if (min === max) {\n      if (min <= 0) { // includes null\n        setMin(1);\n        setMax(10);\n      } else {\n        setMin(changeExponent(min, -1));\n        setMax(changeExponent(max, +1));\n      }\n    }\n    if (min <= 0) {\n      setMin(changeExponent(max, -1));\n    }\n    if (max <= 0) {\n\n      setMax(changeExponent(min, +1));\n    }\n\n    this.min = min;\n    this.max = max;\n  }\n\n  buildTicks() {\n    const opts = this.options;\n\n    const generationOptions = {\n      min: this._userMin,\n      max: this._userMax\n    };\n    const ticks = generateTicks(generationOptions, this);\n\n    // At this point, we need to update our max and min given the tick values,\n    // since we probably have expanded the range of the scale\n    if (opts.bounds === 'ticks') {\n      _setMinAndMaxByKey(ticks, this, 'value');\n    }\n\n    if (opts.reverse) {\n      ticks.reverse();\n\n      this.start = this.max;\n      this.end = this.min;\n    } else {\n      this.start = this.min;\n      this.end = this.max;\n    }\n\n    return ticks;\n  }\n\n  /**\n\t * @param {number} value\n\t * @return {string}\n\t */\n  getLabelForValue(value) {\n    return value === undefined\n      ? '0'\n      : formatNumber(value, this.chart.options.locale, this.options.ticks.format);\n  }\n\n  /**\n\t * @protected\n\t */\n  configure() {\n    const start = this.min;\n\n    super.configure();\n\n    this._startValue = log10(start);\n    this._valueRange = log10(this.max) - log10(start);\n  }\n\n  getPixelForValue(value) {\n    if (value === undefined || value === 0) {\n      value = this.min;\n    }\n    if (value === null || isNaN(value)) {\n      return NaN;\n    }\n    return this.getPixelForDecimal(value === this.min\n      ? 0\n      : (log10(value) - this._startValue) / this._valueRange);\n  }\n\n  getValueForPixel(pixel) {\n    const decimal = this.getDecimalForPixel(pixel);\n    return Math.pow(10, this._startValue + decimal * this._valueRange);\n  }\n}\n"
  },
  {
    "path": "src/scales/scale.radialLinear.js",
    "content": "import defaults from '../core/core.defaults.js';\nimport {_longestText, addRoundedRectPath, renderText, _isPointInArea} from '../helpers/helpers.canvas.js';\nimport {HALF_PI, TAU, toDegrees, toRadians, _normalizeAngle, PI} from '../helpers/helpers.math.js';\nimport LinearScaleBase from './scale.linearbase.js';\nimport Ticks from '../core/core.ticks.js';\nimport {valueOrDefault, isArray, isFinite, callback as callCallback, isNullOrUndef} from '../helpers/helpers.core.js';\nimport {createContext, toFont, toPadding, toTRBLCorners} from '../helpers/helpers.options.js';\n\nfunction getTickBackdropHeight(opts) {\n  const tickOpts = opts.ticks;\n\n  if (tickOpts.display && opts.display) {\n    const padding = toPadding(tickOpts.backdropPadding);\n    return valueOrDefault(tickOpts.font && tickOpts.font.size, defaults.font.size) + padding.height;\n  }\n  return 0;\n}\n\nfunction measureLabelSize(ctx, font, label) {\n  label = isArray(label) ? label : [label];\n  return {\n    w: _longestText(ctx, font.string, label),\n    h: label.length * font.lineHeight\n  };\n}\n\nfunction determineLimits(angle, pos, size, min, max) {\n  if (angle === min || angle === max) {\n    return {\n      start: pos - (size / 2),\n      end: pos + (size / 2)\n    };\n  } else if (angle < min || angle > max) {\n    return {\n      start: pos - size,\n      end: pos\n    };\n  }\n\n  return {\n    start: pos,\n    end: pos + size\n  };\n}\n\n/**\n * Helper function to fit a radial linear scale with point labels\n */\nfunction fitWithPointLabels(scale) {\n\n  // Right, this is really confusing and there is a lot of maths going on here\n  // The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9\n  //\n  // Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif\n  //\n  // Solution:\n  //\n  // We assume the radius of the polygon is half the size of the canvas at first\n  // at each index we check if the text overlaps.\n  //\n  // Where it does, we store that angle and that index.\n  //\n  // After finding the largest index and angle we calculate how much we need to remove\n  // from the shape radius to move the point inwards by that x.\n  //\n  // We average the left and right distances to get the maximum shape radius that can fit in the box\n  // along with labels.\n  //\n  // Once we have that, we can find the centre point for the chart, by taking the x text protrusion\n  // on each side, removing that from the size, halving it and adding the left x protrusion width.\n  //\n  // This will mean we have a shape fitted to the canvas, as large as it can be with the labels\n  // and position it in the most space efficient manner\n  //\n  // https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif\n\n  // Get maximum radius of the polygon. Either half the height (minus the text width) or half the width.\n  // Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points\n  const orig = {\n    l: scale.left + scale._padding.left,\n    r: scale.right - scale._padding.right,\n    t: scale.top + scale._padding.top,\n    b: scale.bottom - scale._padding.bottom\n  };\n  const limits = Object.assign({}, orig);\n  const labelSizes = [];\n  const padding = [];\n  const valueCount = scale._pointLabels.length;\n  const pointLabelOpts = scale.options.pointLabels;\n  const additionalAngle = pointLabelOpts.centerPointLabels ? PI / valueCount : 0;\n\n  for (let i = 0; i < valueCount; i++) {\n    const opts = pointLabelOpts.setContext(scale.getPointLabelContext(i));\n    padding[i] = opts.padding;\n    const pointPosition = scale.getPointPosition(i, scale.drawingArea + padding[i], additionalAngle);\n    const plFont = toFont(opts.font);\n    const textSize = measureLabelSize(scale.ctx, plFont, scale._pointLabels[i]);\n    labelSizes[i] = textSize;\n\n    const angleRadians = _normalizeAngle(scale.getIndexAngle(i) + additionalAngle);\n    const angle = Math.round(toDegrees(angleRadians));\n    const hLimits = determineLimits(angle, pointPosition.x, textSize.w, 0, 180);\n    const vLimits = determineLimits(angle, pointPosition.y, textSize.h, 90, 270);\n    updateLimits(limits, orig, angleRadians, hLimits, vLimits);\n  }\n\n  scale.setCenterPoint(\n    orig.l - limits.l,\n    limits.r - orig.r,\n    orig.t - limits.t,\n    limits.b - orig.b\n  );\n\n  // Now that text size is determined, compute the full positions\n  scale._pointLabelItems = buildPointLabelItems(scale, labelSizes, padding);\n}\n\nfunction updateLimits(limits, orig, angle, hLimits, vLimits) {\n  const sin = Math.abs(Math.sin(angle));\n  const cos = Math.abs(Math.cos(angle));\n  let x = 0;\n  let y = 0;\n  if (hLimits.start < orig.l) {\n    x = (orig.l - hLimits.start) / sin;\n    limits.l = Math.min(limits.l, orig.l - x);\n  } else if (hLimits.end > orig.r) {\n    x = (hLimits.end - orig.r) / sin;\n    limits.r = Math.max(limits.r, orig.r + x);\n  }\n  if (vLimits.start < orig.t) {\n    y = (orig.t - vLimits.start) / cos;\n    limits.t = Math.min(limits.t, orig.t - y);\n  } else if (vLimits.end > orig.b) {\n    y = (vLimits.end - orig.b) / cos;\n    limits.b = Math.max(limits.b, orig.b + y);\n  }\n}\n\nfunction createPointLabelItem(scale, index, itemOpts) {\n  const outerDistance = scale.drawingArea;\n  const {extra, additionalAngle, padding, size} = itemOpts;\n  const pointLabelPosition = scale.getPointPosition(index, outerDistance + extra + padding, additionalAngle);\n  const angle = Math.round(toDegrees(_normalizeAngle(pointLabelPosition.angle + HALF_PI)));\n  const y = yForAngle(pointLabelPosition.y, size.h, angle);\n  const textAlign = getTextAlignForAngle(angle);\n  const left = leftForTextAlign(pointLabelPosition.x, size.w, textAlign);\n  return {\n    // if to draw or overlapped\n    visible: true,\n\n    // Text position\n    x: pointLabelPosition.x,\n    y,\n\n    // Text rendering data\n    textAlign,\n\n    // Bounding box\n    left,\n    top: y,\n    right: left + size.w,\n    bottom: y + size.h\n  };\n}\n\nfunction isNotOverlapped(item, area) {\n  if (!area) {\n    return true;\n  }\n  const {left, top, right, bottom} = item;\n  const apexesInArea = _isPointInArea({x: left, y: top}, area) || _isPointInArea({x: left, y: bottom}, area) ||\n    _isPointInArea({x: right, y: top}, area) || _isPointInArea({x: right, y: bottom}, area);\n  return !apexesInArea;\n}\n\nfunction buildPointLabelItems(scale, labelSizes, padding) {\n  const items = [];\n  const valueCount = scale._pointLabels.length;\n  const opts = scale.options;\n  const {centerPointLabels, display} = opts.pointLabels;\n  const itemOpts = {\n    extra: getTickBackdropHeight(opts) / 2,\n    additionalAngle: centerPointLabels ? PI / valueCount : 0\n  };\n  let area;\n\n  for (let i = 0; i < valueCount; i++) {\n    itemOpts.padding = padding[i];\n    itemOpts.size = labelSizes[i];\n\n    const item = createPointLabelItem(scale, i, itemOpts);\n    items.push(item);\n    if (display === 'auto') {\n      item.visible = isNotOverlapped(item, area);\n      if (item.visible) {\n        area = item;\n      }\n    }\n  }\n  return items;\n}\n\nfunction getTextAlignForAngle(angle) {\n  if (angle === 0 || angle === 180) {\n    return 'center';\n  } else if (angle < 180) {\n    return 'left';\n  }\n\n  return 'right';\n}\n\nfunction leftForTextAlign(x, w, align) {\n  if (align === 'right') {\n    x -= w;\n  } else if (align === 'center') {\n    x -= (w / 2);\n  }\n  return x;\n}\n\nfunction yForAngle(y, h, angle) {\n  if (angle === 90 || angle === 270) {\n    y -= (h / 2);\n  } else if (angle > 270 || angle < 90) {\n    y -= h;\n  }\n  return y;\n}\n\nfunction drawPointLabelBox(ctx, opts, item) {\n  const {left, top, right, bottom} = item;\n  const {backdropColor} = opts;\n\n  if (!isNullOrUndef(backdropColor)) {\n    const borderRadius = toTRBLCorners(opts.borderRadius);\n    const padding = toPadding(opts.backdropPadding);\n    ctx.fillStyle = backdropColor;\n\n    const backdropLeft = left - padding.left;\n    const backdropTop = top - padding.top;\n    const backdropWidth = right - left + padding.width;\n    const backdropHeight = bottom - top + padding.height;\n\n    if (Object.values(borderRadius).some(v => v !== 0)) {\n      ctx.beginPath();\n      addRoundedRectPath(ctx, {\n        x: backdropLeft,\n        y: backdropTop,\n        w: backdropWidth,\n        h: backdropHeight,\n        radius: borderRadius,\n      });\n      ctx.fill();\n    } else {\n      ctx.fillRect(backdropLeft, backdropTop, backdropWidth, backdropHeight);\n    }\n  }\n}\n\nfunction drawPointLabels(scale, labelCount) {\n  const {ctx, options: {pointLabels}} = scale;\n\n  for (let i = labelCount - 1; i >= 0; i--) {\n    const item = scale._pointLabelItems[i];\n    if (!item.visible) {\n      // overlapping\n      continue;\n    }\n    const optsAtIndex = pointLabels.setContext(scale.getPointLabelContext(i));\n    drawPointLabelBox(ctx, optsAtIndex, item);\n    const plFont = toFont(optsAtIndex.font);\n    const {x, y, textAlign} = item;\n\n    renderText(\n      ctx,\n      scale._pointLabels[i],\n      x,\n      y + (plFont.lineHeight / 2),\n      plFont,\n      {\n        color: optsAtIndex.color,\n        textAlign: textAlign,\n        textBaseline: 'middle'\n      }\n    );\n  }\n}\n\nfunction pathRadiusLine(scale, radius, circular, labelCount) {\n  const {ctx} = scale;\n  if (circular) {\n    // Draw circular arcs between the points\n    ctx.arc(scale.xCenter, scale.yCenter, radius, 0, TAU);\n  } else {\n    // Draw straight lines connecting each index\n    let pointPosition = scale.getPointPosition(0, radius);\n    ctx.moveTo(pointPosition.x, pointPosition.y);\n\n    for (let i = 1; i < labelCount; i++) {\n      pointPosition = scale.getPointPosition(i, radius);\n      ctx.lineTo(pointPosition.x, pointPosition.y);\n    }\n  }\n}\n\nfunction drawRadiusLine(scale, gridLineOpts, radius, labelCount, borderOpts) {\n  const ctx = scale.ctx;\n  const circular = gridLineOpts.circular;\n\n  const {color, lineWidth} = gridLineOpts;\n\n  if ((!circular && !labelCount) || !color || !lineWidth || radius < 0) {\n    return;\n  }\n\n  ctx.save();\n  ctx.strokeStyle = color;\n  ctx.lineWidth = lineWidth;\n  ctx.setLineDash(borderOpts.dash || []);\n  ctx.lineDashOffset = borderOpts.dashOffset;\n\n  ctx.beginPath();\n  pathRadiusLine(scale, radius, circular, labelCount);\n  ctx.closePath();\n  ctx.stroke();\n  ctx.restore();\n}\n\nfunction createPointLabelContext(parent, index, label) {\n  return createContext(parent, {\n    label,\n    index,\n    type: 'pointLabel'\n  });\n}\n\nexport default class RadialLinearScale extends LinearScaleBase {\n\n  static id = 'radialLinear';\n\n  /**\n   * @type {any}\n   */\n  static defaults = {\n    display: true,\n\n    // Boolean - Whether to animate scaling the chart from the centre\n    animate: true,\n    position: 'chartArea',\n\n    angleLines: {\n      display: true,\n      lineWidth: 1,\n      borderDash: [],\n      borderDashOffset: 0.0\n    },\n\n    grid: {\n      circular: false\n    },\n\n    startAngle: 0,\n\n    // label settings\n    ticks: {\n      // Boolean - Show a backdrop to the scale label\n      showLabelBackdrop: true,\n\n      callback: Ticks.formatters.numeric\n    },\n\n    pointLabels: {\n      backdropColor: undefined,\n\n      // Number - The backdrop padding above & below the label in pixels\n      backdropPadding: 2,\n\n      // Boolean - if true, show point labels\n      display: true,\n\n      // Number - Point label font size in pixels\n      font: {\n        size: 10\n      },\n\n      // Function - Used to convert point labels\n      callback(label) {\n        return label;\n      },\n\n      // Number - Additionl padding between scale and pointLabel\n      padding: 5,\n\n      // Boolean - if true, center point labels to slices in polar chart\n      centerPointLabels: false\n    }\n  };\n\n  static defaultRoutes = {\n    'angleLines.color': 'borderColor',\n    'pointLabels.color': 'color',\n    'ticks.color': 'color'\n  };\n\n  static descriptors = {\n    angleLines: {\n      _fallback: 'grid'\n    }\n  };\n\n  constructor(cfg) {\n    super(cfg);\n\n    /** @type {number} */\n    this.xCenter = undefined;\n    /** @type {number} */\n    this.yCenter = undefined;\n    /** @type {number} */\n    this.drawingArea = undefined;\n    /** @type {string[]} */\n    this._pointLabels = [];\n    this._pointLabelItems = [];\n  }\n\n  setDimensions() {\n    // Set the unconstrained dimension before label rotation\n    const padding = this._padding = toPadding(getTickBackdropHeight(this.options) / 2);\n    const w = this.width = this.maxWidth - padding.width;\n    const h = this.height = this.maxHeight - padding.height;\n    this.xCenter = Math.floor(this.left + w / 2 + padding.left);\n    this.yCenter = Math.floor(this.top + h / 2 + padding.top);\n    this.drawingArea = Math.floor(Math.min(w, h) / 2);\n  }\n\n  determineDataLimits() {\n    const {min, max} = this.getMinMax(false);\n\n    this.min = isFinite(min) && !isNaN(min) ? min : 0;\n    this.max = isFinite(max) && !isNaN(max) ? max : 0;\n\n    // Common base implementation to handle min, max, beginAtZero\n    this.handleTickRangeOptions();\n  }\n\n  /**\n\t * Returns the maximum number of ticks based on the scale dimension\n\t * @protected\n\t */\n  computeTickLimit() {\n    return Math.ceil(this.drawingArea / getTickBackdropHeight(this.options));\n  }\n\n  generateTickLabels(ticks) {\n    LinearScaleBase.prototype.generateTickLabels.call(this, ticks);\n\n    // Point labels\n    this._pointLabels = this.getLabels()\n      .map((value, index) => {\n        const label = callCallback(this.options.pointLabels.callback, [value, index], this);\n        return label || label === 0 ? label : '';\n      })\n      .filter((v, i) => this.chart.getDataVisibility(i));\n  }\n\n  fit() {\n    const opts = this.options;\n\n    if (opts.display && opts.pointLabels.display) {\n      fitWithPointLabels(this);\n    } else {\n      this.setCenterPoint(0, 0, 0, 0);\n    }\n  }\n\n  setCenterPoint(leftMovement, rightMovement, topMovement, bottomMovement) {\n    this.xCenter += Math.floor((leftMovement - rightMovement) / 2);\n    this.yCenter += Math.floor((topMovement - bottomMovement) / 2);\n    this.drawingArea -= Math.min(this.drawingArea / 2, Math.max(leftMovement, rightMovement, topMovement, bottomMovement));\n  }\n\n  getIndexAngle(index) {\n    const angleMultiplier = TAU / (this._pointLabels.length || 1);\n    const startAngle = this.options.startAngle || 0;\n\n    return _normalizeAngle(index * angleMultiplier + toRadians(startAngle));\n  }\n\n  getDistanceFromCenterForValue(value) {\n    if (isNullOrUndef(value)) {\n      return NaN;\n    }\n\n    // Take into account half font size + the yPadding of the top value\n    const scalingFactor = this.drawingArea / (this.max - this.min);\n    if (this.options.reverse) {\n      return (this.max - value) * scalingFactor;\n    }\n    return (value - this.min) * scalingFactor;\n  }\n\n  getValueForDistanceFromCenter(distance) {\n    if (isNullOrUndef(distance)) {\n      return NaN;\n    }\n\n    const scaledDistance = distance / (this.drawingArea / (this.max - this.min));\n    return this.options.reverse ? this.max - scaledDistance : this.min + scaledDistance;\n  }\n\n  getPointLabelContext(index) {\n    const pointLabels = this._pointLabels || [];\n\n    if (index >= 0 && index < pointLabels.length) {\n      const pointLabel = pointLabels[index];\n      return createPointLabelContext(this.getContext(), index, pointLabel);\n    }\n  }\n\n  getPointPosition(index, distanceFromCenter, additionalAngle = 0) {\n    const angle = this.getIndexAngle(index) - HALF_PI + additionalAngle;\n    return {\n      x: Math.cos(angle) * distanceFromCenter + this.xCenter,\n      y: Math.sin(angle) * distanceFromCenter + this.yCenter,\n      angle\n    };\n  }\n\n  getPointPositionForValue(index, value) {\n    return this.getPointPosition(index, this.getDistanceFromCenterForValue(value));\n  }\n\n  getBasePosition(index) {\n    return this.getPointPositionForValue(index || 0, this.getBaseValue());\n  }\n\n  getPointLabelPosition(index) {\n    const {left, top, right, bottom} = this._pointLabelItems[index];\n    return {\n      left,\n      top,\n      right,\n      bottom,\n    };\n  }\n\n  /**\n\t * @protected\n\t */\n  drawBackground() {\n    const {backgroundColor, grid: {circular}} = this.options;\n    if (backgroundColor) {\n      const ctx = this.ctx;\n      ctx.save();\n      ctx.beginPath();\n      pathRadiusLine(this, this.getDistanceFromCenterForValue(this._endValue), circular, this._pointLabels.length);\n      ctx.closePath();\n      ctx.fillStyle = backgroundColor;\n      ctx.fill();\n      ctx.restore();\n    }\n  }\n\n  /**\n\t * @protected\n\t */\n  drawGrid() {\n    const ctx = this.ctx;\n    const opts = this.options;\n    const {angleLines, grid, border} = opts;\n    const labelCount = this._pointLabels.length;\n\n    let i, offset, position;\n\n    if (opts.pointLabels.display) {\n      drawPointLabels(this, labelCount);\n    }\n\n    if (grid.display) {\n      this.ticks.forEach((tick, index) => {\n        if (index !== 0 || (index === 0 && this.min < 0)) {\n          offset = this.getDistanceFromCenterForValue(tick.value);\n          const context = this.getContext(index);\n          const optsAtIndex = grid.setContext(context);\n          const optsAtIndexBorder = border.setContext(context);\n\n          drawRadiusLine(this, optsAtIndex, offset, labelCount, optsAtIndexBorder);\n        }\n      });\n    }\n\n    if (angleLines.display) {\n      ctx.save();\n\n      for (i = labelCount - 1; i >= 0; i--) {\n        const optsAtIndex = angleLines.setContext(this.getPointLabelContext(i));\n        const {color, lineWidth} = optsAtIndex;\n\n        if (!lineWidth || !color) {\n          continue;\n        }\n\n        ctx.lineWidth = lineWidth;\n        ctx.strokeStyle = color;\n\n        ctx.setLineDash(optsAtIndex.borderDash);\n        ctx.lineDashOffset = optsAtIndex.borderDashOffset;\n\n        offset = this.getDistanceFromCenterForValue(opts.reverse ? this.min : this.max);\n        position = this.getPointPosition(i, offset);\n        ctx.beginPath();\n        ctx.moveTo(this.xCenter, this.yCenter);\n        ctx.lineTo(position.x, position.y);\n        ctx.stroke();\n      }\n\n      ctx.restore();\n    }\n  }\n\n  /**\n\t * @protected\n\t */\n  drawBorder() {}\n\n  /**\n\t * @protected\n\t */\n  drawLabels() {\n    const ctx = this.ctx;\n    const opts = this.options;\n    const tickOpts = opts.ticks;\n\n    if (!tickOpts.display) {\n      return;\n    }\n\n    const startAngle = this.getIndexAngle(0);\n    let offset, width;\n\n    ctx.save();\n    ctx.translate(this.xCenter, this.yCenter);\n    ctx.rotate(startAngle);\n    ctx.textAlign = 'center';\n    ctx.textBaseline = 'middle';\n\n    this.ticks.forEach((tick, index) => {\n      if ((index === 0 && this.min >= 0) && !opts.reverse) {\n        return;\n      }\n\n      const optsAtIndex = tickOpts.setContext(this.getContext(index));\n      const tickFont = toFont(optsAtIndex.font);\n      offset = this.getDistanceFromCenterForValue(this.ticks[index].value);\n\n      if (optsAtIndex.showLabelBackdrop) {\n        ctx.font = tickFont.string;\n        width = ctx.measureText(tick.label).width;\n        ctx.fillStyle = optsAtIndex.backdropColor;\n\n        const padding = toPadding(optsAtIndex.backdropPadding);\n        ctx.fillRect(\n          -width / 2 - padding.left,\n          -offset - tickFont.size / 2 - padding.top,\n          width + padding.width,\n          tickFont.size + padding.height\n        );\n      }\n\n      renderText(ctx, tick.label, 0, -offset, tickFont, {\n        color: optsAtIndex.color,\n        strokeColor: optsAtIndex.textStrokeColor,\n        strokeWidth: optsAtIndex.textStrokeWidth,\n      });\n    });\n\n    ctx.restore();\n  }\n\n  /**\n\t * @protected\n\t */\n  drawTitle() {}\n}\n"
  },
  {
    "path": "src/scales/scale.time.js",
    "content": "import adapters from '../core/core.adapters.js';\nimport {callback as call, isFinite, isNullOrUndef, mergeIf, valueOrDefault} from '../helpers/helpers.core.js';\nimport {toRadians, isNumber, _limitValue} from '../helpers/helpers.math.js';\nimport Scale from '../core/core.scale.js';\nimport {_arrayUnique, _filterBetween, _lookup} from '../helpers/helpers.collection.js';\n\n/**\n * @typedef { import('../core/core.adapters.js').TimeUnit } Unit\n * @typedef {{common: boolean, size: number, steps?: number}} Interval\n * @typedef { import('../core/core.adapters.js').DateAdapter } DateAdapter\n */\n\n/**\n * @type {Object<Unit, Interval>}\n */\nconst INTERVALS = {\n  millisecond: {common: true, size: 1, steps: 1000},\n  second: {common: true, size: 1000, steps: 60},\n  minute: {common: true, size: 60000, steps: 60},\n  hour: {common: true, size: 3600000, steps: 24},\n  day: {common: true, size: 86400000, steps: 30},\n  week: {common: false, size: 604800000, steps: 4},\n  month: {common: true, size: 2.628e9, steps: 12},\n  quarter: {common: false, size: 7.884e9, steps: 4},\n  year: {common: true, size: 3.154e10}\n};\n\n/**\n * @type {Unit[]}\n */\nconst UNITS = /** @type Unit[] */ /* #__PURE__ */ (Object.keys(INTERVALS));\n\n/**\n * @param {number} a\n * @param {number} b\n */\nfunction sorter(a, b) {\n  return a - b;\n}\n\n/**\n * @param {TimeScale} scale\n * @param {*} input\n * @return {number}\n */\nfunction parse(scale, input) {\n  if (isNullOrUndef(input)) {\n    return null;\n  }\n\n  const adapter = scale._adapter;\n  const {parser, round, isoWeekday} = scale._parseOpts;\n  let value = input;\n\n  if (typeof parser === 'function') {\n    value = parser(value);\n  }\n\n  // Only parse if it's not a timestamp already\n  if (!isFinite(value)) {\n    value = typeof parser === 'string'\n      ? adapter.parse(value, parser)\n      : adapter.parse(value);\n  }\n\n  if (value === null) {\n    return null;\n  }\n\n  if (round) {\n    value = round === 'week' && (isNumber(isoWeekday) || isoWeekday === true)\n      ? adapter.startOf(value, 'isoWeek', isoWeekday)\n      : adapter.startOf(value, round);\n  }\n\n  return +value;\n}\n\n/**\n * Figures out what unit results in an appropriate number of auto-generated ticks\n * @param {Unit} minUnit\n * @param {number} min\n * @param {number} max\n * @param {number} capacity\n * @return {object}\n */\nfunction determineUnitForAutoTicks(minUnit, min, max, capacity) {\n  const ilen = UNITS.length;\n\n  for (let i = UNITS.indexOf(minUnit); i < ilen - 1; ++i) {\n    const interval = INTERVALS[UNITS[i]];\n    const factor = interval.steps ? interval.steps : Number.MAX_SAFE_INTEGER;\n\n    if (interval.common && Math.ceil((max - min) / (factor * interval.size)) <= capacity) {\n      return UNITS[i];\n    }\n  }\n\n  return UNITS[ilen - 1];\n}\n\n/**\n * Figures out what unit to format a set of ticks with\n * @param {TimeScale} scale\n * @param {number} numTicks\n * @param {Unit} minUnit\n * @param {number} min\n * @param {number} max\n * @return {Unit}\n */\nfunction determineUnitForFormatting(scale, numTicks, minUnit, min, max) {\n  for (let i = UNITS.length - 1; i >= UNITS.indexOf(minUnit); i--) {\n    const unit = UNITS[i];\n    if (INTERVALS[unit].common && scale._adapter.diff(max, min, unit) >= numTicks - 1) {\n      return unit;\n    }\n  }\n\n  return UNITS[minUnit ? UNITS.indexOf(minUnit) : 0];\n}\n\n/**\n * @param {Unit} unit\n * @return {object}\n */\nfunction determineMajorUnit(unit) {\n  for (let i = UNITS.indexOf(unit) + 1, ilen = UNITS.length; i < ilen; ++i) {\n    if (INTERVALS[UNITS[i]].common) {\n      return UNITS[i];\n    }\n  }\n}\n\n/**\n * @param {object} ticks\n * @param {number} time\n * @param {number[]} [timestamps] - if defined, snap to these timestamps\n */\nfunction addTick(ticks, time, timestamps) {\n  if (!timestamps) {\n    ticks[time] = true;\n  } else if (timestamps.length) {\n    const {lo, hi} = _lookup(timestamps, time);\n    const timestamp = timestamps[lo] >= time ? timestamps[lo] : timestamps[hi];\n    ticks[timestamp] = true;\n  }\n}\n\n/**\n * @param {TimeScale} scale\n * @param {object[]} ticks\n * @param {object} map\n * @param {Unit} majorUnit\n * @return {object[]}\n */\nfunction setMajorTicks(scale, ticks, map, majorUnit) {\n  const adapter = scale._adapter;\n  const first = +adapter.startOf(ticks[0].value, majorUnit);\n  const last = ticks[ticks.length - 1].value;\n  let major, index;\n\n  for (major = first; major <= last; major = +adapter.add(major, 1, majorUnit)) {\n    index = map[major];\n    if (index >= 0) {\n      ticks[index].major = true;\n    }\n  }\n  return ticks;\n}\n\n/**\n * @param {TimeScale} scale\n * @param {number[]} values\n * @param {Unit|undefined} [majorUnit]\n * @return {object[]}\n */\nfunction ticksFromTimestamps(scale, values, majorUnit) {\n  const ticks = [];\n  /** @type {Object<number,object>} */\n  const map = {};\n  const ilen = values.length;\n  let i, value;\n\n  for (i = 0; i < ilen; ++i) {\n    value = values[i];\n    map[value] = i;\n\n    ticks.push({\n      value,\n      major: false\n    });\n  }\n\n  // We set the major ticks separately from the above loop because calling startOf for every tick\n  // is expensive when there is a large number of ticks\n  return (ilen === 0 || !majorUnit) ? ticks : setMajorTicks(scale, ticks, map, majorUnit);\n}\n\nexport default class TimeScale extends Scale {\n\n  static id = 'time';\n\n  /**\n   * @type {any}\n   */\n  static defaults = {\n    /**\n     * Scale boundary strategy (bypassed by min/max time options)\n     * - `data`: make sure data are fully visible, ticks outside are removed\n     * - `ticks`: make sure ticks are fully visible, data outside are truncated\n     * @see https://github.com/chartjs/Chart.js/pull/4556\n     * @since 2.7.0\n     */\n    bounds: 'data',\n\n    adapters: {},\n    time: {\n      parser: false, // false == a pattern string from or a custom callback that converts its argument to a timestamp\n      unit: false, // false == automatic or override with week, month, year, etc.\n      round: false, // none, or override with week, month, year, etc.\n      isoWeekday: false, // override week start day\n      minUnit: 'millisecond',\n      displayFormats: {}\n    },\n    ticks: {\n      /**\n       * Ticks generation input values:\n       * - 'auto': generates \"optimal\" ticks based on scale size and time options.\n       * - 'data': generates ticks from data (including labels from data {t|x|y} objects).\n       * - 'labels': generates ticks from user given `data.labels` values ONLY.\n       * @see https://github.com/chartjs/Chart.js/pull/4507\n       * @since 2.7.0\n       */\n      source: 'auto',\n\n      callback: false,\n\n      major: {\n        enabled: false\n      }\n    }\n  };\n\n  /**\n\t * @param {object} props\n\t */\n  constructor(props) {\n    super(props);\n\n    /** @type {{data: number[], labels: number[], all: number[]}} */\n    this._cache = {\n      data: [],\n      labels: [],\n      all: []\n    };\n\n    /** @type {Unit} */\n    this._unit = 'day';\n    /** @type {Unit=} */\n    this._majorUnit = undefined;\n    this._offsets = {};\n    this._normalized = false;\n    this._parseOpts = undefined;\n  }\n\n  init(scaleOpts, opts = {}) {\n    const time = scaleOpts.time || (scaleOpts.time = {});\n    /** @type {DateAdapter} */\n    const adapter = this._adapter = new adapters._date(scaleOpts.adapters.date);\n\n    adapter.init(opts);\n\n    // Backward compatibility: before introducing adapter, `displayFormats` was\n    // supposed to contain *all* unit/string pairs but this can't be resolved\n    // when loading the scale (adapters are loaded afterward), so let's populate\n    // missing formats on update\n    mergeIf(time.displayFormats, adapter.formats());\n\n    this._parseOpts = {\n      parser: time.parser,\n      round: time.round,\n      isoWeekday: time.isoWeekday\n    };\n\n    super.init(scaleOpts);\n\n    this._normalized = opts.normalized;\n  }\n\n  /**\n\t * @param {*} raw\n\t * @param {number?} [index]\n\t * @return {number}\n\t */\n  parse(raw, index) { // eslint-disable-line no-unused-vars\n    if (raw === undefined) {\n      return null;\n    }\n    return parse(this, raw);\n  }\n\n  beforeLayout() {\n    super.beforeLayout();\n    this._cache = {\n      data: [],\n      labels: [],\n      all: []\n    };\n  }\n\n  determineDataLimits() {\n    const options = this.options;\n    const adapter = this._adapter;\n    const unit = options.time.unit || 'day';\n    // eslint-disable-next-line prefer-const\n    let {min, max, minDefined, maxDefined} = this.getUserBounds();\n\n    /**\n\t\t * @param {object} bounds\n\t\t */\n    function _applyBounds(bounds) {\n      if (!minDefined && !isNaN(bounds.min)) {\n        min = Math.min(min, bounds.min);\n      }\n      if (!maxDefined && !isNaN(bounds.max)) {\n        max = Math.max(max, bounds.max);\n      }\n    }\n\n    // If we have user provided `min` and `max` labels / data bounds can be ignored\n    if (!minDefined || !maxDefined) {\n      // Labels are always considered, when user did not force bounds\n      _applyBounds(this._getLabelBounds());\n\n      // If `bounds` is `'ticks'` and `ticks.source` is `'labels'`,\n      // data bounds are ignored (and don't need to be determined)\n      if (options.bounds !== 'ticks' || options.ticks.source !== 'labels') {\n        _applyBounds(this.getMinMax(false));\n      }\n    }\n\n    min = isFinite(min) && !isNaN(min) ? min : +adapter.startOf(Date.now(), unit);\n    max = isFinite(max) && !isNaN(max) ? max : +adapter.endOf(Date.now(), unit) + 1;\n\n    // Make sure that max is strictly higher than min (required by the timeseries lookup table)\n    this.min = Math.min(min, max - 1);\n    this.max = Math.max(min + 1, max);\n  }\n\n  /**\n\t * @private\n\t */\n  _getLabelBounds() {\n    const arr = this.getLabelTimestamps();\n    let min = Number.POSITIVE_INFINITY;\n    let max = Number.NEGATIVE_INFINITY;\n\n    if (arr.length) {\n      min = arr[0];\n      max = arr[arr.length - 1];\n    }\n    return {min, max};\n  }\n\n  /**\n\t * @return {object[]}\n\t */\n  buildTicks() {\n    const options = this.options;\n    const timeOpts = options.time;\n    const tickOpts = options.ticks;\n    const timestamps = tickOpts.source === 'labels' ? this.getLabelTimestamps() : this._generate();\n\n    if (options.bounds === 'ticks' && timestamps.length) {\n      this.min = this._userMin || timestamps[0];\n      this.max = this._userMax || timestamps[timestamps.length - 1];\n    }\n\n    const min = this.min;\n    const max = this.max;\n\n    const ticks = _filterBetween(timestamps, min, max);\n\n    // PRIVATE\n    // determineUnitForFormatting relies on the number of ticks so we don't use it when\n    // autoSkip is enabled because we don't yet know what the final number of ticks will be\n    this._unit = timeOpts.unit || (tickOpts.autoSkip\n      ? determineUnitForAutoTicks(timeOpts.minUnit, this.min, this.max, this._getLabelCapacity(min))\n      : determineUnitForFormatting(this, ticks.length, timeOpts.minUnit, this.min, this.max));\n    this._majorUnit = !tickOpts.major.enabled || this._unit === 'year' ? undefined\n      : determineMajorUnit(this._unit);\n    this.initOffsets(timestamps);\n\n    if (options.reverse) {\n      ticks.reverse();\n    }\n\n    return ticksFromTimestamps(this, ticks, this._majorUnit);\n  }\n\n  afterAutoSkip() {\n    // Offsets for bar charts need to be handled with the auto skipped\n    // ticks. Once ticks have been skipped, we re-compute the offsets.\n    if (this.options.offsetAfterAutoskip) {\n      this.initOffsets(this.ticks.map(tick => +tick.value));\n    }\n  }\n\n  /**\n\t * Returns the start and end offsets from edges in the form of {start, end}\n\t * where each value is a relative width to the scale and ranges between 0 and 1.\n\t * They add extra margins on the both sides by scaling down the original scale.\n\t * Offsets are added when the `offset` option is true.\n\t * @param {number[]} timestamps\n\t * @protected\n\t */\n  initOffsets(timestamps = []) {\n    let start = 0;\n    let end = 0;\n    let first, last;\n\n    if (this.options.offset && timestamps.length) {\n      first = this.getDecimalForValue(timestamps[0]);\n      if (timestamps.length === 1) {\n        start = 1 - first;\n      } else {\n        start = (this.getDecimalForValue(timestamps[1]) - first) / 2;\n      }\n      last = this.getDecimalForValue(timestamps[timestamps.length - 1]);\n      if (timestamps.length === 1) {\n        end = last;\n      } else {\n        end = (last - this.getDecimalForValue(timestamps[timestamps.length - 2])) / 2;\n      }\n    }\n    const limit = timestamps.length < 3 ? 0.5 : 0.25;\n    start = _limitValue(start, 0, limit);\n    end = _limitValue(end, 0, limit);\n\n    this._offsets = {start, end, factor: 1 / (start + 1 + end)};\n  }\n\n  /**\n\t * Generates a maximum of `capacity` timestamps between min and max, rounded to the\n\t * `minor` unit using the given scale time `options`.\n\t * Important: this method can return ticks outside the min and max range, it's the\n\t * responsibility of the calling code to clamp values if needed.\n\t * @protected\n\t */\n  _generate() {\n    const adapter = this._adapter;\n    const min = this.min;\n    const max = this.max;\n    const options = this.options;\n    const timeOpts = options.time;\n    // @ts-ignore\n    const minor = timeOpts.unit || determineUnitForAutoTicks(timeOpts.minUnit, min, max, this._getLabelCapacity(min));\n    const stepSize = valueOrDefault(options.ticks.stepSize, 1);\n    const weekday = minor === 'week' ? timeOpts.isoWeekday : false;\n    const hasWeekday = isNumber(weekday) || weekday === true;\n    const ticks = {};\n    let first = min;\n    let time, count;\n\n    // For 'week' unit, handle the first day of week option\n    if (hasWeekday) {\n      first = +adapter.startOf(first, 'isoWeek', weekday);\n    }\n\n    // Align first ticks on unit\n    first = +adapter.startOf(first, hasWeekday ? 'day' : minor);\n\n    // Prevent browser from freezing in case user options request millions of milliseconds\n    if (adapter.diff(max, min, minor) > 100000 * stepSize) {\n      throw new Error(min + ' and ' + max + ' are too far apart with stepSize of ' + stepSize + ' ' + minor);\n    }\n\n    const timestamps = options.ticks.source === 'data' && this.getDataTimestamps();\n    for (time = first, count = 0; time < max; time = +adapter.add(time, stepSize, minor), count++) {\n      addTick(ticks, time, timestamps);\n    }\n\n    if (time === max || options.bounds === 'ticks' || count === 1) {\n      addTick(ticks, time, timestamps);\n    }\n\n    // @ts-ignore\n    return Object.keys(ticks).sort(sorter).map(x => +x);\n  }\n\n  /**\n\t * @param {number} value\n\t * @return {string}\n\t */\n  getLabelForValue(value) {\n    const adapter = this._adapter;\n    const timeOpts = this.options.time;\n\n    if (timeOpts.tooltipFormat) {\n      return adapter.format(value, timeOpts.tooltipFormat);\n    }\n    return adapter.format(value, timeOpts.displayFormats.datetime);\n  }\n\n  /**\n\t * @param {number} value\n\t * @param {string|undefined} format\n\t * @return {string}\n\t */\n  format(value, format) {\n    const options = this.options;\n    const formats = options.time.displayFormats;\n    const unit = this._unit;\n    const fmt = format || formats[unit];\n    return this._adapter.format(value, fmt);\n  }\n\n  /**\n\t * Function to format an individual tick mark\n\t * @param {number} time\n\t * @param {number} index\n\t * @param {object[]} ticks\n\t * @param {string|undefined} [format]\n\t * @return {string}\n\t * @private\n\t */\n  _tickFormatFunction(time, index, ticks, format) {\n    const options = this.options;\n    const formatter = options.ticks.callback;\n\n    if (formatter) {\n      return call(formatter, [time, index, ticks], this);\n    }\n\n    const formats = options.time.displayFormats;\n    const unit = this._unit;\n    const majorUnit = this._majorUnit;\n    const minorFormat = unit && formats[unit];\n    const majorFormat = majorUnit && formats[majorUnit];\n    const tick = ticks[index];\n    const major = majorUnit && majorFormat && tick && tick.major;\n\n    return this._adapter.format(time, format || (major ? majorFormat : minorFormat));\n  }\n\n  /**\n\t * @param {object[]} ticks\n\t */\n  generateTickLabels(ticks) {\n    let i, ilen, tick;\n\n    for (i = 0, ilen = ticks.length; i < ilen; ++i) {\n      tick = ticks[i];\n      tick.label = this._tickFormatFunction(tick.value, i, ticks);\n    }\n  }\n\n  /**\n\t * @param {number} value - Milliseconds since epoch (1 January 1970 00:00:00 UTC)\n\t * @return {number}\n\t */\n  getDecimalForValue(value) {\n    return value === null ? NaN : (value - this.min) / (this.max - this.min);\n  }\n\n  /**\n\t * @param {number} value - Milliseconds since epoch (1 January 1970 00:00:00 UTC)\n\t * @return {number}\n\t */\n  getPixelForValue(value) {\n    const offsets = this._offsets;\n    const pos = this.getDecimalForValue(value);\n    return this.getPixelForDecimal((offsets.start + pos) * offsets.factor);\n  }\n\n  /**\n\t * @param {number} pixel\n\t * @return {number}\n\t */\n  getValueForPixel(pixel) {\n    const offsets = this._offsets;\n    const pos = this.getDecimalForPixel(pixel) / offsets.factor - offsets.end;\n    return this.min + pos * (this.max - this.min);\n  }\n\n  /**\n\t * @param {string} label\n\t * @return {{w:number, h:number}}\n\t * @private\n\t */\n  _getLabelSize(label) {\n    const ticksOpts = this.options.ticks;\n    const tickLabelWidth = this.ctx.measureText(label).width;\n    const angle = toRadians(this.isHorizontal() ? ticksOpts.maxRotation : ticksOpts.minRotation);\n    const cosRotation = Math.cos(angle);\n    const sinRotation = Math.sin(angle);\n    const tickFontSize = this._resolveTickFontOptions(0).size;\n\n    return {\n      w: (tickLabelWidth * cosRotation) + (tickFontSize * sinRotation),\n      h: (tickLabelWidth * sinRotation) + (tickFontSize * cosRotation)\n    };\n  }\n\n  /**\n\t * @param {number} exampleTime\n\t * @return {number}\n\t * @private\n\t */\n  _getLabelCapacity(exampleTime) {\n    const timeOpts = this.options.time;\n    const displayFormats = timeOpts.displayFormats;\n\n    // pick the longest format (milliseconds) for guesstimation\n    const format = displayFormats[timeOpts.unit] || displayFormats.millisecond;\n    const exampleLabel = this._tickFormatFunction(exampleTime, 0, ticksFromTimestamps(this, [exampleTime], this._majorUnit), format);\n    const size = this._getLabelSize(exampleLabel);\n    // subtract 1 - if offset then there's one less label than tick\n    // if not offset then one half label padding is added to each end leaving room for one less label\n    const capacity = Math.floor(this.isHorizontal() ? this.width / size.w : this.height / size.h) - 1;\n    return capacity > 0 ? capacity : 1;\n  }\n\n  /**\n\t * @protected\n\t */\n  getDataTimestamps() {\n    let timestamps = this._cache.data || [];\n    let i, ilen;\n\n    if (timestamps.length) {\n      return timestamps;\n    }\n\n    const metas = this.getMatchingVisibleMetas();\n\n    if (this._normalized && metas.length) {\n      return (this._cache.data = metas[0].controller.getAllParsedValues(this));\n    }\n\n    for (i = 0, ilen = metas.length; i < ilen; ++i) {\n      timestamps = timestamps.concat(metas[i].controller.getAllParsedValues(this));\n    }\n\n    return (this._cache.data = this.normalize(timestamps));\n  }\n\n  /**\n\t * @protected\n\t */\n  getLabelTimestamps() {\n    const timestamps = this._cache.labels || [];\n    let i, ilen;\n\n    if (timestamps.length) {\n      return timestamps;\n    }\n\n    const labels = this.getLabels();\n    for (i = 0, ilen = labels.length; i < ilen; ++i) {\n      timestamps.push(parse(this, labels[i]));\n    }\n\n    return (this._cache.labels = this._normalized ? timestamps : this.normalize(timestamps));\n  }\n\n  /**\n\t * @param {number[]} values\n\t * @protected\n\t */\n  normalize(values) {\n    // It seems to be somewhat faster to do sorting first\n    return _arrayUnique(values.sort(sorter));\n  }\n}\n"
  },
  {
    "path": "src/scales/scale.timeseries.js",
    "content": "import TimeScale from './scale.time.js';\nimport {_lookupByKey} from '../helpers/helpers.collection.js';\n\n/**\n * Linearly interpolates the given source `val` using the table. If value is out of bounds, values\n * at edges are used for the interpolation.\n * @param {object} table\n * @param {number} val\n * @param {boolean} [reverse] lookup time based on position instead of vice versa\n * @return {object}\n */\nfunction interpolate(table, val, reverse) {\n  let lo = 0;\n  let hi = table.length - 1;\n  let prevSource, nextSource, prevTarget, nextTarget;\n  if (reverse) {\n    if (val >= table[lo].pos && val <= table[hi].pos) {\n      ({lo, hi} = _lookupByKey(table, 'pos', val));\n    }\n    ({pos: prevSource, time: prevTarget} = table[lo]);\n    ({pos: nextSource, time: nextTarget} = table[hi]);\n  } else {\n    if (val >= table[lo].time && val <= table[hi].time) {\n      ({lo, hi} = _lookupByKey(table, 'time', val));\n    }\n    ({time: prevSource, pos: prevTarget} = table[lo]);\n    ({time: nextSource, pos: nextTarget} = table[hi]);\n  }\n\n  const span = nextSource - prevSource;\n  return span ? prevTarget + (nextTarget - prevTarget) * (val - prevSource) / span : prevTarget;\n}\n\nclass TimeSeriesScale extends TimeScale {\n\n  static id = 'timeseries';\n\n  /**\n   * @type {any}\n   */\n  static defaults = TimeScale.defaults;\n\n  /**\n\t * @param {object} props\n\t */\n  constructor(props) {\n    super(props);\n\n    /** @type {object[]} */\n    this._table = [];\n    /** @type {number} */\n    this._minPos = undefined;\n    /** @type {number} */\n    this._tableRange = undefined;\n  }\n\n  /**\n\t * @protected\n\t */\n  initOffsets() {\n    const timestamps = this._getTimestampsForTable();\n    const table = this._table = this.buildLookupTable(timestamps);\n    this._minPos = interpolate(table, this.min);\n    this._tableRange = interpolate(table, this.max) - this._minPos;\n    super.initOffsets(timestamps);\n  }\n\n  /**\n\t * Returns an array of {time, pos} objects used to interpolate a specific `time` or position\n\t * (`pos`) on the scale, by searching entries before and after the requested value. `pos` is\n\t * a decimal between 0 and 1: 0 being the start of the scale (left or top) and 1 the other\n\t * extremity (left + width or top + height). Note that it would be more optimized to directly\n\t * store pre-computed pixels, but the scale dimensions are not guaranteed at the time we need\n\t * to create the lookup table. The table ALWAYS contains at least two items: min and max.\n\t * @param {number[]} timestamps\n\t * @return {object[]}\n\t * @protected\n\t */\n  buildLookupTable(timestamps) {\n    const {min, max} = this;\n    const items = [];\n    const table = [];\n    let i, ilen, prev, curr, next;\n\n    for (i = 0, ilen = timestamps.length; i < ilen; ++i) {\n      curr = timestamps[i];\n      if (curr >= min && curr <= max) {\n        items.push(curr);\n      }\n    }\n\n    if (items.length < 2) {\n      // In case there is less that 2 timestamps between min and max, the scale is defined by min and max\n      return [\n        {time: min, pos: 0},\n        {time: max, pos: 1}\n      ];\n    }\n\n    for (i = 0, ilen = items.length; i < ilen; ++i) {\n      next = items[i + 1];\n      prev = items[i - 1];\n      curr = items[i];\n\n      // only add points that breaks the scale linearity\n      if (Math.round((next + prev) / 2) !== curr) {\n        table.push({time: curr, pos: i / (ilen - 1)});\n      }\n    }\n    return table;\n  }\n\n  /**\n    * Generates all timestamps defined in the data.\n    * Important: this method can return ticks outside the min and max range, it's the\n    * responsibility of the calling code to clamp values if needed.\n    * @protected\n    */\n  _generate() {\n    const min = this.min;\n    const max = this.max;\n    let timestamps = super.getDataTimestamps();\n    if (!timestamps.includes(min) || !timestamps.length) {\n      timestamps.splice(0, 0, min);\n    }\n    if (!timestamps.includes(max) || timestamps.length === 1) {\n      timestamps.push(max);\n    }\n    return timestamps.sort((a, b) => a - b);\n  }\n\n  /**\n\t * Returns all timestamps\n\t * @return {number[]}\n\t * @private\n\t */\n  _getTimestampsForTable() {\n    let timestamps = this._cache.all || [];\n\n    if (timestamps.length) {\n      return timestamps;\n    }\n\n    const data = this.getDataTimestamps();\n    const label = this.getLabelTimestamps();\n    if (data.length && label.length) {\n      // If combining labels and data (data might not contain all labels),\n      // we need to recheck uniqueness and sort\n      timestamps = this.normalize(data.concat(label));\n    } else {\n      timestamps = data.length ? data : label;\n    }\n    timestamps = this._cache.all = timestamps;\n\n    return timestamps;\n  }\n\n  /**\n\t * @param {number} value - Milliseconds since epoch (1 January 1970 00:00:00 UTC)\n\t * @return {number}\n\t */\n  getDecimalForValue(value) {\n    return (interpolate(this._table, value) - this._minPos) / this._tableRange;\n  }\n\n  /**\n\t * @param {number} pixel\n\t * @return {number}\n\t */\n  getValueForPixel(pixel) {\n    const offsets = this._offsets;\n    const decimal = this.getDecimalForPixel(pixel) / offsets.factor - offsets.end;\n    return interpolate(this._table, decimal * this._tableRange + this._minPos, true);\n  }\n}\n\nexport default TimeSeriesScale;\n"
  },
  {
    "path": "src/types/animation.d.ts",
    "content": "import {Chart} from './index.js';\nimport {AnyObject} from './basic.js';\n\nexport declare class Animation {\n  constructor(cfg: AnyObject, target: AnyObject, prop: string, to?: unknown);\n  active(): boolean;\n  update(cfg: AnyObject, to: unknown, date: number): void;\n  cancel(): void;\n  tick(date: number): void;\n  readonly _to: unknown;\n}\n\nexport interface AnimationEvent {\n  chart: Chart;\n  numSteps: number;\n  initial: boolean;\n  currentStep: number;\n}\n\nexport declare class Animator {\n  listen(chart: Chart, event: 'complete' | 'progress', cb: (event: AnimationEvent) => void): void;\n  add(chart: Chart, items: readonly Animation[]): void;\n  has(chart: Chart): boolean;\n  start(chart: Chart): void;\n  running(chart: Chart): boolean;\n  stop(chart: Chart): void;\n  remove(chart: Chart): boolean;\n}\n\nexport declare class Animations {\n  constructor(chart: Chart, animations: AnyObject);\n  configure(animations: AnyObject): void;\n  update(target: AnyObject, values: AnyObject): undefined | boolean;\n}\n"
  },
  {
    "path": "src/types/basic.d.ts",
    "content": "\nexport type AnyObject = Record<string, any>;\nexport type EmptyObject = Record<string, never>;\n"
  },
  {
    "path": "src/types/color.d.ts",
    "content": "export type Color = string | CanvasGradient | CanvasPattern;\n"
  },
  {
    "path": "src/types/geometric.d.ts",
    "content": "export interface ChartArea {\n  top: number;\n  left: number;\n  right: number;\n  bottom: number;\n  width: number;\n  height: number;\n}\n\nexport interface Point {\n  x: number | null;\n  y: number | null;\n}\n\nexport type TRBL = {\n  top: number;\n  right: number;\n  bottom: number;\n  left: number;\n}\n\nexport type TRBLCorners = {\n  topLeft: number;\n  topRight: number;\n  bottomLeft: number;\n  bottomRight: number;\n};\n\nexport type CornerRadius = number | Partial<TRBLCorners>;\n\nexport type RoundedRect = {\n  x: number;\n  y: number;\n  w: number;\n  h: number;\n  radius?: CornerRadius\n}\n\nexport type Padding = Partial<TRBL> | number | Point;\n\nexport interface SplinePoint {\n  x: number;\n  y: number;\n  skip?: boolean;\n\n  // Both Bezier and monotone interpolations have these fields\n  // but they are added in different spots\n  cp1x?: number;\n  cp1y?: number;\n  cp2x?: number;\n  cp2y?: number;\n}\n"
  },
  {
    "path": "src/types/index.d.ts",
    "content": "/* eslint-disable @typescript-eslint/ban-types */\nimport {DeepPartial, DistributiveArray, UnionToIntersection} from './utils.js';\n\nimport {TimeUnit} from '../core/core.adapters.js';\nimport PointElement from '../elements/element.point.js';\nimport {EasingFunction} from '../helpers/helpers.easing.js';\nimport {AnimationEvent} from './animation.js';\nimport {AnyObject, EmptyObject} from './basic.js';\nimport {Color} from './color.js';\nimport Element from '../core/core.element.js';\nimport {ChartArea, Padding, Point} from './geometric.js';\nimport {LayoutItem, LayoutPosition} from './layout.js';\nimport {ColorsPluginOptions} from '../plugins/plugin.colors.js';\n\nexport {EasingFunction} from '../helpers/helpers.easing.js';\nexport {default as ArcElement, ArcProps} from '../elements/element.arc.js';\nexport {default as PointElement, PointProps} from '../elements/element.point.js';\nexport {Animation, Animations, Animator, AnimationEvent} from './animation.js';\nexport {Color} from './color.js';\nexport {ChartArea, Point, TRBL} from './geometric.js';\nexport {LayoutItem, LayoutPosition} from './layout.js';\n\nexport interface ScriptableContext<TType extends ChartType> {\n  active: boolean;\n  chart: Chart;\n  dataIndex: number;\n  dataset: UnionToIntersection<ChartDataset<TType>>;\n  datasetIndex: number;\n  type: string;\n  mode: string;\n  parsed: UnionToIntersection<ParsedDataType<TType>>;\n  raw: unknown;\n}\n\nexport interface ScriptableLineSegmentContext {\n  type: 'segment',\n  p0: PointElement,\n  p1: PointElement,\n  p0DataIndex: number,\n  p1DataIndex: number,\n  datasetIndex: number\n}\n\nexport type Scriptable<T, TContext> = T | ((ctx: TContext, options: AnyObject) => T | undefined);\nexport type ScriptableOptions<T, TContext> = { [P in keyof T]: Scriptable<T[P], TContext> };\nexport type ScriptableAndScriptableOptions<T, TContext> = Scriptable<T, TContext> | ScriptableOptions<T, TContext>;\nexport type ScriptableAndArray<T, TContext> = readonly T[] | Scriptable<T, TContext>;\nexport type ScriptableAndArrayOptions<T, TContext> = { [P in keyof T]: ScriptableAndArray<T[P], TContext> };\n\nexport interface ParsingOptions {\n  /**\n   * How to parse the dataset. The parsing can be disabled by specifying parsing: false at chart options or dataset. If parsing is disabled, data must be sorted and in the formats the associated chart type and scales use internally.\n   */\n  parsing:\n  {\n    [key: string]: string;\n  }\n  | false;\n\n  /**\n   * Chart.js is fastest if you provide data with indices that are unique, sorted, and consistent across datasets and provide the normalized: true option to let Chart.js know that you have done so.\n   */\n  normalized: boolean;\n}\n\nexport interface ControllerDatasetOptions extends ParsingOptions {\n  /**\n   * The base axis of the chart. 'x' for vertical charts and 'y' for horizontal charts.\n   * @default 'x'\n   */\n  indexAxis: 'x' | 'y';\n  /**\n   * How to clip relative to chartArea. Positive value allows overflow, negative value clips that many pixels inside chartArea. 0 = clip at chartArea. Clipping can also be configured per side: `clip: {left: 5, top: false, right: -2, bottom: 0}`\n   */\n  clip: number | ChartArea | false;\n  /**\n   * The label for the dataset which appears in the legend and tooltips.\n   */\n  label: string;\n  /**\n   * The drawing order of dataset. Also affects order for stacking, tooltip and legend.\n   */\n  order: number;\n\n  /**\n   * The ID of the group to which this dataset belongs to (when stacked, each group will be a separate stack).\n   */\n  stack: string;\n  /**\n     * Configures the visibility state of the dataset. Set it to true, to hide the dataset from the chart.\n   * @default false\n   */\n  hidden: boolean;\n}\n\nexport interface BarControllerDatasetOptions\n  extends ControllerDatasetOptions,\n  ScriptableAndArrayOptions<BarOptions, ScriptableContext<'bar'>>,\n  ScriptableAndArrayOptions<CommonHoverOptions, ScriptableContext<'bar'>>,\n  AnimationOptions<'bar'> {\n  /**\n   * The ID of the x axis to plot this dataset on.\n   */\n  xAxisID: string;\n  /**\n   * The ID of the y axis to plot this dataset on.\n   */\n  yAxisID: string;\n\n  /**\n   * Percent (0-1) of the available width each bar should be within the category width. 1.0 will take the whole category width and put the bars right next to each other.\n   * @default 0.9\n   */\n  barPercentage: number;\n  /**\n   * Percent (0-1) of the available width each category should be within the sample width.\n   * @default 0.8\n   */\n  categoryPercentage: number;\n\n  /**\n   * Manually set width of each bar in pixels. If set to 'flex', it computes \"optimal\" sample widths that globally arrange bars side by side. If not set (default), bars are equally sized based on the smallest interval.\n   */\n  barThickness: number | 'flex';\n\n  /**\n   * Set this to ensure that bars are not sized thicker than this.\n   */\n  maxBarThickness: number;\n\n  /**\n   * Set this to ensure that bars have a minimum length in pixels.\n   */\n  minBarLength: number;\n\n  /**\n   * Point style for the legend\n   * @default 'circle;\n   */\n  pointStyle: PointStyle;\n\n  /**\n   * Should the bars be grouped on index axis\n   * @default true\n   */\n  grouped: boolean;\n}\n\nexport interface BarControllerChartOptions {\n  /**\n   * Should null or undefined values be omitted from drawing\n   */\n  skipNull?: boolean;\n}\n\nexport type BarController = DatasetController\nexport declare const BarController: ChartComponent & {\n  prototype: BarController;\n  new (chart: Chart, datasetIndex: number): BarController;\n};\n\nexport interface BubbleControllerDatasetOptions\n  extends ControllerDatasetOptions,\n  ScriptableAndArrayOptions<PointOptions, ScriptableContext<'bubble'>>,\n  ScriptableAndArrayOptions<PointHoverOptions, ScriptableContext<'bubble'>> {\n  /**\n   * The ID of the x axis to plot this dataset on.\n   */\n  xAxisID: string;\n  /**\n   * The ID of the y axis to plot this dataset on.\n   */\n  yAxisID: string;\n}\n\nexport interface BubbleDataPoint extends Point {\n  /**\n   * Bubble radius in pixels (not scaled).\n   */\n  r?: number;\n}\n\nexport type BubbleController = DatasetController\nexport declare const BubbleController: ChartComponent & {\n  prototype: BubbleController;\n  new (chart: Chart, datasetIndex: number): BubbleController;\n};\n\nexport interface LineControllerDatasetOptions\n  extends ControllerDatasetOptions,\n  ScriptableAndArrayOptions<PointPrefixedOptions, ScriptableContext<'line'>>,\n  ScriptableAndArrayOptions<PointPrefixedHoverOptions, ScriptableContext<'line'>>,\n  ScriptableOptions<Omit<LineOptions, keyof CommonElementOptions>, ScriptableContext<'line'>>,\n  ScriptableAndArrayOptions<CommonElementOptions, ScriptableContext<'line'>>,\n  ScriptableOptions<Omit<LineHoverOptions, keyof CommonHoverOptions>, ScriptableContext<'line'>>,\n  ScriptableAndArrayOptions<CommonHoverOptions, ScriptableContext<'line'>>,\n  AnimationOptions<'line'> {\n  /**\n   * The ID of the x axis to plot this dataset on.\n   */\n  xAxisID: string;\n  /**\n   * The ID of the y axis to plot this dataset on.\n   */\n  yAxisID: string;\n\n  /**\n   * If true, lines will be drawn between points with no or null data. If false, points with NaN data will create a break in the line. Can also be a number specifying the maximum gap length to span. The unit of the value depends on the scale used.\n   * @default false\n   */\n  spanGaps: boolean | number;\n\n  showLine: boolean;\n}\n\nexport interface LineControllerChartOptions {\n  /**\n   * If true, lines will be drawn between points with no or null data. If false, points with NaN data will create a break in the line. Can also be a number specifying the maximum gap length to span. The unit of the value depends on the scale used.\n   * @default false\n   */\n  spanGaps: boolean | number;\n  /**\n   * If false, the lines between points are not drawn.\n   * @default true\n   */\n  showLine: boolean;\n}\n\nexport type LineController = DatasetController\nexport declare const LineController: ChartComponent & {\n  prototype: LineController;\n  new (chart: Chart, datasetIndex: number): LineController;\n};\n\nexport type ScatterControllerDatasetOptions = LineControllerDatasetOptions;\n\nexport type ScatterDataPoint = Point\n\nexport type ScatterControllerChartOptions = LineControllerChartOptions;\n\nexport type ScatterController = LineController\nexport declare const ScatterController: ChartComponent & {\n  prototype: ScatterController;\n  new (chart: Chart, datasetIndex: number): ScatterController;\n};\n\nexport interface DoughnutControllerDatasetOptions\n  extends ControllerDatasetOptions,\n  ScriptableAndArrayOptions<ArcOptions, ScriptableContext<'doughnut'>>,\n  ScriptableAndArrayOptions<ArcHoverOptions, ScriptableContext<'doughnut'>>,\n  AnimationOptions<'doughnut'> {\n\n  /**\n   * Sweep to allow arcs to cover.\n   * @default 360\n   */\n  circumference: number;\n\n  /**\n   * Arc offset (in pixels).\n   */\n  offset: number | number[];\n\n  /**\n   * Starting angle to draw this dataset from.\n   * @default 0\n   */\n  rotation: number;\n\n  /**\n   * The relative thickness of the dataset. Providing a value for weight will cause the pie or doughnut dataset to be drawn with a thickness relative to the sum of all the dataset weight values.\n   * @default 1\n   */\n  weight: number;\n\n  /**\n   * Similar to the `offset` option, but applies to all arcs. This can be used to to add spaces\n   * between arcs\n   * @default 0\n   */\n  spacing: number;\n}\n\nexport interface DoughnutAnimationOptions extends AnimationSpec<'doughnut'> {\n  /**\n   *   If true, the chart will animate in with a rotation animation. This property is in the options.animation object.\n   * @default true\n   */\n  animateRotate: boolean;\n\n  /**\n   * If true, will animate scaling the chart from the center outwards.\n   * @default false\n   */\n  animateScale: boolean;\n}\n\nexport interface DoughnutControllerChartOptions {\n  /**\n   * Sweep to allow arcs to cover.\n   * @default 360\n   */\n  circumference: number;\n\n  /**\n   * The portion of the chart that is cut out of the middle. ('50%' - for doughnut, 0 - for pie)\n   * String ending with '%' means percentage, number means pixels.\n   * @default 50\n   */\n  cutout: Scriptable<number | string, ScriptableContext<'doughnut'>>;\n\n  /**\n   * Arc offset (in pixels).\n   */\n  offset: number | number[];\n\n  /**\n   * The outer radius of the chart. String ending with '%' means percentage of maximum radius, number means pixels.\n   * @default '100%'\n   */\n  radius: Scriptable<number | string, ScriptableContext<'doughnut'>>;\n\n  /**\n   * Starting angle to draw arcs from.\n   * @default 0\n   */\n  rotation: number;\n\n  /**\n   * Spacing between the arcs\n   * @default 0\n   */\n  spacing: number;\n\n  animation: false | DoughnutAnimationOptions;\n}\n\nexport type DoughnutDataPoint = number;\n\nexport interface DoughnutController extends DatasetController {\n  readonly innerRadius: number;\n  readonly outerRadius: number;\n  readonly offsetX: number;\n  readonly offsetY: number;\n\n  calculateTotal(): number;\n  calculateCircumference(value: number): number;\n}\n\nexport declare const DoughnutController: ChartComponent & {\n  prototype: DoughnutController;\n  new (chart: Chart, datasetIndex: number): DoughnutController;\n};\n\nexport interface DoughnutMetaExtensions {\n  total: number;\n}\n\nexport type PieControllerDatasetOptions = DoughnutControllerDatasetOptions;\nexport type PieControllerChartOptions = DoughnutControllerChartOptions;\nexport type PieAnimationOptions = DoughnutAnimationOptions;\n\nexport type PieDataPoint = DoughnutDataPoint;\nexport type PieMetaExtensions = DoughnutMetaExtensions;\n\nexport type PieController = DoughnutController\nexport declare const PieController: ChartComponent & {\n  prototype: PieController;\n  new (chart: Chart, datasetIndex: number): PieController;\n};\n\nexport interface PolarAreaControllerDatasetOptions extends DoughnutControllerDatasetOptions {\n  /**\n   * Arc angle to cover. - for polar only\n   * @default circumference / (arc count)\n   */\n  angle: number;\n}\n\nexport type PolarAreaAnimationOptions = DoughnutAnimationOptions;\n\nexport interface PolarAreaControllerChartOptions {\n  /**\n   * Starting angle to draw arcs for the first item in a dataset. In degrees, 0 is at top.\n   * @default 0\n   */\n  startAngle: number;\n\n  animation: false | PolarAreaAnimationOptions;\n}\n\nexport interface PolarAreaController extends DoughnutController {\n  countVisibleElements(): number;\n}\nexport declare const PolarAreaController: ChartComponent & {\n  prototype: PolarAreaController;\n  new (chart: Chart, datasetIndex: number): PolarAreaController;\n};\n\nexport interface RadarControllerDatasetOptions\n  extends ControllerDatasetOptions,\n  ScriptableAndArrayOptions<PointOptions & PointHoverOptions & PointPrefixedOptions & PointPrefixedHoverOptions, ScriptableContext<'radar'>>,\n  ScriptableAndArrayOptions<LineOptions & LineHoverOptions, ScriptableContext<'radar'>>,\n  AnimationOptions<'radar'> {\n  /**\n   * The ID of the x axis to plot this dataset on.\n   */\n  xAxisID: string;\n  /**\n   * The ID of the y axis to plot this dataset on.\n   */\n  yAxisID: string;\n\n  /**\n   * If true, lines will be drawn between points with no or null data. If false, points with NaN data will create a break in the line. Can also be a number specifying the maximum gap length to span. The unit of the value depends on the scale used.\n   */\n  spanGaps: boolean | number;\n\n  /**\n   * If false, the line is not drawn for this dataset.\n   */\n  showLine: boolean;\n}\n\nexport type RadarControllerChartOptions = LineControllerChartOptions;\n\nexport type RadarController = DatasetController\nexport declare const RadarController: ChartComponent & {\n  prototype: RadarController;\n  new (chart: Chart, datasetIndex: number): RadarController;\n};\n\ninterface ChartMetaClip {\n  left: number | boolean;\n  top: number | boolean;\n  right: number | boolean;\n  bottom: number | boolean;\n  disabled: boolean;\n}\n\ninterface ChartMetaCommon<TElement extends Element = Element, TDatasetElement extends Element = Element> {\n  type: string;\n  controller: DatasetController;\n  order: number;\n\n  label: string;\n  index: number;\n  visible: boolean;\n\n  stack: number;\n\n  indexAxis: 'x' | 'y';\n\n  data: TElement[];\n  dataset?: TDatasetElement;\n\n  hidden: boolean;\n\n  xAxisID?: string;\n  yAxisID?: string;\n  rAxisID?: string;\n  iAxisID: string;\n  vAxisID: string;\n\n  xScale?: Scale;\n  yScale?: Scale;\n  rScale?: Scale;\n  iScale?: Scale;\n  vScale?: Scale;\n\n  _sorted: boolean;\n  _stacked: boolean | 'single';\n  _parsed: unknown[];\n  _clip: ChartMetaClip;\n}\n\nexport type ChartMeta<\n  TType extends ChartType = ChartType,\n  TElement extends Element = Element,\n  TDatasetElement extends Element = Element,\n> = DeepPartial<\n{ [key in ChartType]: ChartTypeRegistry[key]['metaExtensions'] }[TType]\n> & ChartMetaCommon<TElement, TDatasetElement>;\n\nexport interface ActiveDataPoint {\n  datasetIndex: number;\n  index: number;\n}\n\nexport interface ActiveElement extends ActiveDataPoint {\n  element: Element;\n}\n\nexport declare class Chart<\n  TType extends ChartType = ChartType,\n  TData = DefaultDataPoint<TType>,\n  TLabel = unknown\n> {\n  readonly platform: BasePlatform;\n  readonly id: string;\n  readonly canvas: HTMLCanvasElement;\n  readonly ctx: CanvasRenderingContext2D;\n  readonly config: ChartConfiguration<TType, TData, TLabel> | ChartConfigurationCustomTypesPerDataset<TType, TData, TLabel>;\n  readonly width: number;\n  readonly height: number;\n  readonly aspectRatio: number;\n  readonly boxes: LayoutItem[];\n  readonly currentDevicePixelRatio: number;\n  readonly chartArea: ChartArea;\n  readonly scales: { [key: string]: Scale };\n  readonly attached: boolean;\n\n  readonly legend?: LegendElement<TType>; // Only available if legend plugin is registered and enabled\n  readonly tooltip?: TooltipModel<TType>; // Only available if tooltip plugin is registered and enabled\n\n  data: ChartData<TType, TData, TLabel>;\n  options: ChartOptions<TType>;\n\n  constructor(item: ChartItem, config: ChartConfiguration<TType, TData, TLabel> | ChartConfigurationCustomTypesPerDataset<TType, TData, TLabel>);\n\n  clear(): this;\n  stop(): this;\n\n  resize(width?: number, height?: number): void;\n  ensureScalesHaveIDs(): void;\n  buildOrUpdateScales(): void;\n  buildOrUpdateControllers(): void;\n  reset(): void;\n  update(mode?: UpdateMode | ((ctx: { datasetIndex: number }) => UpdateMode)): void;\n  render(): void;\n  draw(): void;\n\n  isPointInArea(point: Point): boolean;\n  getElementsAtEventForMode(e: Event, mode: string, options: InteractionOptions, useFinalPosition: boolean): InteractionItem[];\n\n  getSortedVisibleDatasetMetas(): ChartMeta[];\n  getDatasetMeta(datasetIndex: number): ChartMeta;\n  getVisibleDatasetCount(): number;\n  isDatasetVisible(datasetIndex: number): boolean;\n  setDatasetVisibility(datasetIndex: number, visible: boolean): void;\n  toggleDataVisibility(index: number): void;\n  getDataVisibility(index: number): boolean;\n  hide(datasetIndex: number, dataIndex?: number): void;\n  show(datasetIndex: number, dataIndex?: number): void;\n\n  getActiveElements(): ActiveElement[];\n  setActiveElements(active: ActiveDataPoint[]): void;\n\n  destroy(): void;\n  toBase64Image(type?: string, quality?: unknown): string;\n  bindEvents(): void;\n  unbindEvents(): void;\n  updateHoverStyle(items: InteractionItem[], mode: 'dataset', enabled: boolean): void;\n\n  notifyPlugins(hook: string, args?: AnyObject): boolean | void;\n\n  isPluginEnabled(pluginId: string): boolean;\n\n  getContext(): { chart: Chart, type: string };\n\n  static readonly defaults: Defaults;\n  static readonly overrides: Overrides;\n  static readonly version: string;\n  static readonly instances: { [key: string]: Chart };\n  static readonly registry: Registry;\n  static getChart(key: string | CanvasRenderingContext2D | HTMLCanvasElement): Chart | undefined;\n  static register(...items: ChartComponentLike[]): void;\n  static unregister(...items: ChartComponentLike[]): void;\n}\n\nexport declare const registerables: readonly ChartComponentLike[];\n\nexport declare type ChartItem =\n  | string\n  | CanvasRenderingContext2D\n  | HTMLCanvasElement\n  | { canvas: HTMLCanvasElement }\n  | ArrayLike<CanvasRenderingContext2D | HTMLCanvasElement>;\n\nexport declare enum UpdateModeEnum {\n  resize = 'resize',\n  reset = 'reset',\n  none = 'none',\n  hide = 'hide',\n  show = 'show',\n  default = 'default',\n  active = 'active'\n}\n\nexport type UpdateMode = keyof typeof UpdateModeEnum;\n\nexport declare class DatasetController<\n  TType extends ChartType = ChartType,\n  TElement extends Element = Element,\n  TDatasetElement extends Element = Element,\n  TParsedData = ParsedDataType<TType>,\n> {\n  constructor(chart: Chart, datasetIndex: number);\n\n  readonly chart: Chart;\n  readonly index: number;\n  readonly _cachedMeta: ChartMeta<TType, TElement, TDatasetElement>;\n  enableOptionSharing: boolean;\n  // If true, the controller supports the decimation\n  // plugin. Defaults to `false` for all controllers\n  // except the LineController\n  supportsDecimation: boolean;\n\n  linkScales(): void;\n  getAllParsedValues(scale: Scale): number[];\n  protected getLabelAndValue(index: number): { label: string; value: string };\n  updateElements(elements: TElement[], start: number, count: number, mode: UpdateMode): void;\n  update(mode: UpdateMode): void;\n  updateIndex(datasetIndex: number): void;\n  protected getMaxOverflow(): boolean | number;\n  draw(): void;\n  reset(): void;\n  getDataset(): ChartDataset;\n  getMeta(): ChartMeta<TType, TElement, TDatasetElement>;\n  getScaleForId(scaleID: string): Scale | undefined;\n  configure(): void;\n  initialize(): void;\n  addElements(): void;\n  buildOrUpdateElements(resetNewElements?: boolean): void;\n\n  getStyle(index: number, active: boolean): AnyObject;\n  protected resolveDatasetElementOptions(mode: UpdateMode): AnyObject;\n  protected resolveDataElementOptions(index: number, mode: UpdateMode): AnyObject;\n  /**\n   * Utility for checking if the options are shared and should be animated separately.\n   * @protected\n   */\n  protected getSharedOptions(options: AnyObject): undefined | AnyObject;\n  /**\n   * Utility for determining if `options` should be included in the updated properties\n   * @protected\n   */\n  protected includeOptions(mode: UpdateMode, sharedOptions: AnyObject): boolean;\n  /**\n   * Utility for updating an element with new properties, using animations when appropriate.\n   * @protected\n   */\n\n  protected updateElement(element: TElement | TDatasetElement, index: number | undefined, properties: AnyObject, mode: UpdateMode): void;\n  /**\n   * Utility to animate the shared options, that are potentially affecting multiple elements.\n   * @protected\n   */\n\n  protected updateSharedOptions(sharedOptions: AnyObject, mode: UpdateMode, newOptions: AnyObject): void;\n  removeHoverStyle(element: TElement, datasetIndex: number, index: number): void;\n  setHoverStyle(element: TElement, datasetIndex: number, index: number): void;\n\n  parse(start: number, count: number): void;\n  protected parsePrimitiveData(meta: ChartMeta<TType, TElement, TDatasetElement>, data: AnyObject[], start: number, count: number): AnyObject[];\n  protected parseArrayData(meta: ChartMeta<TType, TElement, TDatasetElement>, data: AnyObject[], start: number, count: number): AnyObject[];\n  protected parseObjectData(meta: ChartMeta<TType, TElement, TDatasetElement>, data: AnyObject[], start: number, count: number): AnyObject[];\n  protected getParsed(index: number): TParsedData;\n  protected applyStack(scale: Scale, parsed: unknown[]): number;\n  protected updateRangeFromParsed(\n    range: { min: number; max: number },\n    scale: Scale,\n    parsed: unknown[],\n    stack: boolean | string\n  ): void;\n  protected getMinMax(scale: Scale, canStack?: boolean): { min: number; max: number };\n}\n\nexport interface DatasetControllerChartComponent extends ChartComponent {\n  defaults: {\n    datasetElementType?: string | null | false;\n    dataElementType?: string | null | false;\n  };\n}\n\nexport interface Defaults extends CoreChartOptions<ChartType>, ElementChartOptions<ChartType>, PluginChartOptions<ChartType> {\n\n  scale: ScaleOptionsByType;\n  scales: {\n    [key in ScaleType]: ScaleOptionsByType<key>;\n  };\n\n  set(values: AnyObject): AnyObject;\n  set(scope: string, values: AnyObject): AnyObject;\n  get(scope: string): AnyObject;\n\n  describe(scope: string, values: AnyObject): AnyObject;\n  override(scope: string, values: AnyObject): AnyObject;\n\n  /**\n   * Routes the named defaults to fallback to another scope/name.\n   * This routing is useful when those target values, like defaults.color, are changed runtime.\n   * If the values would be copied, the runtime change would not take effect. By routing, the\n   * fallback is evaluated at each access, so its always up to date.\n   *\n   * Example:\n   *\n   *   defaults.route('elements.arc', 'backgroundColor', '', 'color')\n   *    - reads the backgroundColor from defaults.color when undefined locally\n   *\n   * @param scope Scope this route applies to.\n   * @param name Property name that should be routed to different namespace when not defined here.\n   * @param targetScope The namespace where those properties should be routed to.\n   * Empty string ('') is the root of defaults.\n   * @param targetName The target name in the target scope the property should be routed to.\n   */\n  route(scope: string, name: string, targetScope: string, targetName: string): void;\n}\n\nexport type Overrides = {\n  [key in ChartType]:\n  CoreChartOptions<key> &\n  ElementChartOptions<key> &\n  PluginChartOptions<key> &\n  DatasetChartOptions<ChartType> &\n  ScaleChartOptions<key> &\n  ChartTypeRegistry[key]['chartOptions'];\n}\n\nexport declare const defaults: Defaults;\nexport interface InteractionOptions {\n  axis?: string;\n  intersect?: boolean;\n  includeInvisible?: boolean;\n}\n\nexport interface InteractionItem {\n  element: Element;\n  datasetIndex: number;\n  index: number;\n}\n\nexport type InteractionModeFunction = (\n  chart: Chart,\n  e: ChartEvent,\n  options: InteractionOptions,\n  useFinalPosition?: boolean\n) => InteractionItem[];\n\nexport interface InteractionModeMap {\n  /**\n   * Returns items at the same index. If the options.intersect parameter is true, we only return items if we intersect something\n   * If the options.intersect mode is false, we find the nearest item and return the items at the same index as that item\n   */\n  index: InteractionModeFunction;\n\n  /**\n   * Returns items in the same dataset. If the options.intersect parameter is true, we only return items if we intersect something\n   * If the options.intersect is false, we find the nearest item and return the items in that dataset\n   */\n  dataset: InteractionModeFunction;\n  /**\n   * Point mode returns all elements that hit test based on the event position\n   * of the event\n   */\n  point: InteractionModeFunction;\n  /**\n   * nearest mode returns the element closest to the point\n   */\n  nearest: InteractionModeFunction;\n  /**\n   * x mode returns the elements that hit-test at the current x coordinate\n   */\n  x: InteractionModeFunction;\n  /**\n   * y mode returns the elements that hit-test at the current y coordinate\n   */\n  y: InteractionModeFunction;\n}\n\nexport type InteractionMode = keyof InteractionModeMap;\n\nexport declare const Interaction: {\n  modes: InteractionModeMap;\n\n  /**\n   * Helper function to select candidate elements for interaction\n   */\n  evaluateInteractionItems(\n    chart: Chart,\n    axis: InteractionAxis,\n    position: Point,\n    handler: (element: Element & VisualElement, datasetIndex: number, index: number) => void,\n    intersect?: boolean\n  ): InteractionItem[];\n};\n\nexport declare const layouts: {\n  /**\n   * Register a box to a chart.\n   * A box is simply a reference to an object that requires layout. eg. Scales, Legend, Title.\n   * @param {Chart} chart - the chart to use\n   * @param {LayoutItem} item - the item to add to be laid out\n   */\n  addBox(chart: Chart, item: LayoutItem): void;\n\n  /**\n   * Remove a layoutItem from a chart\n   * @param {Chart} chart - the chart to remove the box from\n   * @param {LayoutItem} layoutItem - the item to remove from the layout\n   */\n  removeBox(chart: Chart, layoutItem: LayoutItem): void;\n\n  /**\n   * Sets (or updates) options on the given `item`.\n   * @param {Chart} chart - the chart in which the item lives (or will be added to)\n   * @param {LayoutItem} item - the item to configure with the given options\n   * @param options - the new item options.\n   */\n  configure(\n    chart: Chart,\n    item: LayoutItem,\n    options: { fullSize?: number; position?: LayoutPosition; weight?: number }\n  ): void;\n\n  /**\n   * Fits boxes of the given chart into the given size by having each box measure itself\n   * then running a fitting algorithm\n   * @param {Chart} chart - the chart\n   * @param {number} width - the width to fit into\n   * @param {number} height - the height to fit into\n   */\n  update(chart: Chart, width: number, height: number): void;\n};\n\nexport interface Plugin<TType extends ChartType = ChartType, O = AnyObject> extends ExtendedPlugin<TType, O> {\n  id: string;\n\n  /**\n   * The events option defines the browser events that the plugin should listen.\n   * @default ['mousemove', 'mouseout', 'click', 'touchstart', 'touchmove']\n   */\n  events?: (keyof HTMLElementEventMap)[]\n\n  /**\n   * @desc Called when plugin is installed for this chart instance. This hook is also invoked for disabled plugins (options === false).\n   * @param {Chart} chart - The chart instance.\n   * @param {object} args - The call arguments.\n   * @param {object} options - The plugin options.\n   * @since 3.0.0\n   */\n  install?(chart: Chart<TType>, args: EmptyObject, options: O): void;\n  /**\n   * @desc Called when a plugin is starting. This happens when chart is created or plugin is enabled.\n   * @param {Chart} chart - The chart instance.\n   * @param {object} args - The call arguments.\n   * @param {object} options - The plugin options.\n   * @since 3.0.0\n   */\n  start?(chart: Chart<TType>, args: EmptyObject, options: O): void;\n  /**\n   * @desc Called when a plugin stopping. This happens when chart is destroyed or plugin is disabled.\n   * @param {Chart} chart - The chart instance.\n   * @param {object} args - The call arguments.\n   * @param {object} options - The plugin options.\n   * @since 3.0.0\n   */\n  stop?(chart: Chart<TType>, args: EmptyObject, options: O): void;\n  /**\n   * @desc Called before initializing `chart`.\n   * @param {Chart} chart - The chart instance.\n   * @param {object} args - The call arguments.\n   * @param {object} options - The plugin options.\n   */\n  beforeInit?(chart: Chart<TType>, args: EmptyObject, options: O): void;\n  /**\n   * @desc Called after `chart` has been initialized and before the first update.\n   * @param {Chart} chart - The chart instance.\n   * @param {object} args - The call arguments.\n   * @param {object} options - The plugin options.\n   */\n  afterInit?(chart: Chart<TType>, args: EmptyObject, options: O): void;\n  /**\n   * @desc Called before updating `chart`. If any plugin returns `false`, the update\n   * is cancelled (and thus subsequent render(s)) until another `update` is triggered.\n   * @param {Chart} chart - The chart instance.\n   * @param {object} args - The call arguments.\n   * @param {UpdateMode} args.mode - The update mode\n   * @param {object} options - The plugin options.\n   * @returns {boolean} `false` to cancel the chart update.\n   */\n  beforeUpdate?(chart: Chart<TType>, args: { mode: UpdateMode, cancelable: true }, options: O): boolean | void;\n  /**\n   * @desc Called after `chart` has been updated and before rendering. Note that this\n   * hook will not be called if the chart update has been previously cancelled.\n   * @param {Chart} chart - The chart instance.\n   * @param {object} args - The call arguments.\n   * @param {UpdateMode} args.mode - The update mode\n   * @param {object} options - The plugin options.\n   */\n  afterUpdate?(chart: Chart<TType>, args: { mode: UpdateMode }, options: O): void;\n  /**\n   * @desc Called during the update process, before any chart elements have been created.\n   * This can be used for data decimation by changing the data array inside a dataset.\n   * @param {Chart} chart - The chart instance.\n   * @param {object} args - The call arguments.\n   * @param {object} options - The plugin options.\n   */\n  beforeElementsUpdate?(chart: Chart<TType>, args: EmptyObject, options: O): void;\n  /**\n   * @desc Called during chart reset\n   * @param {Chart} chart - The chart instance.\n   * @param {object} args - The call arguments.\n   * @param {object} options - The plugin options.\n   * @since version 3.0.0\n   */\n  reset?(chart: Chart<TType>, args: EmptyObject, options: O): void;\n  /**\n   * @desc Called before updating the `chart` datasets. If any plugin returns `false`,\n   * the datasets update is cancelled until another `update` is triggered.\n   * @param {Chart} chart - The chart instance.\n   * @param {object} args - The call arguments.\n   * @param {UpdateMode} args.mode - The update mode.\n   * @param {object} options - The plugin options.\n   * @returns {boolean} false to cancel the datasets update.\n   * @since version 2.1.5\n   */\n  beforeDatasetsUpdate?(chart: Chart<TType>, args: { mode: UpdateMode }, options: O): boolean | void;\n  /**\n   * @desc Called after the `chart` datasets have been updated. Note that this hook\n   * will not be called if the datasets update has been previously cancelled.\n   * @param {Chart} chart - The chart instance.\n   * @param {object} args - The call arguments.\n   * @param {UpdateMode} args.mode - The update mode.\n   * @param {object} options - The plugin options.\n   * @since version 2.1.5\n   */\n  afterDatasetsUpdate?(chart: Chart<TType>, args: { mode: UpdateMode, cancelable: true }, options: O): void;\n  /**\n   * @desc Called before updating the `chart` dataset at the given `args.index`. If any plugin\n   * returns `false`, the datasets update is cancelled until another `update` is triggered.\n   * @param {Chart} chart - The chart instance.\n   * @param {object} args - The call arguments.\n   * @param {number} args.index - The dataset index.\n   * @param {object} args.meta - The dataset metadata.\n   * @param {UpdateMode} args.mode - The update mode.\n   * @param {object} options - The plugin options.\n   * @returns {boolean} `false` to cancel the chart datasets drawing.\n   */\n  beforeDatasetUpdate?(chart: Chart<TType>, args: { index: number; meta: ChartMeta, mode: UpdateMode, cancelable: true }, options: O): boolean | void;\n  /**\n   * @desc Called after the `chart` datasets at the given `args.index` has been updated. Note\n   * that this hook will not be called if the datasets update has been previously cancelled.\n   * @param {Chart} chart - The chart instance.\n   * @param {object} args - The call arguments.\n   * @param {number} args.index - The dataset index.\n   * @param {object} args.meta - The dataset metadata.\n   * @param {UpdateMode} args.mode - The update mode.\n   * @param {object} options - The plugin options.\n   */\n  afterDatasetUpdate?(chart: Chart<TType>, args: { index: number; meta: ChartMeta, mode: UpdateMode, cancelable: false }, options: O): void;\n  /**\n   * @desc Called before laying out `chart`. If any plugin returns `false`,\n   * the layout update is cancelled until another `update` is triggered.\n   * @param {Chart} chart - The chart instance.\n   * @param {object} args - The call arguments.\n   * @param {object} options - The plugin options.\n   * @returns {boolean} `false` to cancel the chart layout.\n   */\n  beforeLayout?(chart: Chart<TType>, args: { cancelable: true }, options: O): boolean | void;\n  /**\n   * @desc Called before scale data limits are calculated. This hook is called separately for each scale in the chart.\n   * @param {Chart} chart - The chart instance.\n   * @param {object} args - The call arguments.\n   * @param {Scale} args.scale - The scale.\n   * @param {object} options - The plugin options.\n   */\n  beforeDataLimits?(chart: Chart<TType>, args: { scale: Scale }, options: O): void;\n  /**\n   * @desc Called after scale data limits are calculated. This hook is called separately for each scale in the chart.\n   * @param {Chart} chart - The chart instance.\n   * @param {object} args - The call arguments.\n   * @param {Scale} args.scale - The scale.\n   * @param {object} options - The plugin options.\n   */\n  afterDataLimits?(chart: Chart<TType>, args: { scale: Scale }, options: O): void;\n  /**\n   * @desc Called before scale builds its ticks. This hook is called separately for each scale in the chart.\n   * @param {Chart} chart - The chart instance.\n   * @param {object} args - The call arguments.\n   * @param {Scale} args.scale - The scale.\n   * @param {object} options - The plugin options.\n   */\n  beforeBuildTicks?(chart: Chart<TType>, args: { scale: Scale }, options: O): void;\n  /**\n   * @desc Called after scale has build its ticks. This hook is called separately for each scale in the chart.\n   * @param {Chart} chart - The chart instance.\n   * @param {object} args - The call arguments.\n   * @param {Scale} args.scale - The scale.\n   * @param {object} options - The plugin options.\n   */\n  afterBuildTicks?(chart: Chart<TType>, args: { scale: Scale }, options: O): void;\n  /**\n   * @desc Called after the `chart` has been laid out. Note that this hook will not\n   * be called if the layout update has been previously cancelled.\n   * @param {Chart} chart - The chart instance.\n   * @param {object} args - The call arguments.\n   * @param {object} options - The plugin options.\n   */\n  afterLayout?(chart: Chart<TType>, args: EmptyObject, options: O): void;\n  /**\n   * @desc Called before rendering `chart`. If any plugin returns `false`,\n   * the rendering is cancelled until another `render` is triggered.\n   * @param {Chart} chart - The chart instance.\n   * @param {object} args - The call arguments.\n   * @param {object} options - The plugin options.\n   * @returns {boolean} `false` to cancel the chart rendering.\n   */\n  beforeRender?(chart: Chart<TType>, args: { cancelable: true }, options: O): boolean | void;\n  /**\n   * @desc Called after the `chart` has been fully rendered (and animation completed). Note\n   * that this hook will not be called if the rendering has been previously cancelled.\n   * @param {Chart} chart - The chart instance.\n   * @param {object} args - The call arguments.\n   * @param {object} options - The plugin options.\n   */\n  afterRender?(chart: Chart<TType>, args: EmptyObject, options: O): void;\n  /**\n   * @desc Called before drawing `chart` at every animation frame. If any plugin returns `false`,\n   * the frame drawing is cancelled untilanother `render` is triggered.\n   * @param {Chart} chart - The chart instance.\n   * @param {object} args - The call arguments.\n   * @param {object} options - The plugin options.\n   * @returns {boolean} `false` to cancel the chart drawing.\n   */\n  beforeDraw?(chart: Chart<TType>, args: { cancelable: true }, options: O): boolean | void;\n  /**\n   * @desc Called after the `chart` has been drawn. Note that this hook will not be called\n   * if the drawing has been previously cancelled.\n   * @param {Chart} chart - The chart instance.\n   * @param {object} args - The call arguments.\n   * @param {object} options - The plugin options.\n   */\n  afterDraw?(chart: Chart<TType>, args: EmptyObject, options: O): void;\n  /**\n   * @desc Called before drawing the `chart` datasets. If any plugin returns `false`,\n   * the datasets drawing is cancelled until another `render` is triggered.\n   * @param {Chart} chart - The chart instance.\n   * @param {object} args - The call arguments.\n   * @param {object} options - The plugin options.\n   * @returns {boolean} `false` to cancel the chart datasets drawing.\n   */\n  beforeDatasetsDraw?(chart: Chart<TType>, args: { cancelable: true }, options: O): boolean | void;\n  /**\n   * @desc Called after the `chart` datasets have been drawn. Note that this hook\n   * will not be called if the datasets drawing has been previously cancelled.\n   * @param {Chart} chart - The chart instance.\n   * @param {object} args - The call arguments.\n   * @param {object} options - The plugin options.\n   */\n  afterDatasetsDraw?(chart: Chart<TType>, args: EmptyObject, options: O, cancelable: false): void;\n  /**\n   * @desc Called before drawing the `chart` dataset at the given `args.index` (datasets\n   * are drawn in the reverse order). If any plugin returns `false`, the datasets drawing\n   * is cancelled until another `render` is triggered.\n   * @param {Chart} chart - The chart instance.\n   * @param {object} args - The call arguments.\n   * @param {number} args.index - The dataset index.\n   * @param {object} args.meta - The dataset metadata.\n   * @param {object} options - The plugin options.\n   * @returns {boolean} `false` to cancel the chart datasets drawing.\n   */\n  beforeDatasetDraw?(chart: Chart<TType>, args: { index: number; meta: ChartMeta }, options: O): boolean | void;\n  /**\n   * @desc Called after the `chart` datasets at the given `args.index` have been drawn\n   * (datasets are drawn in the reverse order). Note that this hook will not be called\n   * if the datasets drawing has been previously cancelled.\n   * @param {Chart} chart - The chart instance.\n   * @param {object} args - The call arguments.\n   * @param {number} args.index - The dataset index.\n   * @param {object} args.meta - The dataset metadata.\n   * @param {object} options - The plugin options.\n   */\n  afterDatasetDraw?(chart: Chart<TType>, args: { index: number; meta: ChartMeta }, options: O): void;\n  /**\n   * @desc Called before processing the specified `event`. If any plugin returns `false`,\n   * the event will be discarded.\n   * @param {Chart} chart - The chart instance.\n   * @param {object} args - The call arguments.\n   * @param {ChartEvent} args.event - The event object.\n   * @param {boolean} args.replay - True if this event is replayed from `Chart.update`\n   * @param {boolean} args.inChartArea - The event position is inside chartArea\n   * @param {boolean} [args.changed] - Set to true if the plugin needs a render. Should only be changed to true, because this args object is passed through all plugins.\n   * @param {object} options - The plugin options.\n   */\n  beforeEvent?(chart: Chart<TType>, args: { event: ChartEvent, replay: boolean, changed?: boolean; cancelable: true, inChartArea: boolean }, options: O): boolean | void;\n  /**\n   * @desc Called after the `event` has been consumed. Note that this hook\n   * will not be called if the `event` has been previously discarded.\n   * @param {Chart} chart - The chart instance.\n   * @param {object} args - The call arguments.\n   * @param {ChartEvent} args.event - The event object.\n   * @param {boolean} args.replay - True if this event is replayed from `Chart.update`\n   * @param {boolean} args.inChartArea - The event position is inside chartArea\n   * @param {boolean} [args.changed] - Set to true if the plugin needs a render. Should only be changed to true, because this args object is passed through all plugins.\n   * @param {object} options - The plugin options.\n   */\n  afterEvent?(chart: Chart<TType>, args: { event: ChartEvent, replay: boolean, changed?: boolean, cancelable: false, inChartArea: boolean }, options: O): void;\n  /**\n   * @desc Called after the chart as been resized.\n   * @param {Chart} chart - The chart instance.\n   * @param {object} args - The call arguments.\n   * @param {number} args.size - The new canvas display size (eq. canvas.style width & height).\n   * @param {object} options - The plugin options.\n   */\n  resize?(chart: Chart<TType>, args: { size: { width: number, height: number } }, options: O): void;\n  /**\n   * Called before the chart is being destroyed.\n   * @param {Chart} chart - The chart instance.\n   * @param {object} args - The call arguments.\n   * @param {object} options - The plugin options.\n   */\n  beforeDestroy?(chart: Chart<TType>, args: EmptyObject, options: O): void;\n  /**\n   * Called after the chart has been destroyed.\n   * @param {Chart} chart - The chart instance.\n   * @param {object} args - The call arguments.\n   * @param {object} options - The plugin options.\n   */\n  afterDestroy?(chart: Chart<TType>, args: EmptyObject, options: O): void;\n  /**\n   * Called after chart is destroyed on all plugins that were installed for that chart. This hook is also invoked for disabled plugins (options === false).\n   * @param {Chart} chart - The chart instance.\n   * @param {object} args - The call arguments.\n   * @param {object} options - The plugin options.\n   * @since 3.0.0\n   */\n  uninstall?(chart: Chart<TType>, args: EmptyObject, options: O): void;\n\n  /**\n   * Default options used in the plugin\n   */\n  defaults?: Partial<O>;\n}\n\nexport declare type ChartComponentLike = ChartComponent | ChartComponent[] | { [key: string]: ChartComponent } | Plugin | Plugin[];\n\n/**\n * Please use the module's default export which provides a singleton instance\n * Note: class is exported for typedoc\n */\nexport interface Registry {\n  readonly controllers: TypedRegistry<DatasetController>;\n  readonly elements: TypedRegistry<Element>;\n  readonly plugins: TypedRegistry<Plugin>;\n  readonly scales: TypedRegistry<Scale>;\n\n  add(...args: ChartComponentLike[]): void;\n  remove(...args: ChartComponentLike[]): void;\n\n  addControllers(...args: ChartComponentLike[]): void;\n  addElements(...args: ChartComponentLike[]): void;\n  addPlugins(...args: ChartComponentLike[]): void;\n  addScales(...args: ChartComponentLike[]): void;\n\n  getController(id: string): DatasetController | undefined;\n  getElement(id: string): Element | undefined;\n  getPlugin(id: string): Plugin | undefined;\n  getScale(id: string): Scale | undefined;\n}\n\nexport declare const registry: Registry;\n\nexport interface Tick {\n  value: number;\n  label?: string | string[];\n  major?: boolean;\n}\n\nexport interface CoreScaleOptions {\n  /**\n   * Controls the axis global visibility (visible when true, hidden when false). When display: 'auto', the axis is visible only if at least one associated dataset is visible.\n   * @default true\n   */\n  display: boolean | 'auto';\n  /**\n   * Align pixel values to device pixels\n   */\n  alignToPixels: boolean;\n  /**\n   * Background color of the scale area.\n   */\n  backgroundColor: Color;\n  /**\n   * Reverse the scale.\n   * @default false\n   */\n  reverse: boolean;\n  /**\n   * Clip the dataset drawing against the size of the scale instead of chart area.\n   * @default true\n   */\n  clip: boolean;\n  /**\n   * The weight used to sort the axis. Higher weights are further away from the chart area.\n   * @default true\n   */\n  weight: number;\n  /**\n   * User defined minimum value for the scale, overrides minimum value from data.\n   */\n  min: unknown;\n  /**\n   * User defined maximum value for the scale, overrides maximum value from data.\n   */\n  max: unknown;\n  /**\n   * Adjustment used when calculating the maximum data value.\n   */\n  suggestedMin: unknown;\n  /**\n   * Adjustment used when calculating the minimum data value.\n   */\n  suggestedMax: unknown;\n  /**\n   * Callback called before the update process starts.\n   */\n  beforeUpdate(axis: Scale): void;\n  /**\n   * Callback that runs before dimensions are set.\n   */\n  beforeSetDimensions(axis: Scale): void;\n  /**\n   * Callback that runs after dimensions are set.\n   */\n  afterSetDimensions(axis: Scale): void;\n  /**\n   * Callback that runs before data limits are determined.\n   */\n  beforeDataLimits(axis: Scale): void;\n  /**\n   * Callback that runs after data limits are determined.\n   */\n  afterDataLimits(axis: Scale): void;\n  /**\n   * Callback that runs before ticks are created.\n   */\n  beforeBuildTicks(axis: Scale): void;\n  /**\n   * Callback that runs after ticks are created. Useful for filtering ticks.\n   */\n  afterBuildTicks(axis: Scale): void;\n  /**\n   * Callback that runs before ticks are converted into strings.\n   */\n  beforeTickToLabelConversion(axis: Scale): void;\n  /**\n   * Callback that runs after ticks are converted into strings.\n   */\n  afterTickToLabelConversion(axis: Scale): void;\n  /**\n   * Callback that runs before tick rotation is determined.\n   */\n  beforeCalculateLabelRotation(axis: Scale): void;\n  /**\n   * Callback that runs after tick rotation is determined.\n   */\n  afterCalculateLabelRotation(axis: Scale): void;\n  /**\n   * Callback that runs before the scale fits to the canvas.\n   */\n  beforeFit(axis: Scale): void;\n  /**\n   * Callback that runs after the scale fits to the canvas.\n   */\n  afterFit(axis: Scale): void;\n  /**\n   * Callback that runs at the end of the update process.\n   */\n  afterUpdate(axis: Scale): void;\n}\n\nexport interface Scale<O extends CoreScaleOptions = CoreScaleOptions> extends Element<unknown, O>, LayoutItem {\n  readonly id: string;\n  readonly type: string;\n  readonly ctx: CanvasRenderingContext2D;\n  readonly chart: Chart;\n\n  maxWidth: number;\n  maxHeight: number;\n\n  paddingTop: number;\n  paddingBottom: number;\n  paddingLeft: number;\n  paddingRight: number;\n\n  axis: string;\n  labelRotation: number;\n  min: number;\n  max: number;\n  ticks: Tick[];\n  getMatchingVisibleMetas(type?: string): ChartMeta[];\n\n  drawTitle(chartArea: ChartArea): void;\n  drawLabels(chartArea: ChartArea): void;\n  drawGrid(chartArea: ChartArea): void;\n\n  /**\n   * @param {number} pixel\n   * @return {number}\n   */\n  getDecimalForPixel(pixel: number): number;\n  /**\n   * Utility for getting the pixel location of a percentage of scale\n   * The coordinate (0, 0) is at the upper-left corner of the canvas\n   * @param {number} decimal\n   * @return {number}\n   */\n  getPixelForDecimal(decimal: number): number;\n  /**\n   * Returns the location of the tick at the given index\n   * The coordinate (0, 0) is at the upper-left corner of the canvas\n   * @param {number} index\n   * @return {number}\n   */\n  getPixelForTick(index: number): number;\n  /**\n   * Used to get the label to display in the tooltip for the given value\n   * @param {*} value\n   * @return {string}\n   */\n  getLabelForValue(value: number): string;\n\n  /**\n   * Returns the grid line width at given value\n   */\n  getLineWidthForValue(value: number): number;\n\n  /**\n   * Returns the location of the given data point. Value can either be an index or a numerical value\n   * The coordinate (0, 0) is at the upper-left corner of the canvas\n   * @param {*} value\n   * @param {number} [index]\n   * @return {number}\n   */\n  getPixelForValue(value: number, index?: number): number;\n\n  /**\n   * Used to get the data value from a given pixel. This is the inverse of getPixelForValue\n   * The coordinate (0, 0) is at the upper-left corner of the canvas\n   * @param {number} pixel\n   * @return {*}\n   */\n  getValueForPixel(pixel: number): number | undefined;\n\n  getBaseValue(): number;\n  /**\n   * Returns the pixel for the minimum chart value\n   * The coordinate (0, 0) is at the upper-left corner of the canvas\n   * @return {number}\n   */\n  getBasePixel(): number;\n\n  init(options: O): void;\n  parse(raw: unknown, index?: number): unknown;\n  getUserBounds(): { min: number; max: number; minDefined: boolean; maxDefined: boolean };\n  getMinMax(canStack: boolean): { min: number; max: number };\n  getTicks(): Tick[];\n  getLabels(): string[];\n  getLabelItems(chartArea?: ChartArea): LabelItem[];\n  beforeUpdate(): void;\n  configure(): void;\n  afterUpdate(): void;\n  beforeSetDimensions(): void;\n  setDimensions(): void;\n  afterSetDimensions(): void;\n  beforeDataLimits(): void;\n  determineDataLimits(): void;\n  afterDataLimits(): void;\n  beforeBuildTicks(): void;\n  buildTicks(): Tick[];\n  afterBuildTicks(): void;\n  beforeTickToLabelConversion(): void;\n  generateTickLabels(ticks: Tick[]): void;\n  afterTickToLabelConversion(): void;\n  beforeCalculateLabelRotation(): void;\n  calculateLabelRotation(): void;\n  afterCalculateLabelRotation(): void;\n  beforeFit(): void;\n  fit(): void;\n  afterFit(): void;\n\n  isFullSize(): boolean;\n}\nexport declare class Scale {\n  constructor(cfg: {id: string, type: string, ctx: CanvasRenderingContext2D, chart: Chart});\n}\n\nexport interface ScriptableScaleContext {\n  chart: Chart;\n  scale: Scale;\n  index: number;\n  tick: Tick;\n}\n\nexport interface ScriptableScalePointLabelContext {\n  chart: Chart;\n  scale: Scale;\n  index: number;\n  label: string;\n  type: string;\n}\n\nexport interface RenderTextOpts {\n  /**\n   * The fill color of the text. If unset, the existing\n   * fillStyle property of the canvas is unchanged.\n   */\n  color?: Color;\n\n  /**\n   * The width of the strikethrough / underline\n   * @default 2\n   */\n  decorationWidth?: number;\n\n  /**\n   * The max width of the text in pixels\n   */\n  maxWidth?: number;\n\n  /**\n   * A rotation to be applied to the canvas\n   * This is applied after the translation is applied\n   */\n  rotation?: number;\n\n  /**\n   * Apply a strikethrough effect to the text\n   */\n  strikethrough?: boolean;\n\n  /**\n   * The color of the text stroke. If unset, the existing\n   * strokeStyle property of the context is unchanged\n   */\n  strokeColor?: Color;\n\n  /**\n   * The text stroke width. If unset, the existing\n   * lineWidth property of the context is unchanged\n   */\n  strokeWidth?: number;\n\n  /**\n   * The text alignment to use. If unset, the existing\n   * textAlign property of the context is unchanged\n   */\n  textAlign?: CanvasTextAlign;\n\n  /**\n   * The text baseline to use. If unset, the existing\n   * textBaseline property of the context is unchanged\n   */\n  textBaseline?: CanvasTextBaseline;\n\n  /**\n   * If specified, a translation to apply to the context\n   */\n  translation?: [number, number];\n\n  /**\n   * Underline the text\n   */\n  underline?: boolean;\n\n  /**\n   * Dimensions for drawing the label backdrop\n   */\n  backdrop?: BackdropOptions;\n}\n\nexport interface BackdropOptions {\n  /**\n   * Left position of backdrop as pixel\n   */\n  left: number;\n\n  /**\n   * Top position of backdrop as pixel\n   */\n  top: number;\n\n  /**\n   * Width of backdrop in pixels\n   */\n  width: number;\n\n  /**\n   * Height of backdrop in pixels\n   */\n  height: number;\n\n  /**\n   * Color of label backdrops.\n   */\n  color: Scriptable<Color, ScriptableScaleContext>;\n}\n\nexport interface LabelItem {\n  label: string | string[];\n  font: CanvasFontSpec;\n  textOffset: number;\n  options: RenderTextOpts;\n}\n\nexport declare const Ticks: {\n  formatters: {\n    /**\n     * Formatter for value labels\n     * @param value the value to display\n     * @return {string|string[]} the label to display\n     */\n    values(value: unknown): string | string[];\n    /**\n     * Formatter for numeric ticks\n     * @param tickValue the value to be formatted\n     * @param index the position of the tickValue parameter in the ticks array\n     * @param ticks the list of ticks being converted\n     * @return string representation of the tickValue parameter\n     */\n    numeric(this: Scale, tickValue: number, index: number, ticks: { value: number }[]): string;\n    /**\n     * Formatter for logarithmic ticks\n     * @param tickValue the value to be formatted\n     * @param index the position of the tickValue parameter in the ticks array\n     * @param ticks the list of ticks being converted\n     * @return string representation of the tickValue parameter\n     */\n    logarithmic(this: Scale, tickValue: number, index: number, ticks: { value: number }[]): string;\n  };\n};\n\nexport interface TypedRegistry<T> {\n  /**\n   * @param {ChartComponent} item\n   * @returns {string} The scope where items defaults were registered to.\n   */\n  register(item: ChartComponent): string;\n  get(id: string): T | undefined;\n  unregister(item: ChartComponent): void;\n}\n\nexport interface ChartEvent {\n  type:\n  | 'contextmenu'\n  | 'mouseenter'\n  | 'mousedown'\n  | 'mousemove'\n  | 'mouseup'\n  | 'mouseout'\n  | 'click'\n  | 'dblclick'\n  | 'keydown'\n  | 'keypress'\n  | 'keyup'\n  | 'resize';\n  native: Event | null;\n  x: number | null;\n  y: number | null;\n}\nexport interface ChartComponent {\n  id: string;\n  defaults?: AnyObject;\n  defaultRoutes?: { [property: string]: string };\n\n  beforeRegister?(): void;\n  afterRegister?(): void;\n  beforeUnregister?(): void;\n  afterUnregister?(): void;\n}\n\nexport type InteractionAxis = 'x' | 'y' | 'xy' | 'r';\n\nexport interface CoreInteractionOptions {\n  /**\n   * Sets which elements appear in the tooltip. See Interaction Modes for details.\n   * @default 'nearest'\n   */\n  mode: InteractionMode;\n  /**\n   * if true, the hover mode only applies when the mouse position intersects an item on the chart.\n   * @default true\n   */\n  intersect: boolean;\n\n  /**\n   * Defines which directions are used in calculating distances. Defaults to 'x' for 'index' mode and 'xy' in dataset and 'nearest' modes.\n   */\n  axis: InteractionAxis;\n\n  /**\n   * if true, the invisible points that are outside of the chart area will also be included when evaluating interactions.\n   * @default false\n   */\n  includeInvisible: boolean;\n}\n\nexport interface CoreChartOptions<TType extends ChartType> extends ParsingOptions, AnimationOptions<TType> {\n\n  datasets: {\n    [key in ChartType]: ChartTypeRegistry[key]['datasetOptions']\n  }\n\n  /**\n   * The base axis of the chart. 'x' for vertical charts and 'y' for horizontal charts.\n   * @default 'x'\n   */\n  indexAxis: 'x' | 'y';\n\n  /**\n   * How to clip relative to chartArea. Positive value allows overflow, negative value clips that many pixels inside chartArea. 0 = clip at chartArea. Clipping can also be configured per side: `clip: {left: 5, top: false, right: -2, bottom: 0}`\n   */\n  clip: number | ChartArea | false;\n\n  /**\n   * base color\n   * @see Defaults.color\n   */\n  color: Scriptable<Color, ScriptableContext<TType>>;\n  /**\n   * base background color\n   * @see Defaults.backgroundColor\n   */\n  backgroundColor: ScriptableAndArray<Color, ScriptableContext<TType>>;\n  /**\n   * base hover background color\n   * @see Defaults.hoverBackgroundColor\n   */\n  hoverBackgroundColor: ScriptableAndArray<Color, ScriptableContext<TType>>;\n  /**\n   * base border color\n   * @see Defaults.borderColor\n   */\n  borderColor: ScriptableAndArray<Color, ScriptableContext<TType>>;\n  /**\n   * base hover border color\n   * @see Defaults.hoverBorderColor\n   */\n  hoverBorderColor: ScriptableAndArray<Color, ScriptableContext<TType>>;\n  /**\n   * base font\n   * @see Defaults.font\n   */\n  font: Partial<FontSpec>;\n  /**\n   * Resizes the chart canvas when its container does (important note...).\n   * @default true\n   */\n  responsive: boolean;\n  /**\n   * Maintain the original canvas aspect ratio (width / height) when resizing. For this option to work properly the chart must be in its own dedicated container.\n   * @default true\n   */\n  maintainAspectRatio: boolean;\n  /**\n   * Delay the resize update by give amount of milliseconds. This can ease the resize process by debouncing update of the elements.\n   * @default 0\n   */\n  resizeDelay: number;\n\n  /**\n   * Canvas aspect ratio (i.e. width / height, a value of 1 representing a square canvas). Note that this option is ignored if the height is explicitly defined either as attribute or via the style.\n   * @default 2\n   */\n  aspectRatio: number;\n\n  /**\n   * Locale used for number formatting (using `Intl.NumberFormat`).\n   * @default user's browser setting\n   */\n  locale: string;\n\n  /**\n   * Called when a resize occurs. Gets passed two arguments: the chart instance and the new size.\n   */\n  onResize(chart: Chart, size: { width: number; height: number }): void;\n\n  /**\n   * Override the window's default devicePixelRatio.\n   * @default window.devicePixelRatio\n   */\n  devicePixelRatio: number;\n\n  interaction: CoreInteractionOptions;\n\n  hover: CoreInteractionOptions;\n\n  /**\n   * The events option defines the browser events that the chart should listen to for tooltips and hovering.\n   * @default ['mousemove', 'mouseout', 'click', 'touchstart', 'touchmove']\n   */\n  events: (keyof HTMLElementEventMap)[]\n\n  /**\n   * Called when any of the events fire. Passed the event, an array of active elements (bars, points, etc), and the chart.\n   */\n  onHover(event: ChartEvent, elements: ActiveElement[], chart: Chart): void;\n\n  /**\n   * Called if the event is of type 'mouseup' or 'click'. Passed the event, an array of active elements, and the chart.\n   */\n  onClick(event: ChartEvent, elements: ActiveElement[], chart: Chart): void;\n\n  layout: Partial<{\n    autoPadding: boolean;\n    padding: Scriptable<Padding, ScriptableContext<TType>>;\n  }>;\n}\n\nexport type AnimationSpec<TType extends ChartType> = {\n  /**\n   * The number of milliseconds an animation takes.\n   * @default 1000\n   */\n  duration?: Scriptable<number, ScriptableContext<TType>>;\n  /**\n   * Easing function to use\n   * @default 'easeOutQuart'\n   */\n  easing?: Scriptable<EasingFunction, ScriptableContext<TType>>;\n\n  /**\n   * Delay before starting the animations.\n   * @default 0\n   */\n  delay?: Scriptable<number, ScriptableContext<TType>>;\n\n  /**\n   *   If set to true, the animations loop endlessly.\n   * @default false\n   */\n  loop?: Scriptable<boolean, ScriptableContext<TType>>;\n}\n\nexport type AnimationsSpec<TType extends ChartType> = {\n  [name: string]: false | AnimationSpec<TType> & {\n    properties: string[];\n\n    /**\n     * Type of property, determines the interpolator used. Possible values: 'number', 'color' and 'boolean'. Only really needed for 'color', because typeof does not get that right.\n     */\n    type: 'color' | 'number' | 'boolean';\n\n    fn: <T>(from: T, to: T, factor: number) => T;\n\n    /**\n     * Start value for the animation. Current value is used when undefined\n     */\n    from: Scriptable<Color | number | boolean, ScriptableContext<TType>>;\n    /**\n     *\n     */\n    to: Scriptable<Color | number | boolean, ScriptableContext<TType>>;\n  }\n}\n\nexport type TransitionSpec<TType extends ChartType> = {\n  animation: AnimationSpec<TType>;\n  animations: AnimationsSpec<TType>;\n}\n\nexport type TransitionsSpec<TType extends ChartType> = {\n  [mode: string]: TransitionSpec<TType>\n}\n\nexport type AnimationOptions<TType extends ChartType> = {\n  animation: false | AnimationSpec<TType> & {\n    /**\n     * Callback called on each step of an animation.\n     */\n    onProgress?: (this: Chart, event: AnimationEvent) => void;\n    /**\n     * Callback called when all animations are completed.\n     */\n    onComplete?: (this: Chart, event: AnimationEvent) => void;\n  };\n  animations: AnimationsSpec<TType>;\n  transitions: TransitionsSpec<TType>;\n};\n\nexport interface FontSpec {\n  /**\n   * Default font family for all text, follows CSS font-family options.\n   * @default \"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif\"\n   */\n  family: string;\n  /**\n   * Default font size (in px) for text. Does not apply to radialLinear scale point labels.\n   * @default 12\n   */\n  size: number;\n  /**\n   * Default font style. Does not apply to tooltip title or footer. Does not apply to chart title. Follows CSS font-style options (i.e. normal, italic, oblique, initial, inherit)\n   * @default 'normal'\n   */\n  style: 'normal' | 'italic' | 'oblique' | 'initial' | 'inherit';\n  /**\n   * Default font weight (boldness). (see MDN).\n   */\n  weight: 'normal' | 'bold' | 'lighter' | 'bolder' | number | null;\n  /**\n   * Height of an individual line of text (see MDN).\n   * @default 1.2\n   */\n  lineHeight: number | string;\n}\n\nexport interface CanvasFontSpec extends FontSpec {\n  string: string;\n}\n\nexport type TextAlign = 'left' | 'center' | 'right';\nexport type Align = 'start' | 'center' | 'end';\n\nexport interface VisualElement {\n  draw(ctx: CanvasRenderingContext2D, area?: ChartArea): void;\n  inRange(mouseX: number, mouseY: number, useFinalPosition?: boolean): boolean;\n  inXRange(mouseX: number, useFinalPosition?: boolean): boolean;\n  inYRange(mouseY: number, useFinalPosition?: boolean): boolean;\n  getCenterPoint(useFinalPosition?: boolean): Point;\n  getRange?(axis: 'x' | 'y'): number;\n}\n\nexport interface CommonElementOptions {\n  borderWidth: number;\n  borderColor: Color;\n  backgroundColor: Color;\n}\n\nexport interface CommonHoverOptions {\n  hoverBorderWidth: number;\n  hoverBorderColor: Color;\n  hoverBackgroundColor: Color;\n}\n\nexport interface Segment {\n  start: number;\n  end: number;\n  loop: boolean;\n}\n\nexport interface ArcBorderRadius {\n  outerStart: number;\n  outerEnd: number;\n  innerStart: number;\n  innerEnd: number;\n}\n\nexport interface ArcOptions extends CommonElementOptions {\n  /**\n   * If true, Arc can take up 100% of a circular graph without any visual split or cut. This option doesn't support borderRadius and borderJoinStyle miter\n   * @default true\n   */\n  selfJoin: boolean;\n\n  /**\n   * Arc stroke alignment.\n   */\n  borderAlign: 'center' | 'inner';\n  /**\n   * Line dash. See MDN.\n   * @default []\n   */\n  borderDash: number[];\n  /**\n   * Line dash offset. See MDN.\n   * @default 0.0\n   */\n  borderDashOffset: number;\n  /**\n   * Line join style. See MDN. Default is 'round' when `borderAlign` is 'inner', else 'bevel'.\n   */\n  borderJoinStyle: CanvasLineJoin;\n\n  /**\n   * Sets the border radius for arcs\n   * @default 0\n   */\n  borderRadius: number | ArcBorderRadius;\n\n  /**\n   * Arc offset (in pixels).\n   */\n  offset: number;\n\n  /**\n   * If false, Arc will be flat.\n   * @default true\n   */\n  circular: boolean;\n\n  /**\n   * Spacing between arcs\n   */\n  spacing: number\n}\n\nexport interface ArcHoverOptions extends CommonHoverOptions {\n  hoverBorderDash: number[];\n  hoverBorderDashOffset: number;\n  hoverOffset: number;\n}\n\nexport interface LineProps {\n  points: Point[]\n}\n\nexport interface LineOptions extends CommonElementOptions {\n  /**\n   * Line cap style. See MDN.\n   * @default 'butt'\n   */\n  borderCapStyle: CanvasLineCap;\n  /**\n   * Line dash. See MDN.\n   * @default []\n   */\n  borderDash: number[];\n  /**\n   * Line dash offset. See MDN.\n   * @default 0.0\n   */\n  borderDashOffset: number;\n  /**\n   * Line join style. See MDN.\n   * @default 'miter'\n   */\n  borderJoinStyle: CanvasLineJoin;\n  /**\n   *   true to keep Bézier control inside the chart, false for no restriction.\n   * @default true\n   */\n  capBezierPoints: boolean;\n  /**\n   * Interpolation mode to apply.\n   * @default 'default'\n   */\n  cubicInterpolationMode: 'default' | 'monotone';\n  /**\n   * Bézier curve tension (0 for no Bézier curves).\n   * @default 0\n   */\n  tension: number;\n  /**\n   * true to show the line as a stepped line (tension will be ignored).\n   * @default false\n   */\n  stepped: 'before' | 'after' | 'middle' | boolean;\n  /**\n   * Both line and radar charts support a fill option on the dataset object which can be used to create area between two datasets or a dataset and a boundary, i.e. the scale origin, start or end\n   */\n  fill: FillTarget | ComplexFillTarget;\n  /**\n   * If true, lines will be drawn between points with no or null data. If false, points with NaN data will create a break in the line. Can also be a number specifying the maximum gap length to span. The unit of the value depends on the scale used.\n   */\n  spanGaps: boolean | number;\n\n  segment: {\n    backgroundColor: Scriptable<Color|undefined, ScriptableLineSegmentContext>,\n    borderColor: Scriptable<Color|undefined, ScriptableLineSegmentContext>,\n    borderCapStyle: Scriptable<CanvasLineCap|undefined, ScriptableLineSegmentContext>;\n    borderDash: Scriptable<number[]|undefined, ScriptableLineSegmentContext>;\n    borderDashOffset: Scriptable<number|undefined, ScriptableLineSegmentContext>;\n    borderJoinStyle: Scriptable<CanvasLineJoin|undefined, ScriptableLineSegmentContext>;\n    borderWidth: Scriptable<number|undefined, ScriptableLineSegmentContext>;\n  };\n}\n\nexport interface LineHoverOptions extends CommonHoverOptions {\n  hoverBorderCapStyle: CanvasLineCap;\n  hoverBorderDash: number[];\n  hoverBorderDashOffset: number;\n  hoverBorderJoinStyle: CanvasLineJoin;\n}\n\nexport interface LineElement<T extends LineProps = LineProps, O extends LineOptions = LineOptions>\n  extends Element<T, O>,\n  VisualElement {\n  updateControlPoints(chartArea: ChartArea, indexAxis?: 'x' | 'y'): void;\n  points: Point[];\n  readonly segments: Segment[];\n  first(): Point | false;\n  last(): Point | false;\n  interpolate(point: Point, property: 'x' | 'y'): undefined | Point | Point[];\n  pathSegment(ctx: CanvasRenderingContext2D, segment: Segment, params: AnyObject): undefined | boolean;\n  path(ctx: CanvasRenderingContext2D): boolean;\n}\n\nexport declare const LineElement: ChartComponent & {\n  prototype: LineElement;\n  new (cfg: AnyObject): LineElement;\n};\n\nexport type PointStyle =\n  | 'circle'\n  | 'cross'\n  | 'crossRot'\n  | 'dash'\n  | 'line'\n  | 'rect'\n  | 'rectRounded'\n  | 'rectRot'\n  | 'star'\n  | 'triangle'\n  | false\n  | HTMLImageElement\n  | HTMLCanvasElement;\n\nexport interface PointOptions extends CommonElementOptions {\n  /**\n   * Point radius\n   * @default 3\n   */\n  radius: number;\n  /**\n   * Extra radius added to point radius for hit detection.\n   * @default 1\n   */\n  hitRadius: number;\n  /**\n   * Point style\n   * @default 'circle;\n   */\n  pointStyle: PointStyle;\n  /**\n   * Point rotation (in degrees).\n   * @default 0\n   */\n  rotation: number;\n  /**\n   * Draw the active elements over the other elements of the dataset,\n   * @default true\n   */\n  drawActiveElementsOnTop: boolean;\n}\n\nexport interface PointHoverOptions extends CommonHoverOptions {\n  /**\n   * Point radius when hovered.\n   * @default 4\n   */\n  hoverRadius: number;\n}\n\nexport interface PointPrefixedOptions {\n  /**\n   * The fill color for points.\n   */\n  pointBackgroundColor: Color;\n  /**\n   * The border color for points.\n   */\n  pointBorderColor: Color;\n  /**\n   * The width of the point border in pixels.\n   */\n  pointBorderWidth: number;\n  /**\n   * The pixel size of the non-displayed point that reacts to mouse events.\n   */\n  pointHitRadius: number;\n  /**\n   * The radius of the point shape. If set to 0, the point is not rendered.\n   */\n  pointRadius: number;\n  /**\n   * The rotation of the point in degrees.\n   */\n  pointRotation: number;\n  /**\n   * Style of the point.\n   */\n  pointStyle: PointStyle;\n}\n\nexport interface PointPrefixedHoverOptions {\n  /**\n   * Point background color when hovered.\n   */\n  pointHoverBackgroundColor: Color;\n  /**\n   * Point border color when hovered.\n   */\n  pointHoverBorderColor: Color;\n  /**\n   * Border width of point when hovered.\n   */\n  pointHoverBorderWidth: number;\n  /**\n   * The radius of the point when hovered.\n   */\n  pointHoverRadius: number;\n}\n\nexport interface BarProps extends Point {\n  base: number;\n  horizontal: boolean;\n  width: number;\n  height: number;\n}\n\nexport interface BarOptions extends Omit<CommonElementOptions, 'borderWidth'> {\n  /**\n   * The base value for the bar in data units along the value axis.\n   */\n  base: number;\n\n  /**\n   * Skipped (excluded) border: 'start', 'end', 'left',  'right', 'bottom', 'top', 'middle', false (none) or true (all).\n   * @default 'start'\n   */\n  borderSkipped: 'start' | 'end' | 'left' | 'right' | 'bottom' | 'top' | 'middle' | boolean;\n\n  /**\n   * Border radius\n   * @default 0\n   */\n  borderRadius: number | BorderRadius;\n\n  /**\n   * Amount to inflate the rectangle(s). This can be used to hide artifacts between bars.\n   * Unit is pixels. 'auto' translates to 0.33 pixels when barPercentage * categoryPercentage is 1, else 0.\n   * @default 'auto'\n   */\n  inflateAmount: number | 'auto';\n\n  /**\n   * Width of the border, number for all sides, object to specify width for each side specifically\n   * @default 0\n   */\n  borderWidth: number | { top?: number, right?: number, bottom?: number, left?: number };\n}\n\nexport interface BorderRadius {\n  topLeft: number;\n  topRight: number;\n  bottomLeft: number;\n  bottomRight: number;\n}\n\nexport interface BarHoverOptions extends CommonHoverOptions {\n  hoverBorderRadius: number | BorderRadius;\n}\n\nexport interface BarElement<\n  T extends BarProps = BarProps,\n  O extends BarOptions = BarOptions\n> extends Element<T, O>, VisualElement {}\n\nexport declare const BarElement: ChartComponent & {\n  prototype: BarElement;\n  new (cfg: AnyObject): BarElement;\n};\n\nexport interface ElementOptionsByType<TType extends ChartType> {\n  arc: ScriptableAndArrayOptions<ArcOptions & ArcHoverOptions, ScriptableContext<TType>>;\n  bar: ScriptableAndArrayOptions<BarOptions & BarHoverOptions, ScriptableContext<TType>>;\n  line: ScriptableAndArrayOptions<LineOptions & LineHoverOptions, ScriptableContext<TType>>;\n  point: ScriptableAndArrayOptions<PointOptions & PointHoverOptions, ScriptableContext<TType>>;\n}\n\nexport type ElementChartOptions<TType extends ChartType = ChartType> = {\n  elements: ElementOptionsByType<TType>\n};\n\nexport declare class BasePlatform {\n  /**\n   * Called at chart construction time, returns a context2d instance implementing\n   * the [W3C Canvas 2D Context API standard]{@link https://www.w3.org/TR/2dcontext/}.\n   * @param {HTMLCanvasElement} canvas - The canvas from which to acquire context (platform specific)\n   * @param options - The chart options\n   */\n  acquireContext(\n    canvas: HTMLCanvasElement,\n    options?: CanvasRenderingContext2DSettings\n  ): CanvasRenderingContext2D | null;\n  /**\n   * Called at chart destruction time, releases any resources associated to the context\n   * previously returned by the acquireContext() method.\n   * @param {CanvasRenderingContext2D} context - The context2d instance\n   * @returns {boolean} true if the method succeeded, else false\n   */\n  releaseContext(context: CanvasRenderingContext2D): boolean;\n  /**\n   * Registers the specified listener on the given chart.\n   * @param {Chart} chart - Chart from which to listen for event\n   * @param {string} type - The ({@link ChartEvent}) type to listen for\n   * @param listener - Receives a notification (an object that implements\n   * the {@link ChartEvent} interface) when an event of the specified type occurs.\n   */\n  addEventListener(chart: Chart, type: string, listener: (e: ChartEvent) => void): void;\n  /**\n   * Removes the specified listener previously registered with addEventListener.\n   * @param {Chart} chart - Chart from which to remove the listener\n   * @param {string} type - The ({@link ChartEvent}) type to remove\n   * @param listener - The listener function to remove from the event target.\n   */\n  removeEventListener(chart: Chart, type: string, listener: (e: ChartEvent) => void): void;\n  /**\n   * @returns {number} the current devicePixelRatio of the device this platform is connected to.\n   */\n  getDevicePixelRatio(): number;\n  /**\n   * @param {HTMLCanvasElement} canvas - The canvas for which to calculate the maximum size\n   * @param {number} [width] - Parent element's content width\n   * @param {number} [height] - Parent element's content height\n   * @param {number} [aspectRatio] - The aspect ratio to maintain\n   * @returns { width: number, height: number } the maximum size available.\n   */\n  getMaximumSize(canvas: HTMLCanvasElement, width?: number, height?: number, aspectRatio?: number): { width: number, height: number };\n  /**\n   * @param {HTMLCanvasElement} canvas\n   * @returns {boolean} true if the canvas is attached to the platform, false if not.\n   */\n  isAttached(canvas: HTMLCanvasElement): boolean;\n  /**\n   * Updates config with platform specific requirements\n   * @param {ChartConfiguration | ChartConfigurationCustomTypes} config\n   */\n  updateConfig(config: ChartConfiguration | ChartConfigurationCustomTypesPerDataset): void;\n}\n\nexport declare class BasicPlatform extends BasePlatform {}\nexport declare class DomPlatform extends BasePlatform {}\n\nexport declare const Decimation: Plugin;\n\nexport declare const enum DecimationAlgorithm {\n  lttb = 'lttb',\n  minmax = 'min-max',\n}\ninterface BaseDecimationOptions {\n  enabled: boolean;\n  threshold?: number;\n}\n\ninterface LttbDecimationOptions extends BaseDecimationOptions {\n  algorithm: DecimationAlgorithm.lttb | 'lttb';\n  samples?: number;\n}\n\ninterface MinMaxDecimationOptions extends BaseDecimationOptions {\n  algorithm: DecimationAlgorithm.minmax | 'min-max';\n}\n\nexport type DecimationOptions = LttbDecimationOptions | MinMaxDecimationOptions;\n\nexport declare const Filler: Plugin;\nexport interface FillerOptions {\n  drawTime: 'beforeDraw' | 'beforeDatasetDraw' | 'beforeDatasetsDraw';\n  propagate: boolean;\n}\n\nexport type FillTarget = number | string | { value: number } | 'start' | 'end' | 'origin' | 'stack' | 'shape' | boolean;\n\nexport interface ComplexFillTarget {\n  /**\n   * The accepted values are the same as the filling mode values, so you may use absolute and relative dataset indexes and/or boundaries.\n   */\n  target: FillTarget;\n  /**\n   * If no color is set, the default color will be the background color of the chart.\n   */\n  above: Color;\n  /**\n   * Same as the above.\n   */\n  below: Color;\n}\n\nexport interface FillerControllerDatasetOptions {\n  /**\n   * Both line and radar charts support a fill option on the dataset object which can be used to create area between two datasets or a dataset and a boundary, i.e. the scale origin, start or end\n   */\n  fill: FillTarget | ComplexFillTarget;\n}\n\nexport declare const Legend: Plugin;\n\nexport interface LegendItem {\n  /**\n   * Label that will be displayed\n   */\n  text: string;\n\n  /**\n   * Border radius of the legend box\n   * @since 3.1.0\n   */\n  borderRadius?: number | BorderRadius;\n\n  /**\n   * Index of the associated dataset\n   */\n  datasetIndex?: number;\n\n  /**\n   * Index the associated label in the labels array\n   */\n  index?: number\n\n  /**\n   * Fill style of the legend box\n   */\n  fillStyle?: Color;\n\n  /**\n   * Font color for the text\n   * Defaults to LegendOptions.labels.color\n   */\n  fontColor?: Color;\n\n  /**\n   * If true, this item represents a hidden dataset. Label will be rendered with a strike-through effect\n   */\n  hidden?: boolean;\n\n  /**\n   * For box border.\n   * @see https://developer.mozilla.org/en/docs/Web/API/CanvasRenderingContext2D/lineCap\n   */\n  lineCap?: CanvasLineCap;\n\n  /**\n   * For box border.\n   * @see https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash\n   */\n  lineDash?: number[];\n\n  /**\n   * For box border.\n   * @see https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineDashOffset\n   */\n  lineDashOffset?: number;\n\n  /**\n   * For box border.\n   * @see https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineJoin\n   */\n  lineJoin?: CanvasLineJoin;\n\n  /**\n   * Width of box border\n   */\n  lineWidth?: number;\n\n  /**\n   * Stroke style of the legend box\n   */\n  strokeStyle?: Color;\n\n  /**\n   * Point style of the legend box (only used if usePointStyle is true)\n   */\n  pointStyle?: PointStyle;\n\n  /**\n   * Rotation of the point in degrees (only used if usePointStyle is true)\n   */\n  rotation?: number;\n\n  /**\n   * Text alignment\n   */\n  textAlign?: TextAlign;\n}\n\nexport interface LegendElement<TType extends ChartType> extends Element<AnyObject, LegendOptions<TType>>, LayoutItem {\n  chart: Chart<TType>;\n  ctx: CanvasRenderingContext2D;\n  legendItems?: LegendItem[];\n  options: LegendOptions<TType>;\n  fit(): void;\n}\n\nexport interface LegendOptions<TType extends ChartType> {\n  /**\n   * Is the legend shown?\n   * @default true\n   */\n  display: boolean;\n  /**\n   * Position of the legend.\n   * @default 'top'\n   */\n  position: LayoutPosition;\n  /**\n   * Alignment of the legend.\n   * @default 'center'\n   */\n  align: Align;\n  /**\n   * Maximum height of the legend, in pixels\n   */\n  maxHeight: number;\n  /**\n   * Maximum width of the legend, in pixels\n   */\n  maxWidth: number;\n  /**\n   * Marks that this box should take the full width/height of the canvas (moving other boxes). This is unlikely to need to be changed in day-to-day use.\n   * @default true\n   */\n  fullSize: boolean;\n  /**\n   * Legend will show datasets in reverse order.\n   * @default false\n   */\n  reverse: boolean;\n  /**\n   * A callback that is called when a click event is registered on a label item.\n   */\n  onClick(this: LegendElement<TType>, e: ChartEvent, legendItem: LegendItem, legend: LegendElement<TType>): void;\n  /**\n   * A callback that is called when a 'mousemove' event is registered on top of a label item\n   */\n  onHover(this: LegendElement<TType>, e: ChartEvent, legendItem: LegendItem, legend: LegendElement<TType>): void;\n  /**\n   * A callback that is called when a 'mousemove' event is registered outside of a previously hovered label item.\n   */\n  onLeave(this: LegendElement<TType>, e: ChartEvent, legendItem: LegendItem, legend: LegendElement<TType>): void;\n\n  labels: {\n    /**\n     * Width of colored box.\n     * @default 40\n     */\n    boxWidth: number;\n    /**\n     * Height of the coloured box.\n     * @default fontSize\n     */\n    boxHeight: number;\n    /**\n     * Color of label\n     * @see Defaults.color\n     */\n    color: Color;\n    /**\n     * Font of label\n     * @see Defaults.font\n     */\n    font: ScriptableAndScriptableOptions<Partial<FontSpec>, ScriptableChartContext>;\n    /**\n     * Padding between rows of colored boxes.\n     * @default 10\n     */\n    padding: number;\n    /**\n     * If usePointStyle is true, the width of the point style used for the legend.\n     */\n    pointStyleWidth: number;\n    /**\n     * Generates legend items for each thing in the legend. Default implementation returns the text + styling for the color box. See Legend Item for details.\n     */\n    generateLabels(chart: Chart): LegendItem[];\n\n    /**\n     * Filters legend items out of the legend. Receives 2 parameters, a Legend Item and the chart data\n     */\n    filter(item: LegendItem, data: ChartData): boolean;\n\n    /**\n     * Sorts the legend items\n     */\n    sort(a: LegendItem, b: LegendItem, data: ChartData): number;\n\n    /**\n     * Override point style for the legend. Only applies if usePointStyle is true\n     */\n    pointStyle: PointStyle;\n\n    /**\n     * Text alignment\n     */\n    textAlign?: TextAlign;\n\n    /**\n     * Label style will match corresponding point style (size is based on the minimum value between boxWidth and font.size).\n     * @default false\n     */\n    usePointStyle: boolean;\n\n    /**\n     * Label borderRadius will match corresponding borderRadius.\n     * @default false\n     */\n    useBorderRadius: boolean;\n\n    /**\n     * Override the borderRadius to use.\n     * @default undefined\n     */\n    borderRadius: number;\n  };\n  /**\n   * true for rendering the legends from right to left.\n   */\n  rtl: boolean;\n  /**\n   * This will force the text direction 'rtl' or 'ltr' on the canvas for rendering the legend, regardless of the css specified on the canvas\n   * @default canvas's default\n   */\n  textDirection: string;\n\n  title: {\n    /**\n     * Is the legend title displayed.\n     * @default false\n     */\n    display: boolean;\n    /**\n     * Color of title\n     * @see Defaults.color\n     */\n    color: Color;\n    /**\n     * see Fonts\n     */\n    font: ScriptableAndScriptableOptions<Partial<FontSpec>, ScriptableChartContext>;\n    position: 'center' | 'start' | 'end';\n    padding?: number | ChartArea;\n    /**\n     * The string title.\n     */\n    text: string;\n  };\n}\n\nexport declare const SubTitle: Plugin;\nexport declare const Title: Plugin;\n\nexport interface TitleOptions {\n  /**\n   * Alignment of the title.\n   * @default 'center'\n   */\n  align: Align;\n  /**\n   * Is the title shown?\n   * @default false\n   */\n  display: boolean;\n  /**\n   * Position of title\n   * @default 'top'\n   */\n  position: 'top' | 'left' | 'bottom' | 'right';\n  /**\n   * Color of text\n   * @see Defaults.color\n   */\n  color: Color;\n  font: ScriptableAndScriptableOptions<Partial<FontSpec>, ScriptableChartContext>;\n\n  /**\n   * Marks that this box should take the full width/height of the canvas (moving other boxes). If set to `false`, places the box above/beside the\n   * chart area\n   * @default true\n   */\n  fullSize: boolean;\n  /**\n   *   Adds padding above and below the title text if a single number is specified. It is also possible to change top and bottom padding separately.\n   */\n  padding: number | { top: number; bottom: number };\n  /**\n   *   Title text to display. If specified as an array, text is rendered on multiple lines.\n   */\n  text: string | string[];\n}\n\nexport type TooltipXAlignment = 'left' | 'center' | 'right';\nexport type TooltipYAlignment = 'top' | 'center' | 'bottom';\nexport interface TooltipLabelStyle {\n  borderColor: Color;\n  backgroundColor: Color;\n\n  /**\n   * Width of border line\n   * @since 3.1.0\n   */\n  borderWidth?: number;\n\n  /**\n   * Border dash\n   * @since 3.1.0\n   */\n  borderDash?: [number, number];\n\n  /**\n   * Border dash offset\n   * @since 3.1.0\n   */\n  borderDashOffset?: number;\n\n  /**\n   * borderRadius\n   * @since 3.1.0\n   */\n  borderRadius?: number | BorderRadius;\n}\nexport interface TooltipModel<TType extends ChartType> extends Element<AnyObject, TooltipOptions<TType>> {\n  readonly chart: Chart<TType>;\n\n  // The items that we are rendering in the tooltip. See Tooltip Item Interface section\n  dataPoints: TooltipItem<TType>[];\n\n  // Positioning\n  xAlign: TooltipXAlignment;\n  yAlign: TooltipYAlignment;\n\n  // X and Y properties are the top left of the tooltip\n  x: number;\n  y: number;\n  width: number;\n  height: number;\n  // Where the tooltip points to\n  caretX: number;\n  caretY: number;\n\n  // Body\n  // The body lines that need to be rendered\n  // Each object contains 3 parameters\n  // before: string[] // lines of text before the line with the color square\n  // lines: string[]; // lines of text to render as the main item with color square\n  // after: string[]; // lines of text to render after the main lines\n  body: { before: string[]; lines: string[]; after: string[] }[];\n  // lines of text that appear after the title but before the body\n  beforeBody: string[];\n  // line of text that appear after the body and before the footer\n  afterBody: string[];\n\n  // Title\n  // lines of text that form the title\n  title: string[];\n\n  // Footer\n  // lines of text that form the footer\n  footer: string[];\n\n  // Styles to render for each item in body[]. This is the styling of the squares in the tooltip\n  labelColors: TooltipLabelStyle[];\n  labelTextColors: Color[];\n  labelPointStyles: { pointStyle: PointStyle; rotation: number }[];\n\n  // 0 opacity is a hidden tooltip\n  opacity: number;\n\n  // tooltip options\n  options: TooltipOptions<TType>;\n\n  getActiveElements(): ActiveElement[];\n  setActiveElements(active: ActiveDataPoint[], eventPosition: Point): void;\n}\n\nexport interface TooltipPosition extends Point {\n  xAlign?: TooltipXAlignment;\n  yAlign?: TooltipYAlignment;\n}\n\nexport type TooltipPositionerFunction<TType extends ChartType> = (\n  this: TooltipModel<TType>,\n  items: readonly ActiveElement[],\n  eventPosition: Point\n) => TooltipPosition | false;\n\nexport interface TooltipPositionerMap {\n  average: TooltipPositionerFunction<ChartType>;\n  nearest: TooltipPositionerFunction<ChartType>;\n}\n\nexport type TooltipPositioner = keyof TooltipPositionerMap;\n\nexport interface Tooltip extends Plugin {\n  readonly positioners: TooltipPositionerMap;\n}\n\nexport declare const Tooltip: Tooltip;\n\nexport interface TooltipDatasetCallbacks<\n  TType extends ChartType,\n  Model = TooltipModel<TType>,\n  Item = TooltipItem<TType>> {\n  beforeLabel(this: Model, tooltipItem: Item): string | string[] | void;\n  label(this: Model, tooltipItem: Item): string | string[] | void;\n  afterLabel(this: Model, tooltipItem: Item): string | string[] | void;\n\n  labelColor(this: Model, tooltipItem: Item): TooltipLabelStyle | void;\n  labelTextColor(this: Model, tooltipItem: Item): Color | void;\n  labelPointStyle(this: Model, tooltipItem: Item): { pointStyle: PointStyle; rotation: number } | void;\n}\n\nexport interface TooltipCallbacks<\n  TType extends ChartType,\n  Model = TooltipModel<TType>,\n  Item = TooltipItem<TType>> extends TooltipDatasetCallbacks<TType, Model, Item> {\n\n  beforeTitle(this: Model, tooltipItems: Item[]): string | string[] | void;\n  title(this: Model, tooltipItems: Item[]): string | string[] | void;\n  afterTitle(this: Model, tooltipItems: Item[]): string | string[] | void;\n\n  beforeBody(this: Model, tooltipItems: Item[]): string | string[] | void;\n  afterBody(this: Model, tooltipItems: Item[]): string | string[] | void;\n\n  beforeLabel(this: Model, tooltipItem: Item): string | string[] | void;\n  label(this: Model, tooltipItem: Item): string | string[] | void;\n  afterLabel(this: Model, tooltipItem: Item): string | string[] | void;\n\n  labelColor(this: Model, tooltipItem: Item): TooltipLabelStyle | void;\n  labelTextColor(this: Model, tooltipItem: Item): Color | void;\n  labelPointStyle(this: Model, tooltipItem: Item): { pointStyle: PointStyle; rotation: number } | void;\n\n  beforeFooter(this: Model, tooltipItems: Item[]): string | string[] | void;\n  footer(this: Model, tooltipItems: Item[]): string | string[] | void;\n  afterFooter(this: Model, tooltipItems: Item[]): string | string[] | void;\n}\n\nexport interface ExtendedPlugin<\n  TType extends ChartType,\n  O = AnyObject,\n  Model = TooltipModel<TType>> {\n  /**\n   * @desc Called before drawing the `tooltip`. If any plugin returns `false`,\n   * the tooltip drawing is cancelled until another `render` is triggered.\n   * @param {Chart} chart - The chart instance.\n   * @param {object} args - The call arguments.\n   * @param {Tooltip} args.tooltip - The tooltip.\n   * @param {object} options - The plugin options.\n   * @returns {boolean} `false` to cancel the chart tooltip drawing.\n   */\n  beforeTooltipDraw?(chart: Chart, args: { tooltip: Model, cancelable: true }, options: O): boolean | void;\n  /**\n   * @desc Called after drawing the `tooltip`. Note that this hook will not\n   * be called if the tooltip drawing has been previously cancelled.\n   * @param {Chart} chart - The chart instance.\n   * @param {object} args - The call arguments.\n   * @param {Tooltip} args.tooltip - The tooltip.\n   * @param {object} options - The plugin options.\n   */\n  afterTooltipDraw?(chart: Chart, args: { tooltip: Model }, options: O): void;\n}\n\nexport interface ScriptableTooltipContext<TType extends ChartType> {\n  chart: UnionToIntersection<Chart<TType>>;\n  tooltip: UnionToIntersection<TooltipModel<TType>>;\n  tooltipItems: TooltipItem<TType>[];\n}\n\nexport interface TooltipOptions<TType extends ChartType = ChartType> extends CoreInteractionOptions {\n  /**\n   * Are on-canvas tooltips enabled?\n   * @default true\n   */\n  enabled: Scriptable<boolean, ScriptableTooltipContext<TType>>;\n  /**\n   *   See external tooltip section.\n   */\n  external(this: TooltipModel<TType>, args: { chart: Chart; tooltip: TooltipModel<TType> }): void;\n  /**\n   * The mode for positioning the tooltip\n   */\n  position: Scriptable<TooltipPositioner, ScriptableTooltipContext<TType>>\n\n  /**\n   * Override the tooltip alignment calculations\n   */\n  xAlign: Scriptable<TooltipXAlignment, ScriptableTooltipContext<TType>>;\n  yAlign: Scriptable<TooltipYAlignment, ScriptableTooltipContext<TType>>;\n\n  /**\n   * Sort tooltip items.\n   */\n  itemSort: (a: TooltipItem<TType>, b: TooltipItem<TType>, data: ChartData) => number;\n\n  filter: (e: TooltipItem<TType>, index: number, array: TooltipItem<TType>[], data: ChartData) => boolean;\n\n  /**\n   * Background color of the tooltip.\n   * @default 'rgba(0, 0, 0, 0.8)'\n   */\n  backgroundColor: Scriptable<Color, ScriptableTooltipContext<TType>>;\n  /**\n   * Padding between the color box and the text.\n   * @default 1\n   */\n  boxPadding: number;\n  /**\n   * Color of title\n   * @default '#fff'\n   */\n  titleColor: Scriptable<Color, ScriptableTooltipContext<TType>>;\n  /**\n   * See Fonts\n   * @default {weight: 'bold'}\n   */\n  titleFont: ScriptableAndScriptableOptions<Partial<FontSpec>, ScriptableTooltipContext<TType>>;\n  /**\n   * Spacing to add to top and bottom of each title line.\n   * @default 2\n   */\n  titleSpacing: Scriptable<number, ScriptableTooltipContext<TType>>;\n  /**\n   * Margin to add on bottom of title section.\n   * @default 6\n   */\n  titleMarginBottom: Scriptable<number, ScriptableTooltipContext<TType>>;\n  /**\n   * Horizontal alignment of the title text lines.\n   * @default 'left'\n   */\n  titleAlign: Scriptable<TextAlign, ScriptableTooltipContext<TType>>;\n  /**\n   * Spacing to add to top and bottom of each tooltip item.\n   * @default 2\n   */\n  bodySpacing: Scriptable<number, ScriptableTooltipContext<TType>>;\n  /**\n   * Color of body\n   * @default '#fff'\n   */\n  bodyColor: Scriptable<Color, ScriptableTooltipContext<TType>>;\n  /**\n   * See Fonts.\n   * @default {}\n   */\n  bodyFont: ScriptableAndScriptableOptions<Partial<FontSpec>, ScriptableTooltipContext<TType>>;\n  /**\n   * Horizontal alignment of the body text lines.\n   * @default 'left'\n   */\n  bodyAlign: Scriptable<TextAlign, ScriptableTooltipContext<TType>>;\n  /**\n   * Spacing to add to top and bottom of each footer line.\n   * @default 2\n   */\n  footerSpacing: Scriptable<number, ScriptableTooltipContext<TType>>;\n  /**\n   * Margin to add before drawing the footer.\n   * @default 6\n   */\n  footerMarginTop: Scriptable<number, ScriptableTooltipContext<TType>>;\n  /**\n   * Color of footer\n   * @default '#fff'\n   */\n  footerColor: Scriptable<Color, ScriptableTooltipContext<TType>>;\n  /**\n   * See Fonts\n   * @default {weight: 'bold'}\n   */\n  footerFont: ScriptableAndScriptableOptions<Partial<FontSpec>, ScriptableTooltipContext<TType>>;\n  /**\n   * Horizontal alignment of the footer text lines.\n   * @default 'left'\n   */\n  footerAlign: Scriptable<TextAlign, ScriptableTooltipContext<TType>>;\n  /**\n   * Padding to add to the tooltip\n   * @default 6\n   */\n  padding: Scriptable<Padding, ScriptableTooltipContext<TType>>;\n  /**\n   * Extra distance to move the end of the tooltip arrow away from the tooltip point.\n   * @default 2\n   */\n  caretPadding: Scriptable<number, ScriptableTooltipContext<TType>>;\n  /**\n   * Size, in px, of the tooltip arrow.\n   * @default 5\n   */\n  caretSize: Scriptable<number, ScriptableTooltipContext<TType>>;\n  /**\n   * Radius of tooltip corner curves.\n   * @default 6\n   */\n  cornerRadius: Scriptable<number | BorderRadius, ScriptableTooltipContext<TType>>;\n  /**\n   * Color to draw behind the colored boxes when multiple items are in the tooltip.\n   * @default '#fff'\n   */\n  multiKeyBackground: Scriptable<Color, ScriptableTooltipContext<TType>>;\n  /**\n   * If true, color boxes are shown in the tooltip.\n   * @default true\n   */\n  displayColors: Scriptable<boolean, ScriptableTooltipContext<TType>>;\n  /**\n   * Width of the color box if displayColors is true.\n   * @default bodyFont.size\n   */\n  boxWidth: Scriptable<number, ScriptableTooltipContext<TType>>;\n  /**\n   * Height of the color box if displayColors is true.\n   * @default bodyFont.size\n   */\n  boxHeight: Scriptable<number, ScriptableTooltipContext<TType>>;\n  /**\n   * Use the corresponding point style (from dataset options) instead of color boxes, ex: star, triangle etc. (size is based on the minimum value between boxWidth and boxHeight)\n   * @default false\n   */\n  usePointStyle: Scriptable<boolean, ScriptableTooltipContext<TType>>;\n  /**\n   * Color of the border.\n   * @default 'rgba(0, 0, 0, 0)'\n   */\n  borderColor: Scriptable<Color, ScriptableTooltipContext<TType>>;\n  /**\n   * Size of the border.\n   * @default 0\n   */\n  borderWidth: Scriptable<number, ScriptableTooltipContext<TType>>;\n  /**\n   * true for rendering the legends from right to left.\n   */\n  rtl: Scriptable<boolean, ScriptableTooltipContext<TType>>;\n\n  /**\n   * This will force the text direction 'rtl' or 'ltr on the canvas for rendering the tooltips, regardless of the css specified on the canvas\n   * @default canvas's default\n   */\n  textDirection: Scriptable<string, ScriptableTooltipContext<TType>>;\n\n  animation: AnimationSpec<TType> | false;\n  animations: AnimationsSpec<TType> | false;\n  callbacks: TooltipCallbacks<TType>;\n}\n\nexport interface TooltipDatasetOptions<TType extends ChartType = ChartType> {\n  callbacks: TooltipDatasetCallbacks<TType>;\n}\n\nexport interface TooltipItem<TType extends ChartType> {\n  /**\n   * The chart the tooltip is being shown on\n   */\n  chart: Chart;\n\n  /**\n   * Label for the tooltip\n   */\n  label: string;\n\n  /**\n   * Parsed data values for the given `dataIndex` and `datasetIndex`\n   */\n  parsed: UnionToIntersection<ParsedDataType<TType>>;\n\n  /**\n   * Raw data values for the given `dataIndex` and `datasetIndex`\n   */\n  raw: unknown;\n\n  /**\n   * Formatted value for the tooltip\n   */\n  formattedValue: string;\n\n  /**\n   * The dataset the item comes from\n   */\n  dataset: UnionToIntersection<ChartDataset<TType>>;\n\n  /**\n   * Index of the dataset the item comes from\n   */\n  datasetIndex: number;\n\n  /**\n   * Index of this data item in the dataset\n   */\n  dataIndex: number;\n\n  /**\n   * The chart element (point, arc, bar, etc.) for this tooltip item\n   */\n  element: Element;\n}\n\nexport interface PluginDatasetOptionsByType<TType extends ChartType> {\n  tooltip: TooltipDatasetOptions<TType>;\n}\n\nexport interface PluginOptionsByType<TType extends ChartType> {\n  colors: ColorsPluginOptions;\n  decimation: DecimationOptions;\n  filler: FillerOptions;\n  legend: LegendOptions<TType>;\n  subtitle: TitleOptions;\n  title: TitleOptions;\n  tooltip: TooltipOptions<TType>;\n}\nexport interface PluginChartOptions<TType extends ChartType> {\n  plugins: PluginOptionsByType<TType>;\n}\n\nexport interface BorderOptions {\n  /**\n   * @default true\n   */\n  display: boolean\n  /**\n   * @default []\n   */\n  dash: Scriptable<number[], ScriptableScaleContext>;\n  /**\n   * @default 0\n   */\n  dashOffset: Scriptable<number, ScriptableScaleContext>;\n  color: Color;\n  width: number;\n  z: number;\n}\n\nexport interface GridLineOptions {\n  /**\n   * @default true\n   */\n  display: boolean;\n  /**\n   * @default false\n   */\n  circular: boolean;\n  /**\n   * @default 'rgba(0, 0, 0, 0.1)'\n   */\n  color: ScriptableAndArray<Color, ScriptableScaleContext>;\n  /**\n   * @default 1\n   */\n  lineWidth: ScriptableAndArray<number, ScriptableScaleContext>;\n  /**\n   * @default true\n   */\n  drawOnChartArea: boolean;\n  /**\n   * @default true\n   */\n  drawTicks: boolean;\n  /**\n   * @default []\n   */\n  tickBorderDash: Scriptable<number[], ScriptableScaleContext>;\n  /**\n   * @default 0\n   */\n  tickBorderDashOffset: Scriptable<number, ScriptableScaleContext>;\n  /**\n   * @default 'rgba(0, 0, 0, 0.1)'\n   */\n  tickColor: ScriptableAndArray<Color, ScriptableScaleContext>;\n  /**\n   * @default 10\n   */\n  tickLength: number;\n  /**\n   * @default 1\n   */\n  tickWidth: number;\n  /**\n   * @default false\n   */\n  offset: boolean;\n  /**\n   * @default 0\n   */\n  z: number;\n}\n\nexport interface TickOptions {\n  /**\n   * Color of label backdrops.\n   * @default 'rgba(255, 255, 255, 0.75)'\n   */\n  backdropColor: Scriptable<Color, ScriptableScaleContext>;\n  /**\n   * Padding of tick backdrop.\n   * @default 2\n   */\n  backdropPadding: number | ChartArea;\n\n  /**\n   * Returns the string representation of the tick value as it should be displayed on the chart. See callback.\n   */\n  callback: (this: Scale, tickValue: number | string, index: number, ticks: Tick[]) => string | string[] | number | number[] | null | undefined;\n  /**\n   * If true, show tick labels.\n   * @default true\n   */\n  display: boolean;\n  /**\n   * Color of tick\n   * @see Defaults.color\n   */\n  color: ScriptableAndArray<Color, ScriptableScaleContext>;\n  /**\n   * see Fonts\n   */\n  font: ScriptableAndScriptableOptions<Partial<FontSpec>, ScriptableScaleContext>;\n  /**\n   * Sets the offset of the tick labels from the axis\n   */\n  padding: number;\n  /**\n   * If true, draw a background behind the tick labels.\n   * @default false\n   */\n  showLabelBackdrop: Scriptable<boolean, ScriptableScaleContext>;\n  /**\n   * The color of the stroke around the text.\n   * @default undefined\n   */\n  textStrokeColor: Scriptable<Color, ScriptableScaleContext>;\n  /**\n   * Stroke width around the text.\n   * @default 0\n   */\n  textStrokeWidth: Scriptable<number, ScriptableScaleContext>;\n  /**\n   * z-index of tick layer. Useful when ticks are drawn on chart area. Values <= 0 are drawn under datasets, > 0 on top.\n   * @default 0\n   */\n  z: number;\n\n  major: {\n    /**\n     * If true, major ticks are generated. A major tick will affect autoskipping and major will be defined on ticks in the scriptable options context.\n     * @default false\n     */\n    enabled: boolean;\n  };\n}\n\nexport type CartesianTickOptions = TickOptions & {\n  /**\n   * The number of ticks to examine when deciding how many labels will fit. Setting a smaller value will be faster, but may be less accurate when there is large variability in label length.\n   * @default ticks.length\n   */\n  sampleSize: number;\n  /**\n   * The label alignment\n   * @default 'center'\n   */\n  align: Align | 'inner';\n  /**\n   *   If true, automatically calculates how many labels can be shown and hides labels accordingly. Labels will be rotated up to maxRotation before skipping any. Turn autoSkip off to show all labels no matter what.\n   * @default true\n   */\n  autoSkip: boolean;\n  /**\n   * Padding between the ticks on the horizontal axis when autoSkip is enabled.\n   * @default 0\n   */\n  autoSkipPadding: number;\n\n  /**\n   * How is the label positioned perpendicular to the axis direction.\n   * This only applies when the rotation is 0 and the axis position is one of \"top\", \"left\", \"right\", or \"bottom\"\n   * @default 'near'\n   */\n  crossAlign: 'near' | 'center' | 'far';\n\n  /**\n   * Should the defined `min` and `max` values be presented as ticks even if they are not \"nice\".\n   * @default: true\n   */\n  includeBounds: boolean;\n\n  /**\n   * Distance in pixels to offset the label from the centre point of the tick (in the x direction for the x axis, and the y direction for the y axis). Note: this can cause labels at the edges to be cropped by the edge of the canvas\n   * @default 0\n   */\n  labelOffset: number;\n\n  /**\n   * Minimum rotation for tick labels. Note: Only applicable to horizontal scales.\n   * @default 0\n   */\n  minRotation: number;\n  /**\n   * Maximum rotation for tick labels when rotating to condense labels. Note: Rotation doesn't occur until necessary. Note: Only applicable to horizontal scales.\n   * @default 50\n   */\n  maxRotation: number;\n  /**\n   * Flips tick labels around axis, displaying the labels inside the chart instead of outside. Note: Only applicable to vertical scales.\n   * @default false\n   */\n  mirror: boolean;\n  /**\n   *   Padding between the tick label and the axis. When set on a vertical axis, this applies in the horizontal (X) direction. When set on a horizontal axis, this applies in the vertical (Y) direction.\n   * @default 0\n   */\n  padding: number;\n  /**\n   * Maximum number of ticks and gridlines to show.\n   * @default 11\n   */\n  maxTicksLimit: number;\n}\n\nexport interface ScriptableCartesianScaleContext {\n  scale: keyof CartesianScaleTypeRegistry;\n  type: string;\n}\n\nexport interface ScriptableChartContext {\n  chart: Chart;\n  type: string;\n}\n\nexport interface CartesianScaleOptions extends CoreScaleOptions {\n  /**\n   * Scale boundary strategy (bypassed by min/max time options)\n   * - `data`: make sure data are fully visible, ticks outside are removed\n   * - `ticks`: make sure ticks are fully visible, data outside are truncated\n   * @since 2.7.0\n   * @default 'ticks'\n   */\n  bounds: 'ticks' | 'data';\n\n  /**\n   * Position of the axis.\n   */\n  position: 'left' | 'top' | 'right' | 'bottom' | 'center' | { [scale: string]: number };\n\n  /**\n   * Stack group. Axes at the same `position` with same `stack` are stacked.\n   */\n  stack?: string;\n\n  /**\n   * Weight of the scale in stack group. Used to determine the amount of allocated space for the scale within the group.\n   * @default 1\n   */\n  stackWeight?: number;\n\n  /**\n   *   Which type of axis this is. Possible values are: 'x', 'y', 'r'. If not set, this is inferred from the first character of the ID which should be 'x', 'y' or 'r'.\n   */\n  axis: 'x' | 'y' | 'r';\n\n  /**\n   * User defined minimum value for the scale, overrides minimum value from data.\n   */\n  min: number;\n\n  /**\n   * User defined maximum value for the scale, overrides maximum value from data.\n   */\n  max: number;\n\n  /**\n   *   If true, extra space is added to the both edges and the axis is scaled to fit into the chart area. This is set to true for a bar chart by default.\n   * @default false\n   */\n  offset: boolean;\n\n  grid: Partial<GridLineOptions>;\n\n  border: BorderOptions;\n\n  /** Options for the scale title. */\n  title: {\n    /** If true, displays the axis title. */\n    display: boolean;\n    /** Alignment of the axis title. */\n    align: Align;\n    /** The text for the title, e.g. \"# of People\" or \"Response Choices\". */\n    text: string | string[];\n    /** Color of the axis label. */\n    color: Color;\n    /** The color of the text stroke for the axis label.*/\n    strokeColor?: Color;\n    /** The text stroke width for the axis label.*/\n    strokeWidth?: number;\n    /** Information about the axis title font. */\n    font: ScriptableAndScriptableOptions<Partial<FontSpec>, ScriptableCartesianScaleContext>;\n    /** Padding to apply around scale labels. */\n    padding: number | {\n      /** Padding on the (relative) top side of this axis label. */\n      top: number;\n      /** Padding on the (relative) bottom side of this axis label. */\n      bottom: number;\n      /** This is a shorthand for defining top/bottom to the same values. */\n      y: number;\n    };\n  };\n\n  /**\n   *   If true, data will be comprised between datasets of data\n   * @default false\n   */\n  stacked?: boolean | 'single';\n\n  ticks: CartesianTickOptions;\n}\n\nexport type CategoryScaleOptions = Omit<CartesianScaleOptions, 'min' | 'max'> & {\n  min: string | number;\n  max: string | number;\n  labels: string[] | string[][];\n};\n\nexport type CategoryScale<O extends CategoryScaleOptions = CategoryScaleOptions> = Scale<O>\nexport declare const CategoryScale: ChartComponent & {\n  prototype: CategoryScale;\n  new <O extends CategoryScaleOptions = CategoryScaleOptions>(cfg: AnyObject): CategoryScale<O>;\n};\n\nexport type LinearScaleOptions = CartesianScaleOptions & {\n\n  /**\n   *  if true, scale will include 0 if it is not already included.\n   * @default true\n   */\n  beginAtZero: boolean;\n  /**\n   * Adjustment used when calculating the minimum data value.\n   */\n  suggestedMin?: number;\n  /**\n   * Adjustment used when calculating the maximum data value.\n   */\n  suggestedMax?: number;\n  /**\n  * Percentage (string ending with %) or amount (number) for added room in the scale range above and below data.\n  */\n  grace?: string | number;\n\n  ticks: {\n    /**\n     * The Intl.NumberFormat options used by the default label formatter\n     */\n    format: Intl.NumberFormatOptions;\n\n    /**\n     * if defined and stepSize is not specified, the step size will be rounded to this many decimal places.\n     */\n    precision: number;\n\n    /**\n     * User defined fixed step size for the scale\n     */\n    stepSize: number;\n\n    /**\n     * User defined count of ticks\n     */\n    count: number;\n  };\n};\n\nexport type LinearScale<O extends LinearScaleOptions = LinearScaleOptions> = Scale<O>\nexport declare const LinearScale: ChartComponent & {\n  prototype: LinearScale;\n  new <O extends LinearScaleOptions = LinearScaleOptions>(cfg: AnyObject): LinearScale<O>;\n};\n\nexport type LogarithmicScaleOptions = CartesianScaleOptions & {\n  /**\n   * Adjustment used when calculating the maximum data value.\n   */\n  suggestedMin?: number;\n  /**\n   * Adjustment used when calculating the minimum data value.\n   */\n  suggestedMax?: number;\n\n  ticks: {\n    /**\n     * The Intl.NumberFormat options used by the default label formatter\n     */\n    format: Intl.NumberFormatOptions;\n  };\n};\n\nexport type LogarithmicScale<O extends LogarithmicScaleOptions = LogarithmicScaleOptions> = Scale<O>\nexport declare const LogarithmicScale: ChartComponent & {\n  prototype: LogarithmicScale;\n  new <O extends LogarithmicScaleOptions = LogarithmicScaleOptions>(cfg: AnyObject): LogarithmicScale<O>;\n};\n\nexport type TimeScaleTimeOptions = {\n  /**\n   * Custom parser for dates.\n   */\n  parser: string | ((v: unknown) => number);\n  /**\n   * If defined, dates will be rounded to the start of this unit. See Time Units below for the allowed units.\n   */\n  round: false | TimeUnit;\n  /**\n   * If boolean and true and the unit is set to 'week', then the first day of the week will be Monday. Otherwise, it will be Sunday.\n   * If `number`, the index of the first day of the week (0 - Sunday, 6 - Saturday).\n   * @default false\n   */\n  isoWeekday: boolean | number;\n  /**\n   * Sets how different time units are displayed.\n   */\n  displayFormats: {\n    [key: string]: string;\n  };\n  /**\n   * The format string to use for the tooltip.\n   */\n  tooltipFormat: string;\n  /**\n   * If defined, will force the unit to be a certain type. See Time Units section below for details.\n   * @default false\n   */\n  unit: false | TimeUnit;\n  /**\n   * The minimum display format to be used for a time unit.\n   * @default 'millisecond'\n   */\n  minUnit: TimeUnit;\n};\n\nexport type TimeScaleTickOptions = {\n  /**\n   * Ticks generation input values:\n   * - 'auto': generates \"optimal\" ticks based on scale size and time options.\n   * - 'data': generates ticks from data (including labels from data `{t|x|y}` objects).\n   * - 'labels': generates ticks from user given `data.labels` values ONLY.\n   * @see https://github.com/chartjs/Chart.js/pull/4507\n   * @since 2.7.0\n   * @default 'auto'\n   */\n  source: 'labels' | 'auto' | 'data';\n  /**\n   * The number of units between grid lines.\n   * @default 1\n   */\n  stepSize: number;\n};\n\nexport type TimeScaleOptions = Omit<CartesianScaleOptions, 'min' | 'max'> & {\n  min: string | number;\n  max: string | number;\n  suggestedMin: string | number;\n  suggestedMax: string | number;\n  /**\n   * Scale boundary strategy (bypassed by min/max time options)\n   * - `data`: make sure data are fully visible, ticks outside are removed\n   * - `ticks`: make sure ticks are fully visible, data outside are truncated\n   * @since 2.7.0\n   * @default 'data'\n   */\n  bounds: 'ticks' | 'data';\n\n  /**\n   * If true, bar chart offsets are computed with skipped tick sizes\n   * @since 3.8.0\n   * @default false\n   */\n  offsetAfterAutoskip: boolean;\n\n  /**\n   * options for creating a new adapter instance\n   */\n  adapters: {\n    date: unknown;\n  };\n\n  time: TimeScaleTimeOptions;\n\n  ticks: TimeScaleTickOptions;\n};\n\nexport interface TimeScale<O extends TimeScaleOptions = TimeScaleOptions> extends Scale<O> {\n  format(value: number, format?: string): string;\n  getDataTimestamps(): number[];\n  getLabelTimestamps(): string[];\n  normalize(values: number[]): number[];\n}\n\nexport declare const TimeScale: ChartComponent & {\n  prototype: TimeScale;\n  new <O extends TimeScaleOptions = TimeScaleOptions>(cfg: AnyObject): TimeScale<O>;\n};\n\nexport type TimeSeriesScale<O extends TimeScaleOptions = TimeScaleOptions> = TimeScale<O>\nexport declare const TimeSeriesScale: ChartComponent & {\n  prototype: TimeSeriesScale;\n  new <O extends TimeScaleOptions = TimeScaleOptions>(cfg: AnyObject): TimeSeriesScale<O>;\n};\n\nexport type RadialTickOptions = TickOptions & {\n  /**\n   * The Intl.NumberFormat options used by the default label formatter\n   */\n  format: Intl.NumberFormatOptions;\n\n  /**\n   * Maximum number of ticks and gridlines to show.\n   * @default 11\n   */\n  maxTicksLimit: number;\n\n  /**\n   * if defined and stepSize is not specified, the step size will be rounded to this many decimal places.\n   */\n  precision: number;\n\n  /**\n   * User defined fixed step size for the scale.\n   */\n  stepSize: number;\n\n  /**\n   * User defined number of ticks\n   */\n  count: number;\n}\n\nexport type RadialLinearScaleOptions = CoreScaleOptions & {\n  animate: boolean;\n\n  startAngle: number;\n\n  angleLines: {\n    /**\n     * if true, angle lines are shown.\n     * @default true\n     */\n    display: boolean;\n    /**\n     * Color of angled lines.\n     * @default 'rgba(0, 0, 0, 0.1)'\n     */\n    color: Scriptable<Color, ScriptableScaleContext>;\n    /**\n     * Width of angled lines.\n     * @default 1\n     */\n    lineWidth: Scriptable<number, ScriptableScaleContext>;\n    /**\n     * Length and spacing of dashes on angled lines. See MDN.\n     * @default []\n     */\n    borderDash: Scriptable<number[], ScriptableScaleContext>;\n    /**\n     * Offset for line dashes. See MDN.\n     * @default 0\n     */\n    borderDashOffset: Scriptable<number, ScriptableScaleContext>;\n  };\n\n  /**\n   * if true, scale will include 0 if it is not already included.\n   * @default false\n   */\n  beginAtZero: boolean;\n\n  grid: Partial<GridLineOptions>;\n\n  /**\n   * User defined minimum number for the scale, overrides minimum value from data.\n   */\n  min: number;\n  /**\n   * User defined maximum number for the scale, overrides maximum value from data.\n   */\n  max: number;\n\n  pointLabels: {\n    /**\n     * Background color of the point label.\n     * @default undefined\n     */\n    backdropColor: Scriptable<Color, ScriptableScalePointLabelContext>;\n    /**\n     * Padding of label backdrop.\n     * @default 2\n     */\n    backdropPadding: Scriptable<number | ChartArea, ScriptableScalePointLabelContext>;\n\n    /**\n     * Border radius\n     * @default 0\n     * @since 3.8.0\n     */\n    borderRadius: Scriptable<number | BorderRadius, ScriptableScalePointLabelContext>;\n\n    /**\n     * if true, point labels are shown. When `display: 'auto'`, the label is hidden if it overlaps with another label.\n     * @default true\n     */\n    display: boolean | 'auto';\n    /**\n     * Color of label\n     * @see Defaults.color\n     */\n    color: Scriptable<Color, ScriptableScalePointLabelContext>;\n    /**\n     */\n    font: ScriptableAndScriptableOptions<Partial<FontSpec>, ScriptableScalePointLabelContext>;\n\n    /**\n     * Callback function to transform data labels to point labels. The default implementation simply returns the current string.\n     */\n    callback: (label: string, index: number) => string | string[] | number | number[];\n\n    /**\n     * Padding around the pointLabels\n     * @default 5\n     */\n    padding: Scriptable<number, ScriptableScalePointLabelContext>;\n\n    /**\n     * if true, point labels are centered.\n     * @default false\n     */\n    centerPointLabels: boolean;\n  };\n\n  /**\n   * Adjustment used when calculating the maximum data value.\n   */\n  suggestedMax: number;\n  /**\n   * Adjustment used when calculating the minimum data value.\n   */\n  suggestedMin: number;\n\n  ticks: RadialTickOptions;\n};\n\nexport interface RadialLinearScale<O extends RadialLinearScaleOptions = RadialLinearScaleOptions> extends Scale<O> {\n  xCenter: number;\n  yCenter: number;\n  readonly drawingArea: number;\n  setCenterPoint(leftMovement: number, rightMovement: number, topMovement: number, bottomMovement: number): void;\n  getIndexAngle(index: number): number;\n  getDistanceFromCenterForValue(value: number): number;\n  getValueForDistanceFromCenter(distance: number): number;\n  getPointPosition(index: number, distanceFromCenter: number): { x: number; y: number; angle: number };\n  getPointPositionForValue(index: number, value: number): { x: number; y: number; angle: number };\n  getPointLabelPosition(index: number): ChartArea;\n  getBasePosition(index: number): { x: number; y: number; angle: number };\n}\nexport declare const RadialLinearScale: ChartComponent & {\n  prototype: RadialLinearScale;\n  new <O extends RadialLinearScaleOptions = RadialLinearScaleOptions>(cfg: AnyObject): RadialLinearScale<O>;\n};\n\nexport interface CartesianScaleTypeRegistry {\n  linear: {\n    options: LinearScaleOptions;\n  };\n  logarithmic: {\n    options: LogarithmicScaleOptions;\n  };\n  category: {\n    options: CategoryScaleOptions;\n  };\n  time: {\n    options: TimeScaleOptions;\n  };\n  timeseries: {\n    options: TimeScaleOptions;\n  };\n}\n\nexport interface RadialScaleTypeRegistry {\n  radialLinear: {\n    options: RadialLinearScaleOptions;\n  };\n}\n\nexport interface ScaleTypeRegistry extends CartesianScaleTypeRegistry, RadialScaleTypeRegistry {\n}\n\nexport type ScaleType = keyof ScaleTypeRegistry;\n\nexport interface CartesianParsedData extends Point {\n  // Only specified when stacked bars are enabled\n  _stacks?: {\n    // Key is the stack ID which is generally the axis ID\n    [key: string]: {\n      // Inner key is the datasetIndex\n      [key: number]: number;\n    }\n  }\n}\n\nexport interface BarParsedData extends CartesianParsedData {\n  // Only specified if floating bars are show\n  _custom?: {\n    barStart: number;\n    barEnd: number;\n    start: number;\n    end: number;\n    min: number;\n    max: number;\n  }\n}\n\nexport interface BubbleParsedData extends CartesianParsedData {\n  // The bubble radius value\n  _custom: number;\n}\n\nexport interface RadialParsedData {\n  r: number;\n}\n\nexport interface ChartTypeRegistry {\n  bar: {\n    chartOptions: BarControllerChartOptions;\n    datasetOptions: BarControllerDatasetOptions;\n    defaultDataPoint: number | [number, number] | null;\n    metaExtensions: {};\n    parsedDataType: BarParsedData,\n    scales: keyof CartesianScaleTypeRegistry;\n  };\n  line: {\n    chartOptions: LineControllerChartOptions;\n    datasetOptions: LineControllerDatasetOptions & FillerControllerDatasetOptions;\n    defaultDataPoint: ScatterDataPoint | number | null;\n    metaExtensions: {};\n    parsedDataType: CartesianParsedData;\n    scales: keyof CartesianScaleTypeRegistry;\n  };\n  scatter: {\n    chartOptions: ScatterControllerChartOptions;\n    datasetOptions: ScatterControllerDatasetOptions;\n    defaultDataPoint: ScatterDataPoint | number | null;\n    metaExtensions: {};\n    parsedDataType: CartesianParsedData;\n    scales: keyof CartesianScaleTypeRegistry;\n  };\n  bubble: {\n    chartOptions: unknown;\n    datasetOptions: BubbleControllerDatasetOptions;\n    defaultDataPoint: BubbleDataPoint;\n    metaExtensions: {};\n    parsedDataType: BubbleParsedData;\n    scales: keyof CartesianScaleTypeRegistry;\n  };\n  pie: {\n    chartOptions: PieControllerChartOptions;\n    datasetOptions: PieControllerDatasetOptions;\n    defaultDataPoint: PieDataPoint;\n    metaExtensions: PieMetaExtensions;\n    parsedDataType: number;\n    scales: keyof CartesianScaleTypeRegistry;\n  };\n  doughnut: {\n    chartOptions: DoughnutControllerChartOptions;\n    datasetOptions: DoughnutControllerDatasetOptions;\n    defaultDataPoint: DoughnutDataPoint;\n    metaExtensions: DoughnutMetaExtensions;\n    parsedDataType: number;\n    scales: keyof CartesianScaleTypeRegistry;\n  };\n  polarArea: {\n    chartOptions: PolarAreaControllerChartOptions;\n    datasetOptions: PolarAreaControllerDatasetOptions;\n    defaultDataPoint: number;\n    metaExtensions: {};\n    parsedDataType: RadialParsedData;\n    scales: keyof RadialScaleTypeRegistry;\n  };\n  radar: {\n    chartOptions: RadarControllerChartOptions;\n    datasetOptions: RadarControllerDatasetOptions & FillerControllerDatasetOptions;\n    defaultDataPoint: number | null;\n    metaExtensions: {};\n    parsedDataType: RadialParsedData;\n    scales: keyof RadialScaleTypeRegistry;\n  };\n}\n\nexport type ChartType = keyof ChartTypeRegistry;\n\nexport type ScaleOptionsByType<TScale extends ScaleType = ScaleType> =\n  { [key in ScaleType]: { type: key } & ScaleTypeRegistry[key]['options'] }[TScale]\n;\n\n// Convenience alias for creating and manipulating scale options in user code\nexport type ScaleOptions<TScale extends ScaleType = ScaleType> = DeepPartial<ScaleOptionsByType<TScale>>;\n\nexport type DatasetChartOptions<TType extends ChartType = ChartType> = {\n  [key in TType]: {\n    datasets: ChartTypeRegistry[key]['datasetOptions'];\n  };\n};\n\nexport type ScaleChartOptions<TType extends ChartType = ChartType> = {\n  scales: {\n    [key: string]: ScaleOptionsByType<ChartTypeRegistry[TType]['scales']>;\n  };\n};\n\nexport type ChartOptions<TType extends ChartType = ChartType> = Exclude<\nDeepPartial<\nCoreChartOptions<TType> &\nElementChartOptions<TType> &\nPluginChartOptions<TType> &\nDatasetChartOptions<TType> &\nScaleChartOptions<TType> &\nChartTypeRegistry[TType]['chartOptions']\n>,\nDeepPartial<unknown[]>\n>;\n\nexport type DefaultDataPoint<TType extends ChartType> = DistributiveArray<ChartTypeRegistry[TType]['defaultDataPoint']>;\n\nexport type ParsedDataType<TType extends ChartType = ChartType> = ChartTypeRegistry[TType]['parsedDataType'];\n\nexport interface ChartDatasetProperties<TType extends ChartType, TData> {\n  type?: TType;\n  data: TData;\n}\n\nexport interface ChartDatasetPropertiesCustomTypesPerDataset<TType extends ChartType, TData> {\n  type: TType;\n  data: TData;\n}\n\nexport type ChartDataset<\n  TType extends ChartType = ChartType,\n  TData = DefaultDataPoint<TType>\n> = DeepPartial<\n{ [key in ChartType]: { type: key } & ChartTypeRegistry[key]['datasetOptions'] }[TType]\n> & DeepPartial<\nPluginDatasetOptionsByType<TType>\n> & ChartDatasetProperties<TType, TData>;\n\nexport type ChartDatasetCustomTypesPerDataset<\n  TType extends ChartType = ChartType,\n  TData = DefaultDataPoint<TType>\n> = DeepPartial<\n{ [key in ChartType]: { type: key } & ChartTypeRegistry[key]['datasetOptions'] }[TType]\n> & DeepPartial<\nPluginDatasetOptionsByType<TType>\n> & ChartDatasetPropertiesCustomTypesPerDataset<TType, TData>;\n\n/**\n * TData represents the data point type. If unspecified, a default is provided\n *   based on the chart type.\n * TLabel represents the label type\n */\nexport interface ChartData<\n  TType extends ChartType = ChartType,\n  TData = DefaultDataPoint<TType>,\n  TLabel = unknown\n> {\n  labels?: TLabel[];\n  xLabels?: TLabel[];\n  yLabels?: TLabel[];\n  datasets: ChartDataset<TType, TData>[];\n}\n\nexport interface ChartDataCustomTypesPerDataset<\n  TType extends ChartType = ChartType,\n  TData = DefaultDataPoint<TType>,\n  TLabel = unknown\n> {\n  labels?: TLabel[];\n  xLabels?: TLabel[];\n  yLabels?: TLabel[];\n  datasets: ChartDatasetCustomTypesPerDataset<TType, TData>[];\n}\n\nexport interface ChartConfiguration<\n  TType extends ChartType = ChartType,\n  TData = DefaultDataPoint<TType>,\n  TLabel = unknown\n> {\n  type: TType;\n  data: ChartData<TType, TData, TLabel>;\n  options?: ChartOptions<TType> | undefined;\n  plugins?: Plugin<TType>[];\n  platform?: typeof BasePlatform;\n}\n\nexport interface ChartConfigurationCustomTypesPerDataset<\n  TType extends ChartType = ChartType,\n  TData = DefaultDataPoint<TType>,\n  TLabel = unknown\n> {\n  data: ChartDataCustomTypesPerDataset<TType, TData, TLabel>;\n  options?: ChartOptions<TType> | undefined;\n  plugins?: Plugin<TType>[];\n}\n"
  },
  {
    "path": "src/types/layout.d.ts",
    "content": "import {ChartArea} from './geometric.js';\n\nexport type LayoutPosition = 'left' | 'top' | 'right' | 'bottom' | 'center' | 'chartArea' | {[scaleId: string]: number};\n\nexport interface LayoutItem {\n  /**\n   * The position of the item in the chart layout. Possible values are\n   */\n  position: LayoutPosition;\n  /**\n   * The weight used to sort the item. Higher weights are further away from the chart area\n   */\n  weight: number;\n  /**\n   * if true, and the item is horizontal, then push vertical boxes down\n   */\n  fullSize: boolean;\n  /**\n   * Width of item. Must be valid after update()\n   */\n  width: number;\n  /**\n   * Height of item. Must be valid after update()\n   */\n  height: number;\n  /**\n   * Left edge of the item. Set by layout system and cannot be used in update\n   */\n  left: number;\n  /**\n   * Top edge of the item. Set by layout system and cannot be used in update\n   */\n  top: number;\n  /**\n   * Right edge of the item. Set by layout system and cannot be used in update\n   */\n  right: number;\n  /**\n   * Bottom edge of the item. Set by layout system and cannot be used in update\n   */\n  bottom: number;\n\n  /**\n   * Called before the layout process starts\n   */\n  beforeLayout?(): void;\n  /**\n   * Draws the element\n   */\n  draw(chartArea: ChartArea): void;\n  /**\n   * Returns an object with padding on the edges\n   */\n  getPadding?(): ChartArea;\n  /**\n   * returns true if the layout item is horizontal (ie. top or bottom)\n   */\n  isHorizontal(): boolean;\n  /**\n   * Takes two parameters: width and height.\n   * @param width\n   * @param height\n   */\n  update(width: number, height: number, margins?: ChartArea): void;\n}\n"
  },
  {
    "path": "src/types/utils.d.ts",
    "content": "/* eslint-disable @typescript-eslint/ban-types */\n\n// DeepPartial implementation taken from the utility-types NPM package, which is\n// Copyright (c) 2016 Piotr Witek <piotrek.witek@gmail.com> (http://piotrwitek.github.io)\n// and used under the terms of the MIT license\nexport type DeepPartial<T> = T extends Function\n  ? T\n  : T extends Array<infer U>\n    ? _DeepPartialArray<U>\n    : T extends object\n      ? _DeepPartialObject<T>\n      : T | undefined;\n\ntype _DeepPartialArray<T> = Array<DeepPartial<T>>\ntype _DeepPartialObject<T> = { [P in keyof T]?: DeepPartial<T[P]> };\n\nexport type DistributiveArray<T> = [T] extends [unknown] ? Array<T> : never\n\n// https://stackoverflow.com/a/50375286\nexport type UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) extends (k: infer I) => void ? I : never;\n\nexport type AllKeys<T> = T extends any ? keyof T : never;\n\nexport type PickType<T, K extends AllKeys<T>> = T extends { [k in K]?: any }\n  ? T[K]\n  : undefined;\n\nexport type Merge<T extends object> = {\n  [k in AllKeys<T>]: PickType<T, k>;\n};\n"
  },
  {
    "path": "src/types.ts",
    "content": "/**\n * Temporary entry point of the types at the time of the transition.\n * After transition done need to remove it in favor of index.ts\n */\n\nexport * from './index.js';\n/**\n * Explicitly re-exporting to resolve the ambiguity.\n */\nexport {\n  BarController,\n  BubbleController,\n  DoughnutController,\n  LineController,\n  PieController,\n  PolarAreaController,\n  RadarController,\n  ScatterController,\n  Animation,\n  Animations,\n  Chart,\n  DatasetController,\n  Interaction,\n  Scale,\n  Ticks,\n  defaults,\n  layouts,\n  registry,\n  ArcElement,\n  BarElement,\n  LineElement,\n  PointElement,\n  BasePlatform,\n  BasicPlatform,\n  DomPlatform,\n  Decimation,\n  Filler,\n  Legend,\n  SubTitle,\n  Title,\n  Tooltip,\n  CategoryScale,\n  LinearScale,\n  LogarithmicScale,\n  RadialLinearScale,\n  TimeScale,\n  TimeSeriesScale,\n  PluginOptionsByType,\n  ElementOptionsByType,\n  ChartDatasetProperties,\n  UpdateModeEnum,\n  registerables\n} from './types/index.js';\nexport * from './types/index.js';\n"
  },
  {
    "path": "test/.eslintrc.yml",
    "content": "env:\n  jasmine: true\n\nglobals:\n  acquireChart: true\n  afterEvent: true\n  Chart: true\n  moment: true\n  waitForResize: true\n\nrules:\n  max-statements: [\"warn\", 50]\n"
  },
  {
    "path": "test/BasicChartWebWorker.js",
    "content": "// This file is a basic example of using a chart inside a web worker.\n// All it creates a new chart from a transferred OffscreenCanvas and then assert that the correct platform type was\n// used.\n\n// Receives messages with data of type: { type: 'initialize', canvas: OffscreenCanvas }\n// Sends messages with data of types: { type: 'success' } | { type: 'error', errorMessage: string }\n\n// eslint-disable-next-line no-undef\nimportScripts('../src/chart.umd.min.js');\n\nonmessage = function(event) {\n  try {\n    const {type, canvas} = event.data;\n    if (type !== 'initialize') {\n      throw new Error('invalid message type received by worker: ' + type);\n    }\n\n    const chart = new Chart(canvas);\n    if (!(chart.platform instanceof Chart.platforms.BasicPlatform)) {\n      throw new Error('did not use basic platform for chart in web worker');\n    }\n\n    postMessage({type: 'success'});\n  } catch (error) {\n    postMessage({type: 'error', errorMessage: error.stack});\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/aligned-pixels.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      labels: ['a'],\n      datasets: [{\n        data: [-1]\n      }, {\n        data: [1]\n      }]\n    },\n    options: {\n      indexAxis: 'y',\n      events: [],\n      backgroundColor: 'navy',\n      devicePixelRatio: 1.25,\n      scales: {\n        x: {display: false, alignToPixels: true},\n        y: {display: false, stacked: true}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      width: 100,\n      height: 500\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/backgroundColor/indexable.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          backgroundColor: [\n            '#ff0000',\n            '#00ff00',\n            '#0000ff',\n            '#ffff00',\n            '#ff00ff',\n            '#000000'\n          ]\n        },\n        {\n          // option in element (fallback)\n          data: [0, 5, 10, null, -10, -5],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        bar: {\n          backgroundColor: [\n            '#ff88ff',\n            '#888888',\n            '#ff8800',\n            '#00ff88',\n            '#8800ff',\n            '#ffff88'\n          ]\n        }\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/backgroundColor/loopable.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 2, 3, 4, 5, 6],\n          backgroundColor: [\n            '#ff0000',\n            '#00ff00',\n            '#0000ff'\n          ]\n        },\n        {\n          // option in element (fallback)\n          data: [6, 5, 4, 3, 2, 1],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        bar: {\n          backgroundColor: [\n            '#000000',\n            '#888888'\n          ]\n        }\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/backgroundColor/scriptable.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          backgroundColor: function(ctx) {\n            var value = ctx.dataset.data[ctx.dataIndex] || 0;\n            return value > 8 ? '#ff0000'\n              : value > 0 ? '#00ff00'\n              : value > -8 ? '#0000ff'\n              : '#ff00ff';\n          }\n        },\n        {\n          // option in element (fallback)\n          data: [0, 5, 10, null, -10, -5]\n        }\n      ]\n    },\n    options: {\n      elements: {\n        bar: {\n          backgroundColor: function(ctx) {\n            var value = ctx.dataset.data[ctx.dataIndex] || 0;\n            return value > 8 ? '#ff00ff'\n              : value > 0 ? '#0000ff'\n              : value > -8 ? '#ff0000'\n              : '#00ff00';\n          }\n        }\n      },\n      scales: {\n        x: {display: false},\n        y: {\n          display: false,\n          beginAtZero: true\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/backgroundColor/value.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          backgroundColor: '#ff0000'\n        },\n        {\n          // option in element (fallback)\n          data: [0, 5, 10, null, -10, -5],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        bar: {\n          backgroundColor: '#00ff00'\n        }\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/bar-animation-hide-show.js",
    "content": "const canvas = document.createElement('canvas');\ncanvas.width = 512;\ncanvas.height = 512;\nconst ctx = canvas.getContext('2d');\n\nmodule.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      labels: [0],\n      datasets: [\n        {\n          data: [1],\n          backgroundColor: 'rgba(255,0,0,0.5)'\n        },\n        {\n          data: [2],\n          backgroundColor: 'rgba(0,0,255,0.5)'\n        },\n        {\n          data: [3],\n          backgroundColor: 'rgba(0,255,0,0.5)'\n        }\n      ]\n    },\n    options: {\n      animation: {\n        duration: 14000,\n        easing: 'linear'\n      },\n      events: [],\n      scales: {\n        x: {display: false},\n        y: {display: false, max: 4}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 512,\n      width: 512\n    },\n    run: function(chart) {\n      const animator = Chart.animator;\n      const anims = animator._getAnims(chart);\n      // disable animator\n      const backup = animator._refresh;\n      animator._refresh = function() { };\n\n      return new Promise((resolve) => {\n        window.requestAnimationFrame(() => {\n          // make sure previous animation is finished\n          animator._update(Date.now() * 2);\n\n          chart.hide(1);\n          let start = anims.items[0]._start;\n          for (let i = 0; i < 8; i++) {\n            animator._update(start + i * 2000);\n            let x = i % 4 * 128;\n            let y = Math.floor(i / 4) * 128;\n            ctx.drawImage(chart.canvas, x, y, 128, 128);\n          }\n\n          // make sure previous animation is finished\n          animator._update(Date.now() * 2);\n\n          chart.show(1);\n          start = anims.items[0]._start;\n          for (let i = 0; i < 8; i++) {\n            animator._update(start + i * 2000);\n            let x = i % 4 * 128;\n            let y = Math.floor(2 + i / 4) * 128;\n            ctx.drawImage(chart.canvas, x, y, 128, 128);\n          }\n          Chart.helpers.clearCanvas(chart.canvas);\n          chart.ctx.drawImage(canvas, 0, 0);\n\n          animator._refresh = backup;\n          resolve();\n        });\n      });\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/bar-base-value.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      labels: [0, 1, 3, 4],\n      datasets: [\n        {\n          data: [5, 20, 10, 11],\n          base: 10,\n          backgroundColor: '#00ff00',\n          borderColor: '#ff0000',\n          borderWidth: 2,\n        }\n      ]\n    },\n    options: {\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/bar-default-begin-at-zero.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      labels: [0, 1, 3, 4],\n      datasets: [\n        {\n          data: [5, 20, 1, 10],\n          backgroundColor: '#00ff00',\n          borderColor: '#ff0000'\n        }\n      ]\n    },\n    options: {\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/bar-thickness-absolute.json",
    "content": "{\n    \"config\": {\n        \"type\": \"bar\",\n        \"data\": {\n            \"labels\": [\"2017\", \"2018\", \"2019\", \"2024\", \"2025\"],\n            \"datasets\": [{\n                \"backgroundColor\": \"rgba(255, 99, 132, 0.5)\",\n                \"barPercentage\": 1,\n                \"categoryPercentage\": 1,\n                \"barThickness\": 128,\n                \"data\": [1, null, 3, 4, 5]\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"scales\": {\n                \"x\": {\n                    \"type\": \"time\",\n                    \"offset\": true,\n                    \"display\": false,\n                    \"time\": {\n                        \"parser\": \"YYYY\"\n                    },\n                    \"ticks\": {\n                        \"source\": \"labels\"\n                    }\n                },\n                \"y\": {\n                    \"display\": false,\n                    \"beginAtZero\": true\n                }\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/controller.bar/bar-thickness-flex-offset.json",
    "content": "{\n    \"config\": {\n        \"type\": \"bar\",\n        \"data\": {\n            \"labels\": [\"2017\", \"2018\", \"2020\", \"2024\", \"2038\"],\n            \"datasets\": [{\n                \"backgroundColor\": \"#FF6384\",\n                \"barPercentage\": 1,\n                \"categoryPercentage\": 1,\n                \"barThickness\": \"flex\",\n                \"data\": [1, null, 3, 4, 5]\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"scales\": {\n                \"x\": {\n                    \"type\": \"time\",\n                    \"offset\": true,\n                    \"display\": false,\n                    \"time\": {\n                        \"parser\": \"YYYY\"\n                    },\n                    \"ticks\": {\n                        \"source\": \"labels\"\n                    }\n                },\n                \"y\": {\n                    \"display\": false,\n                    \"beginAtZero\": true\n                }\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/controller.bar/bar-thickness-flex-single-reverse.json",
    "content": "{\n    \"config\": {\n        \"type\": \"bar\",\n        \"data\": {\n            \"labels\": [\"2016\", \"2018\", \"2020\", \"2024\", \"2030\"],\n            \"datasets\": [{\n                \"backgroundColor\": \"#FF6384\",\n                \"barThickness\": \"flex\",\n                \"barPercentage\": 1,\n                \"categoryPercentage\": 1,\n                \"data\": [1]\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"scales\": {\n                \"x\": {\n                    \"type\": \"time\",\n                    \"display\": false,\n                    \"offset\": false,\n                    \"time\": {\n                        \"parser\": \"YYYY\"\n                    },\n                    \"reverse\": true,\n                    \"ticks\": {\n                        \"source\": \"labels\"\n                    }\n                },\n                \"y\": {\n                    \"display\": false,\n                    \"beginAtZero\": true\n                }\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/controller.bar/bar-thickness-flex-single.json",
    "content": "{\n    \"config\": {\n        \"type\": \"bar\",\n        \"data\": {\n            \"labels\": [\"2016\", \"2018\", \"2020\", \"2024\", \"2030\"],\n            \"datasets\": [{\n                \"backgroundColor\": \"#FF6384\",\n                \"barThickness\": \"flex\",\n                \"barPercentage\": 1,\n                \"categoryPercentage\": 1,\n                \"data\": [1]\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"scales\": {\n                \"x\": {\n                    \"type\": \"time\",\n                    \"display\": false,\n                    \"offset\": false,\n                    \"time\": {\n                        \"parser\": \"YYYY\"\n                    },\n                    \"ticks\": {\n                        \"source\": \"labels\"\n                    }\n                },\n                \"y\": {\n                    \"display\": false,\n                    \"beginAtZero\": true\n                }\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/controller.bar/bar-thickness-flex.json",
    "content": "{\n    \"config\": {\n        \"type\": \"bar\",\n        \"data\": {\n            \"labels\": [\"2017\", \"2018\", \"2020\", \"2024\", \"2038\"],\n            \"datasets\": [{\n                \"backgroundColor\": \"#FF6384\",\n                \"barPercentage\": 1,\n                \"categoryPercentage\": 1,\n                \"barThickness\": \"flex\",\n                \"data\": [1, null, 3, 4, 5]\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"scales\": {\n                \"x\": {\n                    \"type\": \"time\",\n                    \"display\": false,\n                    \"offset\": false,\n                    \"time\": {\n                        \"parser\": \"YYYY\"\n                    },\n                    \"ticks\": {\n                        \"source\": \"labels\"\n                    }\n                },\n                \"y\": {\n                    \"display\": false,\n                    \"beginAtZero\": true\n                }\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/controller.bar/bar-thickness-max.json",
    "content": "{\n    \"config\": {\n        \"type\": \"bar\",\n        \"data\": {\n            \"labels\": [\"2016\", \"2018\", \"2020\", \"2024\", \"2030\"],\n            \"datasets\": [{\n                \"backgroundColor\": \"#FF6384\",\n                \"barPercentage\": 1,\n                \"categoryPercentage\": 1,\n                \"maxBarThickness\": 8,\n                \"data\": [1, null, 3, 4, 5]\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"scales\": {\n                \"x\": {\n                    \"type\": \"time\",\n                    \"display\": false,\n                    \"offset\": false,\n                    \"time\": {\n                        \"parser\": \"YYYY\"\n                    },\n                    \"ticks\": {\n                        \"source\": \"labels\"\n                    }\n                },\n                \"y\": {\n                    \"display\": false,\n                    \"beginAtZero\": true\n                }\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/controller.bar/bar-thickness-min-interval-multi.json",
    "content": "{\n    \"config\": {\n        \"type\": \"bar\",\n        \"data\": {\n            \"datasets\": [{\n                \"backgroundColor\": \"#FF6384\",\n                \"barPercentage\": 1,\n                \"categoryPercentage\": 1,\n                \"data\": [{\"x\": \"2001\", \"y\": 1}, {\"x\": \"2099\", \"y\": 5}]\n            }, {\n                \"backgroundColor\": \"#8463FF\",\n                \"barPercentage\": 1,\n                \"categoryPercentage\": 1,\n                \"data\": [{\"x\": \"2019\", \"y\": 2}, {\"x\": \"2020\", \"y\": 3}]\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"scales\": {\n                \"x\": {\n                    \"type\": \"time\",\n                    \"display\": false,\n                    \"min\": \"2000\",\n                    \"max\": \"2100\",\n                    \"time\": {\n                        \"parser\": \"YYYY\"\n                    }\n                },\n                \"y\": {\n                    \"display\": false,\n                    \"beginAtZero\": true\n                }\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/controller.bar/bar-thickness-min-interval.json",
    "content": "{\n    \"config\": {\n        \"type\": \"bar\",\n        \"data\": {\n            \"labels\": [\"2016\", \"2018\", \"2020\", \"2024\", \"2030\"],\n            \"datasets\": [{\n                \"backgroundColor\": \"#FF6384\",\n                \"barPercentage\": 1,\n                \"categoryPercentage\": 1,\n                \"data\": [1, null, 3, 4, 5]\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"scales\": {\n                \"x\": {\n                    \"type\": \"time\",\n                    \"display\": false,\n                    \"offset\": false,\n                    \"time\": {\n                        \"parser\": \"YYYY\"\n                    },\n                    \"ticks\": {\n                        \"source\": \"labels\"\n                    }\n                },\n                \"y\": {\n                    \"display\": false,\n                    \"beginAtZero\": true\n                }\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/controller.bar/bar-thickness-multiple.json",
    "content": "{\n    \"config\": {\n        \"type\": \"bar\",\n        \"data\": {\n            \"labels\": [\"2016\", \"2018\", \"2020\", \"2024\", \"2030\"],\n            \"datasets\": [{\n                \"backgroundColor\": \"#FF6384\",\n                \"data\": [1, null, 3, 4, 5]\n            }, {\n                \"backgroundColor\": \"#36A2EB\",\n                \"data\": [5, 4, 3, null, 1]\n            }, {\n                \"backgroundColor\": \"#FFCE56\",\n                \"data\": [3, 5, 2, null, 4]\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"datasets\": {\n              \"bar\": {\n                    \"barPercentage\": 1,\n                    \"categoryPercentage\": 1\n                }\n            },\n            \"scales\": {\n                \"x\": {\n                    \"type\": \"time\",\n                    \"display\": false,\n                    \"offset\": false,\n                    \"time\": {\n                        \"parser\": \"YYYY\"\n                    },\n                    \"ticks\": {\n                        \"source\": \"labels\"\n                    }\n                },\n                \"y\": {\n                    \"display\": false,\n                    \"beginAtZero\": true\n                }\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/controller.bar/bar-thickness-no-overlap.json",
    "content": "{\n    \"config\": {\n        \"type\": \"bar\",\n        \"data\": {\n            \"labels\": [\"2016\", \"2018\", \"2020\", \"2024\", \"2030\"],\n            \"datasets\": [{\n                \"backgroundColor\": \"#FF6384\",\n                \"data\": [\n                    {\"y\": \"1\", \"x\": \"2016\"},\n                    {\"y\": \"2\", \"x\": \"2017\"},\n                    {\"y\": \"3\", \"x\": \"2017-08\"},\n                    {\"y\": \"4\", \"x\": \"2024\"},\n                    {\"y\": \"5\", \"x\": \"2030\"}\n                ]\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"datasets\": {\n              \"bar\": {\n                    \"barPercentage\": 1,\n                    \"categoryPercentage\": 1\n                }\n            },\n            \"scales\": {\n                \"x\": {\n                    \"type\": \"time\",\n                    \"display\": false,\n                    \"offset\": false,\n                    \"time\": {\n                        \"parser\": \"YYYY-MM\"\n                    },\n                    \"ticks\": {\n                        \"source\": \"labels\"\n                    }\n                },\n                \"y\": {\n                    \"display\": false,\n                    \"beginAtZero\": true\n                }\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/controller.bar/bar-thickness-offset.json",
    "content": "{\n    \"config\": {\n        \"type\": \"bar\",\n        \"data\": {\n            \"labels\": [\"2016\", \"2018\", \"2020\", \"2024\", \"2030\"],\n            \"datasets\": [{\n                \"backgroundColor\": \"#FF6384\",\n                \"data\": [1, null, 3, 4, 5]\n            }, {\n                \"backgroundColor\": \"#36A2EB\",\n                \"data\": [5, 4, 3, null, 1]\n            }, {\n                \"backgroundColor\": \"#FFCE56\",\n                \"data\": [3, 5, 2, null, 4]\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"datasets\": {\n              \"bar\": {\n                    \"barPercentage\": 1,\n                    \"categoryPercentage\": 1\n                }\n            },\n            \"scales\": {\n                \"x\": {\n                    \"type\": \"time\",\n                    \"offset\": true,\n                    \"display\": false,\n                    \"time\": {\n                        \"parser\": \"YYYY\"\n                    },\n                    \"ticks\": {\n                        \"source\": \"labels\"\n                    }\n                },\n                \"y\": {\n                    \"display\": false,\n                    \"beginAtZero\": true\n                }\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/controller.bar/bar-thickness-per-dataset-stacked.json",
    "content": "{\n    \"config\": {\n        \"data\": {\n            \"labels\": [\"2016\", \"2018\", \"2020\", \"2024\", \"2030\"],\n            \"datasets\": [{\n                \"type\": \"bar\",\n                \"barThickness\": 16,\n                \"backgroundColor\": \"#FF6384\",\n                \"data\": [1, null, 3, 4, 5]\n            }, {\n                \"type\": \"bar\",\n                \"barThickness\": 8,\n                \"backgroundColor\": \"#36A2EB\",\n                \"data\": [5, 4, 3, null, 1]\n            }, {\n                \"type\": \"bar\",\n                \"barThickness\": 4,\n                \"backgroundColor\": \"#FFCE56\",\n                \"data\": [3, 5, 2, null, 4]\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"scales\": {\n                \"x\": {\n                    \"type\": \"time\",\n                    \"offset\": true,\n                    \"stacked\": true,\n                    \"display\": false,\n                    \"time\": {\n                        \"parser\": \"YYYY\"\n                    },\n                    \"ticks\": {\n                        \"source\": \"labels\"\n                    }\n                },\n                \"y\": {\n                    \"display\": false,\n                    \"stacked\": true,\n                    \"beginAtZero\": true\n                }\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/controller.bar/bar-thickness-per-dataset.json",
    "content": "{\n    \"config\": {\n        \"data\": {\n            \"labels\": [\"2016\", \"2018\", \"2020\", \"2024\", \"2030\"],\n            \"datasets\": [{\n                \"type\": \"bar\",\n                \"barThickness\": 16,\n                \"backgroundColor\": \"#FF6384\",\n                \"data\": [1, null, 3, 4, 5]\n            }, {\n                \"type\": \"bar\",\n                \"barThickness\": 8,\n                \"backgroundColor\": \"#36A2EB\",\n                \"data\": [5, 4, 3, null, 1]\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"scales\": {\n                \"x\": {\n                    \"type\": \"time\",\n                    \"offset\": true,\n                    \"display\": false,\n                    \"time\": {\n                        \"parser\": \"YYYY\"\n                    },\n                    \"ticks\": {\n                        \"source\": \"labels\"\n                    }\n                },\n                \"y\": {\n                    \"display\": false,\n                    \"beginAtZero\": true\n                }\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/controller.bar/bar-thickness-reverse.json",
    "content": "{\n    \"config\": {\n        \"type\": \"bar\",\n        \"data\": {\n            \"labels\": [\"2016\", \"2018\", \"2020\", \"2024\", \"2030\"],\n            \"datasets\": [{\n                \"backgroundColor\": \"#FF6384\",\n                \"data\": [1, null, 3, 4, 5]\n            }, {\n                \"backgroundColor\": \"#36A2EB\",\n                \"data\": [5, 4, 3, null, 1]\n            }, {\n                \"backgroundColor\": \"#FFCE56\",\n                \"data\": [3, 5, 2, null, 4]\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"datasets\": {\n              \"bar\": {\n                    \"barPercentage\": 1,\n                    \"categoryPercentage\": 1\n                }\n            },\n            \"scales\": {\n                \"x\": {\n                    \"type\": \"time\",\n                    \"display\": false,\n                    \"offset\": false,\n                    \"time\": {\n                        \"parser\": \"YYYY\"\n                    },\n                    \"reverse\": true,\n                    \"ticks\": {\n                        \"source\": \"labels\"\n                    }\n                },\n                \"y\": {\n                    \"display\": false,\n                    \"beginAtZero\": true\n                }\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/controller.bar/bar-thickness-single-xy.json",
    "content": "{\n    \"config\": {\n        \"type\": \"bar\",\n        \"data\": {\n            \"labels\": [\"2016\", \"2018\", \"2020\", \"2024\", \"2030\"],\n            \"datasets\": [{\n                \"barPercentage\": 1,\n                \"categoryPercentage\": 1,\n                \"backgroundColor\": \"#FF6384\",\n                \"data\": [{\"x\": \"2022\", \"y\": 42}]\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"scales\": {\n                \"x\": {\n                    \"type\": \"time\",\n                    \"display\": false,\n                    \"offset\": false,\n                    \"time\": {\n                        \"parser\": \"YYYY\"\n                    },\n                    \"ticks\": {\n                        \"source\": \"labels\"\n                    }\n                },\n                \"y\": {\n                    \"display\": false,\n                    \"beginAtZero\": true\n                }\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/controller.bar/bar-thickness-single.json",
    "content": "{\n    \"config\": {\n        \"type\": \"bar\",\n        \"data\": {\n            \"labels\": [\"2016\", \"2018\", \"2020\", \"2024\", \"2030\"],\n            \"datasets\": [{\n                \"barPercentage\": 1,\n                \"categoryPercentage\": 1,\n                \"backgroundColor\": \"#FF6384\",\n                \"data\": [1]\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"scales\": {\n                \"x\": {\n                    \"type\": \"time\",\n                    \"display\": false,\n                    \"offset\": false,\n                    \"time\": {\n                        \"parser\": \"YYYY\"\n                    },\n                    \"min\": \"2013\",\n                    \"ticks\": {\n                        \"source\": \"labels\"\n                    }\n                },\n                \"y\": {\n                    \"display\": false,\n                    \"beginAtZero\": true\n                }\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/controller.bar/bar-thickness-stacked.json",
    "content": "{\n    \"config\": {\n        \"type\": \"bar\",\n        \"data\": {\n            \"labels\": [\"2016\", \"2018\", \"2020\", \"2024\", \"2030\"],\n            \"datasets\": [{\n                \"backgroundColor\": \"#FF6384\",\n                \"data\": [1, null, 3, 4, 5]\n            }, {\n                \"backgroundColor\": \"#36A2EB\",\n                \"data\": [5, 4, 3, null, 1]\n            }, {\n                \"backgroundColor\": \"#FFCE56\",\n                \"data\": [3, 5, 2, null, 4]\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"datasets\": {\n              \"bar\": {\n                    \"barPercentage\": 1,\n                    \"categoryPercentage\": 1\n                }\n            },\n            \"scales\": {\n                \"x\": {\n                    \"type\": \"time\",\n                    \"stacked\": true,\n                    \"display\": false,\n                    \"offset\": false,\n                    \"time\": {\n                        \"parser\": \"YYYY\"\n                    },\n                    \"ticks\": {\n                        \"source\": \"labels\"\n                    }\n                },\n                \"y\": {\n                    \"display\": false,\n                    \"stacked\": true,\n                    \"beginAtZero\": true\n                }\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/controller.bar/baseLine/bottom.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      labels: ['a', 'b'],\n      datasets: [{\n        backgroundColor: '#AAFFCC',\n        borderColor: '#0000FF',\n        borderWidth: 1,\n        data: [1, 2]\n      }]\n    },\n    options: {\n      scales: {\n        x: {\n          display: false\n        },\n        y: {\n          ticks: {\n            display: false\n          },\n          grid: {\n            color: function(context) {\n              return context.tick.value === 0 ? 'red' : 'transparent';\n            },\n            lineWidth: 5,\n            tickLength: 0\n          },\n        }\n      },\n      maintainAspectRatio: false\n    }\n  },\n  options: {\n    canvas: {\n      width: 128,\n      height: 128\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/baseLine/left.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      labels: ['a', 'b'],\n      datasets: [{\n        backgroundColor: '#AAFFCC',\n        borderColor: '#0000FF',\n        borderWidth: 1,\n        data: [1, 2]\n      }]\n    },\n    options: {\n      indexAxis: 'y',\n      scales: {\n        y: {\n          display: false\n        },\n        x: {\n          ticks: {\n            display: false\n          },\n          grid: {\n            color: function(context) {\n              return context.tick.value === 0 ? 'red' : 'transparent';\n            },\n            lineWidth: 5,\n            tickLength: 0\n          },\n        }\n      },\n      maintainAspectRatio: false\n    }\n  },\n  options: {\n    canvas: {\n      width: 128,\n      height: 128\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/baseLine/mid-x.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      labels: ['a', 'b'],\n      datasets: [{\n        backgroundColor: '#AAFFCC',\n        borderColor: '#0000FF',\n        borderWidth: 1,\n        data: [1, -1]\n      }]\n    },\n    options: {\n      indexAxis: 'y',\n      scales: {\n        y: {\n          display: false\n        },\n        x: {\n          ticks: {\n            display: false\n          },\n          grid: {\n            color: function(context) {\n              return context.tick.value === 0 ? 'red' : 'transparent';\n            },\n            lineWidth: 5,\n            tickLength: 0\n          },\n        }\n      },\n      maintainAspectRatio: false\n    }\n  },\n  options: {\n    canvas: {\n      width: 128,\n      height: 128\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/baseLine/mid-y.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      labels: ['a', 'b'],\n      datasets: [{\n        backgroundColor: '#AAFFCC',\n        borderColor: '#0000FF',\n        borderWidth: 1,\n        data: [1, -1]\n      }]\n    },\n    options: {\n      scales: {\n        x: {\n          display: false\n        },\n        y: {\n          ticks: {\n            display: false\n          },\n          grid: {\n            color: function(context) {\n              return context.tick.value === 0 ? 'red' : 'transparent';\n            },\n            lineWidth: 5,\n            tickLength: 0\n          },\n        }\n      },\n      maintainAspectRatio: false\n    }\n  },\n  options: {\n    canvas: {\n      width: 128,\n      height: 128\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/baseLine/right.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      labels: ['a', 'b'],\n      datasets: [{\n        backgroundColor: '#AAFFCC',\n        borderColor: '#0000FF',\n        borderWidth: 1,\n        data: [-1, -2]\n      }]\n    },\n    options: {\n      indexAxis: 'y',\n      scales: {\n        y: {\n          display: false\n        },\n        x: {\n          ticks: {\n            display: false\n          },\n          grid: {\n            color: function(context) {\n              return context.tick.value === 0 ? 'red' : 'transparent';\n            },\n            lineWidth: 5,\n            tickLength: 0,\n            borderWidth: 0\n          },\n        }\n      },\n      maintainAspectRatio: false\n    }\n  },\n  options: {\n    canvas: {\n      width: 128,\n      height: 128\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/baseLine/top.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      labels: ['a', 'b'],\n      datasets: [{\n        backgroundColor: '#AAFFCC',\n        borderColor: '#0000FF',\n        borderWidth: 1,\n        data: [-1, -2]\n      }]\n    },\n    options: {\n      scales: {\n        x: {\n          display: false\n        },\n        y: {\n          ticks: {\n            display: false\n          },\n          grid: {\n            color: function(context) {\n              return context.tick.value === 0 ? 'red' : 'transparent';\n            },\n            borderWidth: 0,\n            lineWidth: 5,\n            tickLength: 0\n          },\n        }\n      },\n      maintainAspectRatio: false\n    }\n  },\n  options: {\n    canvas: {\n      width: 128,\n      height: 128\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/baseLine/value-x.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      labels: ['a', 'b'],\n      datasets: [{\n        backgroundColor: '#AAFFCC',\n        borderColor: '#0000FF',\n        borderWidth: 1,\n        data: [1, 3]\n      }]\n    },\n    options: {\n      base: 2,\n      indexAxis: 'y',\n      scales: {\n        y: {\n          display: false\n        },\n        x: {\n          ticks: {\n            display: false\n          },\n          grid: {\n            color: function(context) {\n              return context.tick.value === 2 ? 'red' : 'transparent';\n            },\n            lineWidth: 5,\n            tickLength: 0\n          },\n        }\n      },\n      maintainAspectRatio: false\n    }\n  },\n  options: {\n    canvas: {\n      width: 128,\n      height: 128\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/baseLine/value-y.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      labels: ['a', 'b'],\n      datasets: [{\n        backgroundColor: '#AAFFCC',\n        borderColor: '#0000FF',\n        borderWidth: 1,\n        data: [1, 3]\n      }]\n    },\n    options: {\n      base: 2,\n      scales: {\n        x: {\n          display: false\n        },\n        y: {\n          ticks: {\n            display: false\n          },\n          grid: {\n            color: function(context) {\n              return context.tick.value === 2 ? 'red' : 'transparent';\n            },\n            lineWidth: 5,\n            tickLength: 0\n          },\n        }\n      },\n      maintainAspectRatio: false\n    }\n  },\n  options: {\n    canvas: {\n      width: 128,\n      height: 128\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/borderColor/border+dpr.js",
    "content": "module.exports = {\n  threshold: 0,\n  tolerance: 0,\n  config: {\n    type: 'bar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5, 6],\n      datasets: [\n        {\n          // option in dataset\n          data: [5, 4, 3, 2, 3, 4, 5],\n        },\n      ]\n    },\n    options: {\n      events: [],\n      devicePixelRatio: 1.5,\n      barPercentage: 1,\n      categoryPercentage: 1,\n      backgroundColor: 'black',\n      borderColor: 'black',\n      borderWidth: 8,\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 501\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/borderColor/indexable.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          borderColor: [\n            '#ff0000',\n            '#00ff00',\n            '#0000ff',\n            '#ffff00',\n            '#ff00ff',\n            '#000000'\n          ]\n        },\n        {\n          // option in element (fallback)\n          data: [0, 5, 10, null, -10, -5],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        bar: {\n          backgroundColor: 'transparent',\n          borderColor: [\n            '#ff88ff',\n            '#888888',\n            '#ff8800',\n            '#00ff88',\n            '#8800ff',\n            '#ffff88'\n          ],\n          borderWidth: 8\n        }\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/borderColor/scriptable.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          borderColor: function(ctx) {\n            var value = ctx.dataset.data[ctx.dataIndex] || 0;\n            return value > 8 ? '#ff0000'\n              : value > 0 ? '#00ff00'\n              : value > -8 ? '#0000ff'\n              : '#ff00ff';\n          }\n        },\n        {\n          // option in element (fallback)\n          data: [0, 5, 10, null, -10, -5]\n        }\n      ]\n    },\n    options: {\n      elements: {\n        bar: {\n          backgroundColor: 'transparent',\n          borderColor: function(ctx) {\n            var value = ctx.dataset.data[ctx.dataIndex] || 0;\n            return value > 8 ? '#ff00ff'\n              : value > 0 ? '#0000ff'\n              : value > -8 ? '#ff0000'\n              : '#00ff00';\n          },\n          borderWidth: 8\n        }\n      },\n      scales: {\n        x: {display: false},\n        y: {\n          display: false,\n          beginAtZero: true\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/borderColor/value.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          borderColor: '#ff0000'\n        },\n        {\n          // option in element (fallback)\n          data: [0, 5, 10, null, -10, -5],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        bar: {\n          backgroundColor: 'transparent',\n          borderColor: '#00ff00',\n          borderWidth: 8\n        }\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/borderRadius/border-radius-stacked-number-mixed-chart.js",
    "content": "module.exports = {\n  threshold: 0.01,\n  config: {\n    type: 'bar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          backgroundColor: 'red',\n          data: [12, 19, 12, 5, 4, 12],\n        },\n        {\n          backgroundColor: 'green',\n          data: [12, 19, -4, 5, 8, 3],\n          type: 'line'\n        },\n        {\n          backgroundColor: 'blue',\n          data: [7, 11, -12, 12, 0, -7],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        bar: {\n          borderRadius: Number.MAX_VALUE,\n          borderWidth: 2,\n        }\n      },\n      scales: {\n        x: {display: false, stacked: true},\n        y: {display: false, stacked: true}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/borderRadius/border-radius-stacked-number-with-order.js",
    "content": "module.exports = {\n  threshold: 0.01,\n  config: {\n    type: 'bar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          backgroundColor: 'red',\n          data: [12, 19, 12, 5, 4, 12],\n          order: 2,\n        },\n        {\n          backgroundColor: 'green',\n          data: [12, 19, -4, 5, 8, 3],\n          order: 1,\n        },\n        {\n          backgroundColor: 'blue',\n          data: [7, 11, -12, 12, 0, -7],\n          order: 0,\n        }\n      ]\n    },\n    options: {\n      elements: {\n        bar: {\n          borderRadius: Number.MAX_VALUE,\n          borderWidth: 2,\n        }\n      },\n      scales: {\n        x: {display: false, stacked: true},\n        y: {display: false, stacked: true}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/borderRadius/border-radius-stacked-number.js",
    "content": "module.exports = {\n  threshold: 0.01,\n  config: {\n    type: 'bar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          backgroundColor: 'red',\n          data: [12, 19, 12, 5, 4, 12],\n        },\n        {\n          backgroundColor: 'green',\n          data: [12, 19, -4, 5, 8, 3],\n        },\n        {\n          backgroundColor: 'blue',\n          data: [7, 11, -12, 12, 0, -7],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        bar: {\n          borderRadius: Number.MAX_VALUE,\n          borderWidth: 2,\n        }\n      },\n      scales: {\n        x: {display: false, stacked: true},\n        y: {display: false, stacked: true}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/borderRadius/border-radius.js",
    "content": "module.exports = {\n  threshold: 0.01,\n  config: {\n    type: 'bar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          borderWidth: 2,\n          borderRadius: 5\n        },\n        {\n          // option in element (fallback)\n          data: [0, 5, 10, null, -10, -5],\n          borderSkipped: false,\n          borderRadius: Number.MAX_VALUE\n        }\n      ]\n    },\n    options: {\n      indexAxis: 'y',\n      elements: {\n        bar: {\n          backgroundColor: '#AAAAAA80',\n          borderColor: '#80808080',\n          borderWidth: {bottom: 6, left: 15, top: 6, right: 15}\n        }\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/borderRadius/no-spacing.js",
    "content": "module.exports = {\n  threshold: 0.01,\n  config: {\n    type: 'bar',\n    data: {\n      labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],\n      datasets: [\n        {\n          data: [9, 25, 13, 17, 12, 21, 20, 19, 6, 12, 14, 20],\n          categoryPercentage: 1,\n          barPercentage: 1,\n          backgroundColor: '#2E5C76',\n          borderWidth: 2,\n          borderColor: '#377395',\n          borderRadius: 5,\n        },\n      ]\n    },\n    options: {\n      devicePixelRatio: 1.25,\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/borderSkipped/indexable.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          borderSkipped: [\n            'top',\n            'top',\n            'right',\n            'right',\n            'bottom',\n            'left'\n          ]\n        },\n        {\n          // option in element (fallback)\n          data: [0, 5, 10, null, -10, -5],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        bar: {\n          backgroundColor: 'transparent',\n          borderColor: '#888',\n          borderWidth: 8,\n          borderSkipped: [\n            'bottom',\n            'bottom',\n            'left',\n            'left',\n            'top',\n            'right'\n          ]\n        }\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/borderSkipped/middle.js",
    "content": "module.exports = {\n  threshold: 0.01,\n  config: {\n    type: 'bar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          backgroundColor: 'red',\n          data: [12, 19, 12, 5, 4, 12],\n        },\n        {\n          backgroundColor: 'green',\n          data: [12, 19, -4, 5, 8, 3],\n        },\n        {\n          backgroundColor: 'blue',\n          data: [7, 11, -12, 12, 0, -7],\n        }\n      ]\n    },\n    options: {\n      borderRadius: Number.MAX_VALUE,\n      borderSkipped: 'middle',\n      borderWidth: 2,\n      scales: {\n        x: {display: false, stacked: true},\n        y: {display: false, stacked: true}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/borderSkipped/scriptable.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          borderSkipped: function(ctx) {\n            var value = ctx.dataset.data[ctx.dataIndex] || 0;\n            return value > 8 ? 'left'\n              : value > 0 ? 'right'\n              : value > -8 ? 'top'\n              : 'bottom';\n          }\n        },\n        {\n          // option in element (fallback)\n          data: [0, 5, 10, null, -10, -5]\n        }\n      ]\n    },\n    options: {\n      elements: {\n        bar: {\n          backgroundColor: 'transparent',\n          borderColor: '#888',\n          borderSkipped: function(ctx) {\n            var index = ctx.dataIndex;\n            return index > 4 ? 'left'\n              : index > 3 ? 'right'\n              : index > 1 ? 'top'\n              : 'bottom';\n          },\n          borderWidth: 8\n        }\n      },\n      scales: {\n        x: {display: false},\n        y: {\n          display: false,\n          beginAtZero: true\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/borderSkipped/value.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      labels: [0, 1, 2, 3],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, -10, null],\n          borderSkipped: 'top'\n        },\n        {\n          // option in dataset\n          data: [0, 5, -10, null],\n          borderSkipped: 'right'\n        },\n        {\n          // option in dataset\n          data: [0, 5, -10, null],\n          borderSkipped: 'bottom'\n        },\n        {\n          // option in element (fallback)\n          data: [0, 5, -10, null],\n        },\n        {\n          // option in dataset\n          data: [0, 5, -10, null],\n          borderSkipped: false\n        }\n      ]\n    },\n    options: {\n      elements: {\n        bar: {\n          backgroundColor: 'transparent',\n          borderColor: '#888',\n          borderSkipped: 'left',\n          borderWidth: 8\n        }\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/borderWidth/indexable-object.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          borderSkipped: false,\n          borderWidth: [\n            {},\n            {bottom: 1, left: 1, top: 1, right: 1},\n            {bottom: 1, left: 2, top: 1, right: 2},\n            {bottom: 1, left: 3, top: 1, right: 3},\n            {bottom: 1, left: 4, top: 1, right: 4},\n            {bottom: 1, left: 5, top: 1, right: 5}\n          ]\n        },\n        {\n          // option in element (fallback)\n          data: [0, 5, 10, null, -10, -5],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        bar: {\n          backgroundColor: 'transparent',\n          borderColor: '#80808080',\n          borderSkipped: false,\n          borderWidth: [\n            {bottom: 1, left: 5, top: 1, right: 5},\n            {bottom: 1, left: 4, top: 1, right: 4},\n            {bottom: 1, left: 3, top: 1, right: 3},\n            {bottom: 1, left: 2, top: 1, right: 2},\n            {bottom: 1, left: 1, top: 1, right: 1},\n            {}\n          ]\n        }\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/borderWidth/indexable.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          borderWidth: [\n            0,\n            1,\n            2,\n            3,\n            4,\n            5\n          ]\n        },\n        {\n          // option in element (fallback)\n          data: [0, 5, 10, null, -10, -5],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        bar: {\n          backgroundColor: 'transparent',\n          borderColor: '#888',\n          borderWidth: [\n            5,\n            4,\n            3,\n            2,\n            1,\n            0\n          ]\n        }\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/borderWidth/negative.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          borderWidth: -2\n        },\n        {\n          // option in element (fallback)\n          data: [0, 5, 10, null, -10, -5],\n        },\n        {\n          data: [0, 5, 10, null, -10, -5],\n          borderWidth: {left: -5, top: -5, bottom: -5, right: -5},\n          borderSkipped: false\n        },\n        {\n          data: [0, 5, 10, null, -10, -5],\n          borderWidth: {}\n        },\n      ]\n    },\n    options: {\n      elements: {\n        bar: {\n          backgroundColor: '#888',\n          borderColor: '#f00',\n          borderWidth: -4\n        }\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/borderWidth/object.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          borderSkipped: false,\n          borderWidth: {bottom: 1, left: 2, top: 3, right: 4}\n        },\n        {\n          // option in element (fallback)\n          data: [0, 5, 10, null, -10, -5],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        bar: {\n          backgroundColor: 'transparent',\n          borderColor: '#888',\n          borderSkipped: false,\n          borderWidth: {bottom: 4, left: 3, top: 2, right: 1}\n        }\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/borderWidth/scriptable-object.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          borderSkipped: false,\n          borderWidth: function(ctx) {\n            var value = ctx.dataset.data[ctx.dataIndex] || 0;\n            return {top: Math.abs(value)};\n          }\n        },\n        {\n          // option in element (fallback)\n          data: [0, 5, 10, null, -10, -5]\n        }\n      ]\n    },\n    options: {\n      elements: {\n        bar: {\n          backgroundColor: 'transparent',\n          borderColor: '#80808080',\n          borderSkipped: false,\n          borderWidth: function(ctx) {\n            return {left: ctx.dataIndex * 2};\n          }\n        }\n      },\n      scales: {\n        x: {display: false},\n        y: {\n          display: false,\n          beginAtZero: true\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/borderWidth/scriptable.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          borderWidth: function(ctx) {\n            var value = ctx.dataset.data[ctx.dataIndex] || 0;\n            return Math.abs(value);\n          }\n        },\n        {\n          // option in element (fallback)\n          data: [0, 5, 10, null, -10, -5]\n        }\n      ]\n    },\n    options: {\n      elements: {\n        bar: {\n          backgroundColor: 'transparent',\n          borderColor: '#888',\n          borderWidth: function(ctx) {\n            return ctx.dataIndex * 2;\n          }\n        }\n      },\n      scales: {\n        x: {display: false},\n        y: {\n          display: false,\n          beginAtZero: true\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/borderWidth/value.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          borderWidth: 2\n        },\n        {\n          // option in element (fallback)\n          data: [0, 5, 10, null, -10, -5],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        bar: {\n          backgroundColor: 'transparent',\n          borderColor: '#888',\n          borderWidth: 4\n        }\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/chart-area-clip.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      labels: [0, 1, 3, 4],\n      datasets: [\n        {\n          data: [5, 20, -5, -20],\n          borderColor: '#ff0000'\n        }\n      ]\n    },\n    options: {\n      layout: {\n        padding: {\n          left: 0,\n          right: 0,\n          top: 50,\n          bottom: 50\n        }\n      },\n      elements: {\n        bar: {\n          backgroundColor: '#00ff00',\n          borderWidth: 8\n        }\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false, min: -10, max: 10}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/data/object-index-axis-y.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      datasets: [{\n        label: '# of Votes',\n        data: {a: 1, b: 3, c: 2}\n      }]\n    },\n    options: {\n      indexAxis: 'y'\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/data/object.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      labels: ['a', 'b', 'c'],\n      datasets: [\n        {\n          data: {a: 10, b: 2, c: -5},\n          backgroundColor: '#ff0000'\n        },\n        {\n          data: {a: 8, b: 12, c: 5},\n          backgroundColor: '#00ff00'\n        }\n      ]\n    },\n    options: {\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/data/parsing.js",
    "content": "const data = [{x: 'Jan', net: 100, cogs: 50, gm: 50}, {x: 'Feb', net: 120, cogs: 55, gm: 75}];\n\nmodule.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      labels: ['Jan', 'Feb'],\n      datasets: [{\n        label: 'Net sales',\n        backgroundColor: 'blue',\n        data: data,\n        parsing: {\n          yAxisKey: 'net'\n        }\n      }, {\n        label: 'Cost of goods sold',\n        backgroundColor: 'red',\n        data: data,\n        parsing: {\n          yAxisKey: 'cogs'\n        }\n      }, {\n        label: 'Gross margin',\n        backgroundColor: 'green',\n        data: data,\n        parsing: {\n          yAxisKey: 'gm'\n        }\n      }]\n    },\n    options: {\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/floatBar/data-as-objects-horizontal.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      labels: ['a', 'b', 'c'],\n      datasets: [\n        {\n          data: [{y: 'b', x: [2, 8]}, {y: 'c', x: [2, 5]}],\n          backgroundColor: '#ff0000'\n        },\n        {\n          data: [{y: 'a', x: 10}, {y: 'c', x: [6, 10]}],\n          backgroundColor: '#00ff00'\n        }\n      ]\n    },\n    options: {\n      indexAxis: 'y',\n      scales: {\n        x: {display: false, min: 0},\n        y: {display: false, stacked: true}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/floatBar/data-as-objects.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      labels: ['a', 'b', 'c'],\n      datasets: [\n        {\n          data: [{x: 'b', y: [2, 8]}, {x: 'c', y: [2, 5]}],\n          backgroundColor: '#ff0000'\n        },\n        {\n          data: [{x: 'a', y: 10}, {x: 'c', y: [6, 10]}],\n          backgroundColor: '#00ff00'\n        }\n      ]\n    },\n    options: {\n      scales: {\n        x: {display: false, stacked: true},\n        y: {display: false, min: 0}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/floatBar/float-bar-horizontal.json",
    "content": "{\n    \"config\": {\n        \"type\": \"bar\",\n        \"data\": {\n            \"labels\": [\"2030\", \"2034\", \"2038\", \"2042\"],\n            \"datasets\": [{\n                \"backgroundColor\": \"#FF6384\",\n                \"data\": [11, [6,2], [-4,-7], -2]\n            }, {\n                \"backgroundColor\": \"#36A2EB\",\n                \"data\": [[1,2], [3,4], [-2,-3], [1,4]]\n            }, {\n                \"backgroundColor\": \"#FFCE56\",\n                \"data\": [[0,1], [1,2], [-2,-1], [1,-7]]\n            }]\n        },\n        \"options\": {\n            \"indexAxis\": \"y\",\n            \"scales\": {\n                \"x\": {\n                    \"display\": false\n                },\n                \"y\": {\n                    \"display\": false\n                }\n            }\n        }\n    },\n    \"debug\": false,\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/controller.bar/floatBar/float-bar-stacked-horizontal.json",
    "content": "{\n    \"config\": {\n        \"type\": \"bar\",\n        \"data\": {\n            \"labels\": [\"2030\", \"2034\", \"2038\", \"2042\"],\n            \"datasets\": [{\n                \"backgroundColor\": \"#FF6384\",\n                \"data\": [11, [6,2], [-4,-7], -2]\n            }, {\n                \"backgroundColor\": \"#36A2EB\",\n                \"data\": [[1,2], [3,4], [-2,-3], [1,4]]\n            }, {\n                \"backgroundColor\": \"#FFCE56\",\n                \"data\": [[0,1], [1,2], [-2,-1], [1,-7]]\n            }]\n        },\n        \"options\": {\n            \"indexAxis\": \"y\",\n            \"scales\": {\n                \"x\": {\n                    \"display\": false,\n                    \"stacked\": true\n                },\n                \"y\": {\n                    \"display\": false,\n                    \"stacked\": true\n                }\n            }\n        }\n    },\n    \"debug\": false,\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/controller.bar/floatBar/float-bar-stacked.json",
    "content": "{\n    \"config\": {\n        \"type\": \"bar\",\n        \"data\": {\n            \"labels\": [\"2030\", \"2034\", \"2038\", \"2042\"],\n            \"datasets\": [{\n                \"backgroundColor\": \"#FF6384\",\n                \"data\": [11, [6,2], [-4,-7], -2]\n            }, {\n                \"backgroundColor\": \"#36A2EB\",\n                \"data\": [[1,2], [3,4], [-2,-3], [1,4]]\n            }, {\n                \"backgroundColor\": \"#FFCE56\",\n                \"data\": [[0,1], [1,2], [-2,-1], [1,-7]]\n            }]\n        },\n        \"options\": {\n            \"scales\": {\n                \"x\": {\n                    \"display\": false,\n                    \"stacked\": true\n                },\n                \"y\": {\n                    \"display\": false,\n                    \"stacked\": true\n                }\n            }\n        }\n    },\n    \"debug\": false,\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/controller.bar/floatBar/float-bar.json",
    "content": "{\n    \"config\": {\n        \"type\": \"bar\",\n        \"data\": {\n            \"labels\": [\"2030\", \"2034\", \"2038\", \"2042\"],\n            \"datasets\": [{\n                \"backgroundColor\": \"#FF6384\",\n                \"data\": [11, [6,2], [-4,-7], -2]\n            }, {\n                \"backgroundColor\": \"#36A2EB\",\n                \"data\": [[1,2], [3,4], [-2,-3], [1,4]]\n            }, {\n                \"backgroundColor\": \"#FFCE56\",\n                \"data\": [[0,1], [1,2], [-2,-1], [1,-7]]\n            }]\n        },\n        \"options\": {\n            \"scales\": {\n                \"x\": {\n                    \"display\": false\n                },\n                \"y\": {\n                    \"display\": false\n                }\n            }\n        }\n    },\n    \"debug\": false,\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/controller.bar/horizontal-borders.js",
    "content": "module.exports = {\n  threshold: 0.01,\n  config: {\n    type: 'bar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          borderWidth: 2\n        },\n        {\n          // option in element (fallback)\n          data: [0, 5, 10, null, -10, -5],\n          borderSkipped: false\n        }\n      ]\n    },\n    options: {\n      indexAxis: 'y',\n      elements: {\n        bar: {\n          backgroundColor: '#AAAAAA80',\n          borderColor: '#80808080',\n          borderWidth: {bottom: 6, left: 15, top: 6, right: 15}\n        }\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/minBarLength/horizontal-neg.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      labels: [0, 1, 2],\n      datasets: [\n        {\n          data: [0, -0.01, -30],\n          backgroundColor: '#00ff00',\n          borderColor: '#000',\n          borderWidth: 4,\n          minBarLength: 20\n        }\n      ]\n    },\n    options: {\n      indexAxis: 'y',\n      scales: {\n        x: {\n          ticks: {\n            display: false\n          }\n        },\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 512,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/minBarLength/horizontal-pos.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      labels: [0, 1, 2],\n      datasets: [\n        {\n          data: [0, 0.01, 30],\n          backgroundColor: '#00ff00',\n          borderColor: '#000',\n          borderWidth: 4,\n          minBarLength: 20\n        }\n      ]\n    },\n    options: {\n      indexAxis: 'y',\n      scales: {\n        x: {\n          ticks: {\n            display: false\n          }\n        },\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 512,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/minBarLength/horizontal-stacked-no-overlap.js",
    "content": "const minBarLength = 50;\n\nmodule.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      labels: [1, 2, 3, 4],\n      datasets: [\n        {\n          data: [1, -1, 1, 20],\n          backgroundColor: '#bb000066',\n          minBarLength\n        },\n        {\n          data: [1, -1, -1, -20],\n          backgroundColor: '#00bb0066',\n          minBarLength\n        },\n        {\n          data: [1, -1, 1, 40],\n          backgroundColor: '#0000bb66',\n          minBarLength\n        },\n        {\n          data: [1, -1, -1, -40],\n          backgroundColor: '#00000066',\n          minBarLength\n        }\n      ]\n    },\n    options: {\n      indexAxis: 'y',\n      scales: {\n        x: {\n          display: false,\n          stacked: true\n        },\n        y: {\n          type: 'linear',\n          position: 'left',\n          stacked: true,\n          ticks: {\n            display: false\n          }\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 512,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/minBarLength/horizontal-stacked.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      labels: [0, 1, 2, 3, 4],\n      datasets: [{\n        data: [0, 0.01, 30],\n        backgroundColor: '#00ff00',\n        borderColor: '#000',\n        borderWidth: 4,\n        minBarLength: 20,\n        xAxisID: 'x2',\n      }]\n    },\n    options: {\n      indexAxis: 'y',\n      scales: {\n        x: {\n          stack: 'demo',\n          ticks: {\n            display: false\n          }\n        },\n        x2: {\n          type: 'linear',\n          position: 'bottom',\n          stack: 'demo',\n          stackWeight: 1,\n          ticks: {\n            display: false\n          }\n        },\n        y: {display: false},\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 512,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/minBarLength/horizontal.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      labels: [0, 1, 2, 3, 4],\n      datasets: [\n        {\n          data: [0, -0.01, 0.01, 30, -30],\n          backgroundColor: '#00ff00',\n          borderColor: '#000',\n          borderSkipped: ctx => ctx.raw === 0 ? false : 'start',\n          borderWidth: 4,\n          minBarLength: 20\n        }\n      ]\n    },\n    options: {\n      indexAxis: 'y',\n      scales: {\n        x: {\n          ticks: {\n            display: false\n          }\n        },\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 512,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/minBarLength/vertical-neg.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      labels: [0, 1, 2],\n      datasets: [\n        {\n          data: [0, -0.01, -30],\n          backgroundColor: '#00ff00',\n          borderColor: '#000',\n          borderWidth: 4,\n          minBarLength: 20\n        }\n      ]\n    },\n    options: {\n      scales: {\n        x: {display: false},\n        y: {\n          ticks: {\n            display: false\n          }\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 512,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/minBarLength/vertical-pos.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      labels: [0, 1, 2],\n      datasets: [\n        {\n          data: [0, 0.01, 30],\n          backgroundColor: '#00ff00',\n          borderColor: '#000',\n          borderWidth: 4,\n          minBarLength: 20\n        }\n      ]\n    },\n    options: {\n      scales: {\n        x: {display: false},\n        y: {\n          ticks: {\n            display: false\n          }\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 512,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/minBarLength/vertical-stacked-no-overlap.js",
    "content": "const minBarLength = 50;\n\nmodule.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      labels: [1, 2, 3, 4],\n      datasets: [\n        {\n          data: [1, -1, 1, 20],\n          backgroundColor: '#bb000066',\n          minBarLength\n        },\n        {\n          data: [1, -1, -1, -20],\n          backgroundColor: '#00bb0066',\n          minBarLength\n        },\n        {\n          data: [1, -1, 1, 40],\n          backgroundColor: '#0000bb66',\n          minBarLength\n        },\n        {\n          data: [1, -1, -1, -40],\n          backgroundColor: '#00000066',\n          minBarLength\n        }\n      ]\n    },\n    options: {\n      scales: {\n        x: {\n          display: false,\n          stacked: true\n        },\n        y: {\n          type: 'linear',\n          position: 'left',\n          stacked: true,\n          ticks: {\n            display: false\n          }\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 512,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/minBarLength/vertical-stacked.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      labels: [0, 1, 2, 3, 4],\n      datasets: [{\n        data: [0, 0.01, 30],\n        backgroundColor: '#00ff00',\n        borderColor: '#000',\n        borderWidth: 4,\n        minBarLength: 20,\n        yAxisID: 'y2',\n      }]\n    },\n    options: {\n      scales: {\n        x: {display: false},\n        y: {\n          stack: 'demo',\n          ticks: {\n            display: false\n          }\n        },\n        y2: {\n          type: 'linear',\n          position: 'left',\n          stack: 'demo',\n          stackWeight: 1,\n          ticks: {\n            display: false\n          }\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 512,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/minBarLength/vertical.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      labels: [0, 1, 2, 3, 4],\n      datasets: [\n        {\n          data: [0, -0.01, 0.01, 30, -30],\n          backgroundColor: '#00ff00',\n          borderColor: '#000',\n          borderSkipped: ctx => ctx.raw === 0 ? false : 'start',\n          borderWidth: 4,\n          minBarLength: 20\n        }\n      ]\n    },\n    options: {\n      scales: {\n        x: {display: false},\n        y: {\n          ticks: {\n            display: false\n          }\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 512,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/not-grouped/mixed.js",
    "content": "module.exports = {\n  description: 'https://github.com/chartjs/Chart.js/issues/9281',\n  config: {\n    type: 'bar',\n    data: {\n      labels: [0, 1, 2],\n      datasets: [\n        {\n          label: 'data 1',\n          data: [1, 2, 2],\n          backgroundColor: 'rgb(255,0,0,0.7)',\n          grouped: true\n        },\n        {\n          label: 'data 2',\n          data: [4, 4, 1],\n          backgroundColor: 'rgb(0,255,0,0.7)',\n          grouped: true\n        },\n        {\n          label: 'data 3',\n          data: [2, 1, 3],\n          backgroundColor: 'rgb(0,0,255,0.7)',\n          grouped: false\n        }\n      ]\n    },\n    options: {\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/not-grouped/on-time.js",
    "content": "const data1 = [\n  {\n    x: '2017-11-02T20:30:00',\n    y: 27\n  },\n  {\n    x: '2017-11-03T20:53:00',\n    y: 30\n  },\n  {\n    x: '2017-11-06T05:46:00',\n    y: 19\n  },\n  {\n    x: '2017-11-06T21:03:00',\n    y: 28\n  },\n  {\n    x: '2017-11-07T20:49:00',\n    y: 29\n  },\n  {\n    x: '2017-11-08T21:52:00',\n    y: 33\n  }\n];\n\nconst data2 = [\n  {\n    x: '2017-11-03T13:07:00',\n    y: 45\n  },\n  {\n    x: '2017-11-04T04:50:00',\n    y: 40\n  },\n  {\n    x: '2017-11-06T12:48:00',\n    y: 38\n  },\n  {\n    x: '2017-11-07T12:28:00',\n    y: 42\n  },\n  {\n    x: '2017-11-08T12:45:00',\n    y: 51\n  },\n  {\n    x: '2017-11-09T05:23:00',\n    y: 57\n  }\n];\n\nconst data3 = [\n  {\n    x: '2017-11-03T16:30:00',\n    y: 32\n  },\n  {\n    x: '2017-11-04T11:50:00',\n    y: 34\n  },\n  {\n    x: '2017-11-06T18:30:00',\n    y: 28\n  },\n  {\n    x: '2017-11-07T15:51:00',\n    y: 31\n  },\n  {\n    x: '2017-11-08T17:27:00',\n    y: 36\n  },\n  {\n    x: '2017-11-09T06:53:00',\n    y: 31\n  }\n];\n\nmodule.exports = {\n  description: 'https://github.com/chartjs/Chart.js/issues/5139',\n  config: {\n    type: 'bar',\n    data: {\n      datasets: [\n        {\n          data: data1,\n          backgroundColor: 'rgb(0,0,255)',\n        },\n        {\n          data: data2,\n          backgroundColor: 'rgb(255,0,0)',\n        },\n        {\n          data: data3,\n          backgroundColor: 'rgb(0,255,0)',\n        },\n      ]\n    },\n    options: {\n      barThickness: 10,\n      grouped: false,\n      scales: {\n        x: {\n          bounds: 'ticks',\n          type: 'time',\n          offset: false,\n          position: 'bottom',\n          display: true,\n          time: {\n            isoWeekday: true,\n            unit: 'day'\n          },\n          grid: {\n            offset: false\n          }\n        },\n        y: {\n          beginAtZero: true,\n          display: false\n        }\n      },\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      width: 1000,\n      height: 300\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/skipNull/bar-skip-null-object-data.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      datasets: [\n        {\n          data: {0: 5, 1: 20, 2: 1, 3: 10},\n          backgroundColor: '#00ff00',\n          borderColor: '#ff0000'\n        },\n        {\n          data: {0: 10, 1: null, 2: 1, 3: NaN},\n          backgroundColor: '#ff0000',\n          borderColor: '#ff0000'\n        }\n      ]\n    },\n    options: {\n      skipNull: true,\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/skipNull/bar-skip-null.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      labels: [0, 1, 3, 4],\n      datasets: [\n        {\n          data: [5, 20, 1, 10],\n          backgroundColor: '#00ff00',\n          borderColor: '#ff0000'\n        },\n        {\n          data: [10, null, 1, undefined],\n          backgroundColor: '#ff0000',\n          borderColor: '#ff0000'\n        }\n      ]\n    },\n    options: {\n      skipNull: true,\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/skipNull/combinations.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      labels: ['0', '1', '2', '3', '4', '5', '6', '7'],\n      datasets: [\n        {\n          data: [null, 1000, null, 1000, null, 1000, null, 1000],\n          backgroundColor: '#00ff00',\n          borderColor: '#ff0000'\n        },\n        {\n          data: [null, null, 1000, 1000, null, null, 1000, 1000],\n          backgroundColor: '#ff0000',\n          borderColor: '#ff0000'\n        },\n        {\n          data: [null, null, null, null, 1000, 1000, 1000, 1000],\n          backgroundColor: '#0000ff',\n          borderColor: '#0000ff'\n        }\n      ]\n    },\n    options: {\n      skipNull: true,\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/stacking/issue-9105.js",
    "content": "module.exports = {\n  description: 'https://github.com/chartjs/Chart.js/issues/9105',\n  config: {\n    type: 'bar',\n    data: {\n      labels: ['January', 'February', 'March', 'April', 'May', 'June'],\n      datasets: [\n        {\n          backgroundColor: 'rgba(255,99,132,0.8)',\n          label: 'Dataset 1',\n          data: [12, 19, 3, 5, 2, 3],\n          stack: '0',\n          yAxisID: 'y'\n        },\n        {\n          backgroundColor: 'rgba(54,162,235,0.8)',\n          label: 'Dataset 2',\n          data: [13, 19, 3, 5, 8, 3],\n          stack: '0',\n          yAxisID: 'y'\n        },\n        {\n          backgroundColor: 'rgba(75,192,192,0.8)',\n          label: 'Dataset 3',\n          data: [13, 19, 3, 5, 8, 3],\n          stack: '0',\n          yAxisID: 'y'\n        }\n      ]\n    },\n    options: {\n      plugins: false,\n      scales: {\n        x: {\n          display: false,\n        },\n        y: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    run(chart) {\n      chart.data.datasets[1].stack = '1';\n      chart.update();\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/stacking/logarithmic-strings.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      datasets: [{\n        data: ['10', '100', '10', '100'],\n        backgroundColor: '#ff0000'\n      }, {\n        data: ['100', '10', '0', '100'],\n        backgroundColor: '#00ff00'\n      }],\n      labels: ['label1', 'label2', 'label3', 'label4']\n    },\n    options: {\n      datasets: {\n        bar: {\n          barPercentage: 1,\n        }\n      },\n      scales: {\n        x: {\n          type: 'category',\n          display: false,\n          stacked: true,\n        },\n        y: {\n          type: 'logarithmic',\n          display: false,\n          stacked: true\n        }\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/stacking/logarithmic.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      datasets: [{\n        data: [10, 100, 10, 100],\n        backgroundColor: '#ff0000'\n      }, {\n        data: [100, 10, 0, 100],\n        backgroundColor: '#00ff00'\n      }],\n      labels: ['label1', 'label2', 'label3', 'label4']\n    },\n    options: {\n      datasets: {\n        bar: {\n          barPercentage: 1,\n        }\n      },\n      scales: {\n        x: {\n          type: 'category',\n          display: false,\n          stacked: true,\n        },\n        y: {\n          type: 'logarithmic',\n          display: false,\n          stacked: true\n        }\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/stacking/order-default.json",
    "content": "{\n    \"config\": {\n        \"type\": \"bar\",\n        \"data\": {\n            \"labels\": [\"2016\", \"2018\", \"2020\", \"2024\", \"2030\"],\n            \"datasets\": [{\n                \"backgroundColor\": \"#FF6384\",\n                \"data\": [1, null, 3, 4, 5]\n            }, {\n                \"backgroundColor\": \"#36A2EB\",\n                \"data\": [5, 4, 3, null, 1]\n            }, {\n                \"backgroundColor\": \"#FFCE56\",\n                \"data\": [3, 5, 2, null, 4]\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"scales\": {\n                \"x\": {\n                    \"display\": false,\n                    \"stacked\": true\n                },\n                \"y\": {\n                    \"display\": false,\n                    \"stacked\": true,\n                    \"beginAtZero\": true\n                }\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/controller.bar/stacking/order-specified.json",
    "content": "{\n    \"config\": {\n        \"type\": \"bar\",\n        \"data\": {\n            \"labels\": [\"2016\", \"2018\", \"2020\", \"2024\", \"2030\"],\n            \"datasets\": [{\n                \"backgroundColor\": \"#FF6384\",\n                \"data\": [1, null, 3, 4, 5],\n                \"order\": 20\n            }, {\n                \"backgroundColor\": \"#36A2EB\",\n                \"data\": [5, 4, 3, null, 1],\n                \"order\": 25\n            }, {\n                \"backgroundColor\": \"#FFCE56\",\n                \"data\": [3, 5, 2, null, 4],\n                \"order\": 10\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"scales\": {\n                \"x\": {\n                    \"display\": false,\n                    \"stacked\": true\n                },\n                \"y\": {\n                    \"display\": false,\n                    \"stacked\": true,\n                    \"beginAtZero\": true\n                }\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/controller.bar/stacking/remove-dataset.js",
    "content": "var barChartData = {\n  labels: [0, 1, 2, 3, 4, 5, 6],\n  datasets: [\n    {\n      backgroundColor: 'red',\n      data: [\n        // { x: 0, y: 0 },\n        {x: 1, y: 5},\n        {x: 2, y: 5},\n        {x: 3, y: 5},\n        {x: 4, y: 5},\n        {x: 5, y: 5},\n        {x: 6, y: 5}\n      ]\n    },\n    {\n      backgroundColor: 'blue',\n      data: [\n        {x: 0, y: 5},\n        // { x: 1, y: 0 },\n        {x: 2, y: 5},\n        {x: 3, y: 5},\n        {x: 4, y: 5},\n        {x: 5, y: 5},\n        {x: 6, y: 5}\n      ]\n    },\n    {\n      backgroundColor: 'green',\n      data: [\n        {x: 0, y: 5},\n        {x: 1, y: 5},\n        // { x: 2, y: 0 },\n        {x: 3, y: 5},\n        {x: 4, y: 5},\n        {x: 5, y: 5},\n        {x: 6, y: 5}\n      ]\n    },\n    {\n      backgroundColor: 'yellow',\n      data: [\n        {x: 0, y: 5},\n        {x: 1, y: 5},\n        {x: 2, y: 5},\n        // {x: 3, y: 0 },\n        {x: 4, y: 5},\n        {x: 5, y: 5},\n        {x: 6, y: 5}\n      ]\n    },\n    {\n      backgroundColor: 'purple',\n      data: [\n        {x: 0, y: 5},\n        {x: 1, y: 5},\n        {x: 2, y: 5},\n        {x: 3, y: 5},\n        // { x: 4, y: 0 },\n        {x: 5, y: 5},\n        {x: 6, y: 5}\n      ]\n    },\n    {\n      backgroundColor: 'grey',\n      data: [\n        {x: 0, y: 5},\n        {x: 1, y: 5},\n        {x: 2, y: 5},\n        {x: 3, y: 5},\n        {x: 4, y: 5},\n        // { x: 5, y: 0 },\n        {x: 6, y: 5}\n      ]\n    }\n  ]\n};\n\nmodule.exports = {\n  config: {\n    type: 'bar',\n    data: barChartData,\n    options: {\n      scales: {\n        x: {\n          display: false,\n          stacked: true\n        },\n        y: {\n          display: false,\n          stacked: true\n        }\n      }\n    }\n  },\n  options: {\n    run(chart) {\n      chart.data.datasets.splice(0, 1);\n      chart.update();\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/stacking/replace-data.js",
    "content": "var barChartData = {\n  labels: ['January', 'February', 'March'],\n  datasets: [\n    {\n      label: 'Dataset 1',\n      backgroundColor: 'red',\n      data: [5, 5, 5]\n    },\n    {\n      label: 'Dataset 2',\n      backgroundColor: 'blue',\n      data: [5, 5, 5]\n    },\n    {\n      label: 'Dataset 3',\n      backgroundColor: 'green',\n      data: [5, 5, 5]\n    }\n  ]\n};\n\nmodule.exports = {\n  description: 'https://github.com/chartjs/Chart.js/issues/8614',\n  config: {\n    type: 'bar',\n    data: barChartData,\n    options: {\n      scales: {\n        x: {\n          display: false,\n          stacked: true\n        },\n        y: {\n          display: false,\n          stacked: true\n        }\n      }\n    }\n  },\n  options: {\n    run(chart) {\n      chart.data.datasets[1].data = [\n        {x: 'January', y: 5},\n        // Februay missing\n        {x: 'March', y: 5}\n      ];\n      chart.update();\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bar/stacking/stacked-and-multiple-axis.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],\n      datasets: [\n        {\n          label: 'Dataset 1',\n          data: [100, 90, 100, 50, 99, 87, 34],\n          backgroundColor: 'rgba(255,99,132,0.8)',\n          stack: 'a',\n          xAxisID: 'x'\n        },\n        {\n          label: 'Dataset 2',\n          data: [20, 25, 30, 32, 58, 14, 12],\n          backgroundColor: 'rgba(54,162,235,0.8)',\n          stack: 'b',\n          xAxisID: 'x2'\n        },\n        {\n          label: 'Dataset 3',\n          data: [80, 30, 40, 60, 70, 80, 47],\n          backgroundColor: 'rgba(75,192,192,0.8)',\n          stack: 'a',\n          xAxisID: 'x3'\n        },\n        {\n          label: 'Dataset 4',\n          data: [80, 30, 40, 60, 70, 80, 47],\n          backgroundColor: 'rgba(54,162,235,0.8)',\n          stack: 'a',\n          xAxisID: 'x3'\n        },\n      ]\n    },\n    options: {\n      plugins: false,\n      barThickness: 'flex',\n      scales: {\n        x: {\n          stacked: true,\n          display: false,\n        },\n        x2: {\n          labels: ['January 2024', 'February 2024', 'March 2024', 'April 2024', 'May 2024', 'June 2024', 'July 2024'],\n          stacked: true,\n          display: false,\n        },\n        x3: {\n          labels: ['January 2025', 'February 2025', 'March 2025', 'April 2025', 'May 2025', 'June 2025', 'July 2025'],\n          stacked: true,\n          display: false,\n        },\n        y: {\n          stacked: true,\n          display: false,\n        }\n      }\n    }\n  },\n  options: {\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bubble/autoPadding-disabled.js",
    "content": "module.exports = {\n  config: {\n    type: 'bubble',\n    data: {\n      datasets: [{\n        backgroundColor: 'red',\n        data: [{x: 12, y: 54, r: 22.4}]\n      }, {\n        backgroundColor: 'blue',\n        data: [{x: 18, y: 38, r: 25}]\n      }]\n    },\n    options: {\n      layout: {\n        autoPadding: false,\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      width: 256,\n      height: 256\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bubble/clip.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: [0, 5, 10, 15, 20, 25, 30, 50, 55, 60],\n      datasets: [{\n        data: [6, 11, 10, 10, 3, 22, 7, 24],\n        type: 'bubble',\n        label: 'test',\n        borderColor: '#3e95cd',\n        fill: false\n      }]\n    },\n    options: {\n      scales: {\n        x: {ticks: {display: false}},\n        y: {\n          min: 8,\n          max: 25,\n          beginAtZero: true,\n          ticks: {\n            display: false\n          }\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 256\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bubble/hover-radius-zero.js",
    "content": "module.exports = {\n  config: {\n    type: 'bubble',\n    data: {\n      labels: [2, 2, 2, 2],\n      datasets: [{\n        data: [\n          [1, 1],\n          [1, 2],\n          [1, 3, 20],\n          [1, 4, 20]\n        ]\n      }, {\n        data: [1, 2, 3, 4]\n      }, {\n        data: [{x: 3, y: 1}, {x: 3, y: 2}, {x: 3, y: 3, r: 15}, {x: 3, y: 4, r: 15}]\n      }]\n    },\n    options: {\n      events: [],\n      radius: 10,\n      hoverRadius: 0,\n      backgroundColor: 'blue',\n      hoverBackgroundColor: 'red',\n      scales: {\n        x: {display: false, bounds: 'data'},\n        y: {display: false}\n      },\n      layout: {\n        padding: 24\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 256\n    },\n    run(chart) {\n      chart.setActiveElements([\n        {datasetIndex: 0, index: 1}, {datasetIndex: 0, index: 2},\n        {datasetIndex: 1, index: 1}, {datasetIndex: 1, index: 2},\n        {datasetIndex: 2, index: 1}, {datasetIndex: 2, index: 2},\n      ]);\n      chart.update();\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bubble/padding-update.js",
    "content": "module.exports = {\n  config: {\n    type: 'bubble',\n    data: {\n      datasets: [{\n        backgroundColor: 'red',\n        data: [{x: 12, y: 54, r: 22.4}]\n      }, {\n        backgroundColor: 'blue',\n        data: [{x: 18, y: 38, r: 25}]\n      }]\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      width: 256,\n      height: 256\n    },\n    run(chart) {\n      chart.update();\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bubble/padding.js",
    "content": "module.exports = {\n  config: {\n    type: 'bubble',\n    data: {\n      datasets: [{\n        backgroundColor: 'red',\n        data: [{x: 12, y: 54, r: 22.4}]\n      }, {\n        backgroundColor: 'blue',\n        data: [{x: 18, y: 38, r: 25}]\n      }]\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      width: 256,\n      height: 256\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bubble/point-style.json",
    "content": "{\n    \"config\": {\n        \"type\": \"bubble\",\n        \"data\": {\n            \"datasets\": [{\n                \"data\": [\n                    {\"x\": 0, \"y\": 3},\n                    {\"x\": 1, \"y\": 3},\n                    {\"x\": 2, \"y\": 3},\n                    {\"x\": 3, \"y\": 3},\n                    {\"x\": 4, \"y\": 3},\n                    {\"x\": 5, \"y\": 3},\n                    {\"x\": 6, \"y\": 3},\n                    {\"x\": 7, \"y\": 3},\n                    {\"x\": 8, \"y\": 3},\n                    {\"x\": 9, \"y\": 3}\n                ],\n                \"backgroundColor\": \"#00ff00\",\n                \"borderColor\": \"transparent\",\n                \"borderWidth\": 0,\n                \"pointStyle\": [\n                    \"circle\",\n                    \"cross\",\n                    \"crossRot\",\n                    \"dash\",\n                    \"line\",\n                    \"rect\",\n                    \"rectRounded\",\n                    \"rectRot\",\n                    \"star\",\n                    \"triangle\"\n                ]\n            }, {\n                \"data\": [\n                    {\"x\": 0, \"y\": 2},\n                    {\"x\": 1, \"y\": 2},\n                    {\"x\": 2, \"y\": 2},\n                    {\"x\": 3, \"y\": 2},\n                    {\"x\": 4, \"y\": 2},\n                    {\"x\": 5, \"y\": 2},\n                    {\"x\": 6, \"y\": 2},\n                    {\"x\": 7, \"y\": 2},\n                    {\"x\": 8, \"y\": 2},\n                    {\"x\": 9, \"y\": 2}\n                ],\n                \"backgroundColor\": \"transparent\",\n                \"borderColor\": \"#0000ff\",\n                \"borderWidth\": 1,\n                \"pointStyle\": [\n                    \"circle\",\n                    \"cross\",\n                    \"crossRot\",\n                    \"dash\",\n                    \"line\",\n                    \"rect\",\n                    \"rectRounded\",\n                    \"rectRot\",\n                    \"star\",\n                    \"triangle\"\n                ]\n            }, {\n                \"data\": [\n                    {\"x\": 0, \"y\": 1},\n                    {\"x\": 1, \"y\": 1},\n                    {\"x\": 2, \"y\": 1},\n                    {\"x\": 3, \"y\": 1},\n                    {\"x\": 4, \"y\": 1},\n                    {\"x\": 5, \"y\": 1},\n                    {\"x\": 6, \"y\": 1},\n                    {\"x\": 7, \"y\": 1},\n                    {\"x\": 8, \"y\": 1},\n                    {\"x\": 9, \"y\": 1}\n                ],\n                \"backgroundColor\": \"#00ff00\",\n                \"borderColor\": \"#0000ff\",\n                \"borderWidth\": 1,\n                \"pointStyle\": [\n                    \"circle\",\n                    \"cross\",\n                    \"crossRot\",\n                    \"dash\",\n                    \"line\",\n                    \"rect\",\n                    \"rectRounded\",\n                    \"rectRot\",\n                    \"star\",\n                    \"triangle\"\n                ]\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"scales\": {\n                \"x\": {\"display\": false},\n                \"y\": {\n                    \"display\": false,\n                    \"min\": 0,\n                    \"max\": 4\n                }\n            },\n            \"elements\": {\n                \"line\": {\n                    \"borderColor\": \"transparent\",\n                    \"borderWidth\": 1,\n                    \"fill\": false\n                },\n                \"point\": {\n                    \"radius\": 16\n                }\n            },\n            \"layout\": {\n                \"padding\": {\n                    \"left\": 24,\n                    \"right\": 24\n                }\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/controller.bubble/radius-data.js",
    "content": "module.exports = {\n  config: {\n    type: 'bubble',\n    data: {\n      datasets: [{\n        data: [\n          {x: 0, y: 5, r: 1},\n          {x: 1, y: 4, r: 2},\n          {x: 2, y: 3, r: 6},\n          {x: 3, y: 2},\n          {x: 4, y: 1, r: 2},\n          {x: 5, y: 0, r: NaN},\n          {x: 6, y: -1, r: undefined},\n          {x: 7, y: -2, r: null},\n          {x: 8, y: -3, r: '4'},\n          {x: 9, y: -4, r: '4px'},\n        ]\n      }]\n    },\n    options: {\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      },\n      elements: {\n        point: {\n          backgroundColor: '#444',\n          radius: 10\n        }\n      },\n      layout: {\n        padding: {\n          left: 24,\n          right: 24\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 128,\n      width: 256\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.bubble/radius-scriptable.js",
    "content": "module.exports = {\n  config: {\n    type: 'bubble',\n    data: {\n      datasets: [{\n        data: [\n          {x: 0, y: 0},\n          {x: 1, y: 0},\n          {x: 2, y: 0},\n          {x: 3, y: 0},\n          {x: 4, y: 0},\n          {x: 5, y: 0}\n        ],\n        radius: function(ctx) {\n          return ctx.dataset.data[ctx.dataIndex].x * 4;\n        }\n      }]\n    },\n    options: {\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      },\n      elements: {\n        point: {\n          backgroundColor: '#444'\n        }\n      },\n      layout: {\n        padding: {\n          left: 24,\n          right: 24\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 128,\n      width: 256\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.doughnut/backgroundColor/indexable.js",
    "content": "module.exports = {\n  config: {\n    type: 'doughnut',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 2, 4, null, 6, 8],\n          backgroundColor: [\n            '#ff0000',\n            '#00ff00',\n            '#0000ff',\n            '#ffff00',\n            '#ff00ff',\n            '#000000'\n          ]\n        },\n        {\n          // option in element (fallback)\n          data: [0, 2, 4, null, 6, 8],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        arc: {\n          backgroundColor: [\n            '#ff88ff',\n            '#888888',\n            '#ff8800',\n            '#00ff88',\n            '#8800ff',\n            '#ffff88'\n          ]\n        }\n      },\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.doughnut/backgroundColor/scriptable.js",
    "content": "module.exports = {\n  config: {\n    type: 'doughnut',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 2, 4, null, 6, 8],\n          backgroundColor: function(ctx) {\n            var value = ctx.dataset.data[ctx.dataIndex] || 0;\n            return value > 8 ? '#ff0000'\n              : value > 6 ? '#00ff00'\n              : value > 2 ? '#0000ff'\n              : '#ff00ff';\n          }\n        },\n        {\n          // option in element (fallback)\n          data: [0, 2, 4, null, 6, 8],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        arc: {\n          backgroundColor: function(ctx) {\n            var value = ctx.dataset.data[ctx.dataIndex] || 0;\n            return value > 8 ? '#ff0000'\n              : value > 6 ? '#00ff00'\n              : value > 2 ? '#0000ff'\n              : '#ff00ff';\n          }\n        }\n      },\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.doughnut/backgroundColor/value.js",
    "content": "module.exports = {\n  config: {\n    type: 'doughnut',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 2, 4, null, 6, 8],\n          backgroundColor: '#ff0000'\n        },\n        {\n          // option in element (fallback)\n          data: [0, 2, 4, null, 6, 8],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        arc: {\n          backgroundColor: '#00ff00'\n        }\n      },\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.doughnut/borderAlign/indexable.js",
    "content": "module.exports = {\n  config: {\n    type: 'doughnut',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 2, 4, null, 6, 8],\n          borderAlign: [\n            'center',\n            'inner',\n            'center',\n            'inner',\n            'center',\n            'inner',\n          ],\n          borderColor: '#00ff00'\n        },\n        {\n          // option in element (fallback)\n          data: [0, 2, 4, null, 6, 8],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        arc: {\n          backgroundColor: 'transparent',\n          borderColor: '#ff0000',\n          borderWidth: 5,\n          borderAlign: [\n            'center',\n            'inner',\n            'center',\n            'inner',\n            'center',\n            'inner',\n          ]\n        }\n      },\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.doughnut/borderAlign/scriptable.js",
    "content": "module.exports = {\n  config: {\n    type: 'doughnut',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 2, 4, null, 6, 8],\n          borderAlign: function(ctx) {\n            var value = ctx.dataset.data[ctx.dataIndex] || 0;\n            return value > 4 ? 'inner' : 'center';\n          },\n          borderColor: '#0000ff',\n        },\n        {\n          // option in element (fallback)\n          data: [0, 2, 4, null, 6, 8],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        arc: {\n          backgroundColor: 'transparent',\n          borderColor: '#ff00ff',\n          borderWidth: 8,\n          borderAlign: function(ctx) {\n            var value = ctx.dataset.data[ctx.dataIndex] || 0;\n            return value > 4 ? 'center' : 'inner';\n          }\n        }\n      },\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.doughnut/borderAlign/value.js",
    "content": "module.exports = {\n  config: {\n    type: 'doughnut',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 2, 4, null, 6, 8],\n          borderAlign: 'inner',\n          borderColor: '#00ff00',\n        },\n        {\n          // option in element (fallback)\n          data: [0, 2, 4, null, 6, 8],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        arc: {\n          backgroundColor: 'transparent',\n          borderAlign: 'center',\n          borderColor: '#0000ff',\n          borderWidth: 4,\n        }\n      },\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.doughnut/borderColor/indexable.js",
    "content": "module.exports = {\n  config: {\n    type: 'doughnut',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 2, 4, null, 6, 8],\n          borderColor: [\n            '#ff0000',\n            '#00ff00',\n            '#0000ff',\n            '#ffff00',\n            '#ff00ff',\n            '#000000'\n          ]\n        },\n        {\n          // option in element (fallback)\n          data: [0, 2, 4, null, 6, 8],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        arc: {\n          backgroundColor: 'transparent',\n          borderColor: [\n            '#ff88ff',\n            '#888888',\n            '#ff8800',\n            '#00ff88',\n            '#8800ff',\n            '#ffff88'\n          ],\n          borderWidth: 8\n        }\n      },\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.doughnut/borderColor/scriptable.js",
    "content": "module.exports = {\n  config: {\n    type: 'doughnut',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 2, 4, null, 6, 8],\n          borderColor: function(ctx) {\n            var value = ctx.dataset.data[ctx.dataIndex] || 0;\n            return value > 8 ? '#ff0000'\n              : value > 6 ? '#00ff00'\n              : value > 2 ? '#0000ff'\n              : '#ff00ff';\n          }\n        },\n        {\n          // option in element (fallback)\n          data: [0, 2, 4, null, 6, 8],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        arc: {\n          backgroundColor: 'transparent',\n          borderColor: function(ctx) {\n            var value = ctx.dataset.data[ctx.dataIndex] || 0;\n            return value > 8 ? '#ff00ff'\n              : value > 6 ? '#0000ff'\n              : value > 2 ? '#ff0000'\n              : '#00ff00';\n          },\n          borderWidth: 8\n        }\n      },\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.doughnut/borderColor/value.js",
    "content": "module.exports = {\n  config: {\n    type: 'doughnut',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 2, 4, null, 6, 8],\n          borderColor: '#ff0000'\n        },\n        {\n          // option in element (fallback)\n          data: [0, 2, 4, null, 6, 8],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        arc: {\n          backgroundColor: 'transparent',\n          borderColor: '#00ff00',\n          borderWidth: 8\n        }\n      },\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.doughnut/borderDash/scriptable.js",
    "content": "module.exports = {\n  config: {\n    type: 'doughnut',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in element (fallback)\n          data: [5, 2, 4, 7, 6, 8]\n        }\n      ]\n    },\n    options: {\n      elements: {\n        arc: {\n          backgroundColor: 'transparent',\n          borderColor: 'black',\n          borderWidth: 1,\n          borderDash: function(ctx) {\n            var value = (ctx.dataIndex || 0) % 2;\n            return value === 0 ? [3, 3] : [];\n          }\n\n        }\n      },\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.doughnut/borderDash/value.js",
    "content": "module.exports = {\n  config: {\n    type: 'polarArea',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [5, 2, 4, 7, 6, 8],\n          borderAlign: 'inner',\n          borderColor: 'black'\n        },\n      ]\n    },\n    options: {\n      elements: {\n        arc: {\n          backgroundColor: 'transparent',\n          borderWidth: 1,\n          borderDash: [3, 3]\n        }\n      },\n      scales: {\n        r: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.doughnut/borderJoinStyle/bevel-default.js",
    "content": "module.exports = {\n  config: {\n    type: 'doughnut',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          data: [0, 2, 4, null, 6, 8],\n          backgroundColor: 'transparent',\n          borderColor: '#000',\n          borderWidth: 10,\n          spacing: 50,\n        },\n      ]\n    },\n    options: {\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.doughnut/borderJoinStyle/miter.js",
    "content": "module.exports = {\n  config: {\n    type: 'doughnut',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          data: [0, 2, 4, null, 6, 8],\n          backgroundColor: 'transparent',\n          borderColor: '#000',\n          borderJoinStyle: 'miter',\n          borderWidth: 10,\n          spacing: 50,\n        },\n      ]\n    },\n    options: {\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.doughnut/borderJoinStyle/round.js",
    "content": "module.exports = {\n  config: {\n    type: 'doughnut',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          data: [0, 2, 4, null, 6, 8],\n          backgroundColor: 'transparent',\n          borderColor: '#000',\n          borderJoinStyle: 'round',\n          borderWidth: 10,\n          spacing: 50,\n        },\n      ]\n    },\n    options: {\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.doughnut/borderRadius/scriptable.js",
    "content": "module.exports = {\n  config: {\n    type: 'doughnut',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 2, 4, null, 6, 8],\n          borderRadius: () => 4,\n        },\n      ]\n    },\n    options: {\n      elements: {\n        arc: {\n          backgroundColor: 'transparent',\n          borderColor: '#888',\n        }\n      },\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.doughnut/borderRadius/value-corners.js",
    "content": "module.exports = {\n  config: {\n    type: 'doughnut',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 2, 4, null, 6, 8],\n          borderRadius: {\n            outerStart: 20,\n            outerEnd: 40,\n          }\n        },\n      ]\n    },\n    options: {\n      elements: {\n        arc: {\n          backgroundColor: 'transparent',\n          borderColor: '#888',\n        }\n      },\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.doughnut/borderRadius/value-large-radius.js",
    "content": "module.exports = {\n  config: {\n    type: 'doughnut',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          data: [60, 15, 33, 44, 12],\n          // Radius is large enough to clip\n          borderRadius: 200,\n          backgroundColor: [\n            'rgb(255, 99, 132)',\n            'rgb(255, 159, 64)',\n            'rgb(255, 205, 86)',\n            'rgb(75, 192, 192)',\n            'rgb(54, 162, 235)'\n          ]\n        },\n      ]\n    },\n    // options: {\n    //   elements: {\n    //     arc: {\n    //       backgroundColor: 'transparent',\n    //       borderColor: '#888',\n    //     }\n    //   },\n    // }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.doughnut/borderRadius/value-small-number.js",
    "content": "module.exports = {\n  config: {\n    type: 'doughnut',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 2, 4, null, 6, 8],\n          borderRadius: 20\n        },\n      ]\n    },\n    options: {\n      elements: {\n        arc: {\n          backgroundColor: 'transparent',\n          borderColor: '#888',\n        }\n      },\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.doughnut/borderWidth/indexable.js",
    "content": "module.exports = {\n  config: {\n    type: 'doughnut',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 2, 4, null, 6, 8],\n          borderWidth: [\n            0,\n            1,\n            2,\n            3,\n            4,\n            5\n          ]\n        },\n        {\n          // option in element (fallback)\n          data: [0, 2, 4, null, 6, 8],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        arc: {\n          backgroundColor: 'transparent',\n          borderColor: '#888',\n          borderWidth: [\n            5,\n            4,\n            3,\n            2,\n            1,\n            0\n          ]\n        }\n      },\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.doughnut/borderWidth/scriptable.js",
    "content": "module.exports = {\n  config: {\n    type: 'doughnut',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 2, 4, null, 6, 8],\n          borderWidth: function(ctx) {\n            var value = ctx.dataset.data[ctx.dataIndex] || 0;\n            return Math.abs(value);\n          }\n        },\n        {\n          // option in element (fallback)\n          data: [0, 2, 4, null, 6, 8],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        arc: {\n          backgroundColor: 'transparent',\n          borderColor: '#888',\n          borderWidth: function(ctx) {\n            return ctx.dataIndex * 2;\n          }\n        }\n      },\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.doughnut/borderWidth/value.js",
    "content": "module.exports = {\n  config: {\n    type: 'doughnut',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 2, 4, null, 6, 8],\n          borderWidth: 2\n        },\n        {\n          // option in element (fallback)\n          data: [0, 2, 4, null, 6, 8],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        arc: {\n          backgroundColor: 'transparent',\n          borderColor: '#888',\n          borderWidth: 4\n        }\n      },\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.doughnut/doughnut-NaN.js",
    "content": "module.exports = {\n  config: {\n    type: 'doughnut',\n    data: {\n      labels: ['A', 'B', 'C', 'D', 'E'],\n      datasets: [{\n        data: [1, 5, NaN, 50, 100],\n        backgroundColor: [\n          'rgba(255, 99, 132, 0.8)',\n          'rgba(54, 162, 235, 0.8)',\n          'rgba(255, 206, 86, 0.8)',\n          'rgba(75, 192, 192, 0.8)',\n          'rgba(153, 102, 255, 0.8)'\n        ],\n        borderColor: [\n          'rgb(255, 99, 132)',\n          'rgb(54, 162, 235)',\n          'rgb(255, 206, 86)',\n          'rgb(75, 192, 192)',\n          'rgb(153, 102, 255)'\n        ]\n      }]\n    },\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.doughnut/doughnut-animation-hide-last.js",
    "content": "const canvas = document.createElement('canvas');\ncanvas.width = 512;\ncanvas.height = 512;\nconst ctx = canvas.getContext('2d');\n\nmodule.exports = {\n  config: {\n    type: 'doughnut',\n    data: {\n      labels: ['A', 'B', 'C', 'D', 'E'],\n      datasets: [{\n        data: [1],\n        backgroundColor: 'rgba(255, 99, 132, 0.8)',\n        borderWidth: 4,\n        borderColor: 'rgb(255, 99, 132)',\n      }]\n    },\n    options: {\n      animation: {\n        duration: 0,\n        easing: 'linear',\n      },\n      responsive: false,\n      plugins: {\n        legend: false,\n        title: false,\n        tooltip: false,\n        filler: false\n      }\n    },\n  },\n  options: {\n    canvas: {\n      height: 512,\n      width: 512\n    },\n    run: function(chart) {\n      chart.options.animation.duration = 8000;\n      chart.toggleDataVisibility(0);\n      chart.update();\n      const animator = Chart.animator;\n      // disable animator\n      const backup = animator._refresh;\n      animator._refresh = function() { };\n\n      return new Promise((resolve) => {\n        window.requestAnimationFrame(() => {\n          const anims = animator._getAnims(chart);\n          const start = anims.items[0]._start;\n          for (let i = 0; i < 16; i++) {\n            animator._update(start + i * 500);\n            let x = i % 4 * 128;\n            let y = Math.floor(i / 4) * 128;\n            ctx.drawImage(chart.canvas, x, y, 128, 128);\n          }\n          Chart.helpers.clearCanvas(chart.canvas);\n          chart.ctx.drawImage(canvas, 0, 0);\n\n          animator._refresh = backup;\n          resolve();\n        });\n      });\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.doughnut/doughnut-animation.js",
    "content": "const canvas = document.createElement('canvas');\ncanvas.width = 512;\ncanvas.height = 512;\nconst ctx = canvas.getContext('2d');\n\nmodule.exports = {\n  config: {\n    type: 'doughnut',\n    data: {\n      labels: ['A', 'B', 'C', 'D', 'E'],\n      datasets: [{\n        data: [1, 5, 10, 50, 100],\n        backgroundColor: [\n          'rgba(255, 99, 132, 0.8)',\n          'rgba(54, 162, 235, 0.8)',\n          'rgba(255, 206, 86, 0.8)',\n          'rgba(75, 192, 192, 0.8)',\n          'rgba(153, 102, 255, 0.8)'\n        ],\n        borderWidth: 4,\n        borderColor: [\n          'rgb(255, 99, 132)',\n          'rgb(54, 162, 235)',\n          'rgb(255, 206, 86)',\n          'rgb(75, 192, 192)',\n          'rgb(153, 102, 255)'\n        ]\n      }]\n    },\n    options: {\n      animation: {\n        duration: 8000,\n        easing: 'linear'\n      },\n      responsive: false,\n      plugins: {\n        legend: false,\n        title: false,\n        tooltip: false,\n        filler: false\n      }\n    },\n    plugins: [{\n      id: 'hide',\n      afterInit(chart) {\n        chart.toggleDataVisibility(4);\n      }\n    }]\n  },\n  options: {\n    canvas: {\n      height: 512,\n      width: 512\n    },\n    run: function(chart) {\n      const animator = Chart.animator;\n      const anims = animator._getAnims(chart);\n      // disable animator\n      const backup = animator._refresh;\n      animator._refresh = function() { };\n\n      return new Promise((resolve) => {\n        window.requestAnimationFrame(() => {\n\n          const start = anims.items[0]._start;\n          for (let i = 0; i < 16; i++) {\n            animator._update(start + i * 500);\n            let x = i % 4 * 128;\n            let y = Math.floor(i / 4) * 128;\n            ctx.drawImage(chart.canvas, x, y, 128, 128);\n          }\n          Chart.helpers.clearCanvas(chart.canvas);\n          chart.ctx.drawImage(canvas, 0, 0);\n\n          animator._refresh = backup;\n          resolve();\n        });\n      });\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.doughnut/doughnut-border-align-center.json",
    "content": "{\n    \"config\": {\n        \"type\": \"doughnut\",\n        \"data\": {\n            \"labels\": [\"A\", \"B\", \"C\", \"D\", \"E\"],\n            \"datasets\": [{\n                \"data\": [1, 5, 10, 50, 100],\n                \"backgroundColor\": [\n                    \"rgba(255, 99, 132, 0.8)\",\n                    \"rgba(54, 162, 235, 0.8)\",\n                    \"rgba(255, 206, 86, 0.8)\",\n                    \"rgba(75, 192, 192, 0.8)\",\n                    \"rgba(153, 102, 255, 0.8)\"\n                ],\n                \"borderWidth\": 20,\n                \"borderColor\": [\n                    \"rgb(255, 99, 132)\",\n                    \"rgb(54, 162, 235)\",\n                    \"rgb(255, 206, 86)\",\n                    \"rgb(75, 192, 192)\",\n                    \"rgb(153, 102, 255)\"\n                ]\n            }]\n        },\n        \"options\": {\n            \"responsive\": false\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/controller.doughnut/doughnut-border-align-inner.json",
    "content": "{\n    \"config\": {\n        \"type\": \"doughnut\",\n        \"data\": {\n            \"labels\": [\"A\", \"B\", \"C\", \"D\", \"E\"],\n            \"datasets\": [{\n                \"data\": [1, 5, 10, 50, 100],\n                \"backgroundColor\": [\n                    \"rgba(255, 99, 132, 0.8)\",\n                    \"rgba(54, 162, 235, 0.8)\",\n                    \"rgba(255, 206, 86, 0.8)\",\n                    \"rgba(75, 192, 192, 0.8)\",\n                    \"rgba(153, 102, 255, 0.8)\"\n                ],\n                \"borderWidth\": 20,\n                \"borderColor\": [\n                    \"rgb(255, 99, 132)\",\n                    \"rgb(54, 162, 235)\",\n                    \"rgb(255, 206, 86)\",\n                    \"rgb(75, 192, 192)\",\n                    \"rgb(153, 102, 255)\"\n                ],\n                \"borderAlign\": \"inner\"\n            }]\n        },\n        \"options\": {\n            \"responsive\": false\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/controller.doughnut/doughnut-circumference-over-2pi.json",
    "content": "{\n    \"config\": {\n        \"type\": \"doughnut\",\n        \"data\": {\n            \"labels\": [\"A\"],\n            \"datasets\": [{\n                \"data\": [100],\n                \"backgroundColor\": [\n                    \"rgba(153, 102, 255, 0.8)\"\n                ],\n                \"borderWidth\": 20,\n                \"borderColor\": [\n                    \"rgb(153, 102, 255)\"\n                ]\n            }]\n        },\n        \"options\": {\n            \"circumference\": 400,\n            \"responsive\": false\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/controller.doughnut/doughnut-circumference-per-dataset.js",
    "content": "module.exports = {\n  config: {\n    type: 'doughnut',\n    data: {\n      labels: ['A', 'B', 'C', 'D', 'E'],\n      datasets: [{\n        data: [1, 5, 10, 50, 100],\n        backgroundColor: [\n          'rgba(255, 99, 132, 0.8)',\n          'rgba(54, 162, 235, 0.8)',\n          'rgba(255, 206, 86, 0.8)',\n          'rgba(75, 192, 192, 0.8)',\n          'rgba(153, 102, 255, 0.8)'\n        ],\n        borderWidth: 1,\n        borderColor: [\n          'rgb(255, 99, 132)',\n          'rgb(54, 162, 235)',\n          'rgb(255, 206, 86)',\n          'rgb(75, 192, 192)',\n          'rgb(153, 102, 255)'\n        ],\n        circumference: 180\n      }]\n    },\n    options: {\n      circumference: 57.32,\n      responsive: false\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.doughnut/doughnut-circumference.json",
    "content": "{\n    \"config\": {\n        \"type\": \"doughnut\",\n        \"data\": {\n            \"labels\": [\"A\", \"B\", \"C\", \"D\", \"E\"],\n            \"datasets\": [{\n                \"data\": [1, 5, 10, 50, 100],\n                \"backgroundColor\": [\n                    \"rgba(255, 99, 132, 0.8)\",\n                    \"rgba(54, 162, 235, 0.8)\",\n                    \"rgba(255, 206, 86, 0.8)\",\n                    \"rgba(75, 192, 192, 0.8)\",\n                    \"rgba(153, 102, 255, 0.8)\"\n                ],\n                \"borderWidth\": 20,\n                \"borderColor\": [\n                    \"rgb(255, 99, 132)\",\n                    \"rgb(54, 162, 235)\",\n                    \"rgb(255, 206, 86)\",\n                    \"rgb(75, 192, 192)\",\n                    \"rgb(153, 102, 255)\"\n                ]\n            }]\n        },\n        \"options\": {\n            \"circumference\": 57.32,\n            \"responsive\": false\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/controller.doughnut/doughnut-full-to-semi.js",
    "content": "module.exports = {\n  description: 'https://github.com/chartjs/Chart.js/issues/9832',\n  config: {\n    type: 'doughnut',\n    data: {\n      datasets: [{\n        label: 'Set 1',\n        data: [50, 50, 25],\n        backgroundColor: ['#BF616A', '#D08770', '#EBCB8B'],\n        borderWidth: 0\n      },\n      {\n        label: 'Se1 2',\n        data: [50, 50, 25],\n        backgroundColor: ['#BF616A', '#D08770', '#EBCB8B'],\n        borderWidth: 0\n      }]\n    },\n    options: {\n      rotation: -90\n    }\n  },\n  options: {\n    canvas: {\n      width: 512,\n      height: 512\n    },\n    run(chart) {\n      chart.options.circumference = 180;\n      chart.update();\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.doughnut/doughnut-hidden-single.js",
    "content": "module.exports = {\n  config: {\n    type: 'doughnut',\n    data: {\n      labels: ['A', 'B', 'C', 'D', 'E'],\n      datasets: [{\n        data: [1, 5, 10, 50, 100],\n        backgroundColor: [\n          'rgba(255, 99, 132, 0.8)',\n          'rgba(54, 162, 235, 0.8)',\n          'rgba(255, 206, 86, 0.8)',\n          'rgba(75, 192, 192, 0.8)',\n          'rgba(153, 102, 255, 0.8)'\n        ],\n      }, {\n        data: [1, 5, 10, 50, 100],\n        backgroundColor: [\n          'rgba(255, 99, 132, 0.8)',\n          'rgba(54, 162, 235, 0.8)',\n          'rgba(255, 206, 86, 0.8)',\n          'rgba(75, 192, 192, 0.8)',\n          'rgba(153, 102, 255, 0.8)'\n        ],\n      }]\n    },\n    options: {\n      responsive: false,\n      plugins: {\n        legend: false,\n        title: false,\n        tooltip: false,\n        filler: false\n      }\n    },\n  },\n  options: {\n    run(chart) {\n      chart.hide(0, 4);\n      chart.hide(1, 2);\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.doughnut/doughnut-hidden.js",
    "content": "module.exports = {\n  config: {\n    type: 'doughnut',\n    data: {\n      labels: ['A', 'B', 'C', 'D', 'E'],\n      datasets: [{\n        data: [1, 5, 10, 50, 100],\n        backgroundColor: [\n          'rgba(255, 99, 132, 0.8)',\n          'rgba(54, 162, 235, 0.8)',\n          'rgba(255, 206, 86, 0.8)',\n          'rgba(75, 192, 192, 0.8)',\n          'rgba(153, 102, 255, 0.8)'\n        ],\n        borderWidth: 4,\n        borderColor: [\n          'rgb(255, 99, 132)',\n          'rgb(54, 162, 235)',\n          'rgb(255, 206, 86)',\n          'rgb(75, 192, 192)',\n          'rgb(153, 102, 255)'\n        ]\n      }]\n    },\n    options: {\n      responsive: false,\n      plugins: {\n        legend: false,\n        title: false,\n        tooltip: false,\n        filler: false\n      }\n    },\n    plugins: [{\n      id: 'hide',\n      afterInit(chart) {\n        chart.toggleDataVisibility(4);\n      }\n    }]\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.doughnut/doughnut-offset.js",
    "content": "module.exports = {\n  config: {\n    type: 'doughnut',\n    data: {\n      labels: ['Red', 'Blue', 'Yellow'],\n      datasets: [{\n        data: [12, 4, 6],\n        backgroundColor: ['red', 'blue', 'yellow']\n      }]\n    },\n    options: {\n      offset: 40,\n      layout: {\n        padding: 50\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.doughnut/doughnut-outer-radius-percent.js",
    "content": "module.exports = {\n  config: {\n    type: 'doughnut',\n    data: {\n      labels: ['A', 'B', 'C', 'D', 'E'],\n      datasets: [{\n        data: [1, 5, 10, 50, 100],\n        backgroundColor: [\n          'rgba(255, 99, 132, 0.8)',\n          'rgba(54, 162, 235, 0.8)',\n          'rgba(255, 206, 86, 0.8)',\n          'rgba(75, 192, 192, 0.8)',\n          'rgba(153, 102, 255, 0.8)'\n        ],\n        borderColor: [\n          'rgb(255, 99, 132)',\n          'rgb(54, 162, 235)',\n          'rgb(255, 206, 86)',\n          'rgb(75, 192, 192)',\n          'rgb(153, 102, 255)'\n        ]\n      }]\n    },\n    options: {\n      radius: '30%',\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.doughnut/doughnut-outer-radius-pixels.js",
    "content": "module.exports = {\n  config: {\n    type: 'doughnut',\n    data: {\n      labels: ['A', 'B', 'C', 'D', 'E'],\n      datasets: [{\n        data: [1, 5, 10, 50, 100],\n        backgroundColor: [\n          'rgba(255, 99, 132, 0.8)',\n          'rgba(54, 162, 235, 0.8)',\n          'rgba(255, 206, 86, 0.8)',\n          'rgba(75, 192, 192, 0.8)',\n          'rgba(153, 102, 255, 0.8)'\n        ],\n        borderColor: [\n          'rgb(255, 99, 132)',\n          'rgb(54, 162, 235)',\n          'rgb(255, 206, 86)',\n          'rgb(75, 192, 192)',\n          'rgb(153, 102, 255)'\n        ]\n      }]\n    },\n    options: {\n      radius: 150,\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.doughnut/doughnut-parsing.js",
    "content": "module.exports = {\n  config: {\n    type: 'doughnut',\n    data: {\n      labels: ['Red', 'Blue', 'Yellow'],\n      datasets: [{\n        data: [\n          {foo: 12},\n          {foo: 4},\n          {foo: 6},\n        ],\n        backgroundColor: ['red', 'blue', 'yellow']\n      }]\n    },\n    options: {\n      parsing: {\n        key: 'foo'\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.doughnut/doughnut-rotation-300.js",
    "content": "module.exports = {\n  config: {\n    type: 'doughnut',\n    data: {\n      labels: ['A', 'B', 'C', 'D', 'E'],\n      datasets: [{\n        data: [1, 5, 10, 50, 100],\n        backgroundColor: [\n          'rgba(255, 99, 132, 0.8)',\n          'rgba(54, 162, 235, 0.8)',\n          'rgba(255, 206, 86, 0.8)',\n          'rgba(75, 192, 192, 0.8)',\n          'rgba(153, 102, 255, 0.8)'\n        ],\n        borderColor: [\n          'rgb(255, 99, 132)',\n          'rgb(54, 162, 235)',\n          'rgb(255, 206, 86)',\n          'rgb(75, 192, 192)',\n          'rgb(153, 102, 255)'\n        ]\n      }]\n    },\n    options: {\n      rotation: 300\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.doughnut/doughnut-rotation-circumference-8x8.js",
    "content": "const canvas = document.createElement('canvas');\ncanvas.width = 512;\ncanvas.height = 512;\nconst ctx = canvas.getContext('2d');\n\nmodule.exports = {\n  config: {\n    type: 'doughnut',\n    data: {\n      labels: ['A', 'B', 'C', 'D', 'E'],\n      datasets: [{\n        data: [1, 5, 10, 50, 100],\n        backgroundColor: [\n          'rgba(255, 99, 132, 0.8)',\n          'rgba(54, 162, 235, 0.8)',\n          'rgba(255, 206, 86, 0.8)',\n          'rgba(75, 192, 192, 0.8)',\n          'rgba(153, 102, 255, 0.8)'\n        ],\n        borderColor: [\n          'rgb(255, 99, 132)',\n          'rgb(54, 162, 235)',\n          'rgb(255, 206, 86)',\n          'rgb(75, 192, 192)',\n          'rgb(153, 102, 255)'\n        ]\n      }]\n    },\n    options: {\n      rotation: -360,\n      circumference: 180,\n      events: []\n    }\n  },\n  options: {\n    canvas: {\n      height: 512,\n      width: 512\n    },\n    run: function(chart) {\n      return new Promise((resolve) => {\n        for (let i = 0; i < 64; i++) {\n          const col = i % 8;\n          const row = Math.floor(i / 8);\n          const evenodd = row % 2 ? 1 : -1;\n          chart.options.rotation = col * 45 * evenodd;\n          chart.options.circumference = 360 - row * 45;\n          chart.update();\n          ctx.drawImage(chart.canvas, col * 64, row * 64, 64, 64);\n        }\n        ctx.strokeStyle = 'red';\n        ctx.lineWidth = 0.5;\n        ctx.beginPath();\n        for (let i = 1; i < 8; i++) {\n          ctx.moveTo(i * 64, 0);\n          ctx.lineTo(i * 64, 511);\n          ctx.moveTo(0, i * 64);\n          ctx.lineTo(511, i * 64);\n        }\n        ctx.stroke();\n        Chart.helpers.clearCanvas(chart.canvas);\n        chart.ctx.drawImage(canvas, 0, 0);\n        resolve();\n      });\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.doughnut/doughnut-rotation-per-dataset.js",
    "content": "module.exports = {\n  config: {\n    type: 'doughnut',\n    data: {\n      labels: ['A', 'B', 'C', 'D', 'E'],\n      datasets: [{\n        data: [1, 5, 10, 50, 100],\n        backgroundColor: [\n          'rgba(255, 99, 132, 0.8)',\n          'rgba(54, 162, 235, 0.8)',\n          'rgba(255, 206, 86, 0.8)',\n          'rgba(75, 192, 192, 0.8)',\n          'rgba(153, 102, 255, 0.8)'\n        ],\n        borderWidth: 1,\n        borderColor: [\n          'rgb(255, 99, 132)',\n          'rgb(54, 162, 235)',\n          'rgb(255, 206, 86)',\n          'rgb(75, 192, 192)',\n          'rgb(153, 102, 255)'\n        ],\n        rotation: -90\n      }, {\n        data: [1, 5, 10, 50, 100],\n        backgroundColor: [\n          'rgba(255, 99, 132, 0.8)',\n          'rgba(54, 162, 235, 0.8)',\n          'rgba(255, 206, 86, 0.8)',\n          'rgba(75, 192, 192, 0.8)',\n          'rgba(153, 102, 255, 0.8)'\n        ],\n        borderWidth: 1,\n        borderColor: [\n          'rgb(255, 99, 132)',\n          'rgb(54, 162, 235)',\n          'rgb(255, 206, 86)',\n          'rgb(75, 192, 192)',\n          'rgb(153, 102, 255)'\n        ],\n        rotation: 0\n      }]\n    },\n    options: {\n      circumference: 180,\n      responsive: false\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.doughnut/doughnut-set-active-elements.js",
    "content": "module.exports = {\n  description: 'https://github.com/chartjs/Chart.js/issues/9248',\n  config: {\n    type: 'doughnut',\n    data: {\n      datasets: [\n        {\n          data: [34, 33, 17, 16],\n          backgroundColor: ['#D92323', '#E45757', '#ED8D8D', '#F5C4C4']\n        }\n      ]\n    },\n    options: {\n      events: [], // for easier saving of the fixture only\n      borderWidth: 0,\n      hoverBorderWidth: 4,\n      hoverBorderColor: 'black',\n      cutout: '80%',\n    }\n  },\n  options: {\n    run(chart) {\n      chart.setActiveElements([{datasetIndex: 0, index: 1}]);\n      chart.update();\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.doughnut/doughnut-spacing-and-offset.js",
    "content": "module.exports = {\n  config: {\n    type: 'doughnut',\n    data: {\n      datasets: [{\n        data: [10, 20, 40, 50, 5],\n        label: 'Dataset 1',\n        backgroundColor: [\n          'red',\n          'orange',\n          'yellow',\n          'green',\n          'blue'\n        ]\n      }],\n      labels: [\n        'Item 1',\n        'Item 2',\n        'Item 3',\n        'Item 4',\n        'Item 5'\n      ],\n    },\n    options: {\n      spacing: 50,\n      offset: [0, 50, 0, 0, 0],\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.doughnut/doughnut-spacing.js",
    "content": "module.exports = {\n  config: {\n    type: 'doughnut',\n    data: {\n      datasets: [{\n        data: [10, 20, 40, 50, 5],\n        label: 'Dataset 1',\n        backgroundColor: [\n          'red',\n          'orange',\n          'yellow',\n          'green',\n          'blue'\n        ]\n      }],\n      labels: [\n        'Item 1',\n        'Item 2',\n        'Item 3',\n        'Item 4',\n        'Item 5'\n      ],\n    },\n    options: {\n      spacing: 50,\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.doughnut/doughnut-weight.json",
    "content": "{\n    \"config\": {\n        \"type\": \"doughnut\",\n        \"data\": {\n            \"datasets\": [{\n                \"data\": [ 1, 1 ],\n                \"backgroundColor\": [\n                    \"rgba(255, 99, 132, 0.8)\",\n                    \"rgba(54, 162, 235, 0.8)\"\n                ],\n                \"borderWidth\": 0\n            },\n            {\n                \"data\": [ 2, 1 ],\n                \"hidden\": true,\n                \"borderWidth\": 0\n            },\n            {\n                \"data\": [ 3, 3 ],\n                \"weight\": 3,\n                \"backgroundColor\": [\n                    \"rgba(255, 206, 86, 0.8)\",\n                    \"rgba(75, 192, 192, 0.8)\"\n                ],\n                \"borderWidth\": 0\n            },\n            {\n                \"data\": [ 4, 0 ],\n                \"weight\": 0,\n                \"borderWidth\": 0\n            },\n            {\n                \"data\": [ 5, 0 ],\n                \"weight\": -2,\n                \"borderWidth\": 0\n            }],\n            \"labels\": [ \"label0\", \"label1\" ]\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 500,\n            \"width\": 500\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/controller.doughnut/event-replay.js",
    "content": "function drawMousePoint(ctx, center) {\n  ctx.beginPath();\n  ctx.arc(center.x, center.y, 8, 0, Math.PI * 2);\n  ctx.fillStyle = 'yellow';\n  ctx.fill();\n}\n\nconst canvas = document.createElement('canvas');\ncanvas.width = 512;\ncanvas.height = 512;\nconst ctx = canvas.getContext('2d');\n\nmodule.exports = {\n  config: {\n    type: 'pie',\n    data: {\n      datasets: [{\n        backgroundColor: ['red', 'green', 'blue'],\n        hoverBackgroundColor: 'black',\n        data: [1, 1, 1]\n      }]\n    }\n  },\n  options: {\n    canvas: {\n      width: 512,\n      height: 512\n    },\n    async run(chart) {\n      ctx.drawImage(chart.canvas, 0, 0, 256, 256);\n\n      const arc = chart.getDatasetMeta(0).data[0];\n      const center = arc.getCenterPoint();\n      await jasmine.triggerMouseEvent(chart, 'mousemove', arc);\n      drawMousePoint(chart.ctx, center);\n      ctx.drawImage(chart.canvas, 256, 0, 256, 256);\n\n      chart.toggleDataVisibility(0);\n      chart.update();\n      drawMousePoint(chart.ctx, center);\n      ctx.drawImage(chart.canvas, 0, 256, 256, 256);\n\n      await jasmine.triggerMouseEvent(chart, 'mouseout', arc);\n      ctx.drawImage(chart.canvas, 256, 256, 256, 256);\n\n      Chart.helpers.clearCanvas(chart.canvas);\n      chart.ctx.drawImage(canvas, 0, 0);\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.doughnut/pie-border-align-center.json",
    "content": "{\n    \"config\": {\n        \"type\": \"pie\",\n        \"data\": {\n            \"labels\": [\"A\", \"B\", \"C\", \"D\", \"E\"],\n            \"datasets\": [{\n                \"data\": [1, 5, 10, 50, 100],\n                \"backgroundColor\": [\n                    \"rgba(255, 99, 132, 0.8)\",\n                    \"rgba(54, 162, 235, 0.8)\",\n                    \"rgba(255, 206, 86, 0.8)\",\n                    \"rgba(75, 192, 192, 0.8)\",\n                    \"rgba(153, 102, 255, 0.8)\"\n                ],\n                \"borderWidth\": 20,\n                \"borderColor\": [\n                    \"rgb(255, 99, 132)\",\n                    \"rgb(54, 162, 235)\",\n                    \"rgb(255, 206, 86)\",\n                    \"rgb(75, 192, 192)\",\n                    \"rgb(153, 102, 255)\"\n                ]\n            }]\n        },\n        \"options\": {\n            \"responsive\": false\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/controller.doughnut/pie-border-align-inner.json",
    "content": "{\n    \"config\": {\n        \"type\": \"pie\",\n        \"data\": {\n            \"labels\": [\"A\", \"B\", \"C\", \"D\", \"E\"],\n            \"datasets\": [{\n                \"data\": [1, 5, 10, 50, 100],\n                \"backgroundColor\": [\n                    \"rgba(255, 99, 132, 0.8)\",\n                    \"rgba(54, 162, 235, 0.8)\",\n                    \"rgba(255, 206, 86, 0.8)\",\n                    \"rgba(75, 192, 192, 0.8)\",\n                    \"rgba(153, 102, 255, 0.8)\"\n                ],\n                \"borderWidth\": 20,\n                \"borderColor\": [\n                    \"rgb(255, 99, 132)\",\n                    \"rgb(54, 162, 235)\",\n                    \"rgb(255, 206, 86)\",\n                    \"rgb(75, 192, 192)\",\n                    \"rgb(153, 102, 255)\"\n                ],\n                \"borderAlign\": \"inner\"\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"plugins\": {\n                \"legend\": false,\n                \"title\": false,\n                \"tooltip\": false\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/controller.doughnut/pie-circumference.json",
    "content": "{\n    \"config\": {\n        \"type\": \"pie\",\n        \"data\": {\n            \"labels\": [\"A\", \"B\", \"C\", \"D\", \"E\"],\n            \"datasets\": [{\n                \"data\": [1, 5, 10, 50, 100],\n                \"backgroundColor\": [\n                    \"rgba(255, 99, 132, 0.8)\",\n                    \"rgba(54, 162, 235, 0.8)\",\n                    \"rgba(255, 206, 86, 0.8)\",\n                    \"rgba(75, 192, 192, 0.8)\",\n                    \"rgba(153, 102, 255, 0.8)\"\n                ],\n                \"borderWidth\": 20,\n                \"borderColor\": [\n                    \"rgb(255, 99, 132)\",\n                    \"rgb(54, 162, 235)\",\n                    \"rgb(255, 206, 86)\",\n                    \"rgb(75, 192, 192)\",\n                    \"rgb(153, 102, 255)\"\n                ]\n            }]\n        },\n        \"options\": {\n            \"circumference\": 57.32,\n            \"responsive\": false\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/controller.doughnut/pie-offset.js",
    "content": "module.exports = {\n  config: {\n    type: 'pie',\n    data: {\n      labels: ['Red', 'Blue', 'Yellow'],\n      datasets: [{\n        data: [12, 4, 6],\n        backgroundColor: ['red', 'blue', 'yellow']\n      }]\n    },\n    options: {\n      offset: 40,\n      layout: {\n        padding: 50\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.doughnut/pie-weight.json",
    "content": "{\n    \"config\": {\n        \"type\": \"pie\",\n        \"data\": {\n            \"datasets\": [\n                {\n                    \"data\": [ 1, 1 ],\n                    \"backgroundColor\": [\n                        \"rgba(255, 99, 132, 0.8)\",\n                        \"rgba(54, 162, 235, 0.8)\"\n                    ],\n                    \"borderWidth\": 0\n                },\n                {\n                    \"data\": [ 2, 1 ],\n                    \"hidden\": true,\n                    \"borderWidth\": 0\n                },\n                {\n                    \"data\": [ 3, 3 ],\n                    \"weight\": 3,\n                    \"backgroundColor\": [\n                        \"rgba(255, 206, 86, 0.8)\",\n                        \"rgba(75, 192, 192, 0.8)\"\n                    ],\n                    \"borderWidth\": 0\n                },\n                {\n                    \"data\": [ 4, 0 ],\n                    \"weight\": 0,\n                    \"borderWidth\": 0\n                },\n                {\n                    \"data\": [ 5, 0 ],\n                    \"weight\": -2,\n                    \"borderWidth\": 0\n                }\n            ],\n            \"labels\": [ \"label0\", \"label1\" ]\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 500,\n            \"width\": 500\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/controller.doughnut/selfJoin/doughnut.js",
    "content": "module.exports = {\n  config: {\n    type: 'doughnut',\n    data: {\n      labels: ['Red'],\n      datasets: [\n        {\n          // option in dataset\n          data: [100],\n          borderWidth: 15,\n          backgroundColor: '#FF0000',\n          borderColor: '#000000',\n          borderAlign: 'center',\n          selfJoin: true\n        }\n      ]\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.doughnut/selfJoin/pie.js",
    "content": "module.exports = {\n  config: {\n    type: 'pie',\n    data: {\n      labels: ['Red'],\n      datasets: [\n        {\n          // option in dataset\n          data: [100],\n          borderWidth: 15,\n          backgroundColor: '#FF0000',\n          borderColor: '#000000',\n          borderAlign: 'center',\n          borderJoinStyle: 'round',\n          selfJoin: true\n        }\n      ]\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.doughnut/single-slice-circumference-405.js",
    "content": "module.exports = {\n  threshold: 0.05,\n  config: {\n    type: 'doughnut',\n    data: {\n      labels: ['A'],\n      datasets: [{\n        data: [1],\n        backgroundColor: 'rgba(0,0,0,0.3)',\n        borderColor: 'rgba(0,0,0,0.5)',\n        circumference: 405\n      }]\n    },\n  },\n  options: {\n    canvas: {\n      width: 256,\n      height: 256\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.doughnut/single-slice-offset.js",
    "content": "module.exports = {\n  config: {\n    type: 'doughnut',\n    data: {\n      labels: ['A'],\n      datasets: [{\n        data: [385],\n        backgroundColor: 'rgba(0,0,0,0.3)',\n        borderColor: 'rgba(0,0,0,0.5)',\n      }]\n    },\n    options: {\n      offset: 20\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.doughnut/single-slice-opacity.js",
    "content": "module.exports = {\n  threshold: 0.05,\n  config: {\n    type: 'doughnut',\n    data: {\n      labels: ['A'],\n      datasets: [{\n        data: [1],\n        backgroundColor: 'rgba(0,0,0,0.3)',\n        borderColor: 'rgba(0,0,0,0.5)'\n      }]\n    },\n  },\n  options: {\n    canvas: {\n      width: 256,\n      height: 256\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.line/backgroundColor/scriptable.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [4, 5, 10, null, -10, -5],\n          backgroundColor: function(ctx) {\n            var index = ctx.index;\n            return index === 0 ? '#ff0000'\n              : index === 1 ? '#00ff00'\n              : '#ff00ff';\n          }\n        },\n        {\n          // option in element (fallback)\n          data: [-4, -5, -10, null, 10, 5],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          fill: true,\n          backgroundColor: function(ctx) {\n            var index = ctx.index;\n            return index === 0 ? '#ff0000'\n              : index === 1 ? '#00ff00'\n              : '#ff00ff';\n          }\n        },\n        point: {\n          backgroundColor: '#0000ff',\n          radius: 10\n        }\n      },\n      layout: {\n        padding: 32\n      },\n      scales: {\n        x: {display: false},\n        y: {\n          display: false,\n          beginAtZero: true\n        }\n      },\n      plugins: {\n        legend: false,\n        title: false,\n        tooltip: false,\n        filler: true\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.line/backgroundColor/value.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          backgroundColor: '#ff0000'\n        },\n        {\n          // option in element (fallback)\n          data: [4, -5, -10, null, 10, 5],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          fill: true,\n          backgroundColor: '#00ff00'\n        },\n        point: {\n          radius: 10\n        }\n      },\n      layout: {\n        padding: 32\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      },\n      plugins: {\n        legend: false,\n        title: false,\n        tooltip: false,\n        filler: true\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.line/borderCapStyle/scriptable.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: [0, 1, 2, 3],\n      datasets: [\n        {\n          // option in dataset\n          data: [null, 3, 3],\n          borderCapStyle: function(ctx) {\n            var index = (ctx.datasetIndex % 2);\n            return index === 0 ? 'round'\n              : index === 1 ? 'square'\n              : 'butt';\n          }\n        },\n        {\n          // option in element (fallback)\n          data: [null, 2, 2],\n        },\n        {\n          // option in element (fallback)\n          data: [null, 1, 1],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          borderCapStyle: function(ctx) {\n            var index = (ctx.datasetIndex % 3);\n            return index === 0 ? 'round'\n              : index === 1 ? 'square'\n              : 'butt';\n          },\n          borderColor: '#ff0000',\n          borderWidth: 32,\n          fill: false\n        },\n        point: {\n          radius: 10,\n        }\n      },\n      layout: {\n        padding: 32\n      },\n      scales: {\n        x: {display: false},\n        y: {\n          display: false,\n          beginAtZero: true\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.line/borderCapStyle/value.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: [0, 1, 2, 3],\n      datasets: [\n        {\n          // option in dataset\n          data: [null, 3, 3],\n          borderCapStyle: 'round',\n        },\n        {\n          // option in dataset\n          data: [null, 2, 2],\n          borderCapStyle: 'square',\n        },\n        {\n          // option in element (fallback)\n          data: [null, 1, 1],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          borderCapStyle: 'butt',\n          borderColor: '#00ff00',\n          borderWidth: 32,\n          fill: false,\n        },\n        point: {\n          radius: 10,\n        }\n      },\n      layout: {\n        padding: 32\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.line/borderColor/scriptable.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [4, 5, 10, null, -10, -5],\n          borderColor: function(ctx) {\n            var index = ctx.index;\n            return index === 0 ? '#ff0000'\n              : index === 1 ? '#00ff00'\n              : '#0000ff';\n          }\n        },\n        {\n          // option in element (fallback)\n          data: [-4, -5, -10, null, 10, 5]\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          borderColor: function(ctx) {\n            var index = ctx.index;\n            return index === 0 ? '#ff0000'\n              : index === 1 ? '#00ff00'\n              : '#0000ff';\n          },\n          borderWidth: 10,\n          fill: false\n        },\n        point: {\n          borderColor: '#ff0000',\n          borderWidth: 10,\n          radius: 16\n        }\n      },\n      layout: {\n        padding: 32\n      },\n      scales: {\n        x: {display: false},\n        y: {\n          display: false,\n          beginAtZero: true\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.line/borderColor/value.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          borderColor: '#ff0000'\n        },\n        {\n          // option in element (fallback)\n          data: [4, -5, -10, null, 10, 5],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          borderColor: '#0000ff',\n          fill: false,\n        },\n        point: {\n          borderColor: '#0000ff',\n          radius: 10,\n        }\n      },\n      layout: {\n        padding: 32\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.line/borderDash/scriptable.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [4, 5, 10, null, -10, -5],\n          borderDash: function(ctx) {\n            return ctx.datasetIndex === 0 ? [5] : [10];\n          }\n        },\n        {\n          // option in element (fallback)\n          data: [-4, -5, -10, null, 10, 5]\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          borderColor: '#00ff00',\n          borderDash: function(ctx) {\n            return ctx.datasetIndex === 0 ? [5] : [10];\n          }\n        },\n        point: {\n          radius: 10,\n        }\n      },\n      layout: {\n        padding: 32\n      },\n      scales: {\n        x: {display: false},\n        y: {\n          display: false,\n          beginAtZero: true\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.line/borderDash/value.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          borderColor: '#ff0000',\n          borderDash: [5]\n        },\n        {\n          // option in element (fallback)\n          data: [4, -5, -10, null, 10, 5],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          borderColor: '#00ff00',\n          borderDash: [10],\n          fill: false,\n        },\n        point: {\n          radius: 10,\n        }\n      },\n      layout: {\n        padding: 32\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.line/borderDashOffset/scriptable.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: [0, 1, 2, 3],\n      datasets: [\n        {\n          // option in dataset\n          data: [1, 1, 1, 1],\n          borderColor: '#ff0000',\n          borderDash: [20],\n          borderDashOffset: function(ctx) {\n            return ctx.datasetIndex === 0 ? 5.0 : 0.0;\n          }\n        },\n        {\n          // option in element (fallback)\n          data: [0, 0, 0, 0]\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          borderColor: '#00ff00',\n          borderDash: [20],\n          borderDashOffset: function(ctx) {\n            return ctx.datasetIndex === 0 ? 5.0 : 0.0;\n          },\n          fill: false,\n        },\n        point: {\n          radius: 10,\n        }\n      },\n      layout: {\n        padding: 32\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.line/borderDashOffset/value.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [1, 1, 1, 1, 1, 1],\n          borderColor: '#ff0000',\n          borderDash: [20],\n          borderDashOffset: 5.0\n        },\n        {\n          // option in element (fallback)\n          data: [0, 0, 0, 0, 0, 0]\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          borderColor: '#00ff00',\n          borderDash: [20],\n          borderDashOffset: 0.0, // default\n          fill: false,\n        },\n        point: {\n          radius: 10,\n        }\n      },\n      layout: {\n        padding: 32\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.line/borderJoinStyle/scriptable.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: [0, 1, 2],\n      datasets: [\n        {\n          // option in dataset\n          data: [6, 18, 6],\n          borderColor: '#ff0000',\n          borderJoinStyle: function(ctx) {\n            var index = ctx.datasetIndex % 3;\n            return index === 0 ? 'round'\n              : index === 1 ? 'miter'\n              : 'bevel';\n          }\n        },\n        {\n          // option in element (fallback)\n          data: [2, 14, 2],\n          borderColor: '#0000ff',\n        },\n        {\n          // option in element (fallback)\n          data: [-2, 10, -2]\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          borderColor: '#00ff00',\n          borderJoinStyle: function(ctx) {\n            var index = (ctx.datasetIndex % 3);\n            return index === 0 ? 'round'\n              : index === 1 ? 'miter'\n              : 'bevel';\n          },\n          borderWidth: 25,\n          fill: false,\n          tension: 0\n        }\n      },\n      layout: {\n        padding: 32\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.line/borderJoinStyle/value.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: [0, 1, 2],\n      datasets: [\n        {\n          // option in dataset\n          data: [6, 18, 6],\n          borderColor: '#ff0000',\n          borderJoinStyle: 'round',\n        },\n        {\n          // option in element (fallback)\n          data: [2, 14, 2],\n          borderColor: '#0000ff',\n          borderJoinStyle: 'bevel',\n        },\n        {\n          // option in element (fallback)\n          data: [-2, 10, -2]\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          borderColor: '#00ff00',\n          borderJoinStyle: 'miter',\n          borderWidth: 25,\n          fill: false,\n          tension: 0\n        }\n      },\n      layout: {\n        padding: 32\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.line/borderWidth/scriptable.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [4, 5, 10, null, -10, -5],\n          borderColor: '#0000ff',\n          borderWidth: function(ctx) {\n            var index = ctx.index;\n            return index % 2 ? 10 : 20;\n          },\n          pointBorderColor: '#00ff00'\n        },\n        {\n          // option in element (fallback)\n          data: [-4, -5, -10, null, 10, 5],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          borderColor: '#ff0000',\n          borderWidth: function(ctx) {\n            var index = ctx.index;\n            return index % 2 ? 10 : 20;\n          },\n          fill: false,\n        },\n        point: {\n          borderColor: '#00ff00',\n          borderWidth: 5,\n          radius: 10\n        }\n      },\n      layout: {\n        padding: 32\n      },\n      scales: {\n        x: {display: false},\n        y: {\n          display: false,\n          beginAtZero: true\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.line/borderWidth/value.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          borderColor: '#0000ff',\n          borderWidth: 6\n        },\n        {\n          // option in element (fallback)\n          data: [4, -5, -10, null, 10, 5],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          borderColor: '#00ff00',\n          borderWidth: 3,\n          fill: false,\n        },\n        point: {\n          radius: 10,\n        }\n      },\n      layout: {\n        padding: 32\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.line/borderWidth/zero.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          backgroundColor: '#0000ff',\n          borderColor: '#0000ff',\n          borderWidth: 0\n        },\n        {\n          // option in element (fallback)\n          data: [4, -5, -10, null, 10, 5],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          borderColor: '#00ff00',\n          borderWidth: 3,\n          fill: false,\n        },\n        point: {\n          backgroundColor: '#00ff00',\n          radius: 10,\n        }\n      },\n      layout: {\n        padding: 32\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.line/clip/default-x-max.json",
    "content": "{\n    \"config\": {\n        \"type\": \"scatter\",\n        \"data\": {\n            \"datasets\": [{\n                \"borderColor\": \"red\",\n                \"data\": [{\"x\":-5,\"y\":5},{\"x\":-4,\"y\":6},{\"x\":-3,\"y\":7},{\"x\":-2,\"y\":6},{\"x\":-1,\"y\":5},{\"x\":0,\"y\":4},{\"x\":1,\"y\":3},{\"x\":2,\"y\":2},{\"x\":3,\"y\":5},{\"x\":4,\"y\":7},{\"x\":5,\"y\":9}],\n                \"fill\": false,\n                \"showLine\": true,\n                \"borderWidth\": 20,\n                \"pointRadius\": 0\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"scales\": {\n                \"x\": {\n                    \"max\": 3,\n                    \"ticks\": {\n                        \"display\": false\n                    }\n                },\n                \"y\": {\"ticks\": {\"display\": false}}\n            },\n            \"layout\": {\n                \"padding\": 24\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/controller.line/clip/default-x-min.json",
    "content": "{\n    \"config\": {\n        \"type\": \"scatter\",\n        \"data\": {\n            \"datasets\": [{\n                \"borderColor\": \"red\",\n                \"data\": [{\"x\":-5,\"y\":5},{\"x\":-4,\"y\":6},{\"x\":-3,\"y\":7},{\"x\":-2,\"y\":6},{\"x\":-1,\"y\":5},{\"x\":0,\"y\":4},{\"x\":1,\"y\":3},{\"x\":2,\"y\":2},{\"x\":3,\"y\":5},{\"x\":4,\"y\":7},{\"x\":5,\"y\":9}],\n                \"fill\": false,\n                \"showLine\": true,\n                \"borderWidth\": 20,\n                \"pointRadius\": 0\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"scales\": {\n                \"x\": {\n                    \"min\": -2,\n                    \"ticks\": {\n                        \"display\": false\n                    }\n                },\n                \"y\": {\"ticks\": {\"display\": false}}\n            },\n            \"layout\": {\n                \"padding\": 24\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/controller.line/clip/default-x.json",
    "content": "{\n    \"config\": {\n        \"type\": \"scatter\",\n        \"data\": {\n            \"datasets\": [{\n                \"borderColor\": \"red\",\n                \"data\": [{\"x\":-5,\"y\":5},{\"x\":-4,\"y\":6},{\"x\":-3,\"y\":7},{\"x\":-2,\"y\":6},{\"x\":-1,\"y\":5},{\"x\":0,\"y\":4},{\"x\":1,\"y\":3},{\"x\":2,\"y\":2},{\"x\":3,\"y\":5},{\"x\":4,\"y\":7},{\"x\":5,\"y\":9}],\n                \"fill\": false,\n                \"showLine\": true,\n                \"borderWidth\": 20,\n                \"pointRadius\": 0\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"scales\": {\n                \"x\": {\n                    \"min\": -2,\n                    \"max\": 3,\n                    \"ticks\": {\n                        \"display\": false\n                    }\n                },\n                \"y\": {\"ticks\": {\"display\": false}}\n            },\n            \"layout\": {\n                \"padding\": 24\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/controller.line/clip/default-y-max.json",
    "content": "{\n    \"config\": {\n        \"type\": \"scatter\",\n        \"data\": {\n            \"datasets\": [{\n                \"borderColor\": \"red\",\n                \"data\": [{\"x\":-5,\"y\":5},{\"x\":-4,\"y\":6},{\"x\":-3,\"y\":7},{\"x\":-2,\"y\":6},{\"x\":-1,\"y\":5},{\"x\":0,\"y\":4},{\"x\":1,\"y\":3},{\"x\":2,\"y\":2},{\"x\":3,\"y\":5},{\"x\":4,\"y\":7},{\"x\":5,\"y\":9}],\n                \"fill\": false,\n                \"showLine\": true,\n                \"borderWidth\": 20,\n                \"pointRadius\": 0\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"scales\": {\n                \"x\": {\"ticks\": {\"display\": false}},\n                \"y\": {\n                    \"max\": 6,\n                    \"ticks\": {\n                        \"display\": false\n                    }\n                }\n            },\n            \"layout\": {\n                \"padding\": 24\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/controller.line/clip/default-y-min.json",
    "content": "{\n    \"config\": {\n        \"type\": \"scatter\",\n        \"data\": {\n            \"datasets\": [{\n                \"borderColor\": \"red\",\n                \"data\": [{\"x\":-5,\"y\":5},{\"x\":-4,\"y\":6},{\"x\":-3,\"y\":7},{\"x\":-2,\"y\":6},{\"x\":-1,\"y\":5},{\"x\":0,\"y\":4},{\"x\":1,\"y\":3},{\"x\":2,\"y\":2},{\"x\":3,\"y\":5},{\"x\":4,\"y\":7},{\"x\":5,\"y\":9}],\n                \"fill\": false,\n                \"showLine\": true,\n                \"borderWidth\": 20,\n                \"pointRadius\": 0\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"scales\": {\n                \"x\": {\"ticks\": {\"display\": false}},\n                \"y\": {\n                    \"min\": 2,\n                    \"ticks\": {\n                        \"display\": false\n                    }\n                }\n            },\n            \"layout\": {\n                \"padding\": 24\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/controller.line/clip/default-y.json",
    "content": "{\n    \"config\": {\n        \"type\": \"scatter\",\n        \"data\": {\n            \"datasets\": [{\n                \"borderColor\": \"red\",\n                \"data\": [{\"x\":-5,\"y\":5},{\"x\":-4,\"y\":6},{\"x\":-3,\"y\":7},{\"x\":-2,\"y\":6},{\"x\":-1,\"y\":5},{\"x\":0,\"y\":4},{\"x\":1,\"y\":3},{\"x\":2,\"y\":2},{\"x\":3,\"y\":5},{\"x\":4,\"y\":7},{\"x\":5,\"y\":9}],\n                \"fill\": false,\n                \"showLine\": true,\n                \"borderWidth\": 20,\n                \"pointRadius\": 0\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"scales\": {\n                \"x\": {\"ticks\": {\"display\": false}},\n                \"y\": {\n                    \"min\": 2,\n                    \"max\": 6,\n                    \"ticks\": {\n                        \"display\": false\n                    }\n                }\n            },\n            \"layout\": {\n                \"padding\": 24\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/controller.line/clip/false.js",
    "content": "const data = [];\nfor (let x = 0.95; x < 1.15; x += 0.002) {\n  data.push({x, y: x});\n}\n\nfor (let x = 0.95; x < 1.15; x += 0.001) {\n  data.push({x, y: 2.1 - x});\n}\n\nmodule.exports = {\n  config: {\n    type: 'scatter',\n    data: {\n      datasets: [{\n        clip: false,\n        radius: 8,\n        borderWidth: 0,\n        backgroundColor: (ctx) => ctx.type !== 'data' || ctx.raw.x < 1 || ctx.raw.x > 1.1 ? 'rgba(255,0,0,0.7)' : 'rgba(0,0,255,0.05)',\n        data\n      }]\n    },\n    options: {\n      plugins: false,\n      scales: {\n        x: {\n          min: 1,\n          max: 1.1\n        },\n        y: {\n          min: 1,\n          max: 1.1\n        },\n      },\n      layout: {\n        padding: 32\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 240,\n      width: 320\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.line/clip/specified.json",
    "content": "{\n    \"config\": {\n        \"type\": \"scatter\",\n        \"data\": {\n            \"datasets\": [\n                {\n                    \"showLine\": true,\n                    \"borderColor\": \"red\",\n                    \"data\": [{\"x\":-4,\"y\":-4},{\"x\":4,\"y\":4}],\n                    \"clip\": false\n                },\n                {\n                    \"showLine\": true,\n                    \"borderColor\": \"green\",\n                    \"data\": [{\"x\":-4,\"y\":-5},{\"x\":4,\"y\":3}],\n                    \"clip\": 5\n                },\n                {\n                    \"showLine\": true,\n                    \"borderColor\": \"blue\",\n                    \"data\": [{\"x\":-4,\"y\":-3},{\"x\":4,\"y\":5}],\n                    \"clip\": -5\n                },\n                {\n                    \"showLine\": true,\n                    \"borderColor\": \"brown\",\n                    \"data\": [{\"x\":-3,\"y\":-3},{\"x\":-1,\"y\":3},{\"x\":1,\"y\":-2},{\"x\":2,\"y\":3}],\n                    \"clip\": {\n                        \"top\": 8,\n                        \"left\": false,\n                        \"right\": -20,\n                        \"bottom\": -20\n                    }\n                }\n            ]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"scales\": {\n                \"x\": {\n                    \"min\": -2,\n                    \"max\": 2,\n                    \"ticks\": {\n                        \"display\": false\n                    }\n                },\n                \"y\": {\n                    \"min\": -2,\n                    \"max\": 2,\n                    \"ticks\": {\n                        \"display\": false\n                    }\n                }\n            },\n            \"layout\": {\n                \"padding\": 24\n            },\n            \"elements\": {\n                \"line\": {\n                    \"fill\": false,\n                    \"borderWidth\": 20\n                },\n                \"point\": {\n                    \"radius\": 0\n                }\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/controller.line/cubicInterpolationMode/scriptable.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 4, 2, 6, 4, 8],\n          borderColor: '#ff0000',\n          cubicInterpolationMode: function(ctx) {\n            return ctx.datasetIndex === 0 ? 'monotone' : 'default';\n          }\n        },\n        {\n          // option in element (fallback)\n          data: [2, 6, 4, 8, 6, 10],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          borderColor: '#00ff00',\n          borderWidth: 20,\n          cubicInterpolationMode: function(ctx) {\n            return ctx.datasetIndex === 0 ? 'monotone' : 'default';\n          },\n          fill: false,\n          tension: 0.4\n        }\n      },\n      layout: {\n        padding: 32\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.line/cubicInterpolationMode/value.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 4, 2, 6, 4, 8],\n          borderColor: '#ff0000',\n          cubicInterpolationMode: 'monotone'\n        },\n        {\n          // option in element (fallback)\n          data: [2, 6, 4, 8, 6, 10]\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          borderColor: '#00ff00',\n          borderWidth: 20,\n          cubicInterpolationMode: 'default',\n          fill: false,\n          tension: 0.4\n        }\n      },\n      layout: {\n        padding: 32\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.line/fill/no-border.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          data: [12, 19, 3, 5, 2, 3],\n          backgroundColor: '#ff0000',\n          borderWidth: 0,\n          tension: 0.4,\n          fill: true\n        },\n      ]\n    },\n    options: {\n      animation: {\n        duration: 1\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      },\n      plugins: {\n        legend: false,\n        title: false,\n        tooltip: false,\n        filler: true\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    },\n    run() {\n      return new Promise(resolve => setTimeout(resolve, 50));\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.line/fill/order-default.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [3, 1, 2, 0, 8, 1],\n          backgroundColor: '#ff0000'\n        },\n        {\n          // option in element (fallback)\n          data: [0, 4, 2, 6, 4, 8],\n          backgroundColor: '#00ff00'\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          fill: true\n        },\n        point: {\n          radius: 0\n        }\n      },\n      layout: {\n        padding: 32\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      },\n      plugins: {\n        legend: false,\n        title: false,\n        tooltip: false,\n        filler: true\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.line/fill/order.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          data: [3, 1, 2, 0, 8, 1],\n          backgroundColor: '#ff0000',\n          order: 2\n        },\n        {\n          data: [0, 4, 2, 6, 4, 8],\n          backgroundColor: '#00ff00',\n          order: 1\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          fill: true\n        },\n        point: {\n          radius: 0\n        }\n      },\n      layout: {\n        padding: 32\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      },\n      plugins: {\n        legend: false,\n        title: false,\n        tooltip: false,\n        filler: true\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.line/fill/scriptable.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [-2, -6, -4, -8, -6, -10],\n          backgroundColor: '#ff0000',\n          fill: function(ctx) {\n            return ctx.datasetIndex === 0 ? true : false;\n          }\n        },\n        {\n          // option in element (fallback)\n          data: [0, 4, 2, 6, 4, 8],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          backgroundColor: '#00ff00',\n          fill: function(ctx) {\n            return ctx.datasetIndex === 0 ? true : false;\n          }\n        }\n      },\n      layout: {\n        padding: 32\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      },\n      plugins: {\n        legend: false,\n        title: false,\n        tooltip: false,\n        filler: true\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.line/fill/value.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [-2, -6, -4, -8, -6, -10],\n          backgroundColor: '#ff0000',\n          fill: false\n        },\n        {\n          // option in element (fallback)\n          data: [0, 4, 2, 6, 4, 8],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          backgroundColor: '#00ff00',\n          fill: true,\n        }\n      },\n      layout: {\n        padding: 32\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      },\n      plugins: {\n        legend: false,\n        title: false,\n        tooltip: false,\n        filler: true\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.line/issue-8902.js",
    "content": "module.exports = {\n  description: 'https://github.com/chartjs/Chart.js/issues/8902',\n  config: {\n    type: 'line',\n    data: {\n      labels: [1, 2, 3, 4, 5, 6, 7, 8],\n      datasets: [{\n        data: [65, 59, NaN, 48, 56, 57, 40],\n        borderColor: 'rgb(75, 192, 192)',\n      }]\n    },\n    options: {\n      plugins: false,\n      scales: {\n        x: {\n          type: 'linear',\n          min: 1,\n          max: 3\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.line/non-numeric-y.json",
    "content": "{\n    \"config\": {\n        \"type\": \"line\",\n        \"data\": {\n            \"xLabels\": [\"January\", \"February\", \"March\", \"April\", \"May\", \"June\", \"July\"],\n            \"yLabels\": [\"\", \"Request Added\", \"Request Viewed\", \"Request Accepted\", \"Request Solved\", \"Solving Confirmed\"],\n            \"datasets\": [{\n                \"label\": \"My First dataset\",\n                \"data\": [\"\", \"Request Added\", \"Request Added\", \"Request Added\", \"Request Viewed\", \"Request Viewed\", \"Request Viewed\"],\n                \"fill\": false,\n                \"borderColor\": \"red\",\n                \"backgroundColor\": \"red\"\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"scales\": {\n                \"x\": {\"display\": false},\n                \"y\": {\n                    \"type\": \"category\",\n                    \"display\": false\n                }\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/controller.line/point-style-offscreen-canvas.json",
    "content": "{\n\t\"config\": {\n\t\t\"type\": \"line\",\n\t\t\"data\": {\n\t\t\t\"labels\": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],\n\t\t\t\"datasets\": [{\n\t\t\t\t\"borderColor\": \"transparent\",\n\t\t\t\t\"data\": [3, 3, 3, 3, 3, 3, 3, 3, 3, 3],\n\t\t\t\t\"pointBackgroundColor\": \"#00ff00\",\n\t\t\t\t\"pointBorderColor\": \"transparent\",\n\t\t\t\t\"pointBorderWidth\": 0,\n\t\t\t\t\"pointStyle\": [\n\t\t\t\t\t\"circle\",\n\t\t\t\t\t\"cross\",\n\t\t\t\t\t\"crossRot\",\n\t\t\t\t\t\"dash\",\n\t\t\t\t\t\"line\",\n\t\t\t\t\t\"rect\",\n\t\t\t\t\t\"rectRounded\",\n\t\t\t\t\t\"rectRot\",\n\t\t\t\t\t\"star\",\n\t\t\t\t\t\"triangle\"\n\t\t\t\t]\n\t\t\t}, {\n\t\t\t\t\"borderColor\": \"transparent\",\n\t\t\t\t\"data\": [2, 2, 2, 2, 2, 2, 2, 2, 2, 2],\n\t\t\t\t\"pointBackgroundColor\": \"transparent\",\n\t\t\t\t\"pointBorderColor\": \"#0000ff\",\n\t\t\t\t\"pointBorderWidth\": 1,\n\t\t\t\t\"pointStyle\": [\n\t\t\t\t\t\"circle\",\n\t\t\t\t\t\"cross\",\n\t\t\t\t\t\"crossRot\",\n\t\t\t\t\t\"dash\",\n\t\t\t\t\t\"line\",\n\t\t\t\t\t\"rect\",\n\t\t\t\t\t\"rectRounded\",\n\t\t\t\t\t\"rectRot\",\n\t\t\t\t\t\"star\",\n\t\t\t\t\t\"triangle\"\n\t\t\t\t]\n\t\t\t}, {\n\t\t\t\t\"borderColor\": \"transparent\",\n\t\t\t\t\"data\": [1, 1, 1, 1, 1, 1, 1, 1, 1, 1],\n\t\t\t\t\"pointBackgroundColor\": \"#00ff00\",\n\t\t\t\t\"pointBorderColor\": \"#0000ff\",\n\t\t\t\t\"pointBorderWidth\": 1,\n\t\t\t\t\"pointStyle\": [\n\t\t\t\t\t\"circle\",\n\t\t\t\t\t\"cross\",\n\t\t\t\t\t\"crossRot\",\n\t\t\t\t\t\"dash\",\n\t\t\t\t\t\"line\",\n\t\t\t\t\t\"rect\",\n\t\t\t\t\t\"rectRounded\",\n\t\t\t\t\t\"rectRot\",\n\t\t\t\t\t\"star\",\n\t\t\t\t\t\"triangle\"\n\t\t\t\t]\n\t\t\t}]\n\t\t},\n\t\t\"options\": {\n\t\t\t\"responsive\": false,\n\t\t\t\"scales\": {\n\t\t\t\t\"x\": {\"display\": false},\n\t\t\t\t\"y\": {\n\t\t\t\t\t\"display\": false,\n\t\t\t\t\t\"min\": 0,\n\t\t\t\t\t\"max\": 4\n\t\t\t\t}\n\t\t\t},\n\t\t\t\"elements\": {\n\t\t\t\t\"line\": {\n\t\t\t\t\t\"fill\": false\n\t\t\t\t},\n\t\t\t\t\"point\": {\n\t\t\t\t\t\"radius\": 16\n\t\t\t\t}\n\t\t\t},\n\t\t\t\"layout\": {\n\t\t\t\t\"padding\": {\n\t\t\t\t\t\"left\": 24,\n\t\t\t\t\t\"right\": 24\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\t\"options\": {\n\t\t\"canvas\": {\n\t\t\t\"height\": 256,\n\t\t\t\"width\": 512\n\t\t},\n\t\t\"useOffscreenCanvas\": true\n\t}\n}\n"
  },
  {
    "path": "test/fixtures/controller.line/point-style.json",
    "content": "{\n    \"config\": {\n        \"type\": \"line\",\n        \"data\": {\n            \"labels\": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],\n            \"datasets\": [{\n                \"borderColor\": \"transparent\",\n                \"data\": [3, 3, 3, 3, 3, 3, 3, 3, 3, 3],\n                \"pointBackgroundColor\": \"#00ff00\",\n                \"pointBorderColor\": \"transparent\",\n                \"pointBorderWidth\": 0,\n                \"pointStyle\": [\n                    \"circle\",\n                    \"cross\",\n                    \"crossRot\",\n                    \"dash\",\n                    \"line\",\n                    \"rect\",\n                    \"rectRounded\",\n                    \"rectRot\",\n                    \"star\",\n                    \"triangle\"\n                ]\n            }, {\n                \"borderColor\": \"transparent\",\n                \"data\": [2, 2, 2, 2, 2, 2, 2, 2, 2, 2],\n                \"pointBackgroundColor\": \"transparent\",\n                \"pointBorderColor\": \"#0000ff\",\n                \"pointBorderWidth\": 1,\n                \"pointStyle\": [\n                    \"circle\",\n                    \"cross\",\n                    \"crossRot\",\n                    \"dash\",\n                    \"line\",\n                    \"rect\",\n                    \"rectRounded\",\n                    \"rectRot\",\n                    \"star\",\n                    \"triangle\"\n                ]\n            }, {\n                \"borderColor\": \"transparent\",\n                \"data\": [1, 1, 1, 1, 1, 1, 1, 1, 1, 1],\n                \"pointBackgroundColor\": \"#00ff00\",\n                \"pointBorderColor\": \"#0000ff\",\n                \"pointBorderWidth\": 1,\n                \"pointStyle\": [\n                    \"circle\",\n                    \"cross\",\n                    \"crossRot\",\n                    \"dash\",\n                    \"line\",\n                    \"rect\",\n                    \"rectRounded\",\n                    \"rectRot\",\n                    \"star\",\n                    \"triangle\"\n                ]\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"scales\": {\n                \"x\": {\"display\": false},\n                \"y\": {\n                    \"display\": false,\n                    \"min\": 0,\n                    \"max\": 4\n                }\n            },\n            \"elements\": {\n                \"line\": {\n                    \"fill\": false\n                },\n                \"point\": {\n                    \"radius\": 16\n                }\n            },\n            \"layout\": {\n                \"padding\": {\n                    \"left\": 24,\n                    \"right\": 24\n                }\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/controller.line/pointBackgroundColor/indexable.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          pointBackgroundColor: [\n            '#ff0000',\n            '#00ff00',\n            '#0000ff',\n            '#ffff00',\n            '#ff00ff',\n            '#000000'\n          ]\n        },\n        {\n          // option in element (fallback)\n          data: [4, -5, -10, null, 10, 5],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          fill: false,\n        },\n        point: {\n          backgroundColor: [\n            '#ff88ff',\n            '#888888',\n            '#ff8800',\n            '#00ff88',\n            '#8800ff',\n            '#ffff88'\n          ],\n          radius: 10\n        }\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.line/pointBackgroundColor/scriptable.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          pointBackgroundColor: function(ctx) {\n            var value = ctx.dataset.data[ctx.dataIndex] || 0;\n            return value > 8 ? '#ff0000'\n              : value > 0 ? '#00ff00'\n              : value > -8 ? '#0000ff'\n              : '#ff00ff';\n          }\n        },\n        {\n          // option in element (fallback)\n          data: [4, -5, -10, null, 10, 5],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          fill: false,\n        },\n        point: {\n          backgroundColor: function(ctx) {\n            var value = ctx.dataset.data[ctx.dataIndex] || 0;\n            return value > 8 ? '#ff00ff'\n              : value > 0 ? '#0000ff'\n              : value > -8 ? '#ff0000'\n              : '#00ff00';\n          },\n          radius: 10,\n        }\n      },\n      scales: {\n        x: {display: false},\n        y: {\n          display: false,\n          beginAtZero: true\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.line/pointBackgroundColor/value.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          pointBackgroundColor: '#ff0000'\n        },\n        {\n          // option in element (fallback)\n          data: [4, -5, -10, null, 10, 5],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          fill: false,\n        },\n        point: {\n          backgroundColor: '#00ff00',\n          radius: 10,\n        }\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.line/pointBorderColor/indexable.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          pointBorderColor: [\n            '#ff0000',\n            '#00ff00',\n            '#0000ff',\n            '#ffff00',\n            '#ff00ff',\n            '#000000'\n          ]\n        },\n        {\n          // option in element (fallback)\n          data: [4, -5, -10, null, 10, 5],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          fill: false,\n        },\n        point: {\n          borderColor: [\n            '#ff88ff',\n            '#888888',\n            '#ff8800',\n            '#00ff88',\n            '#8800ff',\n            '#ffff88'\n          ],\n          radius: 10\n        }\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.line/pointBorderColor/scriptable.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          pointBorderColor: function(ctx) {\n            var value = ctx.dataset.data[ctx.dataIndex] || 0;\n            return value > 8 ? '#ff0000'\n              : value > 0 ? '#00ff00'\n              : value > -8 ? '#0000ff'\n              : '#ff00ff';\n          }\n        },\n        {\n          // option in element (fallback)\n          data: [4, -5, -10, null, 10, 5],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          fill: false,\n        },\n        point: {\n          borderColor: function(ctx) {\n            var value = ctx.dataset.data[ctx.dataIndex] || 0;\n            return value > 8 ? '#ff00ff'\n              : value > 0 ? '#0000ff'\n              : value > -8 ? '#ff0000'\n              : '#00ff00';\n          },\n          radius: 10,\n        }\n      },\n      scales: {\n        x: {display: false},\n        y: {\n          display: false,\n          beginAtZero: true\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.line/pointBorderColor/value.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          pointBorderColor: '#ff0000'\n        },\n        {\n          // option in element (fallback)\n          data: [4, -5, -10, null, 10, 5],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          fill: false,\n        },\n        point: {\n          borderColor: '#00ff00',\n          radius: 10,\n        }\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.line/pointBorderWidth/indexable.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          pointBorderColor: '#00ff00',\n          pointBorderWidth: [\n            1, 2, 3, 4, 5, 6\n          ]\n        },\n        {\n          // option in element (fallback)\n          data: [4, -5, -10, null, 10, 5],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          fill: false,\n        },\n        point: {\n          borderColor: '#ff0000',\n          borderWidth: [\n            6, 5, 4, 3, 2, 1\n          ],\n          radius: 10\n        }\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.line/pointBorderWidth/scriptable.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          pointBorderColor: '#0000ff',\n          pointBorderWidth: function(ctx) {\n            var value = ctx.dataset.data[ctx.dataIndex] || 0;\n            return value > 4 ? 10\n              : value > -4 ? 5\n              : 2;\n          }\n        },\n        {\n          // option in element (fallback)\n          data: [4, -5, -10, null, 10, 5],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          fill: false,\n        },\n        point: {\n          borderColor: '#ff0000',\n          borderWidth: function(ctx) {\n            var value = ctx.dataset.data[ctx.dataIndex] || 0;\n            return value > 4 ? 2\n              : value > -4 ? 5\n              : 10;\n          },\n          radius: 10,\n        }\n      },\n      scales: {\n        x: {display: false},\n        y: {\n          display: false,\n          beginAtZero: true\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.line/pointBorderWidth/value.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          pointBorderColor: '#0000ff',\n          pointBorderWidth: 6\n        },\n        {\n          // option in element (fallback)\n          data: [4, -5, -10, null, 10, 5],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          fill: false,\n        },\n        point: {\n          borderColor: '#00ff00',\n          borderWidth: 3,\n          radius: 10,\n        }\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.line/pointStyle/indexable.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5, 6],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5, 0],\n          pointBackgroundColor: '#ff0000',\n          pointBorderColor: '#ff0000',\n          pointStyle: [\n            'circle',\n            'cross',\n            'crossRot',\n            'dash',\n            'line',\n            'rect',\n            false\n          ]\n        },\n        {\n          // option in element (fallback)\n          data: [4, -5, -10, null, 10, 5, -4],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          fill: false,\n        },\n        point: {\n          backgroundColor: '#00ff00',\n          borderColor: '#00ff00',\n          pointStyle: [\n            'line',\n            'rect',\n            'rectRounded',\n            'rectRot',\n            'star',\n            'triangle'\n          ],\n          radius: 10\n        }\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.line/pointStyle/scriptable.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          pointBackgroundColor: '#ff0000',\n          pointBorderColor: '#ff0000',\n          pointStyle: function(ctx) {\n            var value = ctx.dataset.data[ctx.dataIndex] || 0;\n            return value > 8 ? 'rect'\n              : value > 0 ? 'star'\n              : value > -8 ? 'cross'\n              : 'triangle';\n          }\n        },\n        {\n          // option in element (fallback)\n          data: [4, -5, -10, null, 10, 5],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          fill: false,\n        },\n        point: {\n          backgroundColor: '#0000ff',\n          borderColor: '#0000ff',\n          pointStyle: function(ctx) {\n            var value = ctx.dataset.data[ctx.dataIndex] || 0;\n            return value > 8 ? 'triangle'\n              : value > 0 ? 'cross'\n              : value > -8 ? 'star'\n              : 'rect';\n          },\n          radius: 10,\n        }\n      },\n      scales: {\n        x: {display: false},\n        y: {\n          display: false,\n          beginAtZero: true\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.line/pointStyle/value.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          pointBorderColor: '#ff0000',\n          pointStyle: 'star',\n        },\n        {\n          // option in element (fallback)\n          data: [4, -5, -10, null, 10, 5],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          fill: false,\n        },\n        point: {\n          backgroundColor: '#00ff00',\n          pointStyle: 'rect',\n          radius: 10,\n        }\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.line/radius/indexable.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          pointBackgroundColor: '#00ff00',\n          pointRadius: [\n            1, 2, 3, 4, 5, 6\n          ]\n        },\n        {\n          // option in element (fallback)\n          data: [4, -5, -10, null, 10, 5],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          fill: false,\n        },\n        point: {\n          backgroundColor: '#ff0000',\n          radius: [\n            6, 5, 4, 3, 2, 1\n          ],\n        }\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.line/radius/scriptable-to-value.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: ['A', 'B', 'C'],\n      datasets: [{\n        data: [12, 19, 3]\n      }]\n    },\n    options: {\n      animation: {\n        duration: 0\n      },\n      backgroundColor: 'red',\n      radius: () => 20,\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    },\n    run: (chart) => {\n      chart.options.radius = 5;\n      chart.update();\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.line/radius/scriptable.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          pointBackgroundColor: '#0000ff',\n          pointRadius: function(ctx) {\n            var value = ctx.dataset.data[ctx.dataIndex] || 0;\n            return value > 4 ? 10\n              : value > -4 ? 5\n              : 2;\n          }\n        },\n        {\n          // option in element (fallback)\n          data: [4, -5, -10, null, 10, 5],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          fill: false,\n        },\n        point: {\n          backgroundColor: '#ff0000',\n          radius: function(ctx) {\n            var value = ctx.dataset.data[ctx.dataIndex] || 0;\n            return value > 4 ? 2\n              : value > -4 ? 5\n              : 10;\n          },\n        }\n      },\n      scales: {\n        x: {display: false},\n        y: {\n          display: false,\n          beginAtZero: true\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.line/radius/value.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          pointBackgroundColor: '#0000ff',\n          pointRadius: 6\n        },\n        {\n          // option in element (fallback)\n          data: [4, -5, -10, null, 10, 5],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          fill: false,\n        },\n        point: {\n          backgroundColor: '#00ff00',\n          radius: 3,\n        }\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.line/rotation/indexable.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          pointBorderColor: '#00ff00',\n          pointRotation: [\n            0, 30, 60, 90, 120, 150\n          ]\n        },\n        {\n          // option in element (fallback)\n          data: [4, -5, -10, null, 10, 5],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          fill: false,\n        },\n        point: {\n          borderColor: '#ff0000',\n          borderWidth: 10,\n          pointStyle: 'line',\n          rotation: [\n            150, 120, 90, 60, 30, 0\n          ],\n        }\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.line/rotation/scriptable.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          pointBorderColor: '#0000ff',\n          pointRotation: function(ctx) {\n            var value = ctx.dataset.data[ctx.dataIndex] || 0;\n            return value > 4 ? 120\n              : value > -4 ? 60\n              : 0;\n          }\n        },\n        {\n          // option in element (fallback)\n          data: [4, -5, -10, null, 10, 5],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          fill: false,\n        },\n        point: {\n          borderColor: '#ff0000',\n          rotation: function(ctx) {\n            var value = ctx.dataset.data[ctx.dataIndex] || 0;\n            return value > 4 ? 0\n              : value > -4 ? 60\n              : 120;\n          },\n          pointStyle: 'line',\n          radius: 10,\n        }\n      },\n      scales: {\n        x: {display: false},\n        y: {\n          display: false,\n          beginAtZero: true\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.line/rotation/value.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          pointBorderColor: '#0000ff',\n          pointRotation: 90\n        },\n        {\n          // option in element (fallback)\n          data: [4, -5, -10, null, 10, 5],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          fill: false,\n        },\n        point: {\n          borderColor: '#00ff00',\n          pointStyle: 'line',\n          radius: 10,\n          rotation: 0,\n        }\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.line/segments/gap.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: ['a', 'b', 'c', 'd', 'e', 'f'],\n      datasets: [{\n        data: [1, 3, NaN, NaN, 2, 1],\n        borderColor: 'black',\n        segment: {\n          borderColor: ctx => ctx.p0.skip || ctx.p1.skip ? 'red' : undefined,\n          borderDash: ctx => ctx.p0.skip || ctx.p1.skip ? [5, 5] : undefined\n        },\n        spanGaps: true\n      }]\n    },\n    options: {\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.line/segments/gradient.js",
    "content": "const getGradient = (context) => {\n  const {chart, p0, p1} = context;\n  const ctx = chart.ctx;\n  const {x: x0} = p0.getProps(['x'], true);\n  const {x: x1} = p1.getProps(['x'], true);\n  const gradient = ctx.createLinearGradient(x0, 0, x1, 0);\n  gradient.addColorStop(0, p0.options.backgroundColor);\n  gradient.addColorStop(1, p1.options.backgroundColor);\n  return gradient;\n};\n\nmodule.exports = {\n  config: {\n    type: 'line',\n    data: {\n      datasets: [{\n        data: [{x: 0, y: 0}, {x: 1, y: 1}, {x: 2, y: 2}, {x: 3, y: 3}, {x: 4, y: 4}, {x: 5, y: 5}, {x: 6, y: 6}],\n        pointBackgroundColor: ['red', 'yellow', 'green', 'green', 'blue', 'pink', 'blue'],\n        pointBorderWidth: 0,\n        pointRadius: 10,\n        borderWidth: 5,\n        segment: {\n          borderColor: getGradient,\n        }\n      }]\n    },\n    options: {\n      scales: {\n        x: {type: 'linear', display: false},\n        y: {display: false}\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.line/segments/range.js",
    "content": "function x(ctx, {min = -Infinity, max = Infinity}) {\n  return (ctx.p0.parsed.x >= min || ctx.p1.parsed.x >= min) && (ctx.p0.parsed.x < max && ctx.p1.parsed.x < max);\n}\n\nfunction y(ctx, {min = -Infinity, max = Infinity}) {\n  return (ctx.p0.parsed.y >= min || ctx.p1.parsed.y >= min) && (ctx.p0.parsed.y < max || ctx.p1.parsed.y < max);\n}\n\nfunction xy(ctx, xr, yr) {\n  return x(ctx, xr) && y(ctx, yr);\n}\n\nmodule.exports = {\n  config: {\n    type: 'line',\n    data: {\n      datasets: [{\n        data: [{x: 0, y: 0}, {x: 1, y: 1}, {x: 2, y: 2}, {x: 3, y: 3}, {x: 4, y: 4}, {x: 5, y: 5}, {x: 6, y: 7}, {x: 7, y: 8}],\n        borderColor: 'black',\n        segment: {\n          borderColor: ctx => x(ctx, {min: 3, max: 4}) ? 'red' : y(ctx, {min: 5}) ? 'green' : xy(ctx, {min: 0}, {max: 1}) ? 'blue' : undefined,\n          borderDash: ctx => x(ctx, {min: 3, max: 4}) || y(ctx, {min: 5}) ? [5, 5] : undefined,\n        }\n      }]\n    },\n    options: {\n      scales: {\n        x: {type: 'linear', display: false},\n        y: {display: false}\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.line/segments/single.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: ['a', 'b', 'c', 'd', 'e', 'f'],\n      datasets: [{\n        data: [1, 2, 3, 3, 2, 1],\n        borderColor: 'black',\n        segment: {\n          borderColor: 'red',\n        }\n      }]\n    },\n    options: {\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      width: 256,\n      height: 256\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.line/segments/slope.js",
    "content": "function slope({p0, p1}) {\n  return (p0.y - p1.y) / (p1.x - p0.x);\n}\n\nmodule.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: ['a', 'b', 'c', 'd', 'e', 'f'],\n      datasets: [{\n        data: [1, 2, 3, 3, 2, 1],\n        borderColor: 'black',\n        segment: {\n          borderColor: ctx => slope(ctx) > 0 ? 'green' : slope(ctx) < 0 ? 'red' : undefined,\n          borderDash: ctx => slope(ctx) < 0 ? [5, 5] : undefined\n        }\n      }]\n    },\n    options: {\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.line/segments/spanGaps.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: ['a', 'b', 'c', 'd', 'e', 'f'],\n      datasets: [{\n        data: [1, 3, null, null, 2, 1],\n        segment: {\n          borderColor: ctx => ctx.p1.parsed.x > 2 ? 'red' : undefined,\n          borderDash: ctx => ctx.p1.parsed.x > 3 ? [6, 6] : undefined,\n        },\n        spanGaps: true\n      }, {\n        data: [0, 2, null, null, 1, 0],\n        segment: {\n          borderColor: ctx => ctx.p1.parsed.x > 2 ? 'red' : undefined,\n          borderDash: ctx => ctx.p1.parsed.x > 3 ? [6, 6] : undefined,\n        },\n        spanGaps: false\n      }]\n    },\n    options: {\n      borderColor: 'black',\n      radius: 0,\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.line/showLine/dataset.js",
    "content": "module.exports = {\n  description: 'should draw all elements except lines turned off per dataset',\n  config: {\n    type: 'line',\n    data: {\n      datasets: [{\n        data: [10, 15, 0, -4],\n        label: 'dataset1',\n        borderColor: 'red',\n        backgroundColor: 'green',\n        showLine: false,\n        fill: false\n      }],\n      labels: ['label1', 'label2', 'label3', 'label4']\n    },\n    options: {\n      showLine: true,\n      scales: {\n        x: {\n          display: false\n        },\n        y: {\n          display: false\n        }\n      },\n      plugins: {\n        legend: false,\n        title: false,\n        tooltip: false,\n        filler: true\n      }\n    }\n  },\n  options: {\n    canvas: {\n      width: 512,\n      height: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.line/showLine/false.js",
    "content": "module.exports = {\n  description: 'should draw all elements except lines',\n  config: {\n    type: 'line',\n    data: {\n      datasets: [{\n        data: [10, 15, 0, -4],\n        label: 'dataset1',\n        borderColor: 'red',\n        backgroundColor: 'green',\n        fill: true\n      }],\n      labels: ['label1', 'label2', 'label3', 'label4']\n    },\n    options: {\n      showLine: false,\n      scales: {\n        x: {\n          display: false\n        },\n        y: {\n          display: false\n        }\n      },\n      plugins: {\n        legend: false,\n        title: false,\n        tooltip: false,\n        filler: true\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.line/stacking/bounds-data.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: ['a', 'b'],\n      datasets: [{\n        borderColor: 'red',\n        data: [50, 75],\n      }, {\n        borderColor: 'blue',\n        data: [25, 50],\n      }]\n    },\n    options: {\n      scales: {\n        x: {\n          display: false\n        },\n        y: {\n          stacked: true,\n          bounds: 'data'\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.line/stacking/order-default.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [3, 1, 2, 0, 8, 1],\n          backgroundColor: '#ff0000'\n        },\n        {\n          // option in element (fallback)\n          data: [0, 4, 2, 6, 4, 8],\n          backgroundColor: '#00ff00'\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          fill: true\n        },\n        point: {\n          radius: 0\n        }\n      },\n      layout: {\n        padding: 32\n      },\n      scales: {\n        x: {stacked: true, display: false},\n        y: {stacked: true, display: false}\n      },\n      plugins: {\n        legend: false,\n        title: false,\n        tooltip: false,\n        filler: true\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.line/stacking/order-specified.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [3, 1, 2, 0, 8, 1],\n          backgroundColor: '#ff0000',\n          order: 2\n        },\n        {\n          // option in element (fallback)\n          data: [0, 4, 2, 6, 4, 8],\n          backgroundColor: '#00ff00',\n          order: 1\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          fill: true\n        },\n        point: {\n          radius: 0\n        }\n      },\n      layout: {\n        padding: 32\n      },\n      scales: {\n        x: {stacked: true, display: false},\n        y: {stacked: true, display: false}\n      },\n      plugins: {\n        legend: false,\n        title: false,\n        tooltip: false,\n        filler: true\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.line/stacking/single.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: [0, 1, 2],\n      datasets: [\n        {\n          data: [0, -1, -1],\n          backgroundColor: '#ff0000',\n        },\n        {\n          data: [0, 2, 2],\n          backgroundColor: '#00ff00',\n        },\n        {\n          data: [0, 0, 1],\n          backgroundColor: '#0000ff',\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          fill: '-1',\n        },\n        point: {\n          radius: 0\n        }\n      },\n      layout: {\n        padding: 32\n      },\n      plugins: {\n        legend: false,\n        title: false,\n        tooltip: false,\n        filler: true\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false, stacked: 'single'}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.line/stacking/stacked-scatter.js",
    "content": "module.exports = {\n  config: {\n    type: 'scatter',\n    data: {\n      datasets: [{\n        label: 'label1',\n        data: [{\n          x: 0,\n          y: 30\n        }, {\n          x: 5,\n          y: 35\n        }, {\n          x: 10,\n          y: 20\n        }],\n        backgroundColor: '#42A8E4'\n      },\n      {\n        label: 'label2',\n        data: [{\n          x: 0,\n          y: 10\n        }, {\n          x: 5,\n          y: 15\n        }, {\n          x: 10,\n          y: 15\n        }],\n        backgroundColor: '#FC3F55'\n      },\n      {\n        label: 'label3',\n        data: [{\n          x: 0,\n          y: -15\n        }, {\n          x: 5,\n          y: -10\n        }, {\n          x: 10,\n          y: -20\n        }],\n        backgroundColor: '#FFBE3F'\n      }],\n    },\n    options: {\n      scales: {\n        x: {\n          display: false,\n          position: 'bottom',\n        },\n        y: {\n          stacked: true,\n          display: false,\n          position: 'left',\n        },\n      },\n      plugins: {\n        legend: false,\n        title: false,\n        tooltip: false,\n        filler: true\n      }\n    },\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.line/stacking/updates.js",
    "content": "module.exports = {\n  description: 'https://github.com/chartjs/Chart.js/issues/9424',\n  config: {\n    type: 'line',\n    data: {\n      labels: [0, 1, 2],\n      datasets: [\n        {\n          data: [1, 1, 1],\n          stack: 's1',\n          borderColor: '#ff0000',\n        },\n        {\n          data: [2, 2, 2],\n          stack: 's1',\n          borderColor: '#00ff00',\n        },\n        {\n          data: [3, 3, 3],\n          stack: 's1',\n          borderColor: '#0000ff',\n        }\n      ]\n    },\n    options: {\n      borderWidth: 5,\n      scales: {\n        x: {display: false},\n        y: {display: true, stacked: true}\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    run(chart) {\n      chart.data.datasets.splice(1, 0, {data: [1.5, 1.5, 1.5], stack: 's2', borderColor: '#000000'});\n      chart.update();\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.polarArea/angle-array.json",
    "content": "{\n  \"config\": {\n    \"type\": \"polarArea\",\n    \"data\": {\n      \"labels\": [\"A\", \"B\", \"C\", \"D\", \"E\"],\n      \"datasets\": [\n        {\n          \"data\": [11, 16, 21, 7, 10],\n          \"backgroundColor\": [\n            \"rgba(255, 99, 132, 0.8)\",\n            \"rgba(54, 162, 235, 0.8)\",\n            \"rgba(255, 206, 86, 0.8)\",\n            \"rgba(75, 192, 192, 0.8)\",\n            \"rgba(153, 102, 255, 0.8)\",\n            \"rgba(255, 159, 64, 0.8)\"\n          ]\n        }\n      ]\n    },\n    \"options\": {\n      \"elements\": {\n        \"arc\": {\n          \"angle\": [60.5387, 100.6457, 60.5387, 123.5641, 14.7021]\n        }\n      },\n      \"responsive\": false,\n      \"scales\": {\n        \"r\": {\n          \"display\": false\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "test/fixtures/controller.polarArea/angle-lines.json",
    "content": "{\n  \"threshold\": 0.05,\n  \"config\": {\n    \"type\": \"polarArea\",\n    \"data\": {\n      \"labels\": [\"A\", \"B\", \"C\", \"D\", \"E\"],\n      \"datasets\": [\n        {\n          \"data\": [11, 16, 21, 7, 10],\n          \"backgroundColor\": [\n            \"rgba(255, 99, 132, 0.8)\",\n            \"rgba(54, 162, 235, 0.8)\",\n            \"rgba(255, 206, 86, 0.8)\",\n            \"rgba(75, 192, 192, 0.8)\",\n            \"rgba(153, 102, 255, 0.8)\",\n            \"rgba(255, 159, 64, 0.8)\"\n          ]\n        }\n      ]\n    },\n    \"options\": {\n      \"responsive\": false,\n      \"scales\": {\n        \"r\": {\n          \"display\": true,\n          \"angleLines\": {\n            \"display\": true,\n            \"color\": \"#000\"\n          },\n          \"ticks\": {\n            \"display\": false\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "test/fixtures/controller.polarArea/angle-undefined.json",
    "content": "{\n  \"config\": {\n    \"type\": \"polarArea\",\n    \"data\": {\n      \"labels\": [\"A\", \"B\", \"C\", \"D\", \"E\"],\n      \"datasets\": [\n        {\n          \"data\": [11, 16, 21, 7, 10],\n          \"backgroundColor\": [\n            \"rgba(255, 99, 132, 0.8)\",\n            \"rgba(54, 162, 235, 0.8)\",\n            \"rgba(255, 206, 86, 0.8)\",\n            \"rgba(75, 192, 192, 0.8)\",\n            \"rgba(153, 102, 255, 0.8)\",\n            \"rgba(255, 159, 64, 0.8)\"\n          ]\n        }\n      ]\n    },\n    \"options\": {\n      \"responsive\": false,\n      \"scales\": {\n        \"r\": {\n          \"display\": false\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "test/fixtures/controller.polarArea/backgroundColor/indexable-dataset.js",
    "content": "module.exports = {\n  config: {\n    type: 'polarArea',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 2, 4, null, 6, 8],\n          backgroundColor: [\n            '#ff0000',\n            '#00ff00',\n            '#0000ff',\n            '#ffff00',\n            '#ff00ff',\n            '#000000'\n          ]\n        },\n      ]\n    },\n    options: {\n      scales: {\n        r: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.polarArea/backgroundColor/indexable-element-options.js",
    "content": "module.exports = {\n  config: {\n    type: 'polarArea',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in element (fallback)\n          data: [0, 2, 4, null, 6, 8],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        arc: {\n          backgroundColor: [\n            '#ff88ff',\n            '#888888',\n            '#ff8800',\n            '#00ff88',\n            '#8800ff',\n            '#ffff88'\n          ]\n        }\n      },\n      scales: {\n        r: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.polarArea/backgroundColor/scriptable-dataset.js",
    "content": "module.exports = {\n  config: {\n    type: 'polarArea',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 2, 4, null, 6, 8],\n          backgroundColor: function(ctx) {\n            var value = ctx.dataset.data[ctx.dataIndex] || 0;\n            return value > 8 ? '#ff0000'\n              : value > 6 ? '#00ff00'\n              : value > 2 ? '#0000ff'\n              : '#ff00ff';\n          }\n        },\n      ]\n    },\n    options: {\n      scales: {\n        r: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.polarArea/backgroundColor/scriptable-element-options.js",
    "content": "module.exports = {\n  config: {\n    type: 'polarArea',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in element (fallback)\n          data: [0, 2, 4, null, 6, 8],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        arc: {\n          backgroundColor: function(ctx) {\n            var value = ctx.dataset.data[ctx.dataIndex] || 0;\n            return value > 8 ? '#ff0000'\n              : value > 6 ? '#00ff00'\n              : value > 2 ? '#0000ff'\n              : '#ff00ff';\n          }\n        }\n      },\n      scales: {\n        r: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.polarArea/backgroundColor/value-dataset.js",
    "content": "module.exports = {\n  config: {\n    type: 'polarArea',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 2, 4, null, 6, 8],\n          backgroundColor: '#ff0000'\n        },\n      ]\n    },\n    options: {\n      scales: {\n        r: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.polarArea/backgroundColor/value-element-options.js",
    "content": "module.exports = {\n  config: {\n    type: 'polarArea',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in element (fallback)\n          data: [0, 2, 4, null, 6, 8],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        arc: {\n          backgroundColor: '#00ff00'\n        }\n      },\n      scales: {\n        r: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.polarArea/border-align-center.json",
    "content": "{\n  \"config\": {\n    \"type\": \"polarArea\",\n    \"data\": {\n      \"labels\": [\"A\", \"B\", \"C\", \"D\", \"E\"],\n      \"datasets\": [\n        {\n          \"data\": [11, 16, 21, 1, 10],\n          \"backgroundColor\": [\n            \"rgba(255, 99, 132, 0.8)\",\n            \"rgba(54, 162, 235, 0.8)\",\n            \"rgba(255, 206, 86, 0.8)\",\n            \"rgba(75, 192, 192, 0.8)\",\n            \"rgba(153, 102, 255, 0.8)\"\n          ],\n          \"borderWidth\": 20,\n          \"borderColor\": [\n            \"rgb(255, 99, 132)\",\n            \"rgb(54, 162, 235)\",\n            \"rgb(255, 206, 86)\",\n            \"rgb(75, 192, 192)\",\n            \"rgb(153, 102, 255)\"\n          ]\n        }\n      ]\n    },\n    \"options\": {\n      \"elements\": {\n        \"arc\": {\n          \"angle\": [2.1658, 10.8404, 21.6922, 108.4323, 216.8588]\n        }\n      },\n      \"responsive\": false,\n      \"scales\": {\n        \"r\": {\n          \"display\": false\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "test/fixtures/controller.polarArea/border-align-inner.json",
    "content": "{\n  \"config\": {\n    \"type\": \"polarArea\",\n    \"data\": {\n      \"labels\": [\"A\", \"B\", \"C\", \"D\", \"E\"],\n      \"datasets\": [\n        {\n          \"data\": [11, 16, 21, 1, 10],\n          \"backgroundColor\": [\n            \"rgba(255, 99, 132, 0.8)\",\n            \"rgba(54, 162, 235, 0.8)\",\n            \"rgba(255, 206, 86, 0.8)\",\n            \"rgba(75, 192, 192, 0.8)\",\n            \"rgba(153, 102, 255, 0.8)\"\n          ],\n          \"borderWidth\": 20,\n          \"borderColor\": [\n            \"rgb(255, 99, 132)\",\n            \"rgb(54, 162, 235)\",\n            \"rgb(255, 206, 86)\",\n            \"rgb(75, 192, 192)\",\n            \"rgb(153, 102, 255)\"\n          ],\n          \"borderAlign\": \"inner\"\n        }\n      ]\n    },\n    \"options\": {\n      \"elements\": {\n        \"arc\": {\n          \"angle\": [2.1658, 10.8404, 21.6922, 108.4323, 216.8588]\n        }\n      },\n      \"responsive\": false,\n      \"scales\": {\n        \"r\": {\n          \"display\": false\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "test/fixtures/controller.polarArea/borderAlign/indexable-dataset.js",
    "content": "module.exports = {\n  config: {\n    type: 'polarArea',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 2, 4, null, 6, 8],\n          borderAlign: [\n            'center',\n            'inner',\n            'center',\n            'inner',\n            'center',\n            'inner',\n          ],\n          borderColor: '#00ff00'\n        },\n      ]\n    },\n    options: {\n      elements: {\n        arc: {\n          backgroundColor: 'transparent',\n          borderColor: '#ff0000',\n          borderWidth: 5,\n        }\n      },\n      scales: {\n        r: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.polarArea/borderAlign/indexable-element-options.js",
    "content": "module.exports = {\n  config: {\n    type: 'polarArea',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in element (fallback)\n          data: [0, 2, 4, null, 6, 8],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        arc: {\n          backgroundColor: 'transparent',\n          borderColor: '#ff0000',\n          borderWidth: 5,\n          borderAlign: [\n            'center',\n            'inner',\n            'center',\n            'inner',\n            'center',\n            'inner',\n          ]\n        }\n      },\n      scales: {\n        r: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.polarArea/borderAlign/scriptable-dataset.js",
    "content": "module.exports = {\n  config: {\n    type: 'polarArea',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 2, 4, null, 6, 8],\n          borderAlign: function(ctx) {\n            var value = ctx.dataset.data[ctx.dataIndex] || 0;\n            return value > 4 ? 'inner' : 'center';\n          },\n          borderColor: '#0000ff',\n        },\n      ]\n    },\n    options: {\n      elements: {\n        arc: {\n          backgroundColor: 'transparent',\n          borderColor: '#ff00ff',\n          borderWidth: 8,\n        }\n      },\n      scales: {\n        r: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.polarArea/borderAlign/scriptable-element-options.js",
    "content": "module.exports = {\n  config: {\n    type: 'polarArea',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in element (fallback)\n          data: [0, 2, 4, null, 6, 8],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        arc: {\n          backgroundColor: 'transparent',\n          borderColor: '#ff00ff',\n          borderWidth: 8,\n          borderAlign: function(ctx) {\n            var value = ctx.dataset.data[ctx.dataIndex] || 0;\n            return value > 4 ? 'center' : 'inner';\n          }\n        }\n      },\n      scales: {\n        r: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.polarArea/borderAlign/value-dataset.js",
    "content": "module.exports = {\n  config: {\n    type: 'polarArea',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 2, 4, null, 6, 8],\n          borderAlign: 'inner',\n          borderColor: '#00ff00',\n        },\n      ]\n    },\n    options: {\n      elements: {\n        arc: {\n          backgroundColor: 'transparent',\n          borderColor: '#0000ff',\n          borderWidth: 4,\n        }\n      },\n      scales: {\n        r: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.polarArea/borderAlign/value-element-options.js",
    "content": "module.exports = {\n  config: {\n    type: 'polarArea',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in element (fallback)\n          data: [0, 2, 4, null, 6, 8],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        arc: {\n          backgroundColor: 'transparent',\n          borderAlign: 'center',\n          borderColor: '#0000ff',\n          borderWidth: 4,\n        }\n      },\n      scales: {\n        r: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.polarArea/borderColor/indexable-dataset.js",
    "content": "module.exports = {\n  config: {\n    type: 'polarArea',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 2, 4, null, 6, 8],\n          borderColor: [\n            '#ff0000',\n            '#00ff00',\n            '#0000ff',\n            '#ffff00',\n            '#ff00ff',\n            '#000000'\n          ]\n        },\n      ]\n    },\n    options: {\n      elements: {\n        arc: {\n          backgroundColor: 'transparent',\n          borderWidth: 8\n        }\n      },\n      scales: {\n        r: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.polarArea/borderColor/indexable-element-options.js",
    "content": "module.exports = {\n  config: {\n    type: 'polarArea',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in element (fallback)\n          data: [0, 2, 4, null, 6, 8],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        arc: {\n          backgroundColor: 'transparent',\n          borderColor: [\n            '#ff88ff',\n            '#888888',\n            '#ff8800',\n            '#00ff88',\n            '#8800ff',\n            '#ffff88'\n          ],\n          borderWidth: 8\n        }\n      },\n      scales: {\n        r: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.polarArea/borderColor/scriptable-dataset.js",
    "content": "module.exports = {\n  config: {\n    type: 'polarArea',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 2, 4, null, 6, 8],\n          borderColor: function(ctx) {\n            var value = ctx.dataset.data[ctx.dataIndex] || 0;\n            return value > 8 ? '#ff0000'\n              : value > 6 ? '#00ff00'\n              : value > 2 ? '#0000ff'\n              : '#ff00ff';\n          }\n        },\n      ]\n    },\n    options: {\n      elements: {\n        arc: {\n          backgroundColor: 'transparent',\n          borderWidth: 8\n        }\n      },\n      scales: {\n        r: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.polarArea/borderColor/scriptable-element-options.js",
    "content": "module.exports = {\n  config: {\n    type: 'polarArea',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in element (fallback)\n          data: [0, 2, 4, null, 6, 8],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        arc: {\n          backgroundColor: 'transparent',\n          borderColor: function(ctx) {\n            var value = ctx.dataset.data[ctx.dataIndex] || 0;\n            return value > 8 ? '#ff00ff'\n              : value > 6 ? '#0000ff'\n              : value > 2 ? '#ff0000'\n              : '#00ff00';\n          },\n          borderWidth: 8\n        }\n      },\n      scales: {\n        r: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.polarArea/borderColor/value-dataset.js",
    "content": "module.exports = {\n  config: {\n    type: 'polarArea',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 2, 4, null, 6, 8],\n          borderColor: '#ff0000'\n        },\n      ]\n    },\n    options: {\n      elements: {\n        arc: {\n          backgroundColor: 'transparent',\n          borderWidth: 8\n        }\n      },\n      scales: {\n        r: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.polarArea/borderColor/value-element-options.js",
    "content": "module.exports = {\n  config: {\n    type: 'polarArea',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in element (fallback)\n          data: [0, 2, 4, null, 6, 8],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        arc: {\n          backgroundColor: 'transparent',\n          borderColor: '#00ff00',\n          borderWidth: 8\n        }\n      },\n      scales: {\n        r: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.polarArea/borderDash/scriptable.js",
    "content": "module.exports = {\n  config: {\n    type: 'polarArea',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [5, 2, 4, 7, 6, 8]\n        }\n      ]\n    },\n    options: {\n      elements: {\n        arc: {\n          backgroundColor: 'transparent',\n          borderColor: 'black',\n          borderWidth: 1,\n          borderDash: function(ctx) {\n            var value = (ctx.dataIndex || 0) % 2;\n            return value === 0 ? [3, 3] : [];\n          }\n        }\n      },\n      scales: {\n        r: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.polarArea/borderDash/value.js",
    "content": "module.exports = {\n  config: {\n    type: 'polarArea',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [5, 2, 4, 7, 6, 8],\n          borderAlign: 'inner',\n          borderColor: 'black'\n        },\n      ]\n    },\n    options: {\n      elements: {\n        arc: {\n          backgroundColor: 'transparent',\n          borderWidth: 1,\n          borderDash: [3, 3]\n        }\n      },\n      scales: {\n        r: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.polarArea/borderWidth/indexable-dataset.js",
    "content": "module.exports = {\n  config: {\n    type: 'polarArea',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 2, 4, null, 6, 8],\n          borderWidth: [\n            0,\n            1,\n            2,\n            3,\n            4,\n            5\n          ]\n        },\n      ]\n    },\n    options: {\n      elements: {\n        arc: {\n          backgroundColor: 'transparent',\n          borderColor: '#888',\n        }\n      },\n      scales: {\n        r: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.polarArea/borderWidth/indexable-element-options.js",
    "content": "module.exports = {\n  config: {\n    type: 'polarArea',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in element (fallback)\n          data: [0, 2, 4, null, 6, 8],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        arc: {\n          backgroundColor: 'transparent',\n          borderColor: '#888',\n          borderWidth: [\n            5,\n            4,\n            3,\n            2,\n            1,\n            0\n          ]\n        }\n      },\n      scales: {\n        r: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.polarArea/borderWidth/scriptable-dataset.js",
    "content": "module.exports = {\n  config: {\n    type: 'polarArea',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 2, 4, null, 6, 8],\n          borderWidth: function(ctx) {\n            var value = ctx.dataset.data[ctx.dataIndex] || 0;\n            return Math.abs(value);\n          }\n        },\n      ]\n    },\n    options: {\n      elements: {\n        arc: {\n          backgroundColor: 'transparent',\n          borderColor: '#888',\n        }\n      },\n      scales: {\n        r: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.polarArea/borderWidth/scriptable-element-options.js",
    "content": "module.exports = {\n  config: {\n    type: 'polarArea',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in element (fallback)\n          data: [0, 2, 4, null, 6, 8],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        arc: {\n          backgroundColor: 'transparent',\n          borderColor: '#888',\n          borderWidth: function(ctx) {\n            return ctx.dataIndex * 2;\n          }\n        }\n      },\n      scales: {\n        r: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.polarArea/borderWidth/value-dataset.js",
    "content": "module.exports = {\n  config: {\n    type: 'polarArea',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 2, 4, null, 6, 8],\n          borderWidth: 2\n        },\n      ]\n    },\n    options: {\n      elements: {\n        arc: {\n          backgroundColor: 'transparent',\n          borderColor: '#888',\n        }\n      },\n      scales: {\n        r: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.polarArea/borderWidth/value-element-options.js",
    "content": "module.exports = {\n  config: {\n    type: 'polarArea',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in element (fallback)\n          data: [0, 2, 4, null, 6, 8],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        arc: {\n          backgroundColor: 'transparent',\n          borderColor: '#888',\n          borderWidth: 4\n        }\n      },\n      scales: {\n        r: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.polarArea/last-slice-animate.js",
    "content": "const canvas = document.createElement('canvas');\ncanvas.width = 512;\ncanvas.height = 512;\nconst ctx = canvas.getContext('2d');\n\nmodule.exports = {\n  config: {\n    type: 'polarArea',\n    data: {\n      labels: ['A'],\n      datasets: [{\n        data: [20],\n        backgroundColor: 'red',\n      }]\n    },\n    options: {\n      animation: {\n        duration: 0,\n        easing: 'linear'\n      },\n      responsive: false,\n      plugins: {\n        legend: false,\n        title: false,\n        tooltip: false,\n        filler: false\n      },\n      scales: {\n        r: {\n          ticks: {\n            display: false,\n          }\n        }\n      }\n    },\n  },\n  options: {\n    canvas: {\n      height: 512,\n      width: 512\n    },\n    run: function(chart) {\n      chart.options.animation.duration = 8000;\n      chart.toggleDataVisibility(0);\n      chart.update();\n      const animator = Chart.animator;\n      // disable animator\n      const backup = animator._refresh;\n      animator._refresh = function() { };\n\n      return new Promise((resolve) => {\n        window.requestAnimationFrame(() => {\n          const anims = animator._getAnims(chart);\n          const start = anims.items[0]._start;\n          for (let i = 0; i < 16; i++) {\n            animator._update(start + i * 500);\n            let x = i % 4 * 128;\n            let y = Math.floor(i / 4) * 128;\n            ctx.drawImage(chart.canvas, x, y, 128, 128);\n          }\n          Chart.helpers.clearCanvas(chart.canvas);\n          chart.ctx.drawImage(canvas, 0, 0);\n\n          animator._refresh = backup;\n          resolve();\n        });\n      });\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.polarArea/parse-object-data.json",
    "content": "{\n  \"config\": {\n    \"type\": \"polarArea\",\n    \"data\": {\n      \"datasets\": [\n        {\n          \"data\": [{\"id\": \"Sales\", \"nested\": {\"value\": 10}}, {\"id\": \"Purchases\", \"nested\": {\"value\": 20}}],\n          \"backgroundColor\": [\"red\", \"blue\"]\n        }\n      ]\n    },\n    \"options\": {\n      \"responsive\": false,\n      \"plugins\": {\n        \"legend\": false\n      },\n      \"parsing\": {\n        \"key\": \"nested.value\"\n      },\n      \"scales\": {\n        \"r\": {\n          \"display\": false\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "test/fixtures/controller.polarArea/pointLabels/centered-180.js",
    "content": "module.exports = {\n  config: {\n    type: 'polarArea',\n    data: {\n      datasets: [{\n        data: [1, 2, 3, 4],\n        backgroundColor: ['#f003', '#0f03', '#00f3', '#0003']\n      }],\n      labels: [['label 1', 'line 2'], ['label 2', 'line 2'], ['label 3', 'line 2'], ['label 4', 'line 2']]\n    },\n    options: {\n      scales: {\n        r: {\n          startAngle: 180,\n          pointLabels: {\n            display: true,\n            centerPointLabels: true\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.polarArea/pointLabels/centered-45.js",
    "content": "module.exports = {\n  config: {\n    type: 'polarArea',\n    data: {\n      datasets: [{\n        data: [1, 2, 3, 4],\n        backgroundColor: ['#f003', '#0f03', '#00f3', '#0003']\n      }],\n      labels: [['label 1', 'line 2'], ['label 2', 'line 2'], ['label 3', 'line 2'], ['label 4', 'line 2']]\n    },\n    options: {\n      scales: {\n        r: {\n          startAngle: 45,\n          pointLabels: {\n            display: true,\n            centerPointLabels: true\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.polarArea/pointLabels/centered.js",
    "content": "module.exports = {\n  config: {\n    type: 'polarArea',\n    data: {\n      datasets: [{\n        data: [1, 2, 3, 4],\n        backgroundColor: ['#f003', '#0f03', '#00f3', '#0003']\n      }],\n      labels: [['label 1', 'line 2'], ['label 2', 'line 2'], ['label 3', 'line 2'], ['label 4', 'line 2']]\n    },\n    options: {\n      scales: {\n        r: {\n          pointLabels: {\n            display: true,\n            centerPointLabels: true\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.polarArea/pointLabels/default-180.js",
    "content": "module.exports = {\n  config: {\n    type: 'polarArea',\n    data: {\n      datasets: [{\n        data: [1, 2, 3, 4],\n        backgroundColor: ['#f003', '#0f03', '#00f3', '#0003']\n      }],\n      labels: [['label 1', 'line 2'], ['label 2', 'line 2'], ['label 3', 'line 2'], ['label 4', 'line 2']]\n    },\n    options: {\n      scales: {\n        r: {\n          startAngle: 180,\n          pointLabels: {\n            display: true,\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.polarArea/pointLabels/default-45.js",
    "content": "module.exports = {\n  config: {\n    type: 'polarArea',\n    data: {\n      datasets: [{\n        data: [1, 2, 3, 4],\n        backgroundColor: ['#f003', '#0f03', '#00f3', '#0003']\n      }],\n      labels: [['label 1', 'line 2'], ['label 2', 'line 2'], ['label 3', 'line 2'], ['label 4', 'line 2']]\n    },\n    options: {\n      scales: {\n        r: {\n          startAngle: 45,\n          pointLabels: {\n            display: true\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.polarArea/pointLabels/default.js",
    "content": "module.exports = {\n  config: {\n    type: 'polarArea',\n    data: {\n      datasets: [{\n        data: [1, 2, 3, 4],\n        backgroundColor: ['#f003', '#0f03', '#00f3', '#0003']\n      }],\n      labels: [['label 1', 'line 2'], ['label 2', 'line 2'], ['label 3', 'line 2'], ['label 4', 'line 2']]\n    },\n    options: {\n      scales: {\n        r: {\n          pointLabels: {\n            display: true,\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.polarArea/pointLabels/displayAuto-180.js",
    "content": "module.exports = {\n  config: {\n    type: 'polarArea',\n    data: {\n      datasets: [{\n        data: new Array(50).fill(5),\n        backgroundColor: ['#f003', '#0f03', '#00f3', '#0003']\n      }],\n      labels: new Array(50).fill(0).map((el, i) => ['label ' + i, 'line 2'])\n    },\n    options: {\n      scales: {\n        r: {\n          startAngle: 180,\n          pointLabels: {\n            display: 'auto',\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.polarArea/pointLabels/displayAuto.js",
    "content": "module.exports = {\n  config: {\n    type: 'polarArea',\n    data: {\n      datasets: [{\n        data: new Array(50).fill(5),\n        backgroundColor: ['#f003', '#0f03', '#00f3', '#0003']\n      }],\n      labels: new Array(50).fill(0).map((el, i) => ['label ' + i, 'line 2'])\n    },\n    options: {\n      scales: {\n        r: {\n          pointLabels: {\n            display: 'auto',\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.polarArea/pointLabels/overlapping.js",
    "content": "module.exports = {\n  config: {\n    type: 'polarArea',\n    data: {\n      datasets: [{\n        data: new Array(50).fill(5),\n        backgroundColor: ['#f003', '#0f03', '#00f3', '#0003']\n      }],\n      labels: new Array(50).fill(0).map((el, i) => ['label ' + i, 'line 2'])\n    },\n    options: {\n      scales: {\n        r: {\n          pointLabels: {\n            display: true,\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.polarArea/pointLabels/withTitle/bottom-centered-45.js",
    "content": "module.exports = {\n  config: {\n    type: 'polarArea',\n    data: {\n      datasets: [{\n        data: [1, 2, 3, 4],\n        backgroundColor: ['#f003', '#0f03', '#00f3', '#0003']\n      }],\n      labels: [['label 1', 'line 2'], ['label 2', 'line 2'], ['label 3', 'line 2'], ['label 4', 'line 2']]\n    },\n    options: {\n      plugins: {\n        title: {\n          display: true,\n          position: 'bottom',\n          text: 'Chart Title'\n        },\n        legend: false\n      },\n      scales: {\n        r: {\n          startAngle: 45,\n          pointLabels: {\n            display: true,\n            centerPointLabels: true\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.polarArea/pointLabels/withTitle/bottom-centered.js",
    "content": "module.exports = {\n  config: {\n    type: 'polarArea',\n    data: {\n      datasets: [{\n        data: [1, 2, 3, 4],\n        backgroundColor: ['#f003', '#0f03', '#00f3', '#0003']\n      }],\n      labels: [['label 1', 'line 2'], ['label 2', 'line 2'], ['label 3', 'line 2'], ['label 4', 'line 2']]\n    },\n    options: {\n      plugins: {\n        title: {\n          display: true,\n          position: 'bottom',\n          text: 'Chart Title'\n        },\n        legend: false\n      },\n      scales: {\n        r: {\n          pointLabels: {\n            display: true,\n            centerPointLabels: true\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.polarArea/pointLabels/withTitle/left-centered-45.js",
    "content": "module.exports = {\n  config: {\n    type: 'polarArea',\n    data: {\n      datasets: [{\n        data: [1, 2, 3, 4],\n        backgroundColor: ['#f003', '#0f03', '#00f3', '#0003']\n      }],\n      labels: [['label 1', 'line 2'], ['label 2', 'line 2'], ['label 3', 'line 2'], ['label 4', 'line 2']]\n    },\n    options: {\n      plugins: {\n        title: {\n          display: true,\n          position: 'left',\n          text: 'Chart Title'\n        },\n        legend: false\n      },\n      scales: {\n        r: {\n          startAngle: 45,\n          pointLabels: {\n            display: true,\n            centerPointLabels: true\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.polarArea/pointLabels/withTitle/left-centered.js",
    "content": "module.exports = {\n  config: {\n    type: 'polarArea',\n    data: {\n      datasets: [{\n        data: [1, 2, 3, 4],\n        backgroundColor: ['#f003', '#0f03', '#00f3', '#0003']\n      }],\n      labels: [['label 1', 'line 2'], ['label 2', 'line 2'], ['label 3', 'line 2'], ['label 4', 'line 2']]\n    },\n    options: {\n      plugins: {\n        title: {\n          display: true,\n          position: 'left',\n          text: 'Chart Title'\n        },\n        legend: false\n      },\n      scales: {\n        r: {\n          pointLabels: {\n            display: true,\n            centerPointLabels: true\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.polarArea/pointLabels/withTitle/right-centered-45.js",
    "content": "module.exports = {\n  config: {\n    type: 'polarArea',\n    data: {\n      datasets: [{\n        data: [1, 2, 3, 4],\n        backgroundColor: ['#f003', '#0f03', '#00f3', '#0003']\n      }],\n      labels: [['label 1', 'line 2'], ['label 2', 'line 2'], ['label 3', 'line 2'], ['label 4', 'line 2']]\n    },\n    options: {\n      plugins: {\n        title: {\n          display: true,\n          position: 'right',\n          text: 'Chart Title'\n        },\n        legend: false\n      },\n      scales: {\n        r: {\n          startAngle: 45,\n          pointLabels: {\n            display: true,\n            centerPointLabels: true\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.polarArea/pointLabels/withTitle/right-centered.js",
    "content": "module.exports = {\n  config: {\n    type: 'polarArea',\n    data: {\n      datasets: [{\n        data: [1, 2, 3, 4],\n        backgroundColor: ['#f003', '#0f03', '#00f3', '#0003']\n      }],\n      labels: [['label 1', 'line 2'], ['label 2', 'line 2'], ['label 3', 'line 2'], ['label 4', 'line 2']]\n    },\n    options: {\n      plugins: {\n        title: {\n          display: true,\n          position: 'right',\n          text: 'Chart Title'\n        },\n        legend: false\n      },\n      scales: {\n        r: {\n          pointLabels: {\n            display: true,\n            centerPointLabels: true\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.polarArea/pointLabels/withTitle/top-centered-45.js",
    "content": "module.exports = {\n  config: {\n    type: 'polarArea',\n    data: {\n      datasets: [{\n        data: [1, 2, 3, 4],\n        backgroundColor: ['#f003', '#0f03', '#00f3', '#0003']\n      }],\n      labels: [['label 1', 'line 2'], ['label 2', 'line 2'], ['label 3', 'line 2'], ['label 4', 'line 2']]\n    },\n    options: {\n      plugins: {\n        title: {\n          display: true,\n          text: 'Chart Title'\n        },\n        legend: false\n      },\n      scales: {\n        r: {\n          startAngle: 45,\n          pointLabels: {\n            display: true,\n            centerPointLabels: true\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.polarArea/pointLabels/withTitle/top-centered.js",
    "content": "module.exports = {\n  config: {\n    type: 'polarArea',\n    data: {\n      datasets: [{\n        data: [1, 2, 3, 4],\n        backgroundColor: ['#f003', '#0f03', '#00f3', '#0003']\n      }],\n      labels: [['label 1', 'line 2'], ['label 2', 'line 2'], ['label 3', 'line 2'], ['label 4', 'line 2']]\n    },\n    options: {\n      plugins: {\n        title: {\n          display: true,\n          text: 'Chart Title'\n        },\n        legend: false\n      },\n      scales: {\n        r: {\n          pointLabels: {\n            display: true,\n            centerPointLabels: true\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.polarArea/pointLabels/withTitle/top-default-45.js",
    "content": "module.exports = {\n  config: {\n    type: 'polarArea',\n    data: {\n      datasets: [{\n        data: [1, 2, 3, 4],\n        backgroundColor: ['#f003', '#0f03', '#00f3', '#0003']\n      }],\n      labels: [['label 1', 'line 2'], ['label 2', 'line 2'], ['label 3', 'line 2'], ['label 4', 'line 2']]\n    },\n    options: {\n      plugins: {\n        title: {\n          display: true,\n          text: 'Chart Title'\n        },\n        legend: false\n      },\n      scales: {\n        r: {\n          startAngle: 45,\n          pointLabels: {\n            display: true\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.polarArea/pointLabels/withTitle/top-default.js",
    "content": "module.exports = {\n  config: {\n    type: 'polarArea',\n    data: {\n      datasets: [{\n        data: [1, 2, 3, 4],\n        backgroundColor: ['#f003', '#0f03', '#00f3', '#0003']\n      }],\n      labels: [['label 1', 'line 2'], ['label 2', 'line 2'], ['label 3', 'line 2'], ['label 4', 'line 2']]\n    },\n    options: {\n      plugins: {\n        title: {\n          display: true,\n          text: 'Chart Title'\n        },\n        legend: false\n      },\n      scales: {\n        r: {\n          pointLabels: {\n            display: true\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.polarArea/polar-area-animation-rotate.js",
    "content": "const canvas = document.createElement('canvas');\ncanvas.width = 512;\ncanvas.height = 512;\nconst ctx = canvas.getContext('2d');\n\nmodule.exports = {\n  config: {\n    type: 'polarArea',\n    data: {\n      labels: ['A', 'B', 'C', 'D', 'E'],\n      datasets: [{\n        data: [1, 5, 10, 2, 4],\n        backgroundColor: [\n          'rgba(255, 99, 132, 0.8)',\n          'rgba(54, 162, 235, 0.8)',\n          'rgba(255, 206, 86, 0.8)',\n          'rgba(75, 192, 192, 0.8)',\n          'rgba(153, 102, 255, 0.8)'\n        ],\n        borderWidth: 4,\n        borderColor: [\n          'rgb(255, 99, 132)',\n          'rgb(54, 162, 235)',\n          'rgb(255, 206, 86)',\n          'rgb(75, 192, 192)',\n          'rgb(153, 102, 255)'\n        ]\n      }]\n    },\n    options: {\n      animation: {\n        animateRotate: true,\n        animateScale: false,\n        duration: 8000,\n        easing: 'linear'\n      },\n      responsive: false,\n      plugins: {\n        legend: false,\n        title: false,\n        tooltip: false,\n        filler: false\n      },\n      scales: {\n        r: {\n          ticks: {\n            display: false,\n          }\n        }\n      }\n    },\n  },\n  options: {\n    canvas: {\n      height: 512,\n      width: 512\n    },\n    run: function(chart) {\n      const animator = Chart.animator;\n      const anims = animator._getAnims(chart);\n      // disable animator\n      const backup = animator._refresh;\n      animator._refresh = function() { };\n\n      return new Promise((resolve) => {\n        window.requestAnimationFrame(() => {\n\n          const start = anims.items[0]._start;\n          for (let i = 0; i < 16; i++) {\n            animator._update(start + i * 500);\n            let x = i % 4 * 128;\n            let y = Math.floor(i / 4) * 128;\n            ctx.drawImage(chart.canvas, x, y, 128, 128);\n          }\n\n          Chart.helpers.clearCanvas(chart.canvas);\n          chart.ctx.drawImage(canvas, 0, 0);\n\n          animator._refresh = backup;\n          resolve();\n        });\n      });\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.polarArea/polar-area-animation-scale.js",
    "content": "const canvas = document.createElement('canvas');\ncanvas.width = 512;\ncanvas.height = 512;\nconst ctx = canvas.getContext('2d');\n\nmodule.exports = {\n  config: {\n    type: 'polarArea',\n    data: {\n      labels: ['A', 'B', 'C', 'D', 'E'],\n      datasets: [{\n        data: [1, 5, 10, 2, 4],\n        backgroundColor: [\n          'rgba(255, 99, 132, 0.8)',\n          'rgba(54, 162, 235, 0.8)',\n          'rgba(255, 206, 86, 0.8)',\n          'rgba(75, 192, 192, 0.8)',\n          'rgba(153, 102, 255, 0.8)'\n        ],\n        borderWidth: 4,\n        borderColor: [\n          'rgb(255, 99, 132)',\n          'rgb(54, 162, 235)',\n          'rgb(255, 206, 86)',\n          'rgb(75, 192, 192)',\n          'rgb(153, 102, 255)'\n        ]\n      }]\n    },\n    options: {\n      animation: {\n        animateRotate: false,\n        animateScale: true,\n        duration: 8000,\n        easing: 'linear'\n      },\n      responsive: false,\n      plugins: {\n        legend: false,\n        title: false,\n        tooltip: false,\n        filler: false\n      },\n      scales: {\n        r: {\n          ticks: {\n            display: false,\n          }\n        }\n      }\n    },\n  },\n  options: {\n    canvas: {\n      height: 512,\n      width: 512\n    },\n    run: function(chart) {\n      const animator = Chart.animator;\n      const anims = animator._getAnims(chart);\n      // disable animator\n      const backup = animator._refresh;\n      animator._refresh = function() { };\n\n      return new Promise((resolve) => {\n        window.requestAnimationFrame(() => {\n\n          const start = anims.items[0]._start;\n          for (let i = 0; i < 16; i++) {\n            animator._update(start + i * 500);\n            let x = i % 4 * 128;\n            let y = Math.floor(i / 4) * 128;\n            ctx.drawImage(chart.canvas, x, y, 128, 128);\n          }\n          Chart.helpers.clearCanvas(chart.canvas);\n          chart.ctx.drawImage(canvas, 0, 0);\n\n          animator._refresh = backup;\n          resolve();\n        });\n      });\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.radar/backgroundColor/scriptable.js",
    "content": "module.exports = {\n  config: {\n    type: 'radar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          backgroundColor: function(ctx) {\n            var index = ctx.index;\n            return index === 0 ? '#ff0000'\n              : index === 1 ? '#00ff00'\n              : '#ff00ff';\n          }\n        },\n        {\n          // option in element (fallback)\n          data: [4, -5, -10, null, 10, 5]\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          backgroundColor: function(ctx) {\n            var index = ctx.index;\n            return index === 0 ? '#ff0000'\n              : index === 1 ? '#00ff00'\n              : '#ff00ff';\n          },\n          fill: true,\n        },\n        point: {\n          backgroundColor: '#0000ff',\n          radius: 10\n        }\n      },\n      scales: {\n        r: {\n          display: false,\n          min: -15,\n        },\n      },\n      plugins: {\n        legend: false,\n        title: false,\n        tooltip: false,\n        filler: true\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 512,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.radar/backgroundColor/value.js",
    "content": "module.exports = {\n  config: {\n    type: 'radar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          backgroundColor: '#ff0000'\n        },\n        {\n          // option in element (fallback)\n          data: [4, -5, -10, null, 10, 5]\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          backgroundColor: '#00ff00',\n          fill: true,\n        },\n        point: {\n          radius: 10\n        }\n      },\n      scales: {\n        r: {\n          display: false,\n          min: -15\n        }\n      },\n      plugins: {\n        legend: false,\n        title: false,\n        tooltip: false,\n        filler: true\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 512,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.radar/borderCapStyle/scriptable.js",
    "content": "module.exports = {\n  config: {\n    type: 'radar',\n    data: {\n      labels: [0, 1, 2],\n      datasets: [\n        {\n          // option in dataset\n          data: [null, 3, 3],\n          borderCapStyle: function(ctx) {\n            var index = (ctx.datasetIndex % 2);\n            return index === 0 ? 'round'\n              : index === 1 ? 'square'\n              : 'butt';\n          }\n        },\n        {\n          // option in element (fallback)\n          data: [null, 2, 2]\n        },\n        {\n          // option in element (fallback)\n          data: [null, 1, 1]\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          borderCapStyle: function(ctx) {\n            var index = (ctx.datasetIndex % 3);\n            return index === 0 ? 'round'\n              : index === 1 ? 'square'\n              : 'butt';\n          },\n          borderColor: '#ff0000',\n          borderWidth: 32,\n          fill: false\n        },\n        point: {\n          radius: 10\n        }\n      },\n      layout: {\n        padding: 32\n      },\n      scales: {\n        r: {\n          display: false,\n          beginAtZero: true\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 512,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.radar/borderCapStyle/value.js",
    "content": "module.exports = {\n  config: {\n    type: 'radar',\n    data: {\n      labels: [0, 1, 2],\n      datasets: [\n        {\n          // option in dataset\n          data: [null, 3, 3],\n          borderCapStyle: 'round'\n        },\n        {\n          // option in dataset\n          data: [null, 2, 2],\n          borderCapStyle: 'square'\n        },\n        {\n          // option in element (fallback)\n          data: [null, 1, 1]\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          borderCapStyle: 'butt',\n          borderColor: '#00ff00',\n          borderWidth: 32,\n          fill: false\n        },\n        point: {\n          radius: 10\n        }\n      },\n      layout: {\n        padding: 32\n      },\n      scales: {\n        r: {\n          display: false,\n          beginAtZero: true\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 512,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.radar/borderColor/scriptable.js",
    "content": "module.exports = {\n  config: {\n    type: 'radar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          borderColor: function(ctx) {\n            var index = ctx.index;\n            return index === 0 ? '#ff0000'\n              : index === 1 ? '#00ff00'\n              : '#0000ff';\n          }\n        },\n        {\n          // option in element (fallback)\n          data: [4, -5, -10, null, 10, 5],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          borderColor: function(ctx) {\n            var index = ctx.index;\n            return index === 0 ? '#ff0000'\n              : index === 1 ? '#00ff00'\n              : '#0000ff';\n          },\n          borderWidth: 10,\n          fill: false\n        },\n        point: {\n          borderColor: '#ff0000',\n          borderWidth: 10,\n          radius: 16\n        }\n      },\n      scales: {\n        r: {\n          display: false,\n          min: -15\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 512,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.radar/borderColor/value.js",
    "content": "module.exports = {\n  config: {\n    type: 'radar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          borderColor: '#ff0000'\n        },\n        {\n          // option in element (fallback)\n          data: [4, -5, -10, null, 10, 5]\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          borderColor: '#0000ff',\n          fill: false\n        },\n        point: {\n          borderColor: '#0000ff',\n          radius: 10\n        }\n      },\n      scales: {\n        r: {\n          display: false,\n          min: -15\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 512,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.radar/borderDash/scriptable.js",
    "content": "module.exports = {\n  config: {\n    type: 'radar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          borderDash: function(ctx) {\n            return ctx.datasetIndex === 0 ? [5] : [10];\n          }\n        },\n        {\n          // option in element (fallback)\n          data: [4, -5, -10, null, 10, 5]\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          borderColor: '#00ff00',\n          borderDash: function(ctx) {\n            return ctx.datasetIndex === 0 ? [5] : [10];\n          },\n          fill: true,\n        },\n        point: {\n          radius: 10\n        }\n      },\n      scales: {\n        r: {\n          display: false,\n          min: -15\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 512,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.radar/borderDash/value.js",
    "content": "module.exports = {\n  config: {\n    type: 'radar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          borderColor: '#ff0000',\n          borderDash: [5]\n        },\n        {\n          // option in element (fallback)\n          data: [4, -5, -10, null, 10, 5]\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          borderColor: '#00ff00',\n          borderDash: [10],\n          fill: false\n        },\n        point: {\n          radius: 10\n        }\n      },\n      scales: {\n        r: {\n          display: false,\n          min: -15\n        }\n      }\n\n    }\n  },\n  options: {\n    canvas: {\n      height: 512,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.radar/borderDashOffset/scriptable.js",
    "content": "module.exports = {\n  config: {\n    type: 'radar',\n    data: {\n      labels: [0, 1, 2, 3],\n      datasets: [\n        {\n          // option in dataset\n          data: [1, 1, 1, 1],\n          borderColor: '#ff0000',\n          borderDash: [20],\n          borderDashOffset: function(ctx) {\n            return ctx.datasetIndex === 0 ? 5.0 : 0.0;\n          }\n        },\n        {\n          // option in element (fallback)\n          data: [0, 0, 0, 0]\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          borderColor: '#00ff00',\n          borderDash: [20],\n          borderDashOffset: function(ctx) {\n            return ctx.datasetIndex === 0 ? 5.0 : 0.0;\n          },\n          fill: false\n        },\n        point: {\n          radius: 10\n        }\n      },\n      layout: {\n        padding: 32\n      },\n      scales: {\n        r: {\n          display: false,\n          min: -1\n        }\n      }\n\n    }\n  },\n  options: {\n    canvas: {\n      height: 512,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.radar/borderDashOffset/value.js",
    "content": "module.exports = {\n  config: {\n    type: 'radar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [1, 1, 1, 1, 1, 1],\n          borderColor: '#ff0000',\n          borderDash: [20],\n          borderDashOffset: 5.0\n        },\n        {\n          // option in element (fallback)\n          data: [0, 0, 0, 0, 0, 0]\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          borderColor: '#00ff00',\n          borderDash: [20],\n          borderDashOffset: 0.0, // default\n          fill: false\n        },\n        point: {\n          radius: 10\n        }\n      },\n      layout: {\n        padding: 32\n      },\n      scales: {\n        r: {\n          display: false,\n          min: -1\n        }\n      }\n\n    }\n  },\n  options: {\n    canvas: {\n      height: 512,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.radar/borderJoinStyle/scriptable.js",
    "content": "module.exports = {\n  config: {\n    type: 'radar',\n    data: {\n      labels: [0, 1, 2, 3],\n      datasets: [\n        {\n          // option in dataset\n          data: [3, 3, null, 3],\n          borderColor: '#ff0000',\n          borderJoinStyle: function(ctx) {\n            var index = ctx.datasetIndex % 3;\n            return index === 0 ? 'round'\n              : index === 1 ? 'miter'\n              : 'bevel';\n          }\n        },\n        {\n          // option in element (fallback)\n          data: [2, 2, null, 2],\n          borderColor: '#0000ff'\n        },\n        {\n          // option in element (fallback)\n          data: [1, 1, null, 1]\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          borderColor: '#00ff00',\n          borderJoinStyle: function(ctx) {\n            var index = (ctx.datasetIndex % 3);\n            return index === 0 ? 'round'\n              : index === 1 ? 'miter'\n              : 'bevel';\n          },\n          borderWidth: 25,\n          fill: false,\n          tension: 0\n        }\n      },\n      layout: {\n        padding: 32\n      },\n      scales: {\n        r: {\n          display: false,\n          beginAtZero: true\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 512,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.radar/borderJoinStyle/value.js",
    "content": "module.exports = {\n  config: {\n    type: 'radar',\n    data: {\n      labels: [0, 1, 2, 3],\n      datasets: [\n        {\n          // option in dataset\n          data: [3, 3, null, 3],\n          borderColor: '#ff0000',\n          borderJoinStyle: 'round'\n        },\n        {\n          // option in element (fallback)\n          data: [2, 2, null, 2],\n          borderColor: '#0000ff',\n          borderJoinStyle: 'bevel'\n        },\n        {\n          // option in element (fallback)\n          data: [1, 1, null, 1]\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          borderColor: '#00ff00',\n          borderJoinStyle: 'miter',\n          borderWidth: 25,\n          fill: false,\n          tension: 0\n        }\n      },\n      layout: {\n        padding: 32\n      },\n      scales: {\n        r: {\n          display: false,\n          beginAtZero: true\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 512,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.radar/borderWidth/scriptable.js",
    "content": "module.exports = {\n  config: {\n    type: 'radar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          borderColor: '#0000ff',\n          borderWidth: function(ctx) {\n            var index = ctx.index;\n            return index % 2 ? 10 : 20;\n          },\n          pointBorderColor: '#00ff00'\n        },\n        {\n          // option in element (fallback)\n          data: [4, -5, -10, null, 10, 5]\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          borderColor: '#ff0000',\n          borderWidth: function(ctx) {\n            var index = ctx.index;\n            return index % 2 ? 10 : 20;\n          },\n          fill: false\n        },\n        point: {\n          borderColor: '#00ff00',\n          borderWidth: 5,\n          radius: 10\n        }\n      },\n      scales: {\n        r: {\n          display: false,\n          min: -15\n        }\n      }\n\n    }\n  },\n  options: {\n    canvas: {\n      height: 512,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.radar/borderWidth/value.js",
    "content": "module.exports = {\n  config: {\n    type: 'radar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          borderColor: '#0000ff',\n          borderWidth: 6\n        },\n        {\n          // option in element (fallback)\n          data: [4, -5, -10, null, 10, 5]\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          borderColor: '#00ff00',\n          borderWidth: 3,\n          fill: false\n        },\n        point: {\n          radius: 10\n        }\n      },\n      scales: {\n        r: {\n          display: false,\n          min: -15\n        }\n      }\n\n    }\n  },\n  options: {\n    canvas: {\n      height: 512,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.radar/borderWidth/zero.js",
    "content": "module.exports = {\n  config: {\n    type: 'radar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          backgroundColor: '#0000ff',\n          borderColor: '#0000ff',\n          borderWidth: 0,\n        },\n        {\n          // option in element (fallback)\n          data: [4, -5, -10, null, 10, 5]\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          borderColor: '#00ff00',\n          borderWidth: 1,\n          fill: false\n        },\n        point: {\n          backgroundColor: '#00ff00',\n          radius: 10\n        }\n      },\n      scales: {\n        r: {\n          display: false,\n          min: -15\n        }\n      }\n\n    }\n  },\n  options: {\n    canvas: {\n      height: 512,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.radar/fill/scriptable.js",
    "content": "module.exports = {\n  config: {\n    type: 'radar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          backgroundColor: '#ff0000',\n          fill: function(ctx) {\n            return ctx.datasetIndex === 0 ? true : false;\n          }\n        },\n        {\n          // option in element (fallback)\n          data: [4, -5, -10, null, 10, 5]\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          backgroundColor: '#00ff00',\n          fill: function(ctx) {\n            return ctx.datasetIndex === 0 ? true : false;\n          }\n        }\n      },\n      scales: {\n        r: {\n          display: false,\n          min: -15\n        }\n      },\n      plugins: {\n        legend: false,\n        title: false,\n        tooltip: false,\n        filler: true\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 512,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.radar/fill/value.js",
    "content": "module.exports = {\n  config: {\n    type: 'radar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          backgroundColor: '#ff0000',\n          fill: false\n        },\n        {\n          // option in element (fallback)\n          data: [4, -5, -10, null, 10, 5]\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          backgroundColor: '#00ff00',\n          fill: true\n        }\n      },\n      scales: {\n        r: {\n          display: false,\n          min: -15\n        }\n      },\n      plugins: {\n        legend: false,\n        title: false,\n        tooltip: false,\n        filler: true\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 512,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.radar/point-style.json",
    "content": "{\n  \"config\": {\n    \"type\": \"radar\",\n    \"data\": {\n      \"labels\": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],\n      \"datasets\": [\n        {\n          \"borderColor\": \"transparent\",\n          \"data\": [3, 3, 3, 3, 3, 3, 3, 3, 3, 3],\n          \"pointBackgroundColor\": \"#00ff00\",\n          \"pointBorderColor\": \"transparent\",\n          \"pointBorderWidth\": 0,\n          \"pointRadius\": 16,\n          \"pointStyle\": [\n            \"circle\",\n            \"cross\",\n            \"crossRot\",\n            \"dash\",\n            \"line\",\n            \"rect\",\n            \"rectRounded\",\n            \"rectRot\",\n            \"star\",\n            \"triangle\"\n          ]\n        },\n        {\n          \"borderColor\": \"transparent\",\n          \"data\": [2, 2, 2, 2, 2, 2, 2, 2, 2, 2],\n          \"pointBackgroundColor\": \"transparent\",\n          \"pointBorderColor\": \"#0000ff\",\n          \"pointBorderWidth\": 1,\n          \"pointRadius\": 16,\n          \"pointStyle\": [\n            \"circle\",\n            \"cross\",\n            \"crossRot\",\n            \"dash\",\n            \"line\",\n            \"rect\",\n            \"rectRounded\",\n            \"rectRot\",\n            \"star\",\n            \"triangle\"\n          ]\n        },\n        {\n          \"borderColor\": \"transparent\",\n          \"data\": [1, 1, 1, 1, 1, 1, 1, 1, 1, 1],\n          \"pointBackgroundColor\": \"#00ff00\",\n          \"pointBorderColor\": \"#0000ff\",\n          \"pointBorderWidth\": 1,\n          \"pointRadius\": 16,\n          \"pointStyle\": [\n            \"circle\",\n            \"cross\",\n            \"crossRot\",\n            \"dash\",\n            \"line\",\n            \"rect\",\n            \"rectRounded\",\n            \"rectRot\",\n            \"star\",\n            \"triangle\"\n          ]\n        }\n      ]\n    },\n    \"options\": {\n      \"responsive\": false,\n      \"scales\": {\n        \"r\": {\n          \"display\": false,\n          \"min\": 0,\n          \"max\": 3\n        }\n      },\n      \"elements\": {\n        \"line\": {\n          \"fill\": false\n        }\n      },\n      \"layout\": {\n        \"padding\": {\n          \"left\": 24,\n          \"right\": 24\n        }\n      }\n    }\n  },\n  \"options\": {\n    \"canvas\": {\n      \"height\": 512,\n      \"width\": 512\n    }\n  }\n}\n"
  },
  {
    "path": "test/fixtures/controller.radar/pointBackgroundColor/indexable.js",
    "content": "module.exports = {\n  config: {\n    type: 'radar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          pointBackgroundColor: [\n            '#ff0000',\n            '#00ff00',\n            '#0000ff',\n            '#ffff00',\n            '#ff00ff',\n            '#000000'\n          ]\n        },\n        {\n          // option in element (fallback)\n          data: [4, -5, -10, null, 10, 5]\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          fill: false\n        },\n        point: {\n          backgroundColor: [\n            '#ff88ff',\n            '#888888',\n            '#ff8800',\n            '#00ff88',\n            '#8800ff',\n            '#ffff88'\n          ],\n          radius: 10\n        }\n      },\n      scales: {\n        r: {\n          display: false,\n          min: -15\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 512,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.radar/pointBackgroundColor/scriptable.js",
    "content": "module.exports = {\n  config: {\n    type: 'radar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          pointBackgroundColor: function(ctx) {\n            var value = ctx.dataset.data[ctx.dataIndex] || 0;\n            return value > 8 ? '#ff0000'\n              : value > 0 ? '#00ff00'\n              : value > -8 ? '#0000ff'\n              : '#ff00ff';\n          }\n        },\n        {\n          // option in element (fallback)\n          data: [4, -5, -10, null, 10, 5]\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          fill: false\n        },\n        point: {\n          backgroundColor: function(ctx) {\n            var value = ctx.dataset.data[ctx.dataIndex] || 0;\n            return value > 8 ? '#ff00ff'\n              : value > 0 ? '#0000ff'\n              : value > -8 ? '#ff0000'\n              : '#00ff00';\n          },\n          radius: 10\n        }\n      },\n      scales: {\n        r: {\n          display: false,\n          min: -15\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 512,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.radar/pointBackgroundColor/value.js",
    "content": "module.exports = {\n  config: {\n    type: 'radar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          pointBackgroundColor: '#ff0000'\n        },\n        {\n          // option in element (fallback)\n          data: [4, -5, -10, null, 10, 5]\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          fill: false\n        },\n        point: {\n          backgroundColor: '#00ff00',\n          radius: 10\n        }\n      },\n      scales: {\n        r: {\n          display: false,\n          min: -15\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 512,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.radar/pointBorderColor/indexable.js",
    "content": "module.exports = {\n  config: {\n    type: 'radar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          pointBorderColor: [\n            '#ff0000',\n            '#00ff00',\n            '#0000ff',\n            '#ffff00',\n            '#ff00ff',\n            '#000000'\n          ]\n        },\n        {\n          // option in element (fallback)\n          data: [4, -5, -10, null, 10, 5]\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          fill: false\n        },\n        point: {\n          borderColor: [\n            '#ff88ff',\n            '#888888',\n            '#ff8800',\n            '#00ff88',\n            '#8800ff',\n            '#ffff88'\n          ],\n          borderWidth: 5,\n          radius: 10\n        }\n      },\n      scales: {\n        r: {\n          display: false,\n          min: -15\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 512,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.radar/pointBorderColor/scriptable.js",
    "content": "module.exports = {\n  config: {\n    type: 'radar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          pointBorderColor: function(ctx) {\n            var value = ctx.dataset.data[ctx.dataIndex] || 0;\n            return value > 8 ? '#ff0000'\n              : value > 0 ? '#00ff00'\n              : value > -8 ? '#0000ff'\n              : '#ff00ff';\n          }\n        },\n        {\n          // option in element (fallback)\n          data: [4, -5, -10, null, 10, 5]\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          fill: false\n        },\n        point: {\n          borderColor: function(ctx) {\n            var value = ctx.dataset.data[ctx.dataIndex] || 0;\n            return value > 8 ? '#ff00ff'\n              : value > 0 ? '#0000ff'\n              : value > -8 ? '#ff0000'\n              : '#00ff00';\n          },\n          borderWidth: 5,\n          radius: 10\n        }\n      },\n      scales: {\n        r: {\n          display: false,\n          min: -15\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 512,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.radar/pointBorderColor/value.js",
    "content": "module.exports = {\n  config: {\n    type: 'radar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          pointBorderColor: '#ff0000'\n        },\n        {\n          // option in element (fallback)\n          data: [4, -5, -10, null, 10, 5]\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          fill: false\n        },\n        point: {\n          borderColor: '#00ff00',\n          borderWidth: 5,\n          radius: 10\n        }\n      },\n      scales: {\n        r: {\n          display: false,\n          min: -15\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 512,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.radar/pointBorderWidth/indexable.js",
    "content": "module.exports = {\n  config: {\n    type: 'radar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          pointBorderColor: '#00ff00',\n          pointBorderWidth: [\n            1, 2, 3, 4, 5, 6\n          ]\n        },\n        {\n          // option in element (fallback)\n          data: [4, -5, -10, null, 10, 5]\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          fill: false\n        },\n        point: {\n          borderColor: '#ff0000',\n          borderWidth: [\n            6, 5, 4, 3, 2, 1\n          ],\n          radius: 10\n        }\n      },\n      scales: {\n        r: {\n          display: false,\n          min: -15\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 512,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.radar/pointBorderWidth/scriptable.js",
    "content": "module.exports = {\n  config: {\n    type: 'radar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          pointBorderColor: '#0000ff',\n          pointBorderWidth: function(ctx) {\n            var value = ctx.dataset.data[ctx.dataIndex] || 0;\n            return value > 4 ? 10\n              : value > -4 ? 5\n              : 2;\n          }\n        },\n        {\n          // option in element (fallback)\n          data: [4, -5, -10, null, 10, 5]\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          fill: false\n        },\n        point: {\n          borderColor: '#ff0000',\n          borderWidth: function(ctx) {\n            var value = ctx.dataset.data[ctx.dataIndex] || 0;\n            return value > 4 ? 2\n              : value > -4 ? 5\n              : 10;\n          },\n          radius: 10\n        }\n      },\n      scales: {\n        r: {\n          display: false,\n          min: -15\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 512,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.radar/pointBorderWidth/value.js",
    "content": "module.exports = {\n  config: {\n    type: 'radar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          pointBorderColor: '#0000ff',\n          pointBorderWidth: 6\n        },\n        {\n          // option in element (fallback)\n          data: [4, -5, -10, null, 10, 5]\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          fill: false\n        },\n        point: {\n          borderColor: '#00ff00',\n          borderWidth: 3,\n          radius: 10\n        }\n      },\n      scales: {\n        r: {\n          display: false,\n          min: -15\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 512,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.radar/pointStyle/indexable.js",
    "content": "module.exports = {\n  config: {\n    type: 'radar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5, 6],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5, 0],\n          pointBackgroundColor: '#ff0000',\n          pointBorderColor: '#ff0000',\n          pointStyle: [\n            'circle',\n            'cross',\n            'crossRot',\n            'dash',\n            'line',\n            'rect',\n            false\n          ]\n        },\n        {\n          // option in element (fallback)\n          data: [4, -5, -10, null, 10, 5, -4],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          fill: false,\n        },\n        point: {\n          backgroundColor: '#00ff00',\n          borderColor: '#00ff00',\n          pointStyle: [\n            'line',\n            'rect',\n            'rectRounded',\n            'rectRot',\n            'star',\n            'triangle'\n          ],\n          radius: 10\n        }\n      },\n      scales: {\n        r: {\n          display: false,\n          min: -15\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 512,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.radar/pointStyle/scriptable.js",
    "content": "module.exports = {\n  config: {\n    type: 'radar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          pointBackgroundColor: '#ff0000',\n          pointBorderColor: '#ff0000',\n          pointStyle: function(ctx) {\n            var value = ctx.dataset.data[ctx.dataIndex] || 0;\n            return value > 8 ? 'rect'\n              : value > 0 ? 'star'\n              : value > -8 ? 'cross'\n              : 'triangle';\n          }\n        },\n        {\n          // option in element (fallback)\n          data: [4, -5, -10, null, 10, 5],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          fill: false,\n        },\n        point: {\n          backgroundColor: '#0000ff',\n          borderColor: '#0000ff',\n          pointStyle: function(ctx) {\n            var value = ctx.dataset.data[ctx.dataIndex] || 0;\n            return value > 8 ? 'triangle'\n              : value > 0 ? 'cross'\n              : value > -8 ? 'star'\n              : 'rect';\n          },\n          radius: 10,\n        }\n      },\n      scales: {\n        r: {\n          display: false,\n          min: -15\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 512,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.radar/pointStyle/value.js",
    "content": "module.exports = {\n  config: {\n    type: 'radar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          pointBorderColor: '#ff0000',\n          pointStyle: 'star',\n        },\n        {\n          // option in element (fallback)\n          data: [4, -5, -10, null, 10, 5],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          fill: false,\n        },\n        point: {\n          backgroundColor: '#00ff00',\n          pointStyle: 'rect',\n          radius: 10,\n        }\n      },\n      scales: {\n        r: {\n          display: false,\n          min: -15\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 512,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.radar/radius/indexable.js",
    "content": "module.exports = {\n  config: {\n    type: 'radar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          pointBackgroundColor: '#00ff00',\n          pointRadius: [\n            1, 2, 3, 4, 5, 6\n          ]\n        },\n        {\n          // option in element (fallback)\n          data: [4, -5, -10, null, 10, 5],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          fill: false,\n        },\n        point: {\n          backgroundColor: '#ff0000',\n          radius: [\n            6, 5, 4, 3, 2, 1\n          ],\n        }\n      },\n      scales: {\n        r: {\n          display: false,\n          min: -15\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 512,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.radar/radius/scriptable.js",
    "content": "module.exports = {\n  config: {\n    type: 'radar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          pointBackgroundColor: '#0000ff',\n          pointRadius: function(ctx) {\n            var value = ctx.dataset.data[ctx.dataIndex] || 0;\n            return value > 4 ? 10\n              : value > -4 ? 5\n              : 2;\n          }\n        },\n        {\n          // option in element (fallback)\n          data: [4, -5, -10, null, 10, 5],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          fill: false,\n        },\n        point: {\n          backgroundColor: '#ff0000',\n          radius: function(ctx) {\n            var value = ctx.dataset.data[ctx.dataIndex] || 0;\n            return value > 4 ? 2\n              : value > -4 ? 5\n              : 10;\n          },\n        }\n      },\n      scales: {\n        r: {\n          display: false,\n          min: -15\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 512,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.radar/radius/value.js",
    "content": "module.exports = {\n  config: {\n    type: 'radar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          pointBackgroundColor: '#0000ff',\n          pointRadius: 6\n        },\n        {\n          // option in element (fallback)\n          data: [4, -5, -10, null, 10, 5],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          fill: false,\n        },\n        point: {\n          backgroundColor: '#00ff00',\n          radius: 3,\n        }\n      },\n      scales: {\n        r: {\n          display: false,\n          min: -15\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 512,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.radar/rotation/indexable.js",
    "content": "module.exports = {\n  config: {\n    type: 'radar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          pointBorderColor: '#00ff00',\n          pointRotation: [\n            0, 30, 60, 90, 120, 150\n          ]\n        },\n        {\n          // option in element (fallback)\n          data: [4, -5, -10, null, 10, 5],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          fill: false,\n        },\n        point: {\n          borderColor: '#ff0000',\n          borderWidth: 10,\n          pointStyle: 'line',\n          rotation: [\n            150, 120, 90, 60, 30, 0\n          ],\n        }\n      },\n      scales: {\n        r: {\n          display: false,\n          min: -15\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 512,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.radar/rotation/scriptable.js",
    "content": "module.exports = {\n  config: {\n    type: 'radar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          pointBorderColor: '#0000ff',\n          pointRotation: function(ctx) {\n            var value = ctx.dataset.data[ctx.dataIndex] || 0;\n            return value > 4 ? 120\n              : value > -4 ? 60\n              : 0;\n          }\n        },\n        {\n          // option in element (fallback)\n          data: [4, -5, -10, null, 10, 5],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          fill: false,\n        },\n        point: {\n          borderColor: '#ff0000',\n          rotation: function(ctx) {\n            var value = ctx.dataset.data[ctx.dataIndex] || 0;\n            return value > 4 ? 0\n              : value > -4 ? 60\n              : 120;\n          },\n          pointStyle: 'line',\n          radius: 10,\n        }\n      },\n      scales: {\n        r: {\n          display: false,\n          min: -15\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 512,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.radar/rotation/value.js",
    "content": "module.exports = {\n  config: {\n    type: 'radar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          pointBorderColor: '#0000ff',\n          pointRotation: 90\n        },\n        {\n          // option in element (fallback)\n          data: [4, -5, -10, null, 10, 5],\n        }\n      ]\n    },\n    options: {\n      elements: {\n        line: {\n          fill: false,\n        },\n        point: {\n          borderColor: '#00ff00',\n          pointStyle: 'line',\n          radius: 10,\n          rotation: 0,\n        }\n      },\n      scales: {\n        r: {\n          display: false,\n          min: -15\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 512,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.radar/showLine/value.js",
    "content": "module.exports = {\n  config: {\n    type: 'radar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          // option in dataset\n          data: [0, 5, 10, null, -10, -5],\n          backgroundColor: '#ff0000',\n          fill: false,\n          showLine: true\n        },\n        {\n          // option in element (fallback)\n          data: [4, -5, -10, null, 10, 5]\n        },\n        {\n          data: [1, 1, 1, 1, 1, 1],\n          showLine: true,\n          backgroundColor: 'rgba(0,0,255,0.5)'\n        }\n      ]\n    },\n    options: {\n      showLine: false,\n      elements: {\n        line: {\n          borderColor: '#ff0000',\n          backgroundColor: 'rgba(0,255,0,0.5)',\n          fill: true\n        }\n      },\n      scales: {\n        r: {\n          display: false,\n          min: -15\n        }\n      },\n      plugins: {\n        legend: false,\n        title: false,\n        tooltip: false,\n        filler: true\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 512,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.radar/startAngle/135.js",
    "content": "module.exports = {\n  config: {\n    type: 'radar',\n    data: {\n      datasets: [{\n        data: [6, 3, 2, 3],\n        borderWidth: 3,\n        borderColor: 'blue'\n      }],\n      labels: [['label 1', 'line 2'], ['label 2', 'line 2'], ['label 3', 'line 2'], ['label 4', 'line 2']]\n    },\n    options: {\n      scales: {\n        r: {\n          min: 0,\n          startAngle: 135,\n          pointLabels: {\n            display: true\n          },\n          grid: {\n            circular: true\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.radar/startAngle/180.js",
    "content": "module.exports = {\n  config: {\n    type: 'radar',\n    data: {\n      datasets: [{\n        data: [6, 3, 2, 3],\n        borderWidth: 3,\n        borderColor: 'blue'\n      }],\n      labels: [['label 1', 'line 2'], ['label 2', 'line 2'], ['label 3', 'line 2'], ['label 4', 'line 2']]\n    },\n    options: {\n      scales: {\n        r: {\n          min: 0,\n          startAngle: 180,\n          pointLabels: {\n            display: true\n          },\n          grid: {\n            circular: true\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.radar/startAngle/225.js",
    "content": "module.exports = {\n  config: {\n    type: 'radar',\n    data: {\n      datasets: [{\n        data: [6, 3, 2, 3],\n        borderWidth: 3,\n        borderColor: 'blue'\n      }],\n      labels: [['label 1', 'line 2'], ['label 2', 'line 2'], ['label 3', 'line 2'], ['label 4', 'line 2']]\n    },\n    options: {\n      scales: {\n        r: {\n          min: 0,\n          startAngle: 225,\n          pointLabels: {\n            display: true\n          },\n          grid: {\n            circular: true\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.radar/startAngle/270.js",
    "content": "module.exports = {\n  config: {\n    type: 'radar',\n    data: {\n      datasets: [{\n        data: [6, 3, 2, 3],\n        borderWidth: 3,\n        borderColor: 'blue'\n      }],\n      labels: [['label 1', 'line 2'], ['label 2', 'line 2'], ['label 3', 'line 2'], ['label 4', 'line 2']]\n    },\n    options: {\n      scales: {\n        r: {\n          min: 0,\n          startAngle: 270,\n          pointLabels: {\n            display: true\n          },\n          grid: {\n            circular: true\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.radar/startAngle/315.js",
    "content": "module.exports = {\n  config: {\n    type: 'radar',\n    data: {\n      datasets: [{\n        data: [6, 3, 2, 3],\n        borderWidth: 3,\n        borderColor: 'blue'\n      }],\n      labels: [['label 1', 'line 2'], ['label 2', 'line 2'], ['label 3', 'line 2'], ['label 4', 'line 2']]\n    },\n    options: {\n      scales: {\n        r: {\n          min: 0,\n          startAngle: 315,\n          pointLabels: {\n            display: true\n          },\n          grid: {\n            circular: true\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.radar/startAngle/45.js",
    "content": "module.exports = {\n  config: {\n    type: 'radar',\n    data: {\n      datasets: [{\n        data: [6, 3, 2, 3],\n        borderWidth: 3,\n        borderColor: 'blue'\n      }],\n      labels: [['label 1', 'line 2'], ['label 2', 'line 2'], ['label 3', 'line 2'], ['label 4', 'line 2']]\n    },\n    options: {\n      scales: {\n        r: {\n          min: 0,\n          startAngle: 45,\n          pointLabels: {\n            display: true\n          },\n          grid: {\n            circular: true\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.radar/startAngle/90.js",
    "content": "module.exports = {\n  config: {\n    type: 'radar',\n    data: {\n      datasets: [{\n        data: [6, 3, 2, 3],\n        borderWidth: 3,\n        borderColor: 'blue'\n      }],\n      labels: [['label 1', 'line 2'], ['label 2', 'line 2'], ['label 3', 'line 2'], ['label 4', 'line 2']]\n    },\n    options: {\n      scales: {\n        r: {\n          min: 0,\n          startAngle: 90,\n          pointLabels: {\n            display: true\n          },\n          grid: {\n            circular: true\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.radar/startAngle/default.js",
    "content": "module.exports = {\n  config: {\n    type: 'radar',\n    data: {\n      datasets: [{\n        data: [6, 3, 2, 3],\n        borderWidth: 3,\n        borderColor: 'blue'\n      }],\n      labels: [['label 1', 'line 2'], ['label 2', 'line 2'], ['label 3', 'line 2'], ['label 4', 'line 2']]\n    },\n    options: {\n      scales: {\n        r: {\n          min: 0,\n          pointLabels: {\n            display: true\n          },\n          grid: {\n            circular: true\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.scatter/showLine/changed.js",
    "content": "module.exports = {\n  description: 'showLine option should draw a line if true',\n  config: {\n    type: 'scatter',\n    data: {\n      datasets: [{\n        data: [{x: 10, y: 15}, {x: 15, y: 10}],\n        pointRadius: 10,\n        backgroundColor: 'red',\n        label: 'dataset1'\n      }],\n    },\n    options: {\n      scales: {\n        x: {\n          display: false\n        },\n        y: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      width: 256,\n      height: 256\n    },\n    run(chart) {\n      chart.options.showLine = true;\n      chart.update();\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.scatter/showLine/true.js",
    "content": "module.exports = {\n  description: 'showLine option should draw a line if true',\n  config: {\n    type: 'scatter',\n    data: {\n      datasets: [{\n        data: [{x: 10, y: 15}, {x: 15, y: 10}],\n        pointRadius: 10,\n        backgroundColor: 'red',\n        showLine: true,\n        label: 'dataset1'\n      }],\n    },\n    options: {\n      scales: {\n        x: {\n          display: false\n        },\n        y: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      width: 256,\n      height: 256\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/controller.scatter/showLine/undefined.js",
    "content": "module.exports = {\n  description: 'showLine option should not draw a line if undefined',\n  config: {\n    type: 'scatter',\n    data: {\n      datasets: [{\n        data: [{x: 10, y: 15}, {x: 15, y: 10}],\n        pointRadius: 10,\n        backgroundColor: 'red',\n        label: 'dataset1'\n      }],\n    },\n    options: {\n      scales: {\n        x: {\n          display: false\n        },\n        y: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      width: 256,\n      height: 256\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.datasetController/stacked-initial-render.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5, 6],\n      datasets: [\n        {\n          // option in dataset\n          data: [9, 13, 15, 25, 22, 15, 21],\n          stack: 'construction_stack',\n          borderWidth: 10,\n          borderColor: 'rgb(54, 162, 235)'\n        },\n        {\n          data: [9, 13, 15, 25, 22, 15, 21],\n          stack: 'construction_stack',\n          borderWidth: 10,\n          borderColor: 'rgb(255, 99, 132)'\n        }\n      ]\n    },\n    options: {\n      scales: {\n        x: {\n          ticks: {\n            display: false\n          }\n        },\n        y: {\n          ticks: {\n            display: false\n          }\n        }\n      },\n      plugins: {\n        legend: false,\n        title: false,\n        tooltip: false,\n        filler: false\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 512,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.interaction/drawActiveElementsOnTop-false.js",
    "content": "module.exports = {\n  config: {\n    type: 'bubble',\n    data: {\n      datasets: [{\n        data: [\n          {x: 1, y: 1, r: 80},\n          {x: 1, y: 1, r: 20}\n        ],\n        drawActiveElementsOnTop: false,\n        backgroundColor: (ctx) => (ctx.dataIndex === 1 ? 'red' : 'blue'),\n        hoverBackgroundColor: 'yellow',\n        hoverRadius: 0,\n      }]\n    },\n    options: {\n      scales: {\n        x: {\n          display: false\n        },\n        y: {\n          display: false\n        },\n      },\n      plugins: {\n        tooltip: false,\n        legend: false\n      },\n    }\n  },\n  options: {\n    canvas: {\n      width: 256,\n      height: 256\n    },\n    async run(chart) {\n      const point = chart.getDatasetMeta(0).data[0];\n      await jasmine.triggerMouseEvent(chart, 'click', {y: point.y, x: point.x + 25});\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.interaction/nearest-partial-bar.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      labels: ['a', 'b', 'c'],\n      datasets: [\n        {\n          data: [220, 250, 225],\n        },\n      ],\n    },\n    options: {\n      events: ['click'],\n      interaction: {\n        mode: 'nearest'\n      },\n      plugins: {\n        tooltip: true,\n        legend: false\n      },\n      scales: {\n        y: {\n          beginAtZero: false\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      width: 256,\n      height: 256\n    },\n    async run(chart) {\n      const point = {\n        x: chart.chartArea.left + chart.chartArea.width / 2,\n        y: chart.chartArea.top + chart.chartArea.height / 2,\n      };\n      await jasmine.triggerMouseEvent(chart, 'click', point);\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.interaction/nearest-point-behind-scale.js",
    "content": "module.exports = {\n  config: {\n    type: 'scatter',\n    data: {\n      datasets: [{\n        data: [{x: 1, y: 1}, {x: 48, y: 1}]\n      }]\n    },\n    options: {\n      events: ['click'],\n      interaction: {\n        mode: 'nearest',\n        intersect: false\n      },\n      plugins: {\n        tooltip: true,\n        legend: false\n      },\n      scales: {\n        x: {\n          min: 5,\n          max: 50\n        },\n        y: {\n          min: 0,\n          max: 2\n        }\n      },\n      layout: {\n        padding: 50\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      width: 256,\n      height: 256\n    },\n    async run(chart) {\n      const point = chart.getDatasetMeta(0).data[0];\n      await jasmine.triggerMouseEvent(chart, 'click', {y: point.y, x: chart.chartArea.left});\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.layouts/hidden-vertical-boxes.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      datasets: [\n        {data: [10, 5, 0, 25, 78, -10]}\n      ],\n      labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5', '']\n    },\n    options: {\n      plugins: {\n        legend: false\n      },\n      scales: {\n        x: {\n          display: false\n        },\n        y: {\n          type: 'linear',\n          position: 'left',\n          ticks: {\n            callback: function(value) {\n              return value + ' very long unit!';\n            },\n          }\n        },\n        y1: {\n          type: 'linear',\n          position: 'left',\n          display: false\n        },\n        y2: {\n          type: 'linear',\n          position: 'left',\n          display: false\n        },\n        y3: {\n          type: 'linear',\n          position: 'left',\n          display: false\n        },\n        y4: {\n          type: 'linear',\n          position: 'left',\n          display: false\n        },\n        y5: {\n          type: 'linear',\n          position: 'left',\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 256,\n      width: 256\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.layouts/long-labels.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      datasets: [\n        {data: [10, 5, 0, 25, 78, -10]}\n      ],\n      labels: ['tick1 is very long one', 'tick2', 'tick3', 'tick4', 'tick5', 'tick6 is very long one']\n    },\n    options: {\n      plugins: {\n        legend: false\n      },\n      scales: {\n        x: {\n          type: 'category',\n          ticks: {\n            maxRotation: 0,\n            autoSkip: false\n          }\n        },\n        y: {\n          type: 'linear',\n          position: 'right'\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 150,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.layouts/no-boxes-all-padding.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: [0],\n      datasets: [{\n        data: [0],\n        radius: 16,\n        borderWidth: 0,\n        backgroundColor: 'red'\n      }],\n    },\n    options: {\n      plugins: {\n        legend: false,\n        tooltip: false,\n        title: false,\n        filler: false\n      },\n      scales: {\n        x: {\n          display: false,\n          offset: true\n        },\n        y: {\n          display: false\n        }\n      },\n      layout: {\n        padding: 16\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 32,\n      width: 32\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.layouts/refit-vertical-boxes.js",
    "content": "module.exports = {\n  tolerance: 0.002,\n  config: {\n    type: 'line',\n    data: {\n      labels: [\n        'Aaron',\n        'Adam',\n        'Albert',\n        'Alex',\n        'Allan',\n        'Aman',\n        'Anthony',\n        'Autoenrolment',\n        'Avril',\n        'Bernard'\n      ],\n      datasets: [{\n        backgroundColor: 'rgba(252,233,79,0.5)',\n        borderColor: 'rgba(252,233,79,1)',\n        borderWidth: 1,\n        data: [101,\n          185,\n          24,\n          311,\n          17,\n          21,\n          462,\n          340,\n          140,\n          24\n        ]\n      }]\n    },\n    options: {\n      maintainAspectRatio: false,\n      plugins: {\n        legend: true,\n        title: {\n          display: true,\n          text: 'test'\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 185,\n      width: 185\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.layouts/scriptable.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      datasets: [\n        {data: [10, 5, 0, 25, 78, -10]}\n      ],\n      labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5', 'tick6']\n    },\n    options: {\n      layout: {\n        padding: function(ctx) {\n          // 10% padding\n          const horizontalPadding = ctx.chart.width * 0.1;\n          const verticalPadding = ctx.chart.height * 0.1;\n          return {\n            top: verticalPadding,\n            right: horizontalPadding,\n            bottom: verticalPadding,\n            left: horizontalPadding\n          };\n        }\n      },\n      plugins: {\n        legend: false\n      },\n      scales: {\n        x: {\n          type: 'category',\n          ticks: {\n            maxRotation: 0,\n            autoSkip: false\n          }\n        },\n        y: {\n          type: 'linear',\n          position: 'right'\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 150,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.layouts/stacked-boxes-max-index-without-clip.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      datasets: [\n        {data: [{x: 5, y: 1}, {x: 10, y: 2}, {x: 5, y: 3}], borderColor: 'red'},\n        {data: [{x: 5, y: 1}, {x: 10, y: 2}, {x: 5, y: 3}], yAxisID: 'y1', xAxisID: 'x1', borderColor: 'green'},\n        {data: [{x: 5, y: 1}, {x: 10, y: 2}, {x: 5, y: 3}], yAxisID: 'y2', xAxisID: 'x2', borderColor: 'blue'},\n      ],\n      labels: ['tick1', 'tick2', 'tick3']\n    },\n    options: {\n      plugins: false,\n      scales: {\n        x: {\n          type: 'linear',\n          position: 'bottom',\n          stack: '1',\n          offset: true,\n          clip: false,\n          bounds: 'data',\n          border: {\n            color: 'red'\n          },\n          ticks: {\n            autoSkip: false,\n            maxRotation: 0,\n            count: 3\n          },\n          max: 7\n        },\n        x1: {\n          type: 'linear',\n          position: 'bottom',\n          stack: '1',\n          offset: true,\n          clip: false,\n          bounds: 'data',\n          border: {\n            color: 'green'\n          },\n          ticks: {\n            autoSkip: false,\n            maxRotation: 0,\n            count: 3\n          },\n          max: 7\n        },\n        x2: {\n          type: 'linear',\n          position: 'bottom',\n          stack: '1',\n          offset: true,\n          clip: false,\n          bounds: 'data',\n          border: {\n            color: 'blue'\n          },\n          ticks: {\n            autoSkip: false,\n            maxRotation: 0,\n            count: 3\n          },\n          max: 7\n        },\n        y: {\n          type: 'linear',\n          position: 'left',\n          stack: '1',\n          offset: true,\n          clip: false,\n          border: {\n            color: 'red'\n          },\n          ticks: {\n            precision: 0\n          }\n        },\n        y1: {\n          type: 'linear',\n          position: 'left',\n          stack: '1',\n          offset: true,\n          clip: false,\n          border: {\n            color: 'green'\n          },\n          ticks: {\n            precision: 0\n          }\n        },\n        y2: {\n          type: 'linear',\n          position: 'left',\n          stack: '1',\n          offset: true,\n          clip: false,\n          border: {\n            color: 'blue',\n          },\n          ticks: {\n            precision: 0\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 384,\n      width: 384\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.layouts/stacked-boxes-max-index.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      datasets: [\n        {data: [{x: 5, y: 1}, {x: 10, y: 2}, {x: 5, y: 3}], borderColor: 'red'},\n        {data: [{x: 5, y: 1}, {x: 10, y: 2}, {x: 5, y: 3}], yAxisID: 'y1', xAxisID: 'x1', borderColor: 'green'},\n        {data: [{x: 5, y: 1}, {x: 10, y: 2}, {x: 5, y: 3}], yAxisID: 'y2', xAxisID: 'x2', borderColor: 'blue'},\n      ],\n      labels: ['tick1', 'tick2', 'tick3']\n    },\n    options: {\n      plugins: false,\n      scales: {\n        x: {\n          type: 'linear',\n          position: 'bottom',\n          stack: '1',\n          offset: true,\n          bounds: 'data',\n          border: {\n            color: 'red'\n          },\n          ticks: {\n            autoSkip: false,\n            maxRotation: 0,\n            count: 3\n          },\n          max: 7\n        },\n        x1: {\n          type: 'linear',\n          position: 'bottom',\n          stack: '1',\n          offset: true,\n          bounds: 'data',\n          border: {\n            color: 'green'\n          },\n          ticks: {\n            autoSkip: false,\n            maxRotation: 0,\n            count: 3\n          },\n          max: 7\n        },\n        x2: {\n          type: 'linear',\n          position: 'bottom',\n          stack: '1',\n          offset: true,\n          bounds: 'data',\n          border: {\n            color: 'blue'\n          },\n          ticks: {\n            autoSkip: false,\n            maxRotation: 0,\n            count: 3\n          },\n          max: 7\n        },\n        y: {\n          type: 'linear',\n          position: 'left',\n          stack: '1',\n          offset: true,\n          border: {\n            color: 'red'\n          },\n          ticks: {\n            precision: 0\n          }\n        },\n        y1: {\n          type: 'linear',\n          position: 'left',\n          stack: '1',\n          offset: true,\n          border: {\n            color: 'green'\n          },\n          ticks: {\n            precision: 0\n          }\n        },\n        y2: {\n          type: 'linear',\n          position: 'left',\n          stack: '1',\n          offset: true,\n          border: {\n            color: 'blue',\n          },\n          ticks: {\n            precision: 0\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 384,\n      width: 384\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.layouts/stacked-boxes-max-without-clip.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      datasets: [\n        {data: [{x: 1, y: 5}, {x: 2, y: 10}, {x: 3, y: 5}], borderColor: 'red'},\n        {data: [{x: 1, y: 5}, {x: 2, y: 10}, {x: 3, y: 5}], yAxisID: 'y1', xAxisID: 'x1', borderColor: 'green'},\n        {data: [{x: 1, y: 5}, {x: 2, y: 10}, {x: 3, y: 5}], yAxisID: 'y2', xAxisID: 'x2', borderColor: 'blue'},\n      ],\n      labels: ['tick1', 'tick2', 'tick3']\n    },\n    options: {\n      plugins: false,\n      scales: {\n        x: {\n          type: 'linear',\n          position: 'bottom',\n          stack: '1',\n          offset: true,\n          clip: false,\n          bounds: 'data',\n          border: {\n            color: 'red'\n          },\n          ticks: {\n            autoSkip: false,\n            maxRotation: 0,\n            count: 3\n          }\n        },\n        x1: {\n          type: 'linear',\n          position: 'bottom',\n          stack: '1',\n          offset: true,\n          clip: false,\n          bounds: 'data',\n          border: {\n            color: 'green'\n          },\n          ticks: {\n            autoSkip: false,\n            maxRotation: 0,\n            count: 3\n          }\n        },\n        x2: {\n          type: 'linear',\n          position: 'bottom',\n          stack: '1',\n          offset: true,\n          clip: false,\n          bounds: 'data',\n          border: {\n            color: 'blue'\n          },\n          ticks: {\n            autoSkip: false,\n            maxRotation: 0,\n            count: 3\n          }\n        },\n        y: {\n          type: 'linear',\n          position: 'left',\n          stack: '1',\n          offset: true,\n          clip: false,\n          border: {\n            color: 'red'\n          },\n          ticks: {\n            precision: 0\n          },\n          max: 7\n        },\n        y1: {\n          type: 'linear',\n          position: 'left',\n          stack: '1',\n          offset: true,\n          clip: false,\n          border: {\n            color: 'green'\n          },\n          ticks: {\n            precision: 0\n          },\n          max: 7\n        },\n        y2: {\n          type: 'linear',\n          position: 'left',\n          stack: '1',\n          offset: true,\n          clip: false,\n          border: {\n            color: 'blue',\n          },\n          ticks: {\n            precision: 0\n          },\n          max: 7\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 384,\n      width: 384\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.layouts/stacked-boxes-max.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      datasets: [\n        {data: [{x: 1, y: 5}, {x: 2, y: 10}, {x: 3, y: 5}], borderColor: 'red'},\n        {data: [{x: 1, y: 5}, {x: 2, y: 10}, {x: 3, y: 5}], yAxisID: 'y1', xAxisID: 'x1', borderColor: 'green'},\n        {data: [{x: 1, y: 5}, {x: 2, y: 10}, {x: 3, y: 5}], yAxisID: 'y2', xAxisID: 'x2', borderColor: 'blue'},\n      ],\n      labels: ['tick1', 'tick2', 'tick3']\n    },\n    options: {\n      plugins: false,\n      scales: {\n        x: {\n          type: 'linear',\n          position: 'bottom',\n          stack: '1',\n          offset: true,\n          bounds: 'data',\n          border: {\n            color: 'red'\n          },\n          ticks: {\n            autoSkip: false,\n            maxRotation: 0,\n            count: 3\n          }\n        },\n        x1: {\n          type: 'linear',\n          position: 'bottom',\n          stack: '1',\n          offset: true,\n          bounds: 'data',\n          border: {\n            color: 'green'\n          },\n          ticks: {\n            autoSkip: false,\n            maxRotation: 0,\n            count: 3\n          }\n        },\n        x2: {\n          type: 'linear',\n          position: 'bottom',\n          stack: '1',\n          offset: true,\n          bounds: 'data',\n          border: {\n            color: 'blue'\n          },\n          ticks: {\n            autoSkip: false,\n            maxRotation: 0,\n            count: 3\n          }\n        },\n        y: {\n          type: 'linear',\n          position: 'left',\n          stack: '1',\n          offset: true,\n          border: {\n            color: 'red'\n          },\n          ticks: {\n            precision: 0\n          },\n          max: 7\n        },\n        y1: {\n          type: 'linear',\n          position: 'left',\n          stack: '1',\n          offset: true,\n          border: {\n            color: 'green'\n          },\n          ticks: {\n            precision: 0\n          },\n          max: 7\n        },\n        y2: {\n          type: 'linear',\n          position: 'left',\n          stack: '1',\n          offset: true,\n          border: {\n            color: 'blue',\n          },\n          ticks: {\n            precision: 0\n          },\n          max: 7\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 384,\n      width: 384\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.layouts/stacked-boxes-with-weight.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      datasets: [\n        {data: [{x: 1, y: 1}, {x: 2, y: 2}, {x: 3, y: 3}], borderColor: 'red'},\n        {data: [{x: 1, y: 1}, {x: 2, y: 2}, {x: 3, y: 3}], yAxisID: 'y1', xAxisID: 'x1', borderColor: 'green'},\n        {data: [{x: 1, y: 1}, {x: 2, y: 2}, {x: 3, y: 3}], yAxisID: 'y2', xAxisID: 'x2', borderColor: 'blue'},\n      ],\n      labels: ['tick1', 'tick2', 'tick3']\n    },\n    options: {\n      plugins: false,\n      scales: {\n        x: {\n          type: 'linear',\n          position: 'bottom',\n          stack: '1',\n          stackWeight: 2,\n          offset: true,\n          bounds: 'data',\n          border: {\n            color: 'red'\n          },\n          ticks: {\n            autoSkip: false,\n            maxRotation: 0,\n            count: 3\n          }\n        },\n        x1: {\n          type: 'linear',\n          position: 'bottom',\n          stack: '1',\n          stackWeight: 2,\n          offset: true,\n          bounds: 'data',\n          border: {\n            color: 'green'\n          },\n          ticks: {\n            autoSkip: false,\n            maxRotation: 0,\n            count: 3\n          }\n        },\n        x2: {\n          type: 'linear',\n          position: 'bottom',\n          stack: '1',\n          stackWeight: 6,\n          offset: true,\n          bounds: 'data',\n          border: {\n            color: 'blue'\n          },\n          ticks: {\n            autoSkip: false,\n            maxRotation: 0,\n            count: 3\n          }\n        },\n        y: {\n          type: 'linear',\n          position: 'left',\n          stack: '1',\n          stackWeight: 2,\n          offset: true,\n          border: {\n            color: 'red'\n          },\n          ticks: {\n            precision: 0\n          }\n        },\n        y1: {\n          type: 'linear',\n          position: 'left',\n          stack: '1',\n          offset: true,\n          stackWeight: 2,\n          border: {\n            color: 'green'\n          },\n          ticks: {\n            precision: 0\n          }\n        },\n        y2: {\n          type: 'linear',\n          position: 'left',\n          stack: '1',\n          stackWeight: 3,\n          offset: true,\n          border: {\n            color: 'blue'\n          },\n          ticks: {\n            precision: 0\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 384,\n      width: 384\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.layouts/stacked-boxes.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      datasets: [\n        {data: [{x: 1, y: 1}, {x: 2, y: 2}, {x: 3, y: 3}], borderColor: 'red'},\n        {data: [{x: 1, y: 1}, {x: 2, y: 2}, {x: 3, y: 3}], yAxisID: 'y1', xAxisID: 'x1', borderColor: 'green'},\n        {data: [{x: 1, y: 1}, {x: 2, y: 2}, {x: 3, y: 3}], yAxisID: 'y2', xAxisID: 'x2', borderColor: 'blue'},\n      ],\n      labels: ['tick1', 'tick2', 'tick3']\n    },\n    options: {\n      plugins: false,\n      scales: {\n        x: {\n          type: 'linear',\n          position: 'bottom',\n          stack: '1',\n          offset: true,\n          bounds: 'data',\n          border: {\n            color: 'red'\n          },\n          ticks: {\n            autoSkip: false,\n            maxRotation: 0,\n            count: 3\n          }\n        },\n        x1: {\n          type: 'linear',\n          position: 'bottom',\n          stack: '1',\n          offset: true,\n          bounds: 'data',\n          border: {\n            color: 'green'\n          },\n          ticks: {\n            autoSkip: false,\n            maxRotation: 0,\n            count: 3\n          }\n        },\n        x2: {\n          type: 'linear',\n          position: 'bottom',\n          stack: '1',\n          offset: true,\n          bounds: 'data',\n          border: {\n            color: 'blue'\n          },\n          ticks: {\n            autoSkip: false,\n            maxRotation: 0,\n            count: 3\n          }\n        },\n        y: {\n          type: 'linear',\n          position: 'left',\n          stack: '1',\n          offset: true,\n          border: {\n            color: 'red'\n          },\n          ticks: {\n            precision: 0\n          }\n        },\n        y1: {\n          type: 'linear',\n          position: 'left',\n          stack: '1',\n          offset: true,\n          border: {\n            color: 'green'\n          },\n          ticks: {\n            precision: 0\n          }\n        },\n        y2: {\n          type: 'linear',\n          position: 'left',\n          stack: '1',\n          offset: true,\n          border: {\n            color: 'blue',\n          },\n          ticks: {\n            precision: 0\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 384,\n      width: 384\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.scale/autoSkip/fit-after.js",
    "content": "module.exports = {\n  description: 'https://github.com/chartjs/Chart.js/issues/3694',\n  tolerance: 0.002,\n  config: {\n    type: 'line',\n    data: {\n      labels: [\n        'Aaron',\n        'Adam',\n        'Albert',\n        'Alex',\n        'Allan',\n        'Aman',\n        'Anthony',\n        'Autoenrolment',\n        'Avril',\n        'Bernard'\n      ],\n      datasets: [{\n        backgroundColor: 'rgba(252,233,79,0.5)',\n        borderColor: 'rgba(252,233,79,1)',\n        borderWidth: 1,\n        data: [101,\n          185,\n          24,\n          311,\n          17,\n          21,\n          462,\n          340,\n          140,\n          24\n        ]\n      }],\n    },\n    options: {\n      scales: {\n        x: {\n          backgroundColor: '#eee'\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      width: 185,\n      height: 185\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.scale/autoSkip/no-offset.js",
    "content": "module.exports = {\n  description: 'https://github.com/chartjs/Chart.js/issues/8611',\n  config: {\n    type: 'line',\n    data: {\n      labels: ['Red Red Red', 'Blue Blue Blue', 'Black Black Black', 'Pink Pink Pink'],\n      datasets: [\n        {\n          label: '# of Votes',\n          data: [12, 19, 3, 5]\n        },\n      ]\n    },\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      width: 470,\n      height: 128\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.scale/autoSkip/offset.js",
    "content": "module.exports = {\n  description: 'https://github.com/chartjs/Chart.js/issues/8611',\n  config: {\n    type: 'bar',\n    data: {\n      labels: ['Red Red Red', 'Blue Blue Blue', 'Black Black Black', 'Pink Pink Pink'],\n      datasets: [\n        {\n          label: '# of Votes',\n          data: [12, 19, 3, 5]\n        },\n      ]\n    },\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      width: 506,\n      height: 128\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.scale/backgroundColor.js",
    "content": "const ticks = {\n  display: false\n};\nconst grid = {\n  display: false\n};\nconst title = {\n  display: true,\n  test: ''\n};\nmodule.exports = {\n  config: {\n    type: 'line',\n    options: {\n      events: [],\n      scales: {\n        top: {\n          type: 'linear',\n          backgroundColor: 'red',\n          position: 'top',\n          ticks,\n          grid,\n          title\n        },\n        left: {\n          type: 'linear',\n          backgroundColor: 'green',\n          position: 'left',\n          ticks,\n          grid,\n          title\n        },\n        bottom: {\n          type: 'linear',\n          backgroundColor: 'blue',\n          position: 'bottom',\n          ticks,\n          grid,\n          title\n        },\n        right: {\n          type: 'linear',\n          backgroundColor: 'gray',\n          position: 'right',\n          ticks,\n          grid,\n          title\n        },\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 256\n    },\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.scale/border-behind-elements.js",
    "content": "module.exports = {\n  config: {\n    type: 'bubble',\n    data: {\n      datasets: [\n        {\n          label: '# of Votes',\n          data: [{x: 19, y: 3, r: 3}, {x: 2, y: 2, r: 60}],\n          radius: 100,\n          backgroundColor: 'pink'\n        }\n      ]\n    },\n    options: {\n      plugins: {\n        legend: {\n          display: false\n        }\n      },\n      scales: {\n        y: {\n          ticks: {\n            display: false\n          },\n          border: {\n            color: 'red',\n            width: 5\n          }\n        },\n        x: {\n          ticks: {\n            display: false\n          },\n          border: {\n            color: 'red',\n            width: 5\n          }\n        }\n      }\n    }\n  },\n\n  options: {\n    canvas: {\n      height: 512,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.scale/cartesian-axis-border-settings.json",
    "content": "{\n    \"config\": {\n        \"type\": \"scatter\",\n        \"data\": {\n            \"datasets\": [{\n                \"data\": [{\n                    \"x\": -20,\n                    \"y\": -30\n                }, {\n                    \"x\": 0,\n                    \"y\": 0\n                }, {\n                    \"x\": 20,\n                    \"y\": 15\n                }]\n            }]\n        },\n        \"options\": {\n            \"scales\": {\n                \"x\": {\n                    \"axis\": \"x\",\n                    \"min\": -100,\n                    \"max\": 100,\n                    \"grid\": {\n                        \"color\": \"red\",\n                        \"drawOnChartArea\": false\n                    },\n                    \"border\": {\n                        \"display\": true,\n                        \"color\": \"blue\",\n                        \"width\": 5\n                    },\n                    \"ticks\": {\n                        \"display\": false\n                    }\n                },\n                \"y\": {\n                    \"axis\": \"y\",\n                    \"min\": -100,\n                    \"max\": 100,\n                    \"border\": {\n                        \"color\": \"red\"\n                    },\n                    \"grid\": {\n                        \"color\": \"red\",\n                        \"drawOnChartArea\": false\n                    },\n                    \"ticks\": {\n                        \"display\": false\n                    }\n                }\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/core.scale/crossAlignment/cross-align-bottom-center.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      datasets: [{\n        data: [1, 2, 3],\n      }],\n      labels: [['Label1', 'line 2', 'line3'], 'Label2', 'Label3']\n    },\n    options: {\n      scales: {\n        x: {\n          ticks: {\n            crossAlign: 'center',\n          },\n        },\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.scale/crossAlignment/cross-align-bottom-far.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      datasets: [{\n        data: [1, 2, 3],\n      }],\n      labels: [['Label1', 'line 2', 'line3'], 'Label2', 'Label3']\n    },\n    options: {\n      scales: {\n        x: {\n          ticks: {\n            crossAlign: 'far',\n          },\n        },\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.scale/crossAlignment/cross-align-bottom-near.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      datasets: [{\n        data: [1, 2, 3],\n      }],\n      labels: [['Label1', 'line 2', 'line3'], 'Label2', 'Label3']\n    },\n    options: {\n      scales: {\n        x: {\n          ticks: {\n            crossAlign: 'near',\n          },\n        },\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.scale/crossAlignment/cross-align-left-center.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      datasets: [{\n        data: [1, 2, 3],\n      }],\n      labels: ['Long long label 1', 'Label2', 'Label3']\n    },\n    options: {\n      indexAxis: 'y',\n      scales: {\n        y: {\n          position: 'left',\n          ticks: {\n            crossAlign: 'center',\n          },\n        },\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.scale/crossAlignment/cross-align-left-far-clipped.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      datasets: [{\n        data: [1, 2, 3],\n      }],\n      labels: ['Long long long long label 1', 'Label 2', 'Less more longer label 3']\n    },\n    options: {\n      indexAxis: 'y',\n      scales: {\n        y: {\n          position: 'left',\n          ticks: {\n            crossAlign: 'far',\n          },\n          afterFit: axis => {\n            axis.width = 64;\n          },\n        },\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.scale/crossAlignment/cross-align-left-far.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      datasets: [{\n        data: [1, 2, 3],\n      }],\n      labels: ['Long long label 1', 'Label2', 'Label3']\n    },\n    options: {\n      indexAxis: 'y',\n      scales: {\n        y: {\n          position: 'left',\n          ticks: {\n            crossAlign: 'far',\n          },\n        },\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.scale/crossAlignment/cross-align-left-near.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      datasets: [{\n        data: [1, 2, 3],\n      }],\n      labels: ['Long long label 1', 'Label2', 'Label3']\n    },\n    options: {\n      indexAxis: 'y',\n      scales: {\n        y: {\n          position: 'left',\n          ticks: {\n            crossAlign: 'near',\n          },\n        },\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.scale/crossAlignment/cross-align-right-center.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      datasets: [{\n        data: [1, 2, 3],\n      }],\n      labels: ['Long long label 1', 'Label2', 'Label3']\n    },\n    options: {\n      indexAxis: 'y',\n      scales: {\n        y: {\n          position: 'right',\n          ticks: {\n            crossAlign: 'center',\n          },\n        },\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.scale/crossAlignment/cross-align-right-far-clipped.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      datasets: [{\n        data: [1, 2, 3],\n      }],\n      labels: ['Long long long long label 1', 'Label 2', 'Less more longer label 3']\n    },\n    options: {\n      indexAxis: 'y',\n      scales: {\n        y: {\n          position: 'right',\n          ticks: {\n            crossAlign: 'far',\n          },\n          afterFit: axis => {\n            axis.width = 64;\n          },\n        },\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  },\n  tolerance: 0.1\n};\n"
  },
  {
    "path": "test/fixtures/core.scale/crossAlignment/cross-align-right-far.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      datasets: [{\n        data: [1, 2, 3],\n      }],\n      labels: ['Long long label 1', 'Label2', 'Label3']\n    },\n    options: {\n      indexAxis: 'y',\n      scales: {\n        y: {\n          position: 'right',\n          ticks: {\n            crossAlign: 'far',\n          },\n        },\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.scale/crossAlignment/cross-align-right-near.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      datasets: [{\n        data: [1, 2, 3],\n      }],\n      labels: ['Long long label 1', 'Label2', 'Label3']\n    },\n    options: {\n      indexAxis: 'y',\n      scales: {\n        y: {\n          position: 'right',\n          ticks: {\n            crossAlign: 'near',\n          },\n        },\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.scale/crossAlignment/cross-align-top-center.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      datasets: [{\n        data: [1, 2, 3],\n      }],\n      labels: [['Label1', 'line 2', 'line3'], 'Label2', 'Label3']\n    },\n    options: {\n      scales: {\n        x: {\n          position: 'top',\n          ticks: {\n            crossAlign: 'center',\n          },\n        },\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.scale/crossAlignment/cross-align-top-far.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      datasets: [{\n        data: [1, 2, 3],\n      }],\n      labels: [['Label1', 'line 2', 'line3'], 'Label2', 'Label3']\n    },\n    options: {\n      scales: {\n        x: {\n          position: 'top',\n          ticks: {\n            crossAlign: 'far',\n          },\n        },\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.scale/crossAlignment/cross-align-top-near.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      datasets: [{\n        data: [1, 2, 3],\n      }],\n      labels: [['Label1', 'line 2', 'line3'], 'Label2', 'Label3']\n    },\n    options: {\n      scales: {\n        x: {\n          position: 'top',\n          ticks: {\n            crossAlign: 'near',\n          },\n        },\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.scale/crossAlignment/mirror-cross-align-left-center.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      datasets: [{\n        data: [1, 2, 3],\n      }],\n      labels: ['Long long label 1', 'Label2', 'Label3']\n    },\n    options: {\n      indexAxis: 'y',\n      scales: {\n        y: {\n          position: 'left',\n          ticks: {\n            mirror: true,\n            crossAlign: 'center',\n          },\n        },\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.scale/crossAlignment/mirror-cross-align-left-far.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      datasets: [{\n        data: [1, 2, 3],\n      }],\n      labels: ['Long long label 1', 'Label2', 'Label3']\n    },\n    options: {\n      indexAxis: 'y',\n      scales: {\n        y: {\n          position: 'left',\n          ticks: {\n            mirror: true,\n            crossAlign: 'far',\n          },\n        },\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.scale/crossAlignment/mirror-cross-align-left-near.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      datasets: [{\n        data: [1, 2, 3],\n      }],\n      labels: ['Long long label 1', 'Label2', 'Label3']\n    },\n    options: {\n      indexAxis: 'y',\n      scales: {\n        y: {\n          position: 'left',\n          ticks: {\n            mirror: true,\n            crossAlign: 'near',\n          },\n        },\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.scale/crossAlignment/mirror-cross-align-right-center.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      datasets: [{\n        data: [1, 2, 3],\n      }],\n      labels: ['Long long label 1', 'Label2', 'Label3']\n    },\n    options: {\n      indexAxis: 'y',\n      scales: {\n        y: {\n          position: 'right',\n          ticks: {\n            mirror: true,\n            crossAlign: 'center',\n          },\n        },\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.scale/crossAlignment/mirror-cross-align-right-far.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      datasets: [{\n        data: [1, 2, 3],\n      }],\n      labels: ['Long long label 1', 'Label2', 'Label3']\n    },\n    options: {\n      indexAxis: 'y',\n      scales: {\n        y: {\n          position: 'right',\n          ticks: {\n            mirror: true,\n            crossAlign: 'far',\n          },\n        },\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.scale/crossAlignment/mirror-cross-align-right-near.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      datasets: [{\n        data: [1, 2, 3],\n      }],\n      labels: ['Long long label 1', 'Label2', 'Label3']\n    },\n    options: {\n      indexAxis: 'y',\n      scales: {\n        y: {\n          position: 'right',\n          ticks: {\n            mirror: true,\n            crossAlign: 'near',\n          },\n        },\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.scale/grid/border-over-grid.js",
    "content": "module.exports = {\n  config: {\n    type: 'scatter',\n    options: {\n      scales: {\n        x: {\n          position: {y: 0},\n          min: -10,\n          max: 10,\n          border: {\n            color: 'black',\n            width: 5\n          },\n          grid: {\n            color: 'lightGray',\n            lineWidth: 3,\n          },\n          ticks: {\n            display: false\n          },\n        },\n        y: {\n          position: {x: 0},\n          min: -10,\n          max: 10,\n          border: {\n            color: 'black',\n            width: 5\n          },\n          grid: {\n            color: 'lightGray',\n            lineWidth: 3,\n          },\n          ticks: {\n            display: false\n          },\n        }\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.scale/grid/colors.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: ['1', '2', '3', '4', '5', '6'],\n      datasets: [{\n        label: '# of Votes',\n        data: [12, 19, 3, 5, 2, 3]\n      }],\n    },\n    options: {\n      scales: {\n        x: {\n          ticks: {\n            display: false\n          },\n          border: {\n            color: 'blue',\n            width: 2,\n          },\n          grid: {\n            color: 'green',\n            drawTicks: false,\n          }\n        },\n        y: {\n          ticks: {\n            display: false\n          },\n          border: {\n            color: 'black',\n            width: 2,\n          },\n          grid: {\n            color: 'red',\n            drawTicks: false,\n          }\n        }\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.scale/grid/scriptable-borderDash.js",
    "content": "module.exports = {\n  config: {\n    type: 'scatter',\n    options: {\n      scales: {\n        x: {\n          position: {y: 0},\n          min: -10,\n          max: 10,\n          border: {\n            dash: (ctx) => ctx.index % 2 === 0 ? [6, 3] : [],\n          },\n          grid: {\n            color: 'lightGray',\n            lineWidth: 3,\n          },\n          ticks: {\n            display: false\n          },\n        },\n        y: {\n          position: {x: 0},\n          min: -10,\n          max: 10,\n          border: {\n            dash: (ctx) => ctx.index % 2 === 0 ? [6, 3] : [],\n          },\n          grid: {\n            color: 'lightGray',\n            lineWidth: 3,\n          },\n          ticks: {\n            display: false\n          },\n        }\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.scale/label-align-center.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      datasets: [{\n        data: [1, 2, 3],\n      }],\n      labels: ['Label1', 'Label2', 'Label3']\n    },\n    options: {\n      scales: {\n        x: {\n          ticks: {\n            align: 'center',\n          },\n        },\n        y: {\n          ticks: {\n            align: 'center',\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.scale/label-align-end.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      datasets: [{\n        data: [1, 2, 3],\n      }],\n      labels: ['Label1', 'Label2', 'Label3']\n    },\n    options: {\n      scales: {\n        x: {\n          ticks: {\n            align: 'end',\n          },\n        },\n        y: {\n          ticks: {\n            align: 'end',\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.scale/label-align-inner-onlyX.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      datasets: [{\n        data: [1, 2, 3],\n      }],\n      labels: ['Label1_long', 'Label2_long', 'Label3_long']\n    },\n    options: {\n      scales: {\n        x: {\n          ticks: {\n            align: 'inner',\n          },\n        },\n        y: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.scale/label-align-inner-reverse.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      datasets: [{\n        data: [1, 2, 3],\n      }],\n      labels: ['Label1', 'Label2', 'Label3']\n    },\n    options: {\n      scales: {\n        x: {\n          ticks: {\n            align: 'inner'\n          },\n          reverse: true\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.scale/label-align-inner-rotate.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      datasets: [{\n        data: [1, 2, 3],\n      }],\n      labels: ['Label1', 'Label2', 'Label3']\n    },\n    options: {\n      scales: {\n        x: {\n          ticks: {\n            align: 'inner',\n            maxRotation: 45,\n            minRotation: 45\n          },\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.scale/label-align-inner.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      datasets: [{\n        data: [1, 2, 3],\n      }],\n      labels: ['Label1', 'Label2', 'Label3']\n    },\n    options: {\n      scales: {\n        x: {\n          ticks: {\n            align: 'inner',\n          },\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.scale/label-align-start.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      datasets: [{\n        data: [1, 2, 3],\n      }],\n      labels: ['Label1', 'Label2', 'Label3']\n    },\n    options: {\n      scales: {\n        x: {\n          ticks: {\n            align: 'start',\n          },\n        },\n        y: {\n          ticks: {\n            align: 'start',\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.scale/label-offset-vertical-axes.json",
    "content": "{\n    \"config\": {\n        \"type\": \"bar\",\n        \"data\": {\n            \"labels\": [\"\\u25C0\", \"\\u25A0\", \"\\u25C6\", \"\\u25CF\"],\n            \"datasets\": [{\n                \"data\": [12, 19, 3, 5]\n            }]\n        },\n        \"options\": {\n            \"indexAxis\": \"y\",\n            \"scales\": {\n                \"x\": {\n                    \"ticks\": {\n                        \"display\": false\n                    },\n                    \"grid\":{\n                        \"display\": false\n                    },\n                    \"border\": {\n                        \"display\": false\n                    }\n                },\n                \"y\": {\n                    \"ticks\": {\n                        \"labelOffset\": 25\n                    },\n                    \"border\": {\n                        \"display\": false\n                    },\n                    \"grid\":{\n                        \"display\": false\n                    }\n                }\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/core.scale/tick-backdrop-alignment-inner.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],\n      datasets: [\n        {\n          label: '# of Votes',\n          data: [12, 19, 3, 5, 2, 3],\n        },\n        {\n          label: '# of Points',\n          data: [7, 11, 5, 8, 3, 7],\n        }\n      ]\n    },\n    options: {\n      scales: {\n        y: {\n          ticks: {\n            display: false,\n          },\n          grid: {\n            lineWidth: 0\n          }\n        },\n        x: {\n          position: 'top',\n          ticks: {\n            color: 'transparent',\n            backdropColor: 'red',\n            showLabelBackdrop: true,\n            align: 'inner',\n          },\n          grid: {\n            lineWidth: 0\n          }\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.scale/tick-backdrop-rotation.js",
    "content": "const grid = {\n  display: false\n};\nconst title = {\n  display: false,\n};\nmodule.exports = {\n  tolerance: 0.0016,\n  config: {\n    type: 'line',\n    options: {\n      events: [],\n      scales: {\n        top: {\n          type: 'linear',\n          position: 'top',\n          ticks: {\n            display: true,\n            showLabelBackdrop: true,\n            minRotation: 45,\n            backdropColor: 'blue',\n            backdropPadding: 5,\n            align: 'start',\n            crossAlign: 'near',\n          },\n          grid,\n          title\n        },\n        left: {\n          type: 'linear',\n          position: 'left',\n          ticks: {\n            display: true,\n            showLabelBackdrop: true,\n            minRotation: 90,\n            backdropColor: 'green',\n            backdropPadding: {\n              x: 2,\n              y: 5\n            },\n            crossAlign: 'center',\n          },\n          grid,\n          title\n        },\n        bottom: {\n          type: 'linear',\n          position: 'bottom',\n          ticks: {\n            display: true,\n            showLabelBackdrop: true,\n            backdropColor: 'blue',\n            backdropPadding: {\n              x: 5,\n              y: 5\n            },\n            align: 'end',\n            crossAlign: 'far',\n            minRotation: 60,\n          },\n          grid,\n          title\n        },\n        right: {\n          type: 'linear',\n          position: 'right',\n          ticks: {\n            display: true,\n            showLabelBackdrop: true,\n            backdropColor: 'gray',\n          },\n          grid,\n          title\n        },\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 256\n    },\n    spriteText: true,\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.scale/tick-backdrop.js",
    "content": "const grid = {\n  display: false\n};\nconst title = {\n  display: false,\n};\nmodule.exports = {\n  config: {\n    type: 'line',\n    options: {\n      events: [],\n      scales: {\n        top: {\n          type: 'linear',\n          position: 'top',\n          ticks: {\n            display: true,\n            showLabelBackdrop: true,\n            backdropColor: 'red',\n            backdropPadding: 5,\n            align: 'start',\n            crossAlign: 'near',\n          },\n          grid,\n          title\n        },\n        left: {\n          type: 'linear',\n          position: 'left',\n          ticks: {\n            display: true,\n            showLabelBackdrop: true,\n            backdropColor: 'green',\n            backdropPadding: 5,\n            crossAlign: 'center',\n          },\n          grid,\n          title\n        },\n        bottom: {\n          type: 'linear',\n          position: 'bottom',\n          ticks: {\n            display: true,\n            showLabelBackdrop: true,\n            backdropColor: 'blue',\n            backdropPadding: 5,\n            align: 'end',\n            crossAlign: 'far',\n          },\n          grid,\n          title\n        },\n        right: {\n          type: 'linear',\n          position: 'right',\n          ticks: {\n            display: true,\n            showLabelBackdrop: true,\n            backdropColor: 'gray',\n            backdropPadding: 5,\n          },\n          grid,\n          title\n        },\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 256\n    },\n    spriteText: true,\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.scale/tick-drawing.json",
    "content": "{\n    \"config\": {\n        \"type\": \"bar\",\n        \"data\": {\n            \"labels\": [\"January\", \"February\", \"March\", \"April\", \"May\", \"June\", \"July\"],\n\t\t\t\"datasets\": []\n        },\n        \"options\": {\n            \"indexAxis\": \"y\",\n            \"scales\": {\n                \"x\": {\n                    \"type\": \"category\",\n                    \"position\": \"top\",\n                    \"id\": \"x-axis-1\",\n                    \"ticks\": {\n                        \"display\": false\n                    },\n                    \"border\": {\n                        \"display\": false\n                    },\n                    \"grid\":{\n                        \"drawOnChartArea\": false,\n                        \"color\": \"rgba(0, 0, 0, 1)\"\n                    }\n                },\n                \"x2\": {\n                    \"type\": \"category\",\n                    \"position\": \"bottom\",\n                    \"ticks\": {\n                        \"display\": false\n                    },\n                    \"border\": {\n                        \"display\": false\n                    },\n                    \"grid\":{\n                        \"drawOnChartArea\": false,\n                        \"color\": \"rgba(0, 0, 0, 1)\"\n                    }\n                },\n                \"y\": {\n                    \"position\": \"left\",\n                    \"id\": \"y-axis-1\",\n                    \"type\": \"linear\",\n                    \"offset\": false,\n                    \"min\": -100,\n                    \"max\": 100,\n                    \"ticks\": {\n                        \"display\": false\n                    },\n                    \"border\": {\n                        \"display\": false\n                    },\n                    \"grid\":{\n                        \"offset\": false,\n                        \"drawOnChartArea\": false,\n                        \"color\": \"rgba(0, 0, 0, 1)\"\n                    }\n                },\n                \"y2\": {\n                    \"type\": \"linear\",\n                    \"position\": \"right\",\n                    \"offset\": false,\n                    \"min\": 0,\n                    \"max\": 50,\n                    \"ticks\": {\n                        \"display\": false\n                    },\n                    \"border\": {\n                        \"display\": false\n                    },\n                    \"grid\":{\n                        \"offset\": false,\n                        \"drawOnChartArea\": false,\n                        \"color\": \"rgba(0, 0, 0, 1)\"\n                    }\n                }\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/core.scale/tick-override-styles.json",
    "content": "{\n    \"config\": {\n        \"type\": \"bar\",\n        \"data\": {\n            \"labels\": [\"January\", \"February\", \"March\", \"April\", \"May\", \"June\", \"July\"],\n\t\t\t\"datasets\": []\n        },\n        \"options\": {\n            \"indexAxis\": \"y\",\n            \"scales\": {\n                \"x\": {\n                    \"type\": \"category\",\n                    \"position\": \"top\",\n                    \"id\": \"x-axis-1\",\n                    \"ticks\": {\n                        \"display\": false\n                    },\n                    \n                    \"border\": {\n                        \"display\": false\n                    },\n                    \"grid\":{\n                        \"drawOnChartArea\": false,\n                        \"color\": \"rgba(0, 0, 0, 1)\",\n                        \"width\": 1,\n                        \"tickColor\": \"rgba(255, 0, 0, 1)\",\n                        \"tickWidth\": 5\n                    }\n                },\n                \"y\": {\n                    \"position\": \"left\",\n                    \"id\": \"y-axis-1\",\n                    \"type\": \"linear\",\n                    \"offset\": false,\n                    \"min\": -100,\n                    \"max\": 100,\n                    \"ticks\": {\n                        \"display\": false\n                    },\n                    \"border\": {\n                        \"display\": false\n                    },\n                    \"grid\":{\n                        \"offset\": false,\n                        \"drawOnChartArea\": false,\n                        \"color\": \"rgba(0, 0, 0, 1)\",\n                        \"tickColor\": \"rgba(255, 0, 0, 1)\",\n                        \"tickWidth\": 5\n                    }\n                }\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/core.scale/ticks/rotated-long.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: ['Red Red Red Red', 'Blue Blue Blue Blue', 'Black Black Black Black', 'Green Green Green Green', 'Purple Purple Purple Purple', 'Orange Orange Orange Orange Orange Orange'],\n      datasets: [\n        {\n          data: [12, 19, 3, 5, 2, 3]\n        },\n      ]\n    },\n    options: {\n      plugins: {\n        legend: false,\n        tooltip: false,\n        filler: false,\n        title: false\n      },\n      scales: {\n        x: {\n          type: 'category',\n          position: 'bottom'\n        },\n        x2: {\n          type: 'category',\n          position: 'top'\n        }\n      }\n    },\n    plugins: [{\n      afterDraw(chart) {\n        const ctx = chart.ctx;\n        ctx.save();\n        ctx.strokeStyle = 'red';\n        ctx.strokeRect(0, 0, chart.width, chart.height);\n        ctx.restore();\n      }\n    }]\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      width: 1024,\n      height: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.scale/ticks/rotated-multi-line.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: [['Red', 'Red', 'Red', 'Red'], ['Blue', 'Blue', 'Blue', 'Blue'], ['Black', 'Black', 'Black', 'Black'], ['Green', 'Green', 'Green', 'Green'], ['Purple', 'Purple', 'Purple', 'Purple'], ['Orange Orange', 'Orange', 'Orange', 'Orange', 'Orange Orange']],\n      datasets: [\n        {\n          data: [12, 19, 3, 5, 2, 3]\n        },\n      ]\n    },\n    options: {\n      plugins: {\n        legend: false,\n        tooltip: false,\n        filler: false,\n        title: false\n      },\n      scales: {\n        x: {\n          type: 'category',\n          position: 'bottom'\n        },\n        x2: {\n          type: 'category',\n          position: 'top'\n        }\n      }\n    },\n    plugins: [{\n      afterDraw(chart) {\n        const ctx = chart.ctx;\n        ctx.save();\n        ctx.strokeStyle = 'red';\n        ctx.strokeRect(0, 0, chart.width, chart.height);\n        ctx.restore();\n      }\n    }]\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      width: 610,\n      height: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.scale/ticks/skip-by-callback.js",
    "content": "module.exports = {\n  description: 'https://github.com/chartjs/Chart.js/issues/8892',\n  config: {\n    type: 'line',\n    data: {\n      labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],\n      datasets: [\n        {\n          data: [12, 19, 3, 5, 2, 3],\n        },\n        {\n          data: [7, 11, 5, 8, 3, 7],\n        }\n      ]\n    },\n    options: {\n      scales: {\n        x: {\n          ticks: {\n            callback: function(val, index) {\n              if (index === 1) {\n                return undefined;\n              }\n              if (index === 3) {\n                return null;\n              }\n              return this.getLabelForValue(val);\n            }\n          }\n        },\n        y: {\n          ticks: {\n            callback: function(val, index) {\n              return index % 2 === 0 ? '' + val : null;\n            }\n          }\n        }\n      },\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.scale/ticks-mirror-x.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      datasets: [{\n        data: [1, -1, 3],\n      }],\n      labels: ['Label1', 'Label2', 'Label3']\n    },\n    options: {\n      scales: {\n        x: {\n          ticks: {\n            mirror: true\n          }\n        },\n        y: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.scale/ticks-mirror.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      datasets: [{\n        data: [1, -1, 3],\n      }],\n      labels: ['Label1', 'Label2', 'Label3']\n    },\n    options: {\n      scales: {\n        y: {\n          ticks: {\n            mirror: true\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.scale/title/align-end.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    options: {\n      events: [],\n      scales: {\n        top: {\n          type: 'linear',\n          position: 'top',\n          ticks: {\n            display: false\n          },\n          grid: {\n            display: false\n          },\n          title: {\n            display: true,\n            align: 'end',\n            text: 'top'\n          }\n        },\n        left: {\n          type: 'linear',\n          position: 'left',\n          ticks: {\n            display: false\n          },\n          grid: {\n            display: false\n          },\n          title: {\n            display: true,\n            align: 'end',\n            text: 'left'\n          }\n        },\n        bottom: {\n          type: 'linear',\n          position: 'bottom',\n          ticks: {\n            display: false\n          },\n          grid: {\n            display: false\n          },\n          title: {\n            display: true,\n            align: 'end',\n            text: 'bottom'\n          }\n        },\n        right: {\n          type: 'linear',\n          position: 'right',\n          ticks: {\n            display: false\n          },\n          grid: {\n            display: false\n          },\n          title: {\n            display: true,\n            align: 'end',\n            text: 'right'\n          }\n        },\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 256,\n      width: 256\n    },\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.scale/title/align-start.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    options: {\n      events: [],\n      scales: {\n        top: {\n          type: 'linear',\n          position: 'top',\n          ticks: {\n            display: false\n          },\n          grid: {\n            display: false\n          },\n          title: {\n            display: true,\n            align: 'start',\n            text: 'top'\n          }\n        },\n        left: {\n          type: 'linear',\n          position: 'left',\n          ticks: {\n            display: false\n          },\n          grid: {\n            display: false\n          },\n          title: {\n            display: true,\n            align: 'start',\n            text: 'left'\n          }\n        },\n        bottom: {\n          type: 'linear',\n          position: 'bottom',\n          ticks: {\n            display: false\n          },\n          grid: {\n            display: false\n          },\n          title: {\n            display: true,\n            align: 'start',\n            text: 'bottom'\n          }\n        },\n        right: {\n          type: 'linear',\n          position: 'right',\n          ticks: {\n            display: false\n          },\n          grid: {\n            display: false\n          },\n          title: {\n            display: true,\n            align: 'start',\n            text: 'right'\n          }\n        },\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 256,\n      width: 256\n    },\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.scale/title/default.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    options: {\n      events: [],\n      scales: {\n        top: {\n          type: 'linear',\n          position: 'top',\n          ticks: {\n            display: false\n          },\n          grid: {\n            display: false\n          },\n          title: {\n            display: true,\n            text: 'top'\n          }\n        },\n        left: {\n          type: 'linear',\n          position: 'left',\n          ticks: {\n            display: false\n          },\n          grid: {\n            display: false\n          },\n          title: {\n            display: true,\n            text: 'left'\n          }\n        },\n        bottom: {\n          type: 'linear',\n          position: 'bottom',\n          ticks: {\n            display: false\n          },\n          grid: {\n            display: false\n          },\n          title: {\n            display: true,\n            text: 'bottom'\n          }\n        },\n        right: {\n          type: 'linear',\n          position: 'right',\n          ticks: {\n            display: false\n          },\n          grid: {\n            display: false\n          },\n          title: {\n            display: true,\n            text: 'right'\n          }\n        },\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 256,\n      width: 256\n    },\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.scale/title/horizontal-center.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    options: {\n      events: [],\n      scales: {\n        y: {\n          type: 'linear',\n          position: 'left',\n          min: 0,\n          max: 100,\n          ticks: {\n            display: false\n          },\n          grid: {\n            display: false\n          },\n          title: {\n            display: true,\n            text: 'vertical'\n          }\n        },\n        x: {\n          type: 'linear',\n          position: 'center',\n          min: 0,\n          max: 100,\n          ticks: {\n            display: false\n          },\n          grid: {\n            display: false\n          },\n          title: {\n            display: true,\n            text: 'horizontal'\n          }\n        },\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 256,\n      width: 256\n    },\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.scale/title/horizontal-value.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    options: {\n      events: [],\n      scales: {\n        y: {\n          type: 'linear',\n          position: 'left',\n          min: 0,\n          max: 100,\n          ticks: {\n            display: false\n          },\n          grid: {\n            display: false\n          },\n          title: {\n            display: true,\n            text: 'vertical'\n          }\n        },\n        x: {\n          type: 'linear',\n          position: {\n            y: 40,\n          },\n          min: 0,\n          max: 100,\n          ticks: {\n            display: false\n          },\n          grid: {\n            display: false\n          },\n          title: {\n            display: true,\n            text: 'horizontal'\n          }\n        },\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 256,\n      width: 256\n    },\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.scale/title/multi-line/align-end.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    options: {\n      events: [],\n      scales: {\n        top: {\n          type: 'linear',\n          position: 'top',\n          ticks: {\n            display: false\n          },\n          grid: {\n            display: false\n          },\n          title: {\n            display: true,\n            align: 'end',\n            text: ['top', 'line2', 'line3']\n          }\n        },\n        left: {\n          type: 'linear',\n          position: 'left',\n          ticks: {\n            display: false\n          },\n          grid: {\n            display: false\n          },\n          title: {\n            display: true,\n            align: 'end',\n            text: ['left', 'line2', 'line3']\n          }\n        },\n        bottom: {\n          type: 'linear',\n          position: 'bottom',\n          ticks: {\n            display: false\n          },\n          grid: {\n            display: false\n          },\n          title: {\n            display: true,\n            align: 'end',\n            text: ['bottom', 'line2', 'line3']\n          }\n        },\n        right: {\n          type: 'linear',\n          position: 'right',\n          ticks: {\n            display: false\n          },\n          grid: {\n            display: false\n          },\n          title: {\n            display: true,\n            align: 'end',\n            text: ['right', 'line2', 'line3']\n          }\n        },\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 256,\n      width: 256\n    },\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.scale/title/multi-line/align-start.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    options: {\n      events: [],\n      scales: {\n        top: {\n          type: 'linear',\n          position: 'top',\n          ticks: {\n            display: false\n          },\n          grid: {\n            display: false\n          },\n          title: {\n            display: true,\n            align: 'start',\n            text: ['top', 'line2', 'line3']\n          }\n        },\n        left: {\n          type: 'linear',\n          position: 'left',\n          ticks: {\n            display: false\n          },\n          grid: {\n            display: false\n          },\n          title: {\n            display: true,\n            align: 'start',\n            text: ['left', 'line2', 'line3']\n          }\n        },\n        bottom: {\n          type: 'linear',\n          position: 'bottom',\n          ticks: {\n            display: false\n          },\n          grid: {\n            display: false\n          },\n          title: {\n            display: true,\n            align: 'start',\n            text: ['bottom', 'line2', 'line3']\n          }\n        },\n        right: {\n          type: 'linear',\n          position: 'right',\n          ticks: {\n            display: false\n          },\n          grid: {\n            display: false\n          },\n          title: {\n            display: true,\n            align: 'start',\n            text: ['right', 'line2', 'line3']\n          }\n        },\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 256,\n      width: 256\n    },\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.scale/title/multi-line/default.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    options: {\n      events: [],\n      scales: {\n        top: {\n          type: 'linear',\n          position: 'top',\n          ticks: {\n            display: false\n          },\n          grid: {\n            display: false\n          },\n          title: {\n            display: true,\n            text: ['top', 'line2', 'line3']\n          }\n        },\n        left: {\n          type: 'linear',\n          position: 'left',\n          ticks: {\n            display: false\n          },\n          grid: {\n            display: false\n          },\n          title: {\n            display: true,\n            text: ['left', 'line2', 'line3']\n          }\n        },\n        bottom: {\n          type: 'linear',\n          position: 'bottom',\n          ticks: {\n            display: false\n          },\n          grid: {\n            display: false\n          },\n          title: {\n            display: true,\n            text: ['bottom', 'line2', 'line3']\n          }\n        },\n        right: {\n          type: 'linear',\n          position: 'right',\n          ticks: {\n            display: false\n          },\n          grid: {\n            display: false\n          },\n          title: {\n            display: true,\n            text: ['right', 'line2', 'line3']\n          }\n        },\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 256,\n      width: 256\n    },\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.scale/title/vertical-center.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    options: {\n      events: [],\n      scales: {\n        y: {\n          type: 'linear',\n          position: 'center',\n          min: 0,\n          max: 100,\n          ticks: {\n            display: false\n          },\n          grid: {\n            display: false\n          },\n          title: {\n            display: true,\n            text: 'vertical'\n          }\n        },\n        x: {\n          type: 'linear',\n          position: 'bottom',\n          min: 0,\n          max: 100,\n          ticks: {\n            display: false\n          },\n          grid: {\n            display: false\n          },\n          title: {\n            display: true,\n            text: 'horizontal'\n          }\n        },\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 256,\n      width: 256\n    },\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.scale/title/vertical-value.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    options: {\n      events: [],\n      scales: {\n        y: {\n          type: 'linear',\n          position: {\n            x: 40\n          },\n          min: 0,\n          max: 100,\n          ticks: {\n            display: false\n          },\n          grid: {\n            display: false\n          },\n          title: {\n            display: true,\n            text: 'vertical'\n          }\n        },\n        x: {\n          type: 'linear',\n          position: 'bottom',\n          min: 0,\n          max: 100,\n          ticks: {\n            display: false\n          },\n          grid: {\n            display: false\n          },\n          title: {\n            display: true,\n            text: 'horizontal'\n          }\n        },\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 256,\n      width: 256\n    },\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.scale/x-axis-position-center.json",
    "content": "{\n    \"config\": {\n        \"type\": \"scatter\",\n        \"data\": {\n            \"datasets\": [{\n                \"data\": [{\n                    \"x\": -20,\n                    \"y\": -30\n                }, {\n                    \"x\": 0,\n                    \"y\": 0\n                }, {\n                    \"x\": 20,\n                    \"y\": 15\n                }]\n            }]\n        },\n        \"options\": {\n            \"scales\": {\n                \"x\": {\n                    \"position\": \"center\",\n                    \"axis\": \"x\",\n                    \"min\": -100,\n                    \"max\": 100,\n                    \"border\": {\n                        \"color\": \"red\"\n                    },\n                    \"grid\": {\n                        \"color\": \"red\",\n                        \"drawOnChartArea\": false\n                    },\n                    \"ticks\": {\n                        \"display\": true,\n                        \"color\": \"red\"\n                    }\n                },\n                \"y\": {\n                    \"position\": \"left\",\n                    \"axis\": \"y\",\n                    \"min\": -100,\n                    \"max\": 100,\n                    \"border\": {\n                        \"color\": \"red\"\n                    },\n                    \"grid\": {\n                        \"color\": \"red\",\n                        \"drawOnChartArea\": false\n                    },\n                    \"ticks\": {\n                        \"display\": true\n                    }\n                }\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        },\n        \"spriteText\": true\n    }\n}\n"
  },
  {
    "path": "test/fixtures/core.scale/x-axis-position-dynamic-margin.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    options: {\n      scales: {\n        x: {\n          labels: ['Left Label', 'Center Label', 'Right Label'],\n          position: {\n            y: 30\n          },\n        },\n        y: {\n          display: false,\n          min: -100,\n          max: 100,\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    },\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/core.scale/x-axis-position-dynamic.json",
    "content": "{\n    \"config\": {\n        \"type\": \"scatter\",\n        \"data\": {\n            \"datasets\": [{\n                \"data\": [{\n                    \"x\": -20,\n                    \"y\": -30\n                }, {\n                    \"x\": 0,\n                    \"y\": 0\n                }, {\n                    \"x\": 20,\n                    \"y\": 15\n                }]\n            }]\n        },\n        \"options\": {\n            \"scales\": {\n                \"x\": {\n                    \"position\": {\n                        \"y\": 30\n                    },\n                    \"axis\": \"x\",\n                    \"min\": -100,\n                    \"max\": 100,\n                    \"border\": {\n                        \"color\": \"red\"\n                    },\n                    \"grid\": {\n                        \"color\": \"red\",\n                        \"drawOnChartArea\": false\n                    },\n                    \"ticks\": {\n                        \"display\": true\n                    }\n                },\n                \"y\": {\n                    \"position\": \"left\",\n                    \"axis\": \"y\",\n                    \"min\": -100,\n                    \"max\": 100,\n                    \"border\": {\n                        \"color\": \"red\"\n                    },\n                    \"grid\": {\n                        \"color\": \"red\",\n                        \"drawOnChartArea\": false\n                    },\n                    \"ticks\": {\n                        \"display\": true\n                    }\n                }\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        },\n        \"spriteText\": true\n    }\n}\n"
  },
  {
    "path": "test/fixtures/core.scale/y-axis-position-center.json",
    "content": "{\n    \"config\": {\n        \"type\": \"scatter\",\n        \"data\": {\n            \"datasets\": [{\n                \"data\": [{\n                    \"x\": -20,\n                    \"y\": -30\n                }, {\n                    \"x\": 0,\n                    \"y\": 0\n                }, {\n                    \"x\": 20,\n                    \"y\": 15\n                }]\n            }]\n        },\n        \"options\": {\n            \"scales\": {\n                \"x\": {\n                    \"position\": \"bottom\",\n                    \"axis\": \"x\",\n                    \"min\": -100,\n                    \"max\": 100,\n                    \"border\": {\n                        \"color\": \"red\"\n                    },\n                    \"grid\": {\n                        \"color\": \"red\",\n                        \"drawOnChartArea\": false\n                    },\n                    \"ticks\": {\n                        \"display\": true\n                    }\n                },\n                \"y\": {\n                    \"position\": \"center\",\n                    \"axis\": \"y\",\n                    \"min\": -100,\n                    \"max\": 100,\n                    \"border\": {\n                        \"color\": \"red\"\n                    },\n                    \"grid\": {\n                        \"color\": \"red\",\n                        \"drawOnChartArea\": false\n                    },\n                    \"ticks\": {\n                        \"display\": true\n                    }\n                }\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        },\n        \"spriteText\": true\n    }\n}\n"
  },
  {
    "path": "test/fixtures/core.scale/y-axis-position-dynamic.json",
    "content": "{\n    \"config\": {\n        \"type\": \"scatter\",\n        \"data\": {\n            \"datasets\": [{\n                \"data\": [{\n                    \"x\": -20,\n                    \"y\": -30\n                }, {\n                    \"x\": 0,\n                    \"y\": 0\n                }, {\n                    \"x\": 20,\n                    \"y\": 15\n                }]\n            }]\n        },\n        \"options\": {\n            \"scales\": {\n                \"x\": {\n                    \"position\": \"bottom\",\n                    \"axis\": \"x\",\n                    \"min\": -100,\n                    \"max\": 100,\n                    \"border\": {\n                        \"color\": \"red\"\n                    },\n                    \"grid\": {\n                        \"color\": \"red\",\n                        \"drawOnChartArea\": false\n                    },\n                    \"ticks\": {\n                        \"display\": true\n                    }\n                },\n                \"y\": {\n                    \"position\": {\n                        \"x\": -50\n                    },\n                    \"axis\": \"y\",\n                    \"min\": -100,\n                    \"max\": 100,\n                    \"border\": {\n                        \"color\": \"red\"\n                    },\n                    \"grid\": {\n                        \"color\": \"red\",\n                        \"drawOnChartArea\": false\n                    },\n                    \"ticks\": {\n                        \"display\": true\n                    }\n                }\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        },\n        \"spriteText\": true\n    },\n    \"tolerance\": 0.01\n}\n"
  },
  {
    "path": "test/fixtures/element.line/cubicInterpolationMode/monotone-horizontal.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      datasets: [\n        {\n          data: [{x: 1, y: 10}, {x: 5, y: 0}, {x: 15, y: -10}, {x: 19, y: -5}],\n          borderColor: 'red',\n          fill: false,\n          cubicInterpolationMode: 'monotone'\n        }\n      ]\n    },\n    options: {\n      scales: {\n        x: {type: 'linear', display: false, min: 0, max: 20},\n        y: {display: false, min: -15, max: 15}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/element.line/cubicInterpolationMode/monotone-vertical.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      datasets: [\n        {\n          data: [{x: 10, y: 1}, {x: 0, y: 5}, {x: -10, y: 15}, {x: -5, y: 19}],\n          borderColor: 'red',\n          fill: false,\n          cubicInterpolationMode: 'monotone'\n        }\n      ]\n    },\n    options: {\n      indexAxis: 'y',\n      scales: {\n        x: {display: false, min: -15, max: 15},\n        y: {type: 'linear', display: false, min: 0, max: 20}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/element.line/default.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      datasets: [\n        {\n          data: [{x: 1, y: 10}, {x: 5, y: 0}, {x: 15, y: -10}, {x: 19, y: -5}],\n        }\n      ]\n    },\n    options: {\n      scales: {\n        x: {type: 'linear', display: false, min: 0, max: 20},\n        y: {display: false, min: -15, max: 15}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/element.line/skip/all.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      datasets: [\n        {\n          data: [{x: 0, y: NaN}, {x: NaN, y: 0}, {x: NaN, y: -10}, {x: 19, y: NaN}],\n          borderColor: 'red',\n          fill: true,\n          tension: 0\n        }\n      ]\n    },\n    options: {\n      scales: {\n        x: {type: 'linear', display: false, min: 0, max: 20},\n        y: {display: false, min: -15, max: 15}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/element.line/skip/first-span.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      datasets: [\n        {\n          data: [{x: NaN, y: 10}, {x: 5, y: 0}, {x: 15, y: -10}, {x: 19, y: -5}],\n          borderColor: 'red',\n          fill: true,\n          spanGaps: true,\n          tension: 0\n        }\n      ]\n    },\n    options: {\n      scales: {\n        x: {type: 'linear', display: false, min: 0, max: 20},\n        y: {display: false, min: -15, max: 15}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/element.line/skip/first.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      datasets: [\n        {\n          data: [{x: NaN, y: 10}, {x: 5, y: 0}, {x: 15, y: -10}, {x: 19, y: -5}],\n          borderColor: 'red',\n          fill: true,\n          tension: 0\n        }\n      ]\n    },\n    options: {\n      scales: {\n        x: {type: 'linear', display: false, min: 0, max: 20},\n        y: {display: false, min: -15, max: 15}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/element.line/skip/last-span.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      datasets: [\n        {\n          data: [{x: 0, y: 10}, {x: 5, y: 0}, {x: 15, y: -10}, {x: NaN, y: -5}],\n          borderColor: 'red',\n          fill: true,\n          spanGaps: true,\n          tension: 0\n        }\n      ]\n    },\n    options: {\n      scales: {\n        x: {type: 'linear', display: false, min: 0, max: 20},\n        y: {display: false, min: -15, max: 15}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/element.line/skip/last.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      datasets: [\n        {\n          data: [{x: 0, y: 10}, {x: 5, y: 0}, {x: 15, y: -10}, {x: NaN, y: -5}],\n          borderColor: 'red',\n          fill: true,\n          tension: 0\n        }\n      ]\n    },\n    options: {\n      scales: {\n        x: {type: 'linear', display: false, min: 0, max: 20},\n        y: {display: false, min: -15, max: 15}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/element.line/skip/middle-span.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    parsing: false,\n    data: {\n      datasets: [\n        {\n          data: [{x: 0, y: 10}, {x: 5, y: 0}, {x: null, y: -10}, {x: 19, y: -5}],\n          borderColor: 'red',\n          fill: true,\n          spanGaps: true,\n          tension: 0\n        }\n      ]\n    },\n    options: {\n      scales: {\n        x: {type: 'linear', display: false, min: 0, max: 20},\n        y: {display: false, min: -15, max: 15}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/element.line/skip/middle.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      datasets: [\n        {\n          data: [{x: 0, y: 10}, {x: 5, y: 0}, {x: NaN, y: -10}, {x: 19, y: -5}],\n          borderColor: 'red',\n          fill: true,\n          tension: 0\n        }\n      ]\n    },\n    options: {\n      scales: {\n        x: {type: 'linear', display: false, min: 0, max: 20},\n        y: {display: false, min: -15, max: 15}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/element.line/stepped/after.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      datasets: [\n        {\n          data: [{x: 1, y: 10}, {x: 5, y: 0}, {x: 15, y: -10}, {x: 19, y: -5}],\n          borderColor: 'red',\n          fill: false,\n          tension: 0,\n          stepped: 'after'\n        }\n      ]\n    },\n    options: {\n      scales: {\n        x: {type: 'linear', display: false, min: 0, max: 20},\n        y: {display: false, min: -15, max: 15}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/element.line/stepped/before.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      datasets: [\n        {\n          data: [{x: 1, y: 10}, {x: 5, y: 0}, {x: 15, y: -10}, {x: 19, y: -5}],\n          borderColor: 'red',\n          fill: false,\n          tension: 0,\n          stepped: 'before'\n        }\n      ]\n    },\n    options: {\n      scales: {\n        x: {type: 'linear', display: false, min: 0, max: 20},\n        y: {display: false, min: -15, max: 15}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/element.line/stepped/default.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      datasets: [\n        {\n          data: [{x: 1, y: 10}, {x: 5, y: 0}, {x: 15, y: -10}, {x: 19, y: -5}],\n          borderColor: 'red',\n          fill: false,\n          tension: 0,\n          stepped: true\n        }\n      ]\n    },\n    options: {\n      scales: {\n        x: {type: 'linear', display: false, min: 0, max: 20},\n        y: {display: false, min: -15, max: 15}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/element.line/stepped/middle.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      datasets: [\n        {\n          data: [{x: 1, y: 10}, {x: 5, y: 0}, {x: 15, y: -10}, {x: 19, y: -5}],\n          borderColor: 'red',\n          fill: false,\n          tension: 0,\n          stepped: 'middle'\n        }\n      ]\n    },\n    options: {\n      scales: {\n        x: {type: 'linear', display: false, min: 0, max: 20},\n        y: {display: false, min: -15, max: 15}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/element.line/tension/default.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      datasets: [\n        {\n          data: [{x: 1, y: 10}, {x: 5, y: 0}, {x: 15, y: -10}, {x: 19, y: -5}],\n          borderColor: 'red',\n          fill: false\n        }\n      ]\n    },\n    options: {\n      scales: {\n        x: {type: 'linear', display: false, min: 0, max: 20},\n        y: {display: false, min: -15, max: 15}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/element.line/tension/one.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      datasets: [\n        {\n          data: [{x: 1, y: 10}, {x: 5, y: 0}, {x: 15, y: -10}, {x: 19, y: -5}],\n          borderColor: 'red',\n          fill: false,\n          tension: 1\n        }\n      ]\n    },\n    options: {\n      scales: {\n        x: {type: 'linear', display: false, min: 0, max: 20},\n        y: {display: false, min: -15, max: 15}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/element.line/tension/zero.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      datasets: [\n        {\n          data: [{x: 1, y: 10}, {x: 5, y: 0}, {x: 15, y: -10}, {x: 19, y: -5}],\n          borderColor: 'red',\n          fill: false,\n          tension: 0\n        }\n      ]\n    },\n    options: {\n      scales: {\n        x: {type: 'linear', display: false, min: 0, max: 20},\n        y: {display: false, min: -15, max: 15}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/element.point/point-style-circle.json",
    "content": "{\n    \"config\": {\n        \"type\": \"bubble\",\n        \"data\": {\n            \"datasets\": [{\n                \"data\": [\n                    {\"x\": 0, \"y\": 3, \"r\": 0},\n                    {\"x\": 1, \"y\": 3, \"r\": 2},\n                    {\"x\": 2, \"y\": 3, \"r\": 4},\n                    {\"x\": 3, \"y\": 3, \"r\": 8},\n                    {\"x\": 4, \"y\": 3, \"r\": 16},\n                    {\"x\": 5, \"y\": 3, \"r\": 32}\n                ],\n                \"backgroundColor\": \"#00ff00\",\n                \"borderColor\": \"transparent\",\n                \"borderWidth\": 0\n            }, {\n                \"data\": [\n                    {\"x\": 0, \"y\": 2, \"r\": 0},\n                    {\"x\": 1, \"y\": 2, \"r\": 2},\n                    {\"x\": 2, \"y\": 2, \"r\": 4},\n                    {\"x\": 3, \"y\": 2, \"r\": 8},\n                    {\"x\": 4, \"y\": 2, \"r\": 16},\n                    {\"x\": 5, \"y\": 2, \"r\": 32}\n                ],\n                \"backgroundColor\": \"transparent\",\n                \"borderColor\": \"#0000ff\",\n                \"borderWidth\": 1\n            }, {\n                \"data\": [\n                    {\"x\": 0, \"y\": 1, \"r\": 0},\n                    {\"x\": 1, \"y\": 1, \"r\": 2},\n                    {\"x\": 2, \"y\": 1, \"r\": 4},\n                    {\"x\": 3, \"y\": 1, \"r\": 8},\n                    {\"x\": 4, \"y\": 1, \"r\": 16},\n                    {\"x\": 5, \"y\": 1, \"r\": 32}\n                ],\n                \"backgroundColor\": \"#00ff00\",\n                \"borderColor\": \"#0000ff\",\n                \"borderWidth\": 2\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"elements\": {\n                \"point\": {\n                    \"pointStyle\": \"circle\"\n                }\n            },\n            \"layout\": {\n                \"padding\": 40\n            },\n            \"scales\": {\n                \"x\": {\"display\": false},\n                \"y\": {\"display\": false}\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/element.point/point-style-cross-rot.json",
    "content": "{\n    \"config\": {\n        \"type\": \"bubble\",\n        \"data\": {\n            \"datasets\": [{\n                \"data\": [\n                    {\"x\": 0, \"y\": 3, \"r\": 0},\n                    {\"x\": 1, \"y\": 3, \"r\": 2},\n                    {\"x\": 2, \"y\": 3, \"r\": 4},\n                    {\"x\": 3, \"y\": 3, \"r\": 8},\n                    {\"x\": 4, \"y\": 3, \"r\": 16},\n                    {\"x\": 5, \"y\": 3, \"r\": 32}\n                ],\n                \"backgroundColor\": \"#00ff00\",\n                \"borderColor\": \"transparent\",\n                \"borderWidth\": 0\n            }, {\n                \"data\": [\n                    {\"x\": 0, \"y\": 2, \"r\": 0},\n                    {\"x\": 1, \"y\": 2, \"r\": 2},\n                    {\"x\": 2, \"y\": 2, \"r\": 4},\n                    {\"x\": 3, \"y\": 2, \"r\": 8},\n                    {\"x\": 4, \"y\": 2, \"r\": 16},\n                    {\"x\": 5, \"y\": 2, \"r\": 32}\n                ],\n                \"backgroundColor\": \"transparent\",\n                \"borderColor\": \"#0000ff\",\n                \"borderWidth\": 1\n            }, {\n                \"data\": [\n                    {\"x\": 0, \"y\": 1, \"r\": 0},\n                    {\"x\": 1, \"y\": 1, \"r\": 2},\n                    {\"x\": 2, \"y\": 1, \"r\": 4},\n                    {\"x\": 3, \"y\": 1, \"r\": 8},\n                    {\"x\": 4, \"y\": 1, \"r\": 16},\n                    {\"x\": 5, \"y\": 1, \"r\": 32}\n                ],\n                \"backgroundColor\": \"#00ff00\",\n                \"borderColor\": \"#0000ff\",\n                \"borderWidth\": 2\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"elements\": {\n                \"point\": {\n                    \"pointStyle\": \"crossRot\"\n                }\n            },\n            \"layout\": {\n                \"padding\": 40\n            },\n            \"scales\": {\n                \"x\": {\"display\": false},\n                \"y\": {\"display\": false}\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/element.point/point-style-cross.json",
    "content": "{\n    \"config\": {\n        \"type\": \"bubble\",\n        \"data\": {\n            \"datasets\": [{\n                \"data\": [\n                    {\"x\": 0, \"y\": 3, \"r\": 0},\n                    {\"x\": 1, \"y\": 3, \"r\": 2},\n                    {\"x\": 2, \"y\": 3, \"r\": 4},\n                    {\"x\": 3, \"y\": 3, \"r\": 8},\n                    {\"x\": 4, \"y\": 3, \"r\": 16},\n                    {\"x\": 5, \"y\": 3, \"r\": 32}\n                ],\n                \"backgroundColor\": \"#00ff00\",\n                \"borderColor\": \"transparent\",\n                \"borderWidth\": 0\n            }, {\n                \"data\": [\n                    {\"x\": 0, \"y\": 2, \"r\": 0},\n                    {\"x\": 1, \"y\": 2, \"r\": 2},\n                    {\"x\": 2, \"y\": 2, \"r\": 4},\n                    {\"x\": 3, \"y\": 2, \"r\": 8},\n                    {\"x\": 4, \"y\": 2, \"r\": 16},\n                    {\"x\": 5, \"y\": 2, \"r\": 32}\n                ],\n                \"backgroundColor\": \"transparent\",\n                \"borderColor\": \"#0000ff\",\n                \"borderWidth\": 1\n            }, {\n                \"data\": [\n                    {\"x\": 0, \"y\": 1, \"r\": 0},\n                    {\"x\": 1, \"y\": 1, \"r\": 2},\n                    {\"x\": 2, \"y\": 1, \"r\": 4},\n                    {\"x\": 3, \"y\": 1, \"r\": 8},\n                    {\"x\": 4, \"y\": 1, \"r\": 16},\n                    {\"x\": 5, \"y\": 1, \"r\": 32}\n                ],\n                \"backgroundColor\": \"#00ff00\",\n                \"borderColor\": \"#0000ff\",\n                \"borderWidth\": 2\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"elements\": {\n                \"point\": {\n                    \"pointStyle\": \"cross\"\n                }\n            },\n            \"layout\": {\n                \"padding\": 40\n            },\n            \"scales\": {\n                \"x\": {\"display\": false},\n                \"y\": {\"display\": false}\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/element.point/point-style-dash.json",
    "content": "{\n    \"config\": {\n        \"type\": \"bubble\",\n        \"data\": {\n            \"datasets\": [{\n                \"data\": [\n                    {\"x\": 0, \"y\": 3, \"r\": 0},\n                    {\"x\": 1, \"y\": 3, \"r\": 2},\n                    {\"x\": 2, \"y\": 3, \"r\": 4},\n                    {\"x\": 3, \"y\": 3, \"r\": 8},\n                    {\"x\": 4, \"y\": 3, \"r\": 16},\n                    {\"x\": 5, \"y\": 3, \"r\": 32}\n                ],\n                \"backgroundColor\": \"#00ff00\",\n                \"borderColor\": \"transparent\",\n                \"borderWidth\": 0\n            }, {\n                \"data\": [\n                    {\"x\": 0, \"y\": 2, \"r\": 0},\n                    {\"x\": 1, \"y\": 2, \"r\": 2},\n                    {\"x\": 2, \"y\": 2, \"r\": 4},\n                    {\"x\": 3, \"y\": 2, \"r\": 8},\n                    {\"x\": 4, \"y\": 2, \"r\": 16},\n                    {\"x\": 5, \"y\": 2, \"r\": 32}\n                ],\n                \"backgroundColor\": \"transparent\",\n                \"borderColor\": \"#0000ff\",\n                \"borderWidth\": 1\n            }, {\n                \"data\": [\n                    {\"x\": 0, \"y\": 1, \"r\": 0},\n                    {\"x\": 1, \"y\": 1, \"r\": 2},\n                    {\"x\": 2, \"y\": 1, \"r\": 4},\n                    {\"x\": 3, \"y\": 1, \"r\": 8},\n                    {\"x\": 4, \"y\": 1, \"r\": 16},\n                    {\"x\": 5, \"y\": 1, \"r\": 32}\n                ],\n                \"backgroundColor\": \"#00ff00\",\n                \"borderColor\": \"#0000ff\",\n                \"borderWidth\": 2\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"elements\": {\n                \"point\": {\n                    \"pointStyle\": \"dash\"\n                }\n            },\n            \"layout\": {\n                \"padding\": 40\n            },\n            \"scales\": {\n                \"x\": {\"display\": false},\n                \"y\": {\"display\": false}\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/element.point/point-style-image.js",
    "content": "var imageCanvas = document.createElement('canvas');\nvar imageContext = imageCanvas.getContext('2d');\n\nimageCanvas.width = 40;\nimageCanvas.height = 40;\n\nimageContext.fillStyle = '#f00';\nimageContext.beginPath();\nimageContext.moveTo(20, 0);\nimageContext.lineTo(10, 40);\nimageContext.lineTo(20, 30);\nimageContext.closePath();\nimageContext.fill();\n\nimageContext.fillStyle = '#a00';\nimageContext.beginPath();\nimageContext.moveTo(20, 0);\nimageContext.lineTo(30, 40);\nimageContext.lineTo(20, 30);\nimageContext.closePath();\nimageContext.fill();\n\nmodule.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5, 6, 7],\n      datasets: [{\n        data: [0, 0, 0, 0, 0, 0, 0, 0],\n        showLine: false\n      }]\n    },\n    options: {\n      responsive: false,\n      elements: {\n        point: {\n          pointStyle: imageCanvas,\n          rotation: [0, 45, 90, 135, 180, 225, 270, 315]\n        }\n      },\n      layout: {\n        padding: 20\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/element.point/point-style-line.json",
    "content": "{\n    \"config\": {\n        \"type\": \"bubble\",\n        \"data\": {\n            \"datasets\": [{\n                \"data\": [\n                    {\"x\": 0, \"y\": 3, \"r\": 0},\n                    {\"x\": 1, \"y\": 3, \"r\": 2},\n                    {\"x\": 2, \"y\": 3, \"r\": 4},\n                    {\"x\": 3, \"y\": 3, \"r\": 8},\n                    {\"x\": 4, \"y\": 3, \"r\": 16},\n                    {\"x\": 5, \"y\": 3, \"r\": 32}\n                ],\n                \"backgroundColor\": \"#00ff00\",\n                \"borderColor\": \"transparent\",\n                \"borderWidth\": 0\n            }, {\n                \"data\": [\n                    {\"x\": 0, \"y\": 2, \"r\": 0},\n                    {\"x\": 1, \"y\": 2, \"r\": 2},\n                    {\"x\": 2, \"y\": 2, \"r\": 4},\n                    {\"x\": 3, \"y\": 2, \"r\": 8},\n                    {\"x\": 4, \"y\": 2, \"r\": 16},\n                    {\"x\": 5, \"y\": 2, \"r\": 32}\n                ],\n                \"backgroundColor\": \"transparent\",\n                \"borderColor\": \"#0000ff\",\n                \"borderWidth\": 1\n            }, {\n                \"data\": [\n                    {\"x\": 0, \"y\": 1, \"r\": 0},\n                    {\"x\": 1, \"y\": 1, \"r\": 2},\n                    {\"x\": 2, \"y\": 1, \"r\": 4},\n                    {\"x\": 3, \"y\": 1, \"r\": 8},\n                    {\"x\": 4, \"y\": 1, \"r\": 16},\n                    {\"x\": 5, \"y\": 1, \"r\": 32}\n                ],\n                \"backgroundColor\": \"#00ff00\",\n                \"borderColor\": \"#0000ff\",\n                \"borderWidth\": 2\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"elements\": {\n                \"point\": {\n                    \"pointStyle\": \"line\"\n                }\n            },\n            \"layout\": {\n                \"padding\": 40\n            },\n            \"scales\": {\n                \"x\": {\"display\": false},\n                \"y\": {\"display\": false}\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/element.point/point-style-rect-rot.json",
    "content": "{\n    \"config\": {\n        \"type\": \"bubble\",\n        \"data\": {\n            \"datasets\": [{\n                \"data\": [\n                    {\"x\": 0, \"y\": 3, \"r\": 0},\n                    {\"x\": 1, \"y\": 3, \"r\": 2},\n                    {\"x\": 2, \"y\": 3, \"r\": 4},\n                    {\"x\": 3, \"y\": 3, \"r\": 8},\n                    {\"x\": 4, \"y\": 3, \"r\": 16},\n                    {\"x\": 5, \"y\": 3, \"r\": 32}\n                ],\n                \"backgroundColor\": \"#00ff00\",\n                \"borderColor\": \"transparent\",\n                \"borderWidth\": 0\n            }, {\n                \"data\": [\n                    {\"x\": 0, \"y\": 2, \"r\": 0},\n                    {\"x\": 1, \"y\": 2, \"r\": 2},\n                    {\"x\": 2, \"y\": 2, \"r\": 4},\n                    {\"x\": 3, \"y\": 2, \"r\": 8},\n                    {\"x\": 4, \"y\": 2, \"r\": 16},\n                    {\"x\": 5, \"y\": 2, \"r\": 32}\n                ],\n                \"backgroundColor\": \"transparent\",\n                \"borderColor\": \"#0000ff\",\n                \"borderWidth\": 1\n            }, {\n                \"data\": [\n                    {\"x\": 0, \"y\": 1, \"r\": 0},\n                    {\"x\": 1, \"y\": 1, \"r\": 2},\n                    {\"x\": 2, \"y\": 1, \"r\": 4},\n                    {\"x\": 3, \"y\": 1, \"r\": 8},\n                    {\"x\": 4, \"y\": 1, \"r\": 16},\n                    {\"x\": 5, \"y\": 1, \"r\": 32}\n                ],\n                \"backgroundColor\": \"#00ff00\",\n                \"borderColor\": \"#0000ff\",\n                \"borderWidth\": 2\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"elements\": {\n                \"point\": {\n                    \"pointStyle\": \"rectRot\"\n                }\n            },\n            \"layout\": {\n                \"padding\": 40\n            },\n            \"scales\": {\n                \"x\": {\"display\": false},\n                \"y\": {\"display\": false}\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/element.point/point-style-rect-rounded.json",
    "content": "{\n    \"config\": {\n        \"type\": \"bubble\",\n        \"data\": {\n            \"datasets\": [{\n                \"data\": [\n                    {\"x\": 0, \"y\": 3, \"r\": 0},\n                    {\"x\": 1, \"y\": 3, \"r\": 2},\n                    {\"x\": 2, \"y\": 3, \"r\": 4},\n                    {\"x\": 3, \"y\": 3, \"r\": 8},\n                    {\"x\": 4, \"y\": 3, \"r\": 16},\n                    {\"x\": 5, \"y\": 3, \"r\": 32}\n                ],\n                \"backgroundColor\": \"#00ff00\",\n                \"borderColor\": \"transparent\",\n                \"borderWidth\": 0\n            }, {\n                \"data\": [\n                    {\"x\": 0, \"y\": 2, \"r\": 0},\n                    {\"x\": 1, \"y\": 2, \"r\": 2},\n                    {\"x\": 2, \"y\": 2, \"r\": 4},\n                    {\"x\": 3, \"y\": 2, \"r\": 8},\n                    {\"x\": 4, \"y\": 2, \"r\": 16},\n                    {\"x\": 5, \"y\": 2, \"r\": 32}\n                ],\n                \"backgroundColor\": \"transparent\",\n                \"borderColor\": \"#0000ff\",\n                \"borderWidth\": 1\n            }, {\n                \"data\": [\n                    {\"x\": 0, \"y\": 1, \"r\": 0},\n                    {\"x\": 1, \"y\": 1, \"r\": 2},\n                    {\"x\": 2, \"y\": 1, \"r\": 4},\n                    {\"x\": 3, \"y\": 1, \"r\": 8},\n                    {\"x\": 4, \"y\": 1, \"r\": 16},\n                    {\"x\": 5, \"y\": 1, \"r\": 32}\n                ],\n                \"backgroundColor\": \"#00ff00\",\n                \"borderColor\": \"#0000ff\",\n                \"borderWidth\": 2\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"elements\": {\n                \"point\": {\n                    \"pointStyle\": \"rectRounded\"\n                }\n            },\n            \"layout\": {\n                \"padding\": 40\n            },\n            \"scales\": {\n                \"x\": {\"display\": false},\n                \"y\": {\"display\": false}\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/element.point/point-style-rect.json",
    "content": "{\n    \"config\": {\n        \"type\": \"bubble\",\n        \"data\": {\n            \"datasets\": [{\n                \"data\": [\n                    {\"x\": 0, \"y\": 3, \"r\": 0},\n                    {\"x\": 1, \"y\": 3, \"r\": 2},\n                    {\"x\": 2, \"y\": 3, \"r\": 4},\n                    {\"x\": 3, \"y\": 3, \"r\": 8},\n                    {\"x\": 4, \"y\": 3, \"r\": 16},\n                    {\"x\": 5, \"y\": 3, \"r\": 32}\n                ],\n                \"backgroundColor\": \"#00ff00\",\n                \"borderColor\": \"transparent\",\n                \"borderWidth\": 0\n            }, {\n                \"data\": [\n                    {\"x\": 0, \"y\": 2, \"r\": 0},\n                    {\"x\": 1, \"y\": 2, \"r\": 2},\n                    {\"x\": 2, \"y\": 2, \"r\": 4},\n                    {\"x\": 3, \"y\": 2, \"r\": 8},\n                    {\"x\": 4, \"y\": 2, \"r\": 16},\n                    {\"x\": 5, \"y\": 2, \"r\": 32}\n                ],\n                \"backgroundColor\": \"transparent\",\n                \"borderColor\": \"#0000ff\",\n                \"borderWidth\": 1\n            }, {\n                \"data\": [\n                    {\"x\": 0, \"y\": 1, \"r\": 0},\n                    {\"x\": 1, \"y\": 1, \"r\": 2},\n                    {\"x\": 2, \"y\": 1, \"r\": 4},\n                    {\"x\": 3, \"y\": 1, \"r\": 8},\n                    {\"x\": 4, \"y\": 1, \"r\": 16},\n                    {\"x\": 5, \"y\": 1, \"r\": 32}\n                ],\n                \"backgroundColor\": \"#00ff00\",\n                \"borderColor\": \"#0000ff\",\n                \"borderWidth\": 2\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"elements\": {\n                \"point\": {\n                    \"pointStyle\": \"rect\"\n                }\n            },\n            \"layout\": {\n                \"padding\": 40\n            },\n            \"scales\": {\n                \"x\": {\"display\": false},\n                \"y\": {\"display\": false}\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/element.point/point-style-star.json",
    "content": "{\n    \"config\": {\n        \"type\": \"bubble\",\n        \"data\": {\n            \"datasets\": [{\n                \"data\": [\n                    {\"x\": 0, \"y\": 3, \"r\": 0},\n                    {\"x\": 1, \"y\": 3, \"r\": 2},\n                    {\"x\": 2, \"y\": 3, \"r\": 4},\n                    {\"x\": 3, \"y\": 3, \"r\": 8},\n                    {\"x\": 4, \"y\": 3, \"r\": 16},\n                    {\"x\": 5, \"y\": 3, \"r\": 32}\n                ],\n                \"backgroundColor\": \"#00ff00\",\n                \"borderColor\": \"transparent\",\n                \"borderWidth\": 0\n            }, {\n                \"data\": [\n                    {\"x\": 0, \"y\": 2, \"r\": 0},\n                    {\"x\": 1, \"y\": 2, \"r\": 2},\n                    {\"x\": 2, \"y\": 2, \"r\": 4},\n                    {\"x\": 3, \"y\": 2, \"r\": 8},\n                    {\"x\": 4, \"y\": 2, \"r\": 16},\n                    {\"x\": 5, \"y\": 2, \"r\": 32}\n                ],\n                \"backgroundColor\": \"transparent\",\n                \"borderColor\": \"#0000ff\",\n                \"borderWidth\": 1\n            }, {\n                \"data\": [\n                    {\"x\": 0, \"y\": 1, \"r\": 0},\n                    {\"x\": 1, \"y\": 1, \"r\": 2},\n                    {\"x\": 2, \"y\": 1, \"r\": 4},\n                    {\"x\": 3, \"y\": 1, \"r\": 8},\n                    {\"x\": 4, \"y\": 1, \"r\": 16},\n                    {\"x\": 5, \"y\": 1, \"r\": 32}\n                ],\n                \"backgroundColor\": \"#00ff00\",\n                \"borderColor\": \"#0000ff\",\n                \"borderWidth\": 2\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"elements\": {\n                \"point\": {\n                    \"pointStyle\": \"star\"\n                }\n            },\n            \"layout\": {\n                \"padding\": 40\n            },\n            \"scales\": {\n                \"x\": {\"display\": false},\n                \"y\": {\"display\": false}\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/element.point/point-style-triangle.json",
    "content": "{\n    \"config\": {\n        \"type\": \"bubble\",\n        \"data\": {\n            \"datasets\": [{\n                \"data\": [\n                    {\"x\": 0, \"y\": 3, \"r\": 0},\n                    {\"x\": 1, \"y\": 3, \"r\": 2},\n                    {\"x\": 2, \"y\": 3, \"r\": 4},\n                    {\"x\": 3, \"y\": 3, \"r\": 8},\n                    {\"x\": 4, \"y\": 3, \"r\": 16},\n                    {\"x\": 5, \"y\": 3, \"r\": 32}\n                ],\n                \"backgroundColor\": \"#00ff00\",\n                \"borderColor\": \"transparent\",\n                \"borderWidth\": 0\n            }, {\n                \"data\": [\n                    {\"x\": 0, \"y\": 2, \"r\": 0},\n                    {\"x\": 1, \"y\": 2, \"r\": 2},\n                    {\"x\": 2, \"y\": 2, \"r\": 4},\n                    {\"x\": 3, \"y\": 2, \"r\": 8},\n                    {\"x\": 4, \"y\": 2, \"r\": 16},\n                    {\"x\": 5, \"y\": 2, \"r\": 32}\n                ],\n                \"backgroundColor\": \"transparent\",\n                \"borderColor\": \"#0000ff\",\n                \"borderWidth\": 1\n            }, {\n                \"data\": [\n                    {\"x\": 0, \"y\": 1, \"r\": 0},\n                    {\"x\": 1, \"y\": 1, \"r\": 2},\n                    {\"x\": 2, \"y\": 1, \"r\": 4},\n                    {\"x\": 3, \"y\": 1, \"r\": 8},\n                    {\"x\": 4, \"y\": 1, \"r\": 16},\n                    {\"x\": 5, \"y\": 1, \"r\": 32}\n                ],\n                \"backgroundColor\": \"#00ff00\",\n                \"borderColor\": \"#0000ff\",\n                \"borderWidth\": 2\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"elements\": {\n                \"point\": {\n                    \"pointStyle\": \"triangle\"\n                }\n            },\n            \"layout\": {\n                \"padding\": 40\n            },\n            \"scales\": {\n                \"x\": {\"display\": false},\n                \"y\": {\"display\": false}\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/element.point/rotation.js",
    "content": "var gradient;\n\nvar datasets = ['circle', 'cross', 'crossRot', 'dash', 'line', 'rect', 'rectRounded', 'rectRot', 'star', 'triangle', false].map(function(style, y) {\n  return {\n    pointStyle: style,\n    data: Array.apply(null, Array(17)).map(function(v, x) {\n      return {x: x, y: 11 - y};\n    })\n  };\n});\n\nvar angles = Array.apply(null, Array(17)).map(function(v, i) {\n  return -180 + i * 22.5;\n});\n\nmodule.exports = {\n  config: {\n    type: 'bubble',\n    data: {\n      datasets: datasets\n    },\n    options: {\n      responsive: false,\n      elements: {\n        point: {\n          rotation: angles,\n          radius: 10,\n          backgroundColor: function(context) {\n            if (!gradient) {\n              gradient = context.chart.ctx.createLinearGradient(0, 0, 512, 256);\n              gradient.addColorStop(0, '#ff0000');\n              gradient.addColorStop(1, '#0000ff');\n            }\n            return gradient;\n          },\n          borderColor: '#cccccc'\n        }\n      },\n      layout: {\n        padding: 20\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/mixed/bar+line-stacked.js",
    "content": "module.exports = {\n  config: {\n    data: {\n      datasets: [\n        {\n          type: 'bar',\n          stack: 'mixed',\n          data: [5, 20, 1, 10],\n          backgroundColor: '#00ff00',\n          borderColor: '#ff0000'\n        },\n        {\n          type: 'line',\n          stack: 'mixed',\n          data: [6, 16, 3, 19],\n          borderColor: '#0000ff',\n          fill: false\n        },\n      ]\n    },\n    options: {\n      scales: {\n        x: {\n          axis: 'y',\n          labels: ['a', 'b', 'c', 'd']\n        },\n        y: {\n          stacked: true\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/mixed/bar+line.js",
    "content": "module.exports = {\n  config: {\n    data: {\n      datasets: [\n        {\n          type: 'line',\n          data: [6, 16, 3, 19],\n          borderColor: '#0000ff',\n          fill: false\n        },\n        {\n          type: 'bar',\n          data: [5, 20, 1, 10],\n          backgroundColor: '#00ff00',\n          borderColor: '#ff0000'\n        }\n      ]\n    },\n    options: {\n      indexAxis: 'y',\n      scales: {\n        x: {\n          position: 'top'\n        },\n        y: {\n          axis: 'y',\n          labels: ['a', 'b', 'c', 'd']\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.colors/bar.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          data: [0, 5, 10, null, -10, -5],\n        },\n        {\n          data: [10, 2, 3, null, 10, 5]\n        }\n      ]\n    },\n    options: {\n      scales: {\n        x: {\n          ticks: {\n            display: false,\n          }\n        },\n        y: {\n          ticks: {\n            display: false,\n          }\n        }\n      },\n      plugins: {\n        legend: false,\n        colors: {\n          enabled: true\n        }\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.colors/bubble.js",
    "content": "module.exports = {\n  config: {\n    type: 'bubble',\n    data: {\n      datasets: [{\n        data: [{x: 12, y: 54, r: 22.4}]\n      }, {\n        data: [{x: 18, y: 38, r: 25}]\n      }]\n    },\n    options: {\n      scales: {\n        x: {\n          ticks: {\n            display: false,\n          }\n        },\n        y: {\n          ticks: {\n            display: false,\n          }\n        }\n      },\n      plugins: {\n        legend: false,\n        colors: {\n          enabled: true\n        }\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.colors/chart-options-colors.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          data: [0, 5, 10, null, -10, -5],\n        },\n        {\n          data: [10, 2, 3, null, 10, 5]\n        }\n      ]\n    },\n    options: {\n      backgroundColor: ['red', 'green'],\n      scales: {\n        x: {\n          ticks: {\n            display: false,\n          }\n        },\n        y: {\n          ticks: {\n            display: false,\n          }\n        }\n      },\n      plugins: {\n        legend: false,\n        colors: {\n          enabled: true\n        }\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.colors/doughnut.js",
    "content": "module.exports = {\n  config: {\n    type: 'doughnut',\n    data: {\n      datasets: [\n        {\n          data: [0, 2, 4, null, 6, 8]\n        },\n        {\n          data: [5, 1, 6, 2, null, 9]\n        }\n      ]\n    },\n    options: {\n      plugins: {\n        legend: false,\n        colors: {\n          enabled: true\n        }\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.colors/dynamic-datasets-default.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          data: [5, 5, 5, 5, 5, 5]\n        }\n      ]\n    },\n    options: {\n      scales: {\n        x: {\n          ticks: {\n            display: false,\n          }\n        },\n        y: {\n          ticks: {\n            display: false,\n          }\n        }\n      },\n      plugins: {\n        legend: false,\n        colors: {\n          enabled: true\n        }\n      }\n    }\n  },\n  options: {\n    run(chart) {\n      chart.data.datasets.push({\n        data: [5, 5, 5, 5, 5, 5]\n      });\n\n      chart.update();\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.colors/dynamic-datasets-force-override.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          data: [5, 5, 5, 5, 5, 5]\n        }\n      ]\n    },\n    options: {\n      scales: {\n        x: {\n          ticks: {\n            display: false\n          }\n        },\n        y: {\n          ticks: {\n            display: false\n          }\n        }\n      },\n      plugins: {\n        legend: false,\n        colors: {\n          enabled: true,\n          forceOverride: true\n        }\n      }\n    }\n  },\n  options: {\n    run(chart) {\n      chart.data.datasets.push({\n        data: [5, 5, 5, 5, 5, 5]\n      });\n\n      chart.update();\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.colors/line.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          data: [0, 5, 10, null, -10, -5],\n        },\n        {\n          data: [10, 2, 3, null, 10, 5]\n        }\n      ]\n    },\n    options: {\n      scales: {\n        x: {\n          ticks: {\n            display: false,\n          }\n        },\n        y: {\n          ticks: {\n            display: false,\n          }\n        }\n      },\n      plugins: {\n        legend: false,\n        colors: {\n          enabled: true\n        }\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.colors/mixed.js",
    "content": "module.exports = {\n  config: {\n    data: {\n      labels: [0, 1, 2, 3],\n      datasets: [\n        {\n          type: 'line',\n          data: [5, 20, 1, 10],\n        },\n        {\n          type: 'bar',\n          data: [6, 16, 3, 19]\n        },\n        {\n          type: 'pie',\n          data: [5, 20, 1, 10],\n        }\n      ]\n    },\n    options: {\n      scales: {\n        x: {\n          ticks: {\n            display: false,\n          }\n        },\n        y: {\n          ticks: {\n            display: false,\n          }\n        }\n      },\n      plugins: {\n        legend: false,\n        colors: {\n          enabled: true\n        }\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.colors/pie.js",
    "content": "module.exports = {\n  config: {\n    type: 'pie',\n    data: {\n      datasets: [\n        {\n          data: [0, 2, 4, null, 6, 8]\n        },\n        {\n          data: [5, 1, 6, 2, null, 9]\n        }\n      ]\n    },\n    options: {\n      plugins: {\n        legend: false,\n        colors: {\n          enabled: true\n        }\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.colors/polarArea.js",
    "content": "module.exports = {\n  config: {\n    type: 'polarArea',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          data: [0, 2, 4, null, 6, 8]\n        }\n      ]\n    },\n    options: {\n      scales: {\n        r: {\n          ticks: {\n            display: false\n          }\n        }\n      },\n      plugins: {\n        legend: false,\n        colors: {\n          enabled: true\n        }\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.colors/radar.js",
    "content": "module.exports = {\n  config: {\n    type: 'radar',\n    data: {\n      labels: [0, 1, 2, 3, 4, 5],\n      datasets: [\n        {\n          data: [0, 5, 10, null, -10, -5]\n        },\n        {\n          data: [4, -5, -10, null, 10, 5]\n        }\n      ]\n    },\n    options: {\n      scales: {\n        r: {\n          ticks: {\n            display: false\n          },\n          pointLabels: {\n            display: false,\n          }\n        }\n      },\n      plugins: {\n        legend: false,\n        colors: {\n          enabled: true\n        }\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.colors/scatter.js",
    "content": "module.exports = {\n  config: {\n    type: 'scatter',\n    data: {\n      datasets: [{\n        data: [{x: 10, y: 15}, {x: 15, y: 10}],\n        pointRadius: 10,\n        showLine: true,\n        label: 'dataset1'\n      }, {\n        data: [{x: 20, y: 45}, {x: 5, y: 15}],\n        pointRadius: 20,\n        label: 'dataset2'\n      }],\n    },\n    options: {\n      scales: {\n        x: {\n          ticks: {\n            display: false,\n          }\n        },\n        y: {\n          ticks: {\n            display: false,\n          }\n        }\n      },\n      plugins: {\n        legend: false,\n        colors: {\n          enabled: true\n        },\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.filler/line/above-below-vertical-linechart.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: [1, 2, 3, 4],\n      datasets: [\n        {\n          data: [200, 400, 200, 400],\n          cubicInterpolationMode: 'monotone',\n          tension: 0.4,\n          spanGaps: true,\n          borderColor: 'blue',\n          pointRadius: 0,\n          fill: {\n            target: 1,\n            below: 'rgba(255, 0, 0, 0.4)',\n            above: 'rgba(53, 221, 53, 0.4)',\n          }\n        },\n        {\n          data: [400, 200, 400, 200],\n          cubicInterpolationMode: 'monotone',\n          tension: 0.4,\n          spanGaps: true,\n          borderColor: 'orange',\n          pointRadius: 0,\n        },\n      ]\n    },\n    options: {\n      indexAxis: 'y',\n      // maintainAspectRatio: false,\n      plugins: {\n        filler: {\n          propagate: false\n        },\n        datalabels: {\n          display: false\n        },\n        legend: {\n          display: false\n        },\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.filler/line/before-dataset-draw.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: ['15:00', '16:00', '17:00', '18:00', '19:00', '20:00'],\n      datasets: [\n        {\n          borderColor: '#00ADEE80',\n          backgroundColor: '#00ADEE',\n          data: [0, 1, 1, 2, 2, 0],\n        },\n        {\n          borderColor: '#BD262880',\n          backgroundColor: '#BD2628',\n          data: [0, 2, 2, 1, 1, 1],\n        }\n      ]\n    },\n    options: {\n      borderWidth: 4,\n      fill: true,\n      radius: 20,\n      pointBackgroundColor: '#ffff',\n      cubicInterpolationMode: 'monotone',\n      plugins: {\n        legend: false,\n        filler: {\n          drawTime: 'beforeDatasetDraw'\n        }\n      },\n      scales: {\n        x: {\n          display: false,\n        },\n        y: {\n          display: false\n        }\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.filler/line/before-datasets-draw.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: ['15:00', '16:00', '17:00', '18:00', '19:00', '20:00'],\n      datasets: [\n        {\n          borderColor: '#00ADEE80',\n          backgroundColor: '#00ADEE',\n          data: [0, 1, 1, 2, 2, 0],\n        },\n        {\n          borderColor: '#BD262880',\n          backgroundColor: '#BD2628',\n          data: [0, 2, 2, 1, 1, 1],\n        }\n      ]\n    },\n    options: {\n      borderWidth: 4,\n      fill: true,\n      radius: 20,\n      pointBackgroundColor: '#ffff',\n      cubicInterpolationMode: 'monotone',\n      plugins: {\n        legend: false,\n        filler: {\n          drawTime: 'beforeDatasetsDraw'\n        }\n      },\n      scales: {\n        x: {\n          display: false,\n        },\n        y: {\n          display: false\n        }\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.filler/line/boundary/above-below-line-null-start.json",
    "content": "{\n    \"config\": {\n        \"type\": \"line\",\n        \"data\": {\n            \"labels\": [\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\", \"10\", \"11\", \"12\", \"13\"],\n            \"datasets\": [{\n                \"borderColor\": \"rgb(42, 90, 145)\",\n                \"data\": [null, 12, 30, 36, 45, 53, 68, 79, null, 95, 18, 18, 180],\n                \"fill\": {\n                  \"target\": \"+1\",\n                  \"above\": \"rgba(4, 142, 43, 0.5)\",\n                  \"below\": \"rgba(241, 49, 34, 0.5)\"\n                }\n            }, {\n                \"borderColor\": \"#00ADEE\",\n                \"data\": [null, 0, 0, 0, 0, 0, 20, 108, null, 72, 72, 72, 72],\n                \"fill\": false\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"spanGaps\": false,\n            \"scales\": {\n                \"x\": {\n                    \"display\": false\n                },\n                \"y\": {\n                    \"display\": false\n                }\n            },\n            \"elements\": {\n                \"point\": {\n                    \"radius\": 0\n                },\n                \"line\": {\n                    \"cubicInterpolationMode\": \"monotone\",\n                    \"borderColor\": \"transparent\"\n                }\n            },\n            \"plugins\": {\n                \"legend\": false,\n                \"title\": false,\n                \"tooltip\": false\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/plugin.filler/line/boundary/above-below-line-null.json",
    "content": "{\n    \"config\": {\n        \"type\": \"line\",\n        \"data\": {\n            \"labels\": [\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\", \"10\", \"11\", \"12\", \"13\"],\n            \"datasets\": [{\n                \"borderColor\": \"rgb(42, 90, 145)\",\n                \"data\": [4, 12, 30, 36, 45, 53, 68, 79, null, 95, 18, null, 18, 180],\n                \"fill\": {\n                  \"target\": \"+1\",\n                  \"above\": \"rgba(4, 142, 43, 0.5)\",\n                  \"below\": \"rgba(241, 49, 34, 0.5)\"\n                }\n            }, {\n                \"borderColor\": \"#00ADEE\",\n                \"data\": [0, 0, 0, 0, 0, 0, 20, 108, null, 72, 72, null, 72, 72],\n                \"fill\": false\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"spanGaps\": false,\n            \"scales\": {\n                \"x\": {\n                    \"display\": false\n                },\n                \"y\": {\n                    \"display\": false\n                }\n            },\n            \"elements\": {\n                \"point\": {\n                    \"radius\": 0\n                },\n                \"line\": {\n                    \"cubicInterpolationMode\": \"monotone\",\n                    \"borderColor\": \"transparent\"\n                }\n            },\n            \"plugins\": {\n                \"legend\": false,\n                \"title\": false,\n                \"tooltip\": false\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/plugin.filler/line/boundary/end-span.json",
    "content": "{\n    \"config\": {\n        \"type\": \"line\",\n        \"data\": {\n            \"labels\": [\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\"],\n            \"datasets\": [{\n                \"backgroundColor\": \"rgba(0, 0, 192, 0.25)\",\n                \"data\": [null, null, 2, 3, 4, -4, -2, 1, 0]\n            }, {\n                \"backgroundColor\": \"rgba(0, 192, 0, 0.25)\",\n                \"data\": [5.5, 2, null, 4, 5, null, null, 2, 1]\n            }, {\n                \"backgroundColor\": \"rgba(192, 0, 0, 0.25)\",\n                \"data\": [7, 3, 4, 5, 6, 1, 4, null, null]\n            }, {\n                \"backgroundColor\": \"rgba(0, 0, 192, 0.25)\",\n                \"data\": [8, 7, 6.5, -6, -4, -6, 4, 5, 8]\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"spanGaps\": true,\n            \"scales\": {\n                \"x\": {\n                    \"display\": false\n                },\n                \"y\": {\n                    \"display\": false\n                }\n            },\n            \"elements\": {\n                \"point\": {\n                    \"radius\": 0\n                },\n                \"line\": {\n                    \"borderColor\": \"transparent\",\n                    \"fill\": \"end\",\n                    \"tension\": 0\n                }\n            },\n            \"plugins\": {\n                \"legend\": false,\n                \"title\": false,\n                \"tooltip\": false\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/plugin.filler/line/boundary/end.json",
    "content": "{\n    \"config\": {\n        \"type\": \"line\",\n        \"data\": {\n            \"labels\": [\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\"],\n            \"datasets\": [{\n                \"backgroundColor\": \"rgba(0, 0, 192, 0.25)\",\n                \"data\": [null, null, 2, 3, 4, -4, -2, 1, 0]\n            }, {\n                \"backgroundColor\": \"rgba(0, 192, 0, 0.25)\",\n                \"data\": [5.5, 2, null, 4, 5, null, null, 2, 1]\n            }, {\n                \"backgroundColor\": \"rgba(192, 0, 0, 0.25)\",\n                \"data\": [7, 3, 4, 5, 6, 1, 4, null, null]\n            }, {\n                \"backgroundColor\": \"rgba(0, 0, 192, 0.25)\",\n                \"data\": [8, 7, 6.5, -6, -4, -6, 4, 5, 8]\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"spanGaps\": false,\n            \"scales\": {\n                \"x\": {\n                    \"display\": false\n                },\n                \"y\": {\n                    \"display\": false\n                }\n            },\n            \"elements\": {\n                \"point\": {\n                    \"radius\": 0\n                },\n                \"line\": {\n                    \"borderColor\": \"transparent\",\n                    \"fill\": \"end\",\n                    \"tension\": 0\n                }\n            },\n            \"plugins\": {\n                \"legend\": false,\n                \"title\": false,\n                \"tooltip\": false\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/plugin.filler/line/boundary/origin-span-dual.json",
    "content": "{\n    \"config\": {\n        \"type\": \"line\",\n        \"data\": {\n            \"labels\": [\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\"],\n            \"datasets\": [{\n                \"backgroundColor\": \"rgba(0, 0, 192, 0.25)\",\n                \"data\": [null, null, 2, 3, 4, -4, -2, 1, 0]\n            }, {\n                \"backgroundColor\": \"rgba(0, 192, 0, 0.25)\",\n                \"data\": [6, 2, null, 4, 5, null, null, 2, 1]\n            }, {\n                \"backgroundColor\": \"rgba(192, 0, 0, 0.25)\",\n                \"data\": [7, 3, 4, 5, 6, 1, 4, null, null]\n            }, {\n                \"backgroundColor\": \"rgba(0, 64, 192, 0.25)\",\n                \"data\": [8, 7, 6, -6, -4, -6, 4, 5, 8]\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"spanGaps\": true,\n            \"scales\": {\n                \"x\": {\n                    \"display\": false\n                },\n                \"y\": {\n                    \"display\": false\n                }\n            },\n            \"elements\": {\n                \"point\": {\n                    \"radius\": 0\n                },\n                \"line\": {\n                    \"borderColor\": \"transparent\",\n                    \"fill\": {\n                      \"target\": \"origin\",\n                      \"below\": \"rgba(255, 0, 0, 0.25)\"\n                    },\n                    \"tension\": 0\n                }\n            },\n            \"plugins\": {\n                \"legend\": false,\n                \"title\": false,\n                \"tooltip\": false\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/plugin.filler/line/boundary/origin-span.json",
    "content": "{\n    \"config\": {\n        \"type\": \"line\",\n        \"data\": {\n            \"labels\": [\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\"],\n            \"datasets\": [{\n                \"backgroundColor\": \"rgba(0, 0, 192, 0.25)\",\n                \"data\": [null, null, 2, 3, 4, -4, -2, 1, 0]\n            }, {\n                \"backgroundColor\": \"rgba(0, 192, 0, 0.25)\",\n                \"data\": [6, 2, null, 4, 5, null, null, 2, 1]\n            }, {\n                \"backgroundColor\": \"rgba(192, 0, 0, 0.25)\",\n                \"data\": [7, 3, 4, 5, 6, 1, 4, null, null]\n            }, {\n                \"backgroundColor\": \"rgba(0, 64, 192, 0.25)\",\n                \"data\": [8, 7, 6, -6, -4, -6, 4, 5, 8]\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"spanGaps\": true,\n            \"scales\": {\n                \"x\": {\n                    \"display\": false\n                },\n                \"y\": {\n                    \"display\": false\n                }\n            },\n            \"elements\": {\n                \"point\": {\n                    \"radius\": 0\n                },\n                \"line\": {\n                    \"borderColor\": \"transparent\",\n                    \"fill\": \"origin\",\n                    \"tension\": 0\n                }\n            },\n            \"plugins\": {\n                \"legend\": false,\n                \"title\": false,\n                \"tooltip\": false\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/plugin.filler/line/boundary/origin-spline-above.json",
    "content": "{\n    \"config\": {\n        \"type\": \"line\",\n        \"data\": {\n            \"labels\": [\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\"],\n            \"datasets\": [{\n                \"backgroundColor\": \"rgba(0, 0, 192, 0.25)\",\n                \"data\": [null, null, 2, 4, 2, 1, -1, 1, 2]\n            }, {\n                \"backgroundColor\": \"rgba(0, 192, 0, 0.25)\",\n                \"data\": [4, 2, null, 3, 2.5, null, -2, 1.5, 3]\n            }, {\n                \"backgroundColor\": \"rgba(192, 0, 0, 0.25)\",\n                \"data\": [3.5, 2, 1, 2.5, -2, 3, -1, null, null]\n            }, {\n                \"backgroundColor\": \"rgba(128, 0, 128, 0.25)\",\n                \"data\": [5, 6, 5, -2, -4, -3, 4, 2, 4.5]\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"spanGaps\": false,\n            \"scales\": {\n                \"x\": {\n                    \"display\": false\n                },\n                \"y\": {\n                    \"display\": false\n                }\n            },\n            \"elements\": {\n                \"point\": {\n                    \"radius\": 0\n                },\n                \"line\": {\n                    \"cubicInterpolationMode\": \"monotone\",\n                    \"borderColor\": \"transparent\",\n                    \"fill\": {\n                      \"target\": \"origin\",\n                      \"below\": \"transparent\"\n                    }\n                }\n            },\n            \"plugins\": {\n                \"legend\": false,\n                \"title\": false,\n                \"tooltip\": false\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/plugin.filler/line/boundary/origin-spline-span.json",
    "content": "{\n    \"config\": {\n        \"type\": \"line\",\n        \"data\": {\n            \"labels\": [\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\"],\n            \"datasets\": [{\n                \"backgroundColor\": \"rgba(0, 0, 192, 0.25)\",\n                \"data\": [null, null, 2, 4, 2, 1, -1, 1, 2]\n            }, {\n                \"backgroundColor\": \"rgba(0, 192, 0, 0.25)\",\n                \"data\": [4, 2, null, 3, 2.5, null, -2, 1.5, 3]\n            }, {\n                \"backgroundColor\": \"rgba(192, 0, 0, 0.25)\",\n                \"data\": [3.5, 2, 1, 2.5, -2, 3, -1, null, null]\n            }, {\n                \"backgroundColor\": \"rgba(128, 0, 128, 0.25)\",\n                \"data\": [5, 6, 5, -2, -4, -3, 4, 2, 4.5]\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"spanGaps\": true,\n            \"scales\": {\n                \"x\": {\n                    \"display\": false\n                },\n                \"y\": {\n                    \"display\": false\n                }\n            },\n            \"elements\": {\n                \"point\": {\n                    \"radius\": 0\n                },\n                \"line\": {\n                    \"cubicInterpolationMode\": \"monotone\",\n                    \"borderColor\": \"transparent\",\n                    \"fill\": \"origin\"\n                }\n            },\n            \"plugins\": {\n                \"legend\": false,\n                \"title\": false,\n                \"tooltip\": false\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/plugin.filler/line/boundary/origin-spline.json",
    "content": "{\n    \"config\": {\n        \"type\": \"line\",\n        \"data\": {\n            \"labels\": [\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\"],\n            \"datasets\": [{\n                \"backgroundColor\": \"rgba(0, 0, 192, 0.25)\",\n                \"data\": [null, null, 2, 4, 2, 1, -1, 1, 2]\n            }, {\n                \"backgroundColor\": \"rgba(0, 192, 0, 0.25)\",\n                \"data\": [4, 2, null, 3, 2.5, null, -2, 1.5, 3]\n            }, {\n                \"backgroundColor\": \"rgba(192, 0, 0, 0.25)\",\n                \"data\": [3.5, 2, 1, 2.5, -2, 3, -1, null, null]\n            }, {\n                \"backgroundColor\": \"rgba(128, 0, 128, 0.25)\",\n                \"data\": [5, 6, 5, -2, -4, -3, 4, 2, 4.5]\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"spanGaps\": false,\n            \"scales\": {\n                \"x\": {\n                    \"display\": false\n                },\n                \"y\": {\n                    \"display\": false\n                }\n            },\n            \"elements\": {\n                \"point\": {\n                    \"radius\": 0\n                },\n                \"line\": {\n                    \"cubicInterpolationMode\": \"monotone\",\n                    \"borderColor\": \"transparent\",\n                    \"fill\": \"origin\"\n                }\n            },\n            \"plugins\": {\n                \"legend\": false,\n                \"title\": false,\n                \"tooltip\": false\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/plugin.filler/line/boundary/origin-stepped-span.json",
    "content": "{\n    \"config\": {\n        \"type\": \"line\",\n        \"data\": {\n            \"labels\": [\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\"],\n            \"datasets\": [{\n                \"backgroundColor\": \"rgba(0, 0, 192, 0.25)\",\n                \"data\": [null, null, 2, 4, 2, 1, -1, 1, 2]\n            }, {\n                \"backgroundColor\": \"rgba(0, 192, 0, 0.25)\",\n                \"data\": [4, 2, null, 3, 2.5, null, -2, 1.5, 3]\n            }, {\n                \"backgroundColor\": \"rgba(192, 0, 0, 0.25)\",\n                \"data\": [3.5, 2, 1, 2.5, -2, 3, -1, null, null]\n            }, {\n                \"backgroundColor\": \"rgba(128, 0, 128, 0.25)\",\n                \"data\": [5, 6, 5, -2, -4, -3, 4, 2, 4.5]\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"spanGaps\": true,\n            \"scales\": {\n                \"x\": {\n                    \"display\": false\n                },\n                \"y\": {\n                    \"display\": false\n                }\n            },\n            \"elements\": {\n                \"point\": {\n                    \"radius\": 0\n                },\n                \"line\": {\n                    \"cubicInterpolationMode\": \"monotone\",\n                    \"borderColor\": \"transparent\",\n                    \"stepped\": true,\n                    \"fill\": \"origin\"\n                }\n            },\n            \"plugins\": {\n                \"legend\": false,\n                \"title\": false,\n                \"tooltip\": false\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/plugin.filler/line/boundary/origin-stepped.json",
    "content": "{\n    \"config\": {\n        \"type\": \"line\",\n        \"data\": {\n            \"labels\": [\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\"],\n            \"datasets\": [{\n                \"backgroundColor\": \"rgba(0, 0, 192, 0.25)\",\n                \"data\": [null, null, 2, 4, 2, 1, -1, 1, 2]\n            }, {\n                \"backgroundColor\": \"rgba(0, 192, 0, 0.25)\",\n                \"data\": [4, 2, null, 3, 2.5, null, -2, 1.5, 3]\n            }, {\n                \"backgroundColor\": \"rgba(192, 0, 0, 0.25)\",\n                \"data\": [3.5, 2, 1, 2.5, -2, 3, -1, null, null]\n            }, {\n                \"backgroundColor\": \"rgba(128, 0, 128, 0.25)\",\n                \"data\": [5, 6, 5, -2, -4, -3, 4, 2, 4.5]\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"spanGaps\": false,\n            \"scales\": {\n                \"x\": {\n                    \"display\": false\n                },\n                \"y\": {\n                    \"display\": false\n                }\n            },\n            \"elements\": {\n                \"point\": {\n                    \"radius\": 0\n                },\n                \"line\": {\n                    \"cubicInterpolationMode\": \"monotone\",\n                    \"borderColor\": \"transparent\",\n                    \"stepped\": true,\n                    \"fill\": \"origin\"\n                }\n            },\n            \"plugins\": {\n                \"legend\": false,\n                \"title\": false,\n                \"tooltip\": false\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/plugin.filler/line/boundary/origin.json",
    "content": "{\n    \"config\": {\n        \"type\": \"line\",\n        \"data\": {\n            \"labels\": [\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\"],\n            \"datasets\": [{\n                \"backgroundColor\": \"rgba(0, 0, 192, 0.25)\",\n                \"data\": [null, null, 2, 3, 4, -4, -2, 1, 0]\n            }, {\n                \"backgroundColor\": \"rgba(0, 192, 0, 0.25)\",\n                \"data\": [6, 2, null, 4, 5, null, null, 2, 1]\n            }, {\n                \"backgroundColor\": \"rgba(192, 0, 0, 0.25)\",\n                \"data\": [7, 3, 4, 5, 6, 1, 4, null, null]\n            }, {\n                \"backgroundColor\": \"rgba(0, 64, 192, 0.25)\",\n                \"data\": [8, 7, 6, -6, -4, -6, 4, 5, 8]\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"spanGaps\": false,\n            \"scales\": {\n                \"x\": {\n                    \"display\": false\n                },\n                \"y\": {\n                    \"display\": false\n                }\n            },\n            \"elements\": {\n                \"point\": {\n                    \"radius\": 0\n                },\n                \"line\": {\n                    \"borderColor\": \"transparent\",\n                    \"fill\": \"origin\",\n                    \"tension\": 0\n                }\n            },\n            \"plugins\": {\n                \"legend\": false,\n                \"title\": false,\n                \"tooltip\": false\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/plugin.filler/line/boundary/start-span.json",
    "content": "{\n    \"config\": {\n        \"type\": \"line\",\n        \"data\": {\n            \"labels\": [\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\"],\n            \"datasets\": [{\n                \"backgroundColor\": \"rgba(0, 0, 255, 0.25)\",\n                \"data\": [null, null, 2, 3, 4, -4, -2, 1, 0]\n            }, {\n                \"backgroundColor\": \"rgba(0, 255, 0, 0.25)\",\n                \"data\": [6, 2, null, 4, 5, null, null, 2, 1]\n            }, {\n                \"backgroundColor\": \"rgba(255, 0, 0, 0.25)\",\n                \"data\": [7, 3, 4, 5, 6, 1, 4, null, null]\n            }, {\n                \"backgroundColor\": \"rgba(0, 0, 255, 0.25)\",\n                \"data\": [8, 7, 6, -6, -4, -6, 4, 5, 8]\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"spanGaps\": true,\n            \"scales\": {\n                \"x\": {\n                    \"display\": false\n                },\n                \"y\": {\n                    \"display\": false\n                }\n            },\n            \"elements\": {\n                \"point\": {\n                    \"radius\": 0\n                },\n                \"line\": {\n                    \"borderColor\": \"transparent\",\n                    \"fill\": \"start\",\n                    \"tension\": 0\n                }\n            },\n            \"plugins\": {\n                \"legend\": false,\n                \"title\": false,\n                \"tooltip\": false\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/plugin.filler/line/boundary/start.json",
    "content": "{\n    \"config\": {\n        \"type\": \"line\",\n        \"data\": {\n            \"labels\": [\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\"],\n            \"datasets\": [{\n                \"backgroundColor\": \"rgba(0, 0, 255, 0.25)\",\n                \"data\": [null, null, 2, 3, 4, -4, -2, 1, 0]\n            }, {\n                \"backgroundColor\": \"rgba(0, 255, 0, 0.25)\",\n                \"data\": [6, 2, null, 4, 5, null, null, 2, 1]\n            }, {\n                \"backgroundColor\": \"rgba(255, 0, 0, 0.25)\",\n                \"data\": [7, 3, 4, 5, 6, 1, 4, null, null]\n            }, {\n                \"backgroundColor\": \"rgba(0, 0, 255, 0.25)\",\n                \"data\": [8, 7, 6, -6, -4, -6, 4, 5, 8]\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"spanGaps\": false,\n            \"scales\": {\n                \"x\": {\n                    \"display\": false\n                },\n                \"y\": {\n                    \"display\": false\n                }\n            },\n            \"elements\": {\n                \"point\": {\n                    \"radius\": 0\n                },\n                \"line\": {\n                    \"borderColor\": \"transparent\",\n                    \"fill\": \"start\",\n                    \"tension\": 0\n                }\n            },\n            \"plugins\": {\n                \"legend\": false,\n                \"title\": false,\n                \"tooltip\": false\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/plugin.filler/line/dataset/border.json",
    "content": "{\n    \"config\": {\n        \"type\": \"line\",\n        \"data\": {\n            \"labels\": [\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\"],\n            \"datasets\": [{\n                \"backgroundColor\": \"rgba(255, 0, 0, 0.25)\",\n                \"data\": [null, null, 0, -1, 0, 1, 0, -1, 0],\n                \"fill\": 1\n            }, {\n                \"backgroundColor\": \"rgba(0, 255, 0, 0.25)\",\n                \"data\": [1, 0, null, 1, 0, null, -1, 0, 1],\n                \"fill\": \"+1\"\n            }, {\n                \"backgroundColor\": \"rgba(0, 0, 255, 0.25)\",\n                \"data\": [0, 2, 0, -2, 0, 2, 0],\n                \"fill\": 3\n            }, {\n                \"backgroundColor\": \"rgba(255, 0, 255, 0.25)\",\n                \"data\": [2, 0, -2, 0, 2, 0, -2, 0, 2],\n                \"fill\": \"-2\"\n            }, {\n                \"backgroundColor\": \"rgba(255, 255, 0, 0.25)\",\n                \"data\": [3, 1, -1, -3, -1, 1, 3, 1, -1],\n                \"fill\": \"-1\"\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"spanGaps\": false,\n            \"scales\": {\n                \"x\": {\n                    \"display\": false\n                },\n                \"y\": {\n                    \"display\": false\n                }\n            },\n            \"elements\": {\n                \"point\": {\n                    \"radius\": 0\n                },\n                \"line\": {\n\t\t\t\t\t\"borderColor\": \"black\",\n\t\t\t\t\t\"borderWidth\": 5,\n                    \"tension\": 0\n                }\n            },\n            \"plugins\": {\n                \"legend\": false,\n                \"title\": false,\n                \"tooltip\": false\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/plugin.filler/line/dataset/clip-bounds-x-off.js",
    "content": "const labels = [1, 2, 3, 4, 5, 6, 7];\nconst values = [65, 59, 80, 81, 56, 55, 40];\n\nmodule.exports = {\n  description: 'https://github.com/chartjs/Chart.js/issues/12052',\n  config: {\n    type: 'line',\n    data: {\n      labels,\n      datasets: [\n        {\n          data: values.map(v => v - 10),\n          fill: '1',\n          borderColor: 'rgb(255, 0, 0)',\n          backgroundColor: 'rgba(255, 0, 0, 0.25)',\n          xAxisID: 'x1',\n        },\n        {\n          data: values,\n          fill: false,\n          borderColor: 'rgb(255, 0, 0)',\n          xAxisID: 'x1',\n        },\n        {\n          data: values,\n          fill: false,\n          borderColor: 'rgb(0, 0, 255)',\n          xAxisID: 'x2',\n        },\n        {\n          data: values.map(v => v + 10),\n          fill: '-1',\n          borderColor: 'rgb(0, 0, 255)',\n          backgroundColor: 'rgba(0, 0, 255, 0.25)',\n          xAxisID: 'x2',\n        }\n      ]\n    },\n    options: {\n      clip: false,\n      indexAxis: 'y',\n      animation: false,\n      responsive: false,\n      plugins: {\n        legend: false,\n        title: false,\n        tooltip: false\n      },\n      elements: {\n        point: {\n          radius: 0\n        },\n        line: {\n          cubicInterpolationMode: 'monotone',\n          borderColor: 'transparent',\n          tension: 0\n        }\n      },\n      scales: {\n        x2: {\n          axis: 'x',\n          stack: 'stack',\n          max: 80,\n          display: false,\n        },\n        x1: {\n          min: 50,\n          axis: 'x',\n          stack: 'stack',\n          display: false,\n        },\n        y: {\n          display: false,\n        }\n      }\n    }\n  },\n};\n"
  },
  {
    "path": "test/fixtures/plugin.filler/line/dataset/clip-bounds-x.js",
    "content": "const labels = [1, 2, 3, 4, 5, 6, 7];\nconst values = [65, 59, 80, 81, 56, 55, 40];\n\nmodule.exports = {\n  description: 'https://github.com/chartjs/Chart.js/issues/12052',\n  config: {\n    type: 'line',\n    data: {\n      labels,\n      datasets: [\n        {\n          data: values.map(v => v - 10),\n          fill: '1',\n          borderColor: 'rgb(255, 0, 0)',\n          backgroundColor: 'rgba(255, 0, 0, 0.25)',\n          xAxisID: 'x1',\n        },\n        {\n          data: values,\n          fill: false,\n          borderColor: 'rgb(255, 0, 0)',\n          xAxisID: 'x1',\n        },\n        {\n          data: values,\n          fill: false,\n          borderColor: 'rgb(0, 0, 255)',\n          xAxisID: 'x2',\n        },\n        {\n          data: values.map(v => v + 10),\n          fill: '-1',\n          borderColor: 'rgb(0, 0, 255)',\n          backgroundColor: 'rgba(0, 0, 255, 0.25)',\n          xAxisID: 'x2',\n        }\n      ]\n    },\n    options: {\n      indexAxis: 'y',\n      animation: false,\n      responsive: false,\n      plugins: {\n        legend: false,\n        title: false,\n        tooltip: false\n      },\n      elements: {\n        point: {\n          radius: 0\n        },\n        line: {\n          cubicInterpolationMode: 'monotone',\n          borderColor: 'transparent',\n          tension: 0\n        }\n      },\n      scales: {\n        x2: {\n          axis: 'x',\n          stack: 'stack',\n          max: 80,\n          display: false,\n        },\n        x1: {\n          min: 50,\n          axis: 'x',\n          stack: 'stack',\n          display: false,\n        },\n        y: {\n          display: false,\n        }\n      }\n    }\n  },\n};\n"
  },
  {
    "path": "test/fixtures/plugin.filler/line/dataset/clip-bounds-y-off.js",
    "content": "const labels = [1, 2, 3, 4, 5, 6, 7];\nconst values = [65, 59, 80, 81, 56, 55, 40];\n\nmodule.exports = {\n  description: 'https://github.com/chartjs/Chart.js/issues/12052',\n  config: {\n    type: 'line',\n    data: {\n      labels,\n      datasets: [\n        {\n          data: values.map(v => v - 10),\n          fill: '1',\n          borderColor: 'rgb(255, 0, 0)',\n          backgroundColor: 'rgba(255, 0, 0, 0.25)',\n          yAxisID: 'y1',\n        },\n        {\n          data: values,\n          fill: false,\n          borderColor: 'rgb(255, 0, 0)',\n          yAxisID: 'y1',\n        },\n        {\n          data: values,\n          fill: false,\n          borderColor: 'rgb(0, 0, 255)',\n          yAxisID: 'y2',\n        },\n        {\n          data: values.map(v => v + 10),\n          fill: '-1',\n          borderColor: 'rgb(0, 0, 255)',\n          backgroundColor: 'rgba(0, 0, 255, 0.25)',\n          yAxisID: 'y2',\n        }\n      ]\n    },\n    options: {\n      clip: false,\n      animation: false,\n      responsive: false,\n      plugins: {\n        legend: false,\n        title: false,\n        tooltip: false\n      },\n      elements: {\n        point: {\n          radius: 0\n        },\n        line: {\n          cubicInterpolationMode: 'monotone',\n          borderColor: 'transparent',\n          tension: 0\n        }\n      },\n      scales: {\n        y2: {\n          axis: 'y',\n          stack: 'stack',\n          max: 80,\n          display: false,\n        },\n        y1: {\n          min: 50,\n          axis: 'y',\n          stack: 'stack',\n          display: false,\n        },\n        x: {\n          display: false,\n        }\n      }\n    }\n  },\n};\n"
  },
  {
    "path": "test/fixtures/plugin.filler/line/dataset/clip-bounds-y.js",
    "content": "const labels = [1, 2, 3, 4, 5, 6, 7];\nconst values = [65, 59, 80, 81, 56, 55, 40];\n\nmodule.exports = {\n  description: 'https://github.com/chartjs/Chart.js/issues/12052',\n  config: {\n    type: 'line',\n    data: {\n      labels,\n      datasets: [\n        {\n          data: values.map(v => v - 10),\n          fill: '1',\n          borderColor: 'rgb(255, 0, 0)',\n          backgroundColor: 'rgba(255, 0, 0, 0.25)',\n          yAxisID: 'y1',\n        },\n        {\n          data: values,\n          fill: false,\n          borderColor: 'rgb(255, 0, 0)',\n          yAxisID: 'y1',\n        },\n        {\n          data: values,\n          fill: false,\n          borderColor: 'rgb(0, 0, 255)',\n          yAxisID: 'y2',\n        },\n        {\n          data: values.map(v => v + 10),\n          fill: '-1',\n          borderColor: 'rgb(0, 0, 255)',\n          backgroundColor: 'rgba(0, 0, 255, 0.25)',\n          yAxisID: 'y2',\n        }\n      ]\n    },\n    options: {\n      animation: false,\n      responsive: false,\n      plugins: {\n        legend: false,\n        title: false,\n        tooltip: false\n      },\n      elements: {\n        point: {\n          radius: 0\n        },\n        line: {\n          cubicInterpolationMode: 'monotone',\n          borderColor: 'transparent',\n          tension: 0\n        }\n      },\n      scales: {\n        y2: {\n          axis: 'y',\n          stack: 'stack',\n          max: 80,\n          display: false,\n        },\n        y1: {\n          min: 50,\n          axis: 'y',\n          stack: 'stack',\n          display: false,\n        },\n        x: {\n          display: false,\n        }\n      }\n    }\n  },\n};\n"
  },
  {
    "path": "test/fixtures/plugin.filler/line/dataset/dual.json",
    "content": "{\n    \"config\": {\n        \"type\": \"line\",\n        \"data\": {\n            \"labels\": [\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\"],\n            \"datasets\": [{\n                \"backgroundColor\": \"rgba(255, 0, 0, 0.25)\",\n                \"data\": [0, 1, 2, -1, 0, 2, 1, -1, -2],\n                \"fill\": {\n                  \"target\": \"+1\",\n                  \"above\": \"rgba(255, 0, 0, 0.25)\",\n                  \"below\": \"rgba(0, 0, 255, 0.25)\"\n                }\n            }, {\n                \"data\": [0, 0, 0, 0, 0, 0, 0, 0, 0]\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"spanGaps\": true,\n            \"scales\": {\n                \"x\": {\n                    \"display\": false\n                },\n                \"y\": {\n                    \"display\": false\n                }\n            },\n            \"elements\": {\n                \"point\": {\n                    \"radius\": 0\n                },\n                \"line\": {\n                    \"borderColor\": \"transparent\",\n                    \"tension\": 0\n                }\n            },\n            \"plugins\": {\n                \"legend\": false,\n                \"title\": false,\n                \"tooltip\": false\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/plugin.filler/line/dataset/interpolated.js",
    "content": "const data1 = [];\nconst data2 = [];\nconst data3 = [];\nfor (let i = 0; i < 200; i++) {\n  const a = i / Math.PI / 10;\n\n  data1.push({x: i, y: i < 86 || i > 104 && i < 178 ? Math.sin(a) : NaN});\n\n  if (i % 10 === 0) {\n    data2.push({x: i, y: Math.cos(a)});\n  }\n\n  if (i % 15 === 0) {\n    data3.push({x: i, y: Math.cos(a + Math.PI / 2)});\n  }\n}\n\nmodule.exports = {\n  config: {\n    type: 'line',\n    data: {\n      datasets: [{\n        borderColor: 'rgba(255, 0, 0, 0.5)',\n        backgroundColor: 'rgba(255, 0, 0, 0.25)',\n        data: data1,\n        fill: false,\n      }, {\n        borderColor: 'rgba(0, 0, 255, 0.5)',\n        backgroundColor: 'rgba(0, 0, 255, 0.25)',\n        data: data2,\n        fill: 0,\n      }, {\n        borderColor: 'rgba(0, 255, 0, 0.5)',\n        backgroundColor: 'rgba(0, 255, 0, 0.25)',\n        data: data3,\n        fill: 1,\n      }]\n    },\n    options: {\n      animation: false,\n      responsive: false,\n      datasets: {\n        line: {\n          tension: 0.4,\n          borderWidth: 1,\n          pointRadius: 1.5,\n        }\n      },\n      plugins: {\n        legend: false,\n        title: false,\n        tooltip: false\n      },\n      scales: {\n        x: {\n          type: 'linear',\n          display: false\n        },\n        y: {\n          type: 'linear',\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      height: 512,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.filler/line/dataset/no-border.json",
    "content": "{\n    \"config\": {\n        \"type\": \"line\",\n        \"data\": {\n            \"labels\": [\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\"],\n            \"datasets\": [{\n                \"backgroundColor\": \"rgba(255, 0, 0, 0.25)\",\n                \"data\": [null, null, 0, -1, 0, 1, 0, -1, 0],\n                \"fill\": 1\n            }, {\n                \"backgroundColor\": \"rgba(0, 255, 0, 0.25)\",\n                \"data\": [1, 0, null, 1, 0, null, -1, 0, 1],\n                \"fill\": \"+1\"\n            }, {\n                \"backgroundColor\": \"rgba(0, 0, 255, 0.25)\",\n                \"data\": [0, 2, 0, -2, 0, 2, 0],\n                \"fill\": 3\n            }, {\n                \"backgroundColor\": \"rgba(255, 0, 255, 0.25)\",\n                \"data\": [2, 0, -2, 0, 2, 0, -2, 0, 2],\n                \"fill\": \"-2\"\n            }, {\n                \"backgroundColor\": \"rgba(255, 255, 0, 0.25)\",\n                \"data\": [3, 1, -1, -3, -1, 1, 3, 1, -1],\n                \"fill\": \"-1\"\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"spanGaps\": false,\n            \"scales\": {\n                \"x\": {\n                    \"display\": false\n                },\n                \"y\": {\n                    \"display\": false\n                }\n            },\n            \"elements\": {\n                \"point\": {\n                    \"radius\": 0\n                },\n                \"line\": {\n                    \"borderColor\": \"transparent\",\n                    \"tension\": 0\n                }\n            },\n            \"plugins\": {\n                \"legend\": false,\n                \"title\": false,\n                \"tooltip\": false\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/plugin.filler/line/dataset/span-dual.json",
    "content": "{\n    \"config\": {\n        \"type\": \"line\",\n        \"data\": {\n            \"labels\": [\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\"],\n            \"datasets\": [{\n                \"backgroundColor\": \"rgba(255, 0, 0, 0.25)\",\n                \"data\": [null, null, 0, -1, 0, 1, 0, -1, 0],\n                \"fill\": {\n                  \"target\": 1,\n                  \"above\": \"rgba(255, 0, 0, 0.25)\",\n                  \"below\": \"rgba(122, 0, 0, 0.25)\"\n                }\n            }, {\n                \"backgroundColor\": \"rgba(0, 255, 0, 0.25)\",\n                \"data\": [1, 0, null, 1, 0, null, -1, 0, 1],\n                \"fill\": {\n                  \"target\": \"+1\",\n                  \"above\": \"rgba(0, 255, 0, 0.25)\",\n                  \"below\": \"rgba(0, 255, 120, 0.25)\"\n                }\n            }, {\n                \"backgroundColor\": \"rgba(255, 0, 255, 0.25)\",\n                \"data\": [2, 0, -2, 0, 2, 0, -2, 0, 2],\n                \"fill\": {\n                  \"target\": \"-2\",\n                  \"above\": \"rgba(255, 0, 255, 0.25)\",\n                  \"below\": \"rgba(255, 0, 120, 0.25)\"\n                }\n            }, {\n                \"backgroundColor\": \"rgba(255, 255, 0, 0.25)\",\n                \"data\": [3, 1, -1, -3, -1, 1, 3, 1, -1],\n                \"fill\": {\n                  \"target\": \"-1\",\n                  \"above\": \"rgba(255, 255, 0, 0.25)\",\n                  \"below\": \"rgba(255, 120, 0, 0.25)\"\n                }\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"spanGaps\": true,\n            \"scales\": {\n                \"x\": {\n                    \"display\": false\n                },\n                \"y\": {\n                    \"display\": false\n                }\n            },\n            \"elements\": {\n                \"point\": {\n                    \"radius\": 0\n                },\n                \"line\": {\n                    \"cubicInterpolationMode\": \"monotone\",\n                    \"borderColor\": \"transparent\",\n                    \"tension\": 0\n                }\n            },\n            \"plugins\": {\n                \"legend\": false,\n                \"title\": false,\n                \"tooltip\": false\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/plugin.filler/line/dataset/span.json",
    "content": "{\n    \"config\": {\n        \"type\": \"line\",\n        \"data\": {\n            \"labels\": [\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\"],\n            \"datasets\": [{\n                \"backgroundColor\": \"rgba(255, 0, 0, 0.25)\",\n                \"data\": [null, null, 0, -1, 0, 1, 0, -1, 0],\n                \"fill\": 1\n            }, {\n                \"backgroundColor\": \"rgba(0, 255, 0, 0.25)\",\n                \"data\": [1, 0, null, 1, 0, null, -1, 0, 1],\n                \"fill\": \"+1\"\n            }, {\n                \"backgroundColor\": \"rgba(0, 0, 255, 0.25)\",\n                \"data\": [0, 2, 0, -2, 0, 2, 0],\n                \"fill\": 3\n            }, {\n                \"backgroundColor\": \"rgba(255, 0, 255, 0.25)\",\n                \"data\": [2, 0, -2, 0, 2, 0, -2, 0, 2],\n                \"fill\": \"-2\"\n            }, {\n                \"backgroundColor\": \"rgba(255, 255, 0, 0.25)\",\n                \"data\": [3, 1, -1, -3, -1, 1, 3, 1, -1],\n                \"fill\": \"-1\"\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"spanGaps\": true,\n            \"scales\": {\n                \"x\": {\n                    \"display\": false\n                },\n                \"y\": {\n                    \"display\": false\n                }\n            },\n            \"elements\": {\n                \"point\": {\n                    \"radius\": 0\n                },\n                \"line\": {\n                    \"borderColor\": \"transparent\",\n                    \"tension\": 0\n                }\n            },\n            \"plugins\": {\n                \"legend\": false,\n                \"title\": false,\n                \"tooltip\": false\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/plugin.filler/line/dataset/spline-span-above.json",
    "content": "{\n    \"config\": {\n        \"type\": \"line\",\n        \"data\": {\n            \"labels\": [\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\"],\n            \"datasets\": [{\n                \"backgroundColor\": \"rgba(255, 0, 0, 0.25)\",\n                \"data\": [null, null, 0, -1, 0, 1, 0, -1, 0],\n                \"fill\": {\n                  \"target\": 1,\n                  \"below\": \"transparent\"\n                }\n            }, {\n                \"backgroundColor\": \"rgba(0, 255, 0, 0.25)\",\n                \"data\": [1, 0, null, 1, 0, null, -1, 0, 1],\n                \"fill\": {\n                  \"target\": \"+1\",\n                  \"below\": \"transparent\"\n                }\n            }, {\n                \"backgroundColor\": \"rgba(255, 0, 255, 0.25)\",\n                \"data\": [2, 0, -2, 0, 2, 0, -2, 0, 2],\n                \"fill\": {\n                  \"target\": \"-2\",\n                  \"below\": \"transparent\"\n                }\n            }, {\n                \"backgroundColor\": \"rgba(255, 255, 0, 0.25)\",\n                \"data\": [3, 1, -1, -3, -1, 1, 3, 1, -1],\n                \"fill\": {\n                  \"target\": \"-1\",\n                  \"below\": \"transparent\"\n                }\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"spanGaps\": true,\n            \"scales\": {\n                \"x\": {\n                    \"display\": false\n                },\n                \"y\": {\n                    \"display\": false\n                }\n            },\n            \"elements\": {\n                \"point\": {\n                    \"radius\": 0\n                },\n                \"line\": {\n                    \"cubicInterpolationMode\": \"monotone\",\n                    \"borderColor\": \"transparent\"\n                }\n            },\n            \"plugins\": {\n                \"legend\": false,\n                \"title\": false,\n                \"tooltip\": false\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/plugin.filler/line/dataset/spline-span-below.json",
    "content": "{\n    \"config\": {\n        \"type\": \"line\",\n        \"data\": {\n            \"labels\": [\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\"],\n            \"datasets\": [{\n                \"backgroundColor\": \"rgba(255, 0, 0, 0.25)\",\n                \"data\": [null, null, 0, -1, 0, 1, 0, -1, 0],\n                \"fill\": {\n                  \"target\": 1,\n                  \"above\": \"transparent\"\n                }\n            }, {\n                \"backgroundColor\": \"rgba(0, 255, 0, 0.25)\",\n                \"data\": [1, 0, null, 1, 0, null, -1, 0, 1],\n                \"fill\": {\n                  \"target\": \"+1\",\n                  \"above\": \"transparent\"\n                }\n            }, {\n                \"backgroundColor\": \"rgba(255, 0, 255, 0.25)\",\n                \"data\": [2, 0, -2, 0, 2, 0, -2, 0, 2],\n                \"fill\": {\n                  \"target\": \"-2\",\n                  \"above\": \"transparent\"\n                }\n            }, {\n                \"backgroundColor\": \"rgba(255, 255, 0, 0.25)\",\n                \"data\": [3, 1, -1, -3, -1, 1, 3, 1, -1],\n                \"fill\": {\n                  \"target\": \"-1\",\n                  \"above\": \"transparent\"\n                }\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"spanGaps\": true,\n            \"scales\": {\n                \"x\": {\n                    \"display\": false\n                },\n                \"y\": {\n                    \"display\": false\n                }\n            },\n            \"elements\": {\n                \"point\": {\n                    \"radius\": 0\n                },\n                \"line\": {\n                    \"cubicInterpolationMode\": \"monotone\",\n                    \"borderColor\": \"transparent\"\n                }\n            },\n            \"plugins\": {\n                \"legend\": false,\n                \"title\": false,\n                \"tooltip\": false\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/plugin.filler/line/dataset/spline-span.json",
    "content": "{\n    \"config\": {\n        \"type\": \"line\",\n        \"data\": {\n            \"labels\": [\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\"],\n            \"datasets\": [{\n                \"backgroundColor\": \"rgba(255, 0, 0, 0.25)\",\n                \"data\": [null, null, 0, -1, 0, 1, 0, -1, 0],\n                \"fill\": 1\n            }, {\n                \"backgroundColor\": \"rgba(0, 255, 0, 0.25)\",\n                \"data\": [1, 0, null, 1, 0, null, -1, 0, 1],\n                \"fill\": \"+1\"\n            }, {\n                \"backgroundColor\": \"rgba(0, 0, 255, 0.25)\",\n                \"data\": [0, 2, 0, -2, 0, 2, 0],\n                \"fill\": 3\n            }, {\n                \"backgroundColor\": \"rgba(255, 0, 255, 0.25)\",\n                \"data\": [2, 0, -2, 0, 2, 0, -2, 0, 2],\n                \"fill\": \"-2\"\n            }, {\n                \"backgroundColor\": \"rgba(255, 255, 0, 0.25)\",\n                \"data\": [3, 1, -1, -3, -1, 1, 3, 1, -1],\n                \"fill\": \"-1\"\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"spanGaps\": true,\n            \"scales\": {\n                \"x\": {\n                    \"display\": false\n                },\n                \"y\": {\n                    \"display\": false\n                }\n            },\n            \"elements\": {\n                \"point\": {\n                    \"radius\": 0\n                },\n                \"line\": {\n                    \"cubicInterpolationMode\": \"monotone\",\n                    \"borderColor\": \"transparent\"\n                }\n            },\n            \"plugins\": {\n                \"legend\": false,\n                \"title\": false,\n                \"tooltip\": false\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/plugin.filler/line/dataset/spline.json",
    "content": "{\n    \"config\": {\n        \"type\": \"line\",\n        \"data\": {\n            \"labels\": [\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\"],\n            \"datasets\": [{\n                \"backgroundColor\": \"rgba(255, 0, 0, 0.25)\",\n                \"data\": [null, null, 0, -1, 0, 1, 0, -1, 0],\n                \"fill\": 1\n            }, {\n                \"backgroundColor\": \"rgba(0, 255, 0, 0.25)\",\n                \"data\": [1, 0, null, 1, 0, null, -1, 0, 1],\n                \"fill\": \"+1\"\n            }, {\n                \"backgroundColor\": \"rgba(0, 0, 255, 0.25)\",\n                \"data\": [0, 2, 0, -2, 0, 2, 0],\n                \"fill\": 3\n            }, {\n                \"backgroundColor\": \"rgba(255, 0, 255, 0.25)\",\n                \"data\": [2, 0, -2, 0, 2, 0, -2, 0, 2],\n                \"fill\": \"-2\"\n            }, {\n                \"backgroundColor\": \"rgba(255, 255, 0, 0.25)\",\n                \"data\": [3, 1, -1, -3, -1, 1, 3, 1, -1],\n                \"fill\": \"-1\"\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"spanGaps\": false,\n            \"scales\": {\n                \"x\": {\n                    \"display\": false\n                },\n                \"y\": {\n                    \"display\": false\n                }\n            },\n            \"elements\": {\n                \"point\": {\n                    \"radius\": 0\n                },\n                \"line\": {\n                    \"cubicInterpolationMode\": \"monotone\",\n                    \"borderColor\": \"transparent\"\n                }\n            },\n            \"plugins\": {\n                \"legend\": false,\n                \"title\": false,\n                \"tooltip\": false\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/plugin.filler/line/dataset/stepped.json",
    "content": "{\n    \"config\": {\n        \"type\": \"line\",\n        \"data\": {\n            \"labels\": [\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\"],\n            \"datasets\": [{\n                \"backgroundColor\": \"rgba(255, 0, 0, 0.25)\",\n                \"stepped\": true,\n                \"data\": [null, null, 0, -1, 0, 1, 0, -1, 0],\n                \"fill\": 1\n            }, {\n                \"backgroundColor\": \"rgba(0, 255, 0, 0.25)\",\n                \"stepped\": \"after\",\n                \"data\": [1, 0, null, 1, 0, null, -1, 0, 1],\n                \"fill\": \"+1\"\n            }, {\n                \"backgroundColor\": \"rgba(0, 0, 255, 0.25)\",\n                \"stepped\": \"before\",\n                \"data\": [0, 2, 0, -2, 0, 2, 0],\n                \"fill\": 3\n            }, {\n                \"backgroundColor\": \"rgba(255, 0, 255, 0.25)\",\n                \"stepped\": \"middle\",\n                \"data\": [2, 0, -2, 0, 2, 0, -2, 0, 2],\n                \"fill\": \"-2\"\n            }, {\n                \"backgroundColor\": \"rgba(255, 255, 0, 0.25)\",\n                \"stepped\": false,\n                \"data\": [3, 1, -1, -3, -1, 1, 3, 1, -1],\n                \"fill\": \"-1\"\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"spanGaps\": false,\n            \"scales\": {\n                \"x\": {\n                    \"display\": false\n                },\n                \"y\": {\n                    \"display\": false\n                }\n            },\n            \"elements\": {\n                \"point\": {\n                    \"radius\": 0\n                },\n                \"line\": {\n                    \"borderColor\": \"black\"\n                }\n            },\n            \"plugins\": {\n                \"legend\": false,\n                \"title\": false,\n                \"tooltip\": false\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/plugin.filler/line/drawTimeFillFalse/beforeDatasetDraw.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: ['0', '1', '2', '3', '4', '5'],\n      datasets: [{\n        backgroundColor: 'red',\n        data: [3, -3, 0, 5, -5, 0],\n        fill: false\n      }]\n    },\n    options: {\n      plugins: {\n        legend: false,\n        title: false,\n        filler: {\n          drawTime: 'beforeDatasetDraw'\n        }\n      },\n    }\n  },\n};\n"
  },
  {
    "path": "test/fixtures/plugin.filler/line/drawTimeFillFalse/beforeDatasetsDraw.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: ['0', '1', '2', '3', '4', '5'],\n      datasets: [{\n        backgroundColor: 'red',\n        data: [3, -3, 0, 5, -5, 0],\n        fill: false\n      }]\n    },\n    options: {\n      plugins: {\n        legend: false,\n        title: false,\n        filler: {\n          drawTime: 'beforeDatasetsDraw'\n        }\n      },\n    }\n  },\n};\n"
  },
  {
    "path": "test/fixtures/plugin.filler/line/drawTimeFillFalse/beforeDraw.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: ['0', '1', '2', '3', '4', '5'],\n      datasets: [{\n        backgroundColor: 'red',\n        data: [3, -3, 0, 5, -5, 0],\n        fill: false\n      }]\n    },\n    options: {\n      plugins: {\n        legend: false,\n        title: false,\n        filler: {\n          drawTime: 'beforeDraw'\n        }\n      },\n    }\n  },\n};\n"
  },
  {
    "path": "test/fixtures/plugin.filler/line/points-outside-canvas-initial.js",
    "content": "module.exports = {\n  description: 'https://github.com/chartjs/Chart.js/issues/8699',\n  config: {\n    type: 'line',\n    data: {\n      datasets: [{\n        backgroundColor: 'red',\n        data: [{x: 0, y: 3}, {x: 2, y: -3}, {x: 4, y: 0}, {x: 6, y: 5}, {x: 8, y: -5}, {x: 10, y: 0}],\n        fill: 'origin'\n      }]\n    },\n    options: {\n      plugins: {\n        legend: false,\n        title: false,\n      },\n      scales: {\n        x: {\n          display: false,\n          type: 'linear',\n          min: 5\n        },\n        y: {\n          display: false\n        }\n      }\n    }\n  },\n};\n"
  },
  {
    "path": "test/fixtures/plugin.filler/line/points-outside-canvas-update.js",
    "content": "module.exports = {\n  description: 'https://github.com/chartjs/Chart.js/issues/8699',\n  config: {\n    type: 'line',\n    data: {\n      datasets: [{\n        backgroundColor: 'red',\n        data: [{x: 0, y: 3}, {x: 2, y: -3}, {x: 4, y: 0}, {x: 6, y: 5}, {x: 8, y: -5}, {x: 10, y: 0}],\n        fill: 'origin'\n      }]\n    },\n    options: {\n      plugins: {\n        legend: false,\n        title: false,\n      },\n      scales: {\n        x: {\n          type: 'linear',\n          display: false\n        },\n        y: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    run(chart) {\n      chart.scales.x.options.min = 5;\n      chart.update();\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.filler/line/segments/alignToPixels.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      datasets: [\n        {\n          data: [\n            {x: 0, y: 0},\n            {x: 1, y: 20},\n            {x: 1.00001, y: 30},\n            {x: 2, y: 100},\n            {x: 2.00001, y: 100}\n          ],\n          backgroundColor: '#FF000070',\n          borderColor: 'black',\n          radius: 0,\n          segment: {\n            borderDash: ctx => ctx.p0.parsed.x > 1 ? [10, 5] : undefined,\n          },\n          fill: true\n        }\n      ]\n    },\n    options: {\n      plugins: {\n        legend: false\n      },\n      scales: {\n        x: {\n          type: 'linear',\n          alignToPixels: true,\n          display: false\n        },\n        y: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      width: 300,\n      height: 240\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.filler/line/segments/gap.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: ['a', 'b', 'c', 'd', 'e', 'f'],\n      datasets: [{\n        data: [1, 3, NaN, NaN, 2, 1],\n        borderColor: 'transparent',\n        backgroundColor: 'black',\n        fill: true,\n        segment: {\n          backgroundColor: ctx => ctx.p0.skip || ctx.p1.skip ? 'red' : undefined,\n        },\n        spanGaps: true\n      }]\n    },\n    options: {\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.filler/line/segments/slope.js",
    "content": "function slope({p0, p1}) {\n  return (p0.y - p1.y) / (p1.x - p0.x);\n}\n\nmodule.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: ['a', 'b', 'c', 'd', 'e', 'f'],\n      datasets: [{\n        data: [1, 2, 3, 3, 2, 1],\n        backgroundColor: 'black',\n        borderColor: 'orange',\n        fill: true,\n        segment: {\n          backgroundColor: ctx => slope(ctx) > 0 ? 'green' : slope(ctx) < 0 ? 'red' : undefined,\n        }\n      }]\n    },\n    options: {\n      plugins: {\n        legend: false\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.filler/line/shape.js",
    "content": "const data = [];\nfor (let rad = 0; rad <= Math.PI * 2; rad += Math.PI / 45) {\n  data.push({\n    x: Math.cos(rad),\n    y: Math.sin(rad)\n  });\n}\n\nmodule.exports = {\n  config: {\n    type: 'line',\n    data: {\n      datasets: [{\n        data,\n        fill: 'shape',\n        backgroundColor: 'rgba(255, 0, 0, 0.5)',\n      }]\n    },\n    options: {\n      plugins: {\n        legend: false\n      },\n      scales: {\n        x: {\n          type: 'linear',\n          display: false\n        },\n        y: {\n          type: 'linear',\n          display: false\n        },\n      },\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.filler/line/stack-multiple-scales.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: ['0', '1', '2', '3'],\n      datasets: [{\n        backgroundColor: 'rgba(255, 0, 0, 0.5)',\n        data: [null, 1, 1, 1],\n        fill: 'stack'\n      }, {\n        backgroundColor: 'rgba(0, 255, 0, 0.5)',\n        data: [null, 2, 2, 2],\n        fill: 'stack'\n      }, {\n        backgroundColor: 'rgba(0, 0, 255, 0.5)',\n        data: [null, 3, 3, 3],\n        fill: 'stack'\n      }, {\n        backgroundColor: 'rgba(255, 0, 255, 0.5)',\n        data: [0.5, 0.5, 0.5, null],\n        fill: 'stack',\n        yAxisID: 'y2'\n      }, {\n        backgroundColor: 'rgba(0, 0, 0, 0.5)',\n        data: [1.5, 1.5, 1.5, null],\n        fill: 'stack',\n        yAxisID: 'y2'\n      }, {\n        backgroundColor: 'rgba(255, 255, 0, 0.5)',\n        data: [2.5, 2.5, 2.5, null],\n        fill: 'stack',\n        yAxisID: 'y2'\n      }]\n    },\n    options: {\n      responsive: false,\n      spanGaps: false,\n      scales: {\n        x: {\n          display: false\n        },\n        y: {\n          position: 'right',\n          stacked: true,\n          min: 0\n        },\n        y2: {\n          position: 'left',\n          stacked: true,\n          min: 0\n        }\n      },\n      elements: {\n        point: {\n          radius: 0\n        },\n        line: {\n          borderColor: 'transparent',\n          tension: 0\n        }\n      },\n      plugins: {\n        legend: false,\n        title: false,\n        tooltip: false\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.filler/line/stack.json",
    "content": "{\n    \"config\": {\n        \"type\": \"line\",\n        \"data\": {\n            \"labels\": [\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\"],\n            \"datasets\": [{\n                \"backgroundColor\": \"rgba(255, 0, 0, 0.25)\",\n                \"data\": [null, null, 0, 1, 0, 1, null, 0, 1],\n                \"fill\": \"stack\"\n            }, {\n                \"backgroundColor\": \"rgba(0, 255, 0, 0.25)\",\n                \"data\": [1, 1, null, 1, 0, null, 1, 1, 0],\n                \"fill\": \"stack\"\n            }, {\n                \"backgroundColor\": \"rgba(0, 0, 255, 0.25)\",\n                \"data\": [0, 2, null, 2, 0, 2, 0],\n                \"fill\": \"stack\"\n            }, {\n                \"backgroundColor\": \"rgba(255, 0, 255, 0.25)\",\n                \"data\": [2, 0, null, 0, 2, 0, 2, 0, 2],\n                \"fill\": \"stack\"\n\t\t\t}, {\n\t\t\t\t\"backgroundColor\": \"rgba(0, 0, 0, 0.25)\",\n\t\t\t\t\"data\": [null, null, null, 2, null, 2, 2],\n\t\t\t\t\"fill\": \"stack\"\n\t\t\t}, {\n                \"backgroundColor\": \"rgba(255, 255, 0, 0.25)\",\n                \"data\": [3, 1, 1, 3, 1, 1, 3, 1, 1],\n                \"fill\": \"stack\"\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"spanGaps\": false,\n            \"scales\": {\n                \"x\": {\n                    \"display\": false\n                },\n                \"y\": {\n\t\t\t\t\t\"display\": false,\n\t\t\t\t\t\"stacked\": true,\n\t\t\t\t\t\"min\": 0\n                }\n            },\n            \"elements\": {\n                \"point\": {\n                    \"radius\": 0\n                },\n                \"line\": {\n                    \"borderColor\": \"transparent\",\n                    \"tension\": 0\n                }\n            },\n            \"plugins\": {\n                \"legend\": false,\n                \"title\": false,\n                \"tooltip\": false\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/plugin.filler/line/value.json",
    "content": "{\n    \"config\": {\n        \"type\": \"line\",\n        \"data\": {\n            \"labels\": [\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\"],\n            \"datasets\": [{\n                \"backgroundColor\": \"rgba(255, 0, 0, 0.25)\",\n                \"data\": [-4, 4, 0, -1, 0, 1, 0, -1, 0],\n                \"fill\": { \"value\": 2 }\n            }]\n        },\n        \"options\": {\n            \"responsive\": false,\n            \"spanGaps\": false,\n            \"scales\": {\n                \"x\": {\n                    \"display\": false\n                },\n                \"y\": {\n                    \"display\": false\n                }\n            },\n            \"elements\": {\n                \"point\": {\n                    \"radius\": 0\n                },\n                \"line\": {\n                    \"borderColor\": \"transparent\",\n                    \"tension\": 0\n                }\n            },\n            \"plugins\": {\n                \"legend\": false,\n                \"title\": false,\n                \"tooltip\": false\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}\n"
  },
  {
    "path": "test/fixtures/plugin.filler/line/vertical.js",
    "content": "const data = [\n  {y: 1, x: 12},\n  {y: 3, x: 14},\n  {y: 4, x: 20},\n  {y: 6, x: 13},\n  {y: 9, x: 18},\n];\n\nmodule.exports = {\n  config: {\n    type: 'line',\n    data: {\n      datasets: [{\n        data: data,\n        borderColor: 'red',\n        fill: false,\n      }, {\n        data: data.map((v) => ({y: v.y, x: 2 * v.x - 1.5 * v.y})),\n        fill: '-1',\n        borderColor: 'blue',\n        backgroundColor: 'rgba(255, 200, 0, 0.5)',\n      }]\n    },\n    options: {\n      indexAxis: 'y',\n      radius: 0,\n      plugins: {\n        legend: false\n      },\n      scales: {\n        x: {\n          display: false,\n          type: 'linear'\n        },\n        y: {\n          display: false,\n          type: 'linear'\n        }\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.filler/radar/beforeDraw.js",
    "content": "module.exports = {\n  config: {\n    type: 'radar',\n    data: {\n      labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],\n      datasets: [{\n        label: '# of Votes',\n        data: [9, 7, 3, 5, 2, 3],\n        fill: 'origin',\n        borderColor: 'red',\n        backgroundColor: 'green',\n        pointRadius: 12,\n        pointBackgroundColor: 'red'\n      }]\n    },\n    options: {\n      layout: {\n        padding: 20\n      },\n      plugins: {\n        legend: false,\n        filler: {\n          drawTime: 'beforeDraw'\n        }\n      },\n      scales: {\n        r: {\n          angleLines: {\n            color: 'rgba(0,0,0,0.5)',\n            lineWidth: 2\n          },\n          grid: {\n            color: 'rgba(0,0,0,0.5)',\n            lineWidth: 2\n          },\n          pointLabels: {\n            display: false\n          },\n          ticks: {\n            beginAtZero: true,\n            display: false\n          },\n        }\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.filler/radar/boundary/end-circular.json",
    "content": "{\n  \"config\": {\n    \"type\": \"radar\",\n    \"data\": {\n      \"labels\": [\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\"],\n      \"datasets\": [\n        {\n          \"backgroundColor\": \"rgba(0, 0, 192, 0.25)\",\n          \"data\": [null, null, 2, 3, 4, -4, -2, 1, 0]\n        },\n        {\n          \"backgroundColor\": \"rgba(0, 192, 0, 0.25)\",\n          \"data\": [5.5, 2, null, 4, 5, null, null, 2, 1]\n        },\n        {\n          \"backgroundColor\": \"rgba(192, 0, 0, 0.25)\",\n          \"data\": [7, 3, 4, 5, 6, 1, 4, null, null]\n        },\n        {\n          \"backgroundColor\": \"rgba(0, 0, 192, 0.25)\",\n          \"data\": [8, 7, 6.5, -6, -4, -6, 4, 5, 8]\n        }\n      ]\n    },\n    \"options\": {\n      \"responsive\": false,\n      \"spanGaps\": false,\n      \"scales\": {\n        \"r\": {\n          \"display\": false,\n          \"grid\": {\n            \"circular\": true\n          }\n        }\n      },\n      \"elements\": {\n        \"point\": {\n          \"radius\": 0\n        },\n        \"line\": {\n          \"borderColor\": \"transparent\",\n          \"fill\": \"end\"\n        }\n      },\n      \"plugins\": {\n        \"legend\": false,\n        \"title\": false,\n        \"tooltip\": false\n      }\n    }\n  },\n  \"options\": {\n    \"canvas\": {\n      \"height\": 256,\n      \"width\": 256\n    }\n  }\n}\n"
  },
  {
    "path": "test/fixtures/plugin.filler/radar/boundary/end-span.json",
    "content": "{\n  \"config\": {\n    \"type\": \"radar\",\n    \"data\": {\n      \"labels\": [\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\"],\n      \"datasets\": [\n        {\n          \"backgroundColor\": \"rgba(0, 0, 192, 0.25)\",\n          \"data\": [null, null, 2, 3, 4, -4, -2, 1, 0]\n        },\n        {\n          \"backgroundColor\": \"rgba(0, 192, 0, 0.25)\",\n          \"data\": [5.5, 2, null, 4, 5, null, null, 2, 1]\n        },\n        {\n          \"backgroundColor\": \"rgba(192, 0, 0, 0.25)\",\n          \"data\": [7, 3, 4, 5, 6, 1, 4, null, null]\n        },\n        {\n          \"backgroundColor\": \"rgba(0, 0, 192, 0.25)\",\n          \"data\": [8, 7, 6.5, -6, -4, -6, 4, 5, 8]\n        }\n      ]\n    },\n    \"options\": {\n      \"responsive\": false,\n      \"spanGaps\": true,\n      \"scales\": {\n        \"r\": {\n          \"display\": false\n        }\n      },\n      \"elements\": {\n        \"point\": {\n          \"radius\": 0\n        },\n        \"line\": {\n          \"borderColor\": \"transparent\",\n          \"fill\": \"end\"\n        }\n      },\n      \"plugins\": {\n        \"legend\": false,\n        \"title\": false,\n        \"tooltip\": false\n      }\n    }\n  },\n  \"options\": {\n    \"canvas\": {\n      \"height\": 256,\n      \"width\": 256\n    }\n  }\n}\n"
  },
  {
    "path": "test/fixtures/plugin.filler/radar/boundary/end.json",
    "content": "{\n  \"config\": {\n    \"type\": \"radar\",\n    \"data\": {\n      \"labels\": [\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\"],\n      \"datasets\": [\n        {\n          \"backgroundColor\": \"rgba(0, 0, 192, 0.25)\",\n          \"data\": [null, null, 2, 3, 4, -4, -2, 1, 0]\n        },\n        {\n          \"backgroundColor\": \"rgba(0, 192, 0, 0.25)\",\n          \"data\": [5.5, 2, null, 4, 5, null, null, 2, 1]\n        },\n        {\n          \"backgroundColor\": \"rgba(192, 0, 0, 0.25)\",\n          \"data\": [7, 3, 4, 5, 6, 1, 4, null, null]\n        },\n        {\n          \"backgroundColor\": \"rgba(0, 0, 192, 0.25)\",\n          \"data\": [8, 7, 6.5, -6, -4, -6, 4, 5, 8]\n        }\n      ]\n    },\n    \"options\": {\n      \"responsive\": false,\n      \"spanGaps\": false,\n      \"scales\": {\n        \"r\": {\n          \"display\": false\n        }\n      },\n      \"elements\": {\n        \"point\": {\n          \"radius\": 0\n        },\n        \"line\": {\n          \"borderColor\": \"transparent\",\n          \"fill\": \"end\"\n        }\n      },\n      \"plugins\": {\n        \"legend\": false,\n        \"title\": false,\n        \"tooltip\": false\n      }\n    }\n  },\n  \"options\": {\n    \"canvas\": {\n      \"height\": 256,\n      \"width\": 256\n    }\n  }\n}\n"
  },
  {
    "path": "test/fixtures/plugin.filler/radar/boundary/origin-circular.json",
    "content": "{\n  \"config\": {\n    \"type\": \"radar\",\n    \"data\": {\n      \"labels\": [\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\"],\n      \"datasets\": [\n        {\n          \"backgroundColor\": \"rgba(0, 0, 192, 0.25)\",\n          \"data\": [null, null, 2, 4, 2, 1, -1, 1, 2]\n        },\n        {\n          \"backgroundColor\": \"rgba(0, 192, 0, 0.25)\",\n          \"data\": [4, 2, null, 3, 2.5, null, -2, 1.5, 3]\n        },\n        {\n          \"backgroundColor\": \"rgba(192, 0, 0, 0.25)\",\n          \"data\": [3.5, 2, 1, 2.5, -2, 3, -1, null, null]\n        },\n        {\n          \"backgroundColor\": \"rgba(128, 0, 128, 0.25)\",\n          \"data\": [5, 6, 5, -2, -4, -3, 4, 2, 4.5]\n        }\n      ]\n    },\n    \"options\": {\n      \"responsive\": false,\n      \"spanGaps\": false,\n      \"scales\": {\n        \"r\": {\n          \"display\": false,\n          \"grid\": {\n            \"circular\": true\n          }\n        }\n      },\n      \"elements\": {\n        \"point\": {\n          \"radius\": 0\n        },\n        \"line\": {\n          \"borderColor\": \"transparent\",\n          \"fill\": \"origin\"\n        }\n      },\n      \"plugins\": {\n        \"legend\": false,\n        \"title\": false,\n        \"tooltip\": false\n      }\n    }\n  },\n  \"options\": {\n    \"canvas\": {\n      \"height\": 256,\n      \"width\": 256\n    }\n  }\n}\n"
  },
  {
    "path": "test/fixtures/plugin.filler/radar/boundary/origin-span.json",
    "content": "{\n  \"config\": {\n    \"type\": \"radar\",\n    \"data\": {\n      \"labels\": [\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\"],\n      \"datasets\": [\n        {\n          \"backgroundColor\": \"rgba(0, 0, 192, 0.25)\",\n          \"data\": [null, null, 2, 3, 4, -4, -2, 1, 0]\n        },\n        {\n          \"backgroundColor\": \"rgba(0, 192, 0, 0.25)\",\n          \"data\": [6, 2, null, 4, 5, null, null, 2, 1]\n        },\n        {\n          \"backgroundColor\": \"rgba(192, 0, 0, 0.25)\",\n          \"data\": [7, 3, 4, 5, 6, 1, 4, null, null]\n        },\n        {\n          \"backgroundColor\": \"rgba(0, 64, 192, 0.25)\",\n          \"data\": [8, 7, 6, -6, -4, -6, 4, 5, 8]\n        }\n      ]\n    },\n    \"options\": {\n      \"responsive\": false,\n      \"spanGaps\": true,\n      \"scales\": {\n        \"r\": {\n          \"display\": false\n        }\n      },\n      \"elements\": {\n        \"point\": {\n          \"radius\": 0\n        },\n        \"line\": {\n          \"borderColor\": \"transparent\",\n          \"fill\": \"origin\"\n        }\n      },\n      \"plugins\": {\n        \"legend\": false,\n        \"title\": false,\n        \"tooltip\": false\n      }\n    }\n  },\n  \"options\": {\n    \"canvas\": {\n      \"height\": 256,\n      \"width\": 256\n    }\n  }\n}\n"
  },
  {
    "path": "test/fixtures/plugin.filler/radar/boundary/origin-spline-span.json",
    "content": "{\n  \"config\": {\n    \"type\": \"radar\",\n    \"data\": {\n      \"labels\": [\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\"],\n      \"datasets\": [\n        {\n          \"backgroundColor\": \"rgba(0, 0, 192, 0.25)\",\n          \"data\": [null, null, 2, 4, 2, 1, -1, 1, 2]\n        },\n        {\n          \"backgroundColor\": \"rgba(0, 192, 0, 0.25)\",\n          \"data\": [4, 2, null, 3, 2.5, null, -2, 1.5, 3]\n        },\n        {\n          \"backgroundColor\": \"rgba(192, 0, 0, 0.25)\",\n          \"data\": [3.5, 2, 1, 2.5, -2, 3, -1, null, null]\n        },\n        {\n          \"backgroundColor\": \"rgba(128, 0, 128, 0.25)\",\n          \"data\": [5, 6, 5, -2, -4, -3, 4, 2, 4.5]\n        }\n      ]\n    },\n    \"options\": {\n      \"responsive\": false,\n      \"spanGaps\": true,\n      \"scales\": {\n        \"r\": {\n          \"display\": false\n        }\n      },\n      \"elements\": {\n        \"point\": {\n          \"radius\": 0\n        },\n        \"line\": {\n          \"borderColor\": \"transparent\",\n          \"tension\": 0.5,\n          \"fill\": \"origin\"\n        }\n      },\n      \"plugins\": {\n        \"legend\": false,\n        \"title\": false,\n        \"tooltip\": false\n      }\n    }\n  },\n  \"options\": {\n    \"canvas\": {\n      \"height\": 256,\n      \"width\": 256\n    }\n  }\n}\n"
  },
  {
    "path": "test/fixtures/plugin.filler/radar/boundary/origin-spline.json",
    "content": "{\n  \"config\": {\n    \"type\": \"radar\",\n    \"data\": {\n      \"labels\": [\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\"],\n      \"datasets\": [\n        {\n          \"backgroundColor\": \"rgba(0, 0, 192, 0.25)\",\n          \"data\": [null, null, 2, 4, 2, 1, -1, 1, 2]\n        },\n        {\n          \"backgroundColor\": \"rgba(0, 192, 0, 0.25)\",\n          \"data\": [4, 2, null, 3, 2.5, null, -2, 1.5, 3]\n        },\n        {\n          \"backgroundColor\": \"rgba(192, 0, 0, 0.25)\",\n          \"data\": [3.5, 2, 1, 2.5, -2, 3, -1, null, null]\n        },\n        {\n          \"backgroundColor\": \"rgba(128, 0, 128, 0.25)\",\n          \"data\": [5, 6, 5, -2, -4, -3, 4, 2, 4.5]\n        }\n      ]\n    },\n    \"options\": {\n      \"responsive\": false,\n      \"spanGaps\": false,\n      \"scales\": {\n        \"r\": {\n          \"display\": false\n        }\n      },\n      \"elements\": {\n        \"point\": {\n          \"radius\": 0\n        },\n        \"line\": {\n          \"borderColor\": \"transparent\",\n          \"tension\": 0.5,\n          \"fill\": \"origin\"\n        }\n      },\n      \"plugins\": {\n        \"legend\": false,\n        \"title\": false,\n        \"tooltip\": false\n      }\n    }\n  },\n  \"options\": {\n    \"canvas\": {\n      \"height\": 256,\n      \"width\": 256\n    }\n  }\n}\n"
  },
  {
    "path": "test/fixtures/plugin.filler/radar/boundary/origin.json",
    "content": "{\n  \"config\": {\n    \"type\": \"radar\",\n    \"data\": {\n      \"labels\": [\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\"],\n      \"datasets\": [\n        {\n          \"backgroundColor\": \"rgba(0, 0, 192, 0.25)\",\n          \"data\": [null, null, 2, 3, 4, -4, -2, 1, 0]\n        },\n        {\n          \"backgroundColor\": \"rgba(0, 192, 0, 0.25)\",\n          \"data\": [6, 2, null, 4, 5, null, null, 2, 1]\n        },\n        {\n          \"backgroundColor\": \"rgba(192, 0, 0, 0.25)\",\n          \"data\": [7, 3, 4, 5, 6, 1, 4, null, null]\n        },\n        {\n          \"backgroundColor\": \"rgba(0, 64, 192, 0.25)\",\n          \"data\": [8, 7, 6, -6, -4, -6, 4, 5, 8]\n        }\n      ]\n    },\n    \"options\": {\n      \"responsive\": false,\n      \"spanGaps\": false,\n      \"scales\": {\n        \"r\": {\n          \"display\": false\n        }\n      },\n      \"elements\": {\n        \"point\": {\n          \"radius\": 0\n        },\n        \"line\": {\n          \"borderColor\": \"transparent\",\n          \"fill\": \"origin\"\n        }\n      },\n      \"plugins\": {\n        \"legend\": false,\n        \"title\": false,\n        \"tooltip\": false\n      }\n    }\n  },\n  \"options\": {\n    \"canvas\": {\n      \"height\": 256,\n      \"width\": 256\n    }\n  }\n}\n"
  },
  {
    "path": "test/fixtures/plugin.filler/radar/boundary/start-circular.json",
    "content": "{\n  \"config\": {\n    \"type\": \"radar\",\n    \"data\": {\n      \"labels\": [\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\"],\n      \"datasets\": [\n        {\n          \"backgroundColor\": \"rgba(0, 0, 255, 0.25)\",\n          \"data\": [null, null, 2, 3, 4, -4, -2, 1, 0]\n        },\n        {\n          \"backgroundColor\": \"rgba(0, 255, 0, 0.25)\",\n          \"data\": [6, 2, null, 4, 5, null, null, 2, 1]\n        },\n        {\n          \"backgroundColor\": \"rgba(255, 0, 0, 0.25)\",\n          \"data\": [7, 3, 4, 5, 6, 1, 4, null, null]\n        },\n        {\n          \"backgroundColor\": \"rgba(0, 0, 255, 0.25)\",\n          \"data\": [8, 7, 6, -6, -4, -6, 4, 5, 8]\n        }\n      ]\n    },\n    \"options\": {\n      \"responsive\": false,\n      \"spanGaps\": false,\n      \"scales\": {\n        \"r\": {\n          \"display\": false,\n          \"grid\": {\n            \"circular\": true\n          }\n        }\n      },\n      \"elements\": {\n        \"point\": {\n          \"radius\": 0\n        },\n        \"line\": {\n          \"borderColor\": \"transparent\",\n          \"fill\": \"start\"\n        }\n      },\n      \"plugins\": {\n        \"legend\": false,\n        \"title\": false,\n        \"tooltip\": false\n      }\n    }\n  },\n  \"options\": {\n    \"canvas\": {\n      \"height\": 256,\n      \"width\": 256\n    }\n  }\n}\n"
  },
  {
    "path": "test/fixtures/plugin.filler/radar/boundary/start-span.json",
    "content": "{\n  \"config\": {\n    \"type\": \"radar\",\n    \"data\": {\n      \"labels\": [\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\"],\n      \"datasets\": [\n        {\n          \"backgroundColor\": \"rgba(0, 0, 255, 0.25)\",\n          \"data\": [null, null, 2, 3, 4, -4, -2, 1, 0]\n        },\n        {\n          \"backgroundColor\": \"rgba(0, 255, 0, 0.25)\",\n          \"data\": [6, 2, null, 4, 5, null, null, 2, 1]\n        },\n        {\n          \"backgroundColor\": \"rgba(255, 0, 0, 0.25)\",\n          \"data\": [7, 3, 4, 5, 6, 1, 4, null, null]\n        },\n        {\n          \"backgroundColor\": \"rgba(0, 0, 255, 0.25)\",\n          \"data\": [8, 7, 6, -6, -4, -6, 4, 5, 8]\n        }\n      ]\n    },\n    \"options\": {\n      \"responsive\": false,\n      \"spanGaps\": true,\n      \"scales\": {\n        \"r\": {\n          \"display\": false\n        }\n      },\n      \"elements\": {\n        \"point\": {\n          \"radius\": 0\n        },\n        \"line\": {\n          \"borderColor\": \"transparent\",\n          \"fill\": \"start\"\n        }\n      },\n      \"plugins\": {\n        \"legend\": false,\n        \"title\": false,\n        \"tooltip\": false\n      }\n    }\n  },\n  \"options\": {\n    \"canvas\": {\n      \"height\": 256,\n      \"width\": 256\n    }\n  }\n}\n"
  },
  {
    "path": "test/fixtures/plugin.filler/radar/boundary/start.json",
    "content": "{\n  \"config\": {\n    \"type\": \"radar\",\n    \"data\": {\n      \"labels\": [\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\"],\n      \"datasets\": [\n        {\n          \"backgroundColor\": \"rgba(0, 0, 255, 0.25)\",\n          \"data\": [null, null, 2, 3, 4, -4, -2, 1, 0]\n        },\n        {\n          \"backgroundColor\": \"rgba(0, 255, 0, 0.25)\",\n          \"data\": [6, 2, null, 4, 5, null, null, 2, 1]\n        },\n        {\n          \"backgroundColor\": \"rgba(255, 0, 0, 0.25)\",\n          \"data\": [7, 3, 4, 5, 6, 1, 4, null, null]\n        },\n        {\n          \"backgroundColor\": \"rgba(0, 0, 255, 0.25)\",\n          \"data\": [8, 7, 6, -6, -4, -6, 4, 5, 8]\n        }\n      ]\n    },\n    \"options\": {\n      \"responsive\": false,\n      \"spanGaps\": false,\n      \"plugins\": {\n        \"legend\": false,\n        \"title\": false\n      },\n      \"scales\": {\n        \"r\": {\n          \"display\": false\n        }\n      },\n      \"elements\": {\n        \"point\": {\n          \"radius\": 0\n        },\n        \"line\": {\n          \"borderColor\": \"transparent\",\n          \"fill\": \"start\"\n        }\n      }\n    }\n  },\n  \"options\": {\n    \"canvas\": {\n      \"height\": 256,\n      \"width\": 256\n    }\n  }\n}\n"
  },
  {
    "path": "test/fixtures/plugin.filler/radar/dataset/border.json",
    "content": "{\n  \"config\": {\n    \"type\": \"radar\",\n    \"data\": {\n      \"labels\": [\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\"],\n      \"datasets\": [\n        {\n          \"backgroundColor\": \"rgba(255, 0, 0, 0.25)\",\n          \"data\": [null, null, 0, -1, 0, 1, 0, -1, 0],\n          \"fill\": 1\n        },\n        {\n          \"backgroundColor\": \"rgba(0, 255, 0, 0.25)\",\n          \"data\": [1, 0, null, 1, 0, null, -1, 0, 1],\n          \"fill\": \"+1\"\n        },\n        {\n          \"backgroundColor\": \"rgba(0, 0, 255, 0.25)\",\n          \"data\": [0, 2, 0, -2, 0, 2, 0],\n          \"fill\": 3\n        },\n        {\n          \"backgroundColor\": \"rgba(255, 0, 255, 0.25)\",\n          \"data\": [2, 0, -2, 0, 2, 0, -2, 0, 2],\n          \"fill\": \"-2\"\n        },\n        {\n          \"backgroundColor\": \"rgba(255, 255, 0, 0.25)\",\n          \"data\": [3, 1, -1, -3, -1, 1, 3, 1, -1],\n          \"fill\": \"-1\"\n        }\n      ]\n    },\n    \"options\": {\n      \"responsive\": false,\n      \"spanGaps\": false,\n      \"scales\": {\n        \"r\": {\n          \"display\": false\n        }\n      },\n      \"elements\": {\n        \"point\": {\n          \"radius\": 0\n        },\n        \"line\": {\n          \"borderColor\": \"black\",\n          \"borderWidth\": 5,\n          \"tension\": 0\n        }\n      },\n      \"plugins\": {\n        \"legend\": false,\n        \"title\": false,\n        \"tooltip\": false\n      }\n    }\n  },\n  \"options\": {\n    \"canvas\": {\n      \"height\": 256,\n      \"width\": 256\n    }\n  }\n}\n"
  },
  {
    "path": "test/fixtures/plugin.filler/radar/dataset/default.json",
    "content": "{\n  \"config\": {\n    \"type\": \"radar\",\n    \"data\": {\n      \"labels\": [\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\"],\n      \"datasets\": [\n        {\n          \"backgroundColor\": \"rgba(255, 0, 0, 0.25)\",\n          \"data\": [null, null, 0, -1, 0, 1, 0, -1, 0],\n          \"fill\": 1\n        },\n        {\n          \"backgroundColor\": \"rgba(0, 255, 0, 0.25)\",\n          \"data\": [1, 0, null, 1, 0, null, -1, 0, 1],\n          \"fill\": \"+1\"\n        },\n        {\n          \"backgroundColor\": \"rgba(0, 0, 255, 0.25)\",\n          \"data\": [0, 2, 0, -2, 0, 2, 0],\n          \"fill\": 3\n        },\n        {\n          \"backgroundColor\": \"rgba(255, 0, 255, 0.25)\",\n          \"data\": [2, 0, -2, 0, 2, 0, -2, 0, 2],\n          \"fill\": \"-2\"\n        },\n        {\n          \"backgroundColor\": \"rgba(255, 255, 0, 0.25)\",\n          \"data\": [3, 1, -1, -3, -1, 1, 3, 1, -1],\n          \"fill\": \"-1\"\n        }\n      ]\n    },\n    \"options\": {\n      \"responsive\": false,\n      \"spanGaps\": false,\n      \"scales\": {\n        \"r\": {\n          \"display\": false\n        }\n      },\n      \"elements\": {\n        \"point\": {\n          \"radius\": 0\n        },\n        \"line\": {\n          \"borderColor\": \"transparent\",\n          \"tension\": 0\n        }\n      },\n      \"plugins\": {\n        \"legend\": false,\n        \"title\": false,\n        \"tooltip\": false\n      }\n    }\n  },\n  \"options\": {\n    \"canvas\": {\n      \"height\": 256,\n      \"width\": 256\n    }\n  }\n}\n"
  },
  {
    "path": "test/fixtures/plugin.filler/radar/dataset/order.js",
    "content": "module.exports = {\n  config: {\n    type: 'radar',\n    data: {\n      labels: ['English', 'Maths', 'Physics', 'Chemistry', 'Biology', 'History'],\n      datasets: [\n        {\n          order: 1,\n          borderColor: '#D50000',\n          backgroundColor: 'rgba(245, 205, 121,0.5)',\n          data: [65, 75, 70, 80, 60, 80]\n        },\n        {\n          order: 0,\n          backgroundColor: 'rgba(0, 168, 255,1)',\n          data: [54, 65, 60, 70, 70, 75]\n        }\n      ]\n    },\n    options: {\n      plugins: {\n        legend: false,\n        title: false,\n        tooltip: false\n      },\n      scales: {\n        r: {\n          display: false\n        }\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.filler/radar/dataset/span.json",
    "content": "{\n  \"config\": {\n    \"type\": \"radar\",\n    \"data\": {\n      \"labels\": [\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\"],\n      \"datasets\": [\n        {\n          \"backgroundColor\": \"rgba(255, 0, 0, 0.25)\",\n          \"data\": [null, null, 0, -1, 0, 1, 0, -1, 0],\n          \"fill\": 1\n        },\n        {\n          \"backgroundColor\": \"rgba(0, 255, 0, 0.25)\",\n          \"data\": [1, 0, null, 1, 0, null, -1, 0, 1],\n          \"fill\": \"+1\"\n        },\n        {\n          \"backgroundColor\": \"rgba(0, 0, 255, 0.25)\",\n          \"data\": [0, 2, 0, -2, 0, 2, 0],\n          \"fill\": 3\n        },\n        {\n          \"backgroundColor\": \"rgba(255, 0, 255, 0.25)\",\n          \"data\": [2, 0, -2, 0, 2, 0, -2, 0, 2],\n          \"fill\": \"-2\"\n        },\n        {\n          \"backgroundColor\": \"rgba(255, 255, 0, 0.25)\",\n          \"data\": [3, 1, -1, -3, -1, 1, 3, 1, -1],\n          \"fill\": \"-1\"\n        }\n      ]\n    },\n    \"options\": {\n      \"responsive\": false,\n      \"spanGaps\": true,\n      \"scales\": {\n        \"r\": {\n          \"display\": false\n        }\n      },\n      \"elements\": {\n        \"point\": {\n          \"radius\": 0\n        },\n        \"line\": {\n          \"borderColor\": \"transparent\",\n          \"tension\": 0\n        }\n      },\n      \"plugins\": {\n        \"legend\": false,\n        \"title\": false,\n        \"tooltip\": false\n      }\n    }\n  },\n  \"options\": {\n    \"canvas\": {\n      \"height\": 256,\n      \"width\": 256\n    }\n  }\n}\n"
  },
  {
    "path": "test/fixtures/plugin.filler/radar/dataset/spline.json",
    "content": "{\n  \"config\": {\n    \"type\": \"radar\",\n    \"data\": {\n      \"labels\": [\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\"],\n      \"datasets\": [\n        {\n          \"backgroundColor\": \"rgba(255, 0, 0, 0.25)\",\n          \"data\": [null, null, 0, -1, 0, 1, 0, -1, 0],\n          \"fill\": 1\n        },\n        {\n          \"backgroundColor\": \"rgba(0, 255, 0, 0.25)\",\n          \"data\": [1, 0, null, 1, 0, null, -1, 0, 1],\n          \"fill\": \"+1\"\n        },\n        {\n          \"backgroundColor\": \"rgba(0, 0, 255, 0.25)\",\n          \"data\": [0, 2, 0, -2, 0, 2, 0],\n          \"fill\": 3\n        },\n        {\n          \"backgroundColor\": \"rgba(255, 0, 255, 0.25)\",\n          \"data\": [2, 0, -2, 0, 2, 0, -2, 0, 2],\n          \"fill\": \"-2\"\n        },\n        {\n          \"backgroundColor\": \"rgba(255, 255, 0, 0.25)\",\n          \"data\": [3, 1, -1, -3, -1, 1, 3, 1, -1],\n          \"fill\": \"-1\"\n        }\n      ]\n    },\n    \"options\": {\n      \"responsive\": false,\n      \"spanGaps\": false,\n      \"plugins\": {\n        \"legend\": false,\n        \"title\": false\n      },\n      \"scales\": {\n        \"r\": {\n          \"display\": false\n        }\n      },\n      \"elements\": {\n        \"point\": {\n          \"radius\": 0\n        },\n        \"line\": {\n          \"borderColor\": \"transparent\",\n          \"tension\": 0.5\n        }\n      }\n    }\n  },\n  \"options\": {\n    \"canvas\": {\n      \"height\": 256,\n      \"width\": 256\n    }\n  }\n}\n"
  },
  {
    "path": "test/fixtures/plugin.filler/radar/value.json",
    "content": "{\n  \"config\": {\n    \"type\": \"radar\",\n    \"data\": {\n      \"labels\": [\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\"],\n      \"datasets\": [\n        {\n          \"backgroundColor\": \"rgba(0, 0, 192, 0.25)\",\n          \"data\": [0, -4, 2, 4, 2, 1, -1, 1, 2]\n        }\n      ]\n    },\n    \"options\": {\n      \"responsive\": false,\n      \"spanGaps\": false,\n      \"scales\": {\n        \"r\": {\n          \"display\": false,\n          \"grid\": {\n            \"circular\": true\n          }\n        }\n      },\n      \"elements\": {\n        \"point\": {\n          \"radius\": 0\n        },\n        \"line\": {\n          \"borderColor\": \"transparent\",\n          \"fill\": { \"value\": 3 }\n        }\n      },\n      \"plugins\": {\n        \"legend\": false,\n        \"title\": false,\n        \"tooltip\": false\n      }\n    }\n  },\n  \"options\": {\n    \"canvas\": {\n      \"height\": 256,\n      \"width\": 256\n    }\n  }\n}\n"
  },
  {
    "path": "test/fixtures/plugin.legend/borderRadius/legend-border-radius.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],\n      datasets: [\n        {\n          label: '# of Votes',\n          data: [12, 19, 3, 5, 2, 3],\n          borderWidth: 1,\n          borderColor: '#FF0000',\n          backgroundColor: '#00FF00',\n        },\n        {\n          label: '# of Points',\n          data: [7, 11, 5, 8, 3, 7],\n          borderWidth: 2,\n          borderColor: '#FF00FF',\n          backgroundColor: '#0000FF',\n        }\n      ]\n    },\n    options: {\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      },\n      plugins: {\n        title: false,\n        tooltip: false,\n        filler: false,\n        legend: {\n          labels: {\n            generateLabels: (chart) => {\n              const items = Chart.defaults.plugins.legend.labels.generateLabels(chart);\n\n              for (const item of items) {\n                item.borderRadius = 5;\n              }\n\n              return items;\n            }\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      width: 512,\n      height: 256\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.legend/horizontal-rtl-hitbox.js",
    "content": "module.exports = {\n  description: 'https://github.com/chartjs/Chart.js/issues/9278',\n  config: {\n    type: 'pie',\n    data: {\n      labels: ['aaa', 'bb', 'c'],\n      datasets: [{\n        data: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],\n        backgroundColor: 'red'\n      }]\n    },\n    options: {\n      plugins: {\n        legend: {\n          position: 'top',\n          rtl: 'true',\n        }\n      },\n      layout: {\n        padding: {\n          top: 50,\n          left: 30,\n          right: 30,\n          bottom: 50\n        }\n      }\n    },\n    plugins: [{\n      id: 'legend-hit-box',\n      afterDraw(chart) {\n        const ctx = chart.ctx;\n        ctx.save();\n        ctx.strokeStyle = 'green';\n        ctx.lineWidth = 1;\n\n        const legend = chart.legend;\n        legend.legendHitBoxes.forEach(box => {\n          ctx.strokeRect(box.left, box.top, box.width, box.height);\n        });\n\n        ctx.restore();\n      }\n    }]\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      width: 400,\n      height: 300\n    },\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.legend/label-textAlign/center.js",
    "content": "module.exports = {\n  config: {\n    type: 'pie',\n    data: {\n      labels: ['aaaa', 'bb', 'c'],\n      datasets: [\n        {\n          data: [1, 2, 3]\n        }\n      ]\n    },\n    options: {\n      plugins: {\n        legend: {\n          position: 'right',\n          labels: {\n            textAlign: 'center'\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      width: 256,\n      height: 256\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.legend/label-textAlign/horizontal-left.js",
    "content": "module.exports = {\n  config: {\n    type: 'pie',\n    data: {\n      labels: ['aaaa', 'bb', 'c'],\n      datasets: [\n        {\n          data: [1, 2, 3]\n        }\n      ]\n    },\n    options: {\n      plugins: {\n        legend: {\n          position: 'top',\n          labels: {\n            textAlign: 'left'\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      width: 256,\n      height: 256\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.legend/label-textAlign/horizontal-right.js",
    "content": "module.exports = {\n  config: {\n    type: 'pie',\n    data: {\n      labels: ['aaaa', 'bb', 'c'],\n      datasets: [\n        {\n          data: [1, 2, 3]\n        }\n      ]\n    },\n    options: {\n      plugins: {\n        legend: {\n          position: 'top',\n          labels: {\n            textAlign: 'right'\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      width: 256,\n      height: 256\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.legend/label-textAlign/horizontal-rtl-left.js",
    "content": "module.exports = {\n  config: {\n    type: 'pie',\n    data: {\n      labels: ['aaaa', 'bb', 'c'],\n      datasets: [\n        {\n          data: [1, 2, 3]\n        }\n      ]\n    },\n    options: {\n      plugins: {\n        legend: {\n          position: 'top',\n          rtl: true,\n          labels: {\n            textAlign: 'left'\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      width: 256,\n      height: 256\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.legend/label-textAlign/horizontal-rtl-right.js",
    "content": "module.exports = {\n  config: {\n    type: 'pie',\n    data: {\n      labels: ['aaaa', 'bb', 'c'],\n      datasets: [\n        {\n          data: [1, 2, 3]\n        }\n      ]\n    },\n    options: {\n      plugins: {\n        legend: {\n          rtl: true,\n          position: 'top',\n          labels: {\n            textAlign: 'right'\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      width: 256,\n      height: 256\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.legend/label-textAlign/left.js",
    "content": "module.exports = {\n  config: {\n    type: 'pie',\n    data: {\n      labels: ['aaaa', 'bb', 'c'],\n      datasets: [\n        {\n          data: [1, 2, 3]\n        }\n      ]\n    },\n    options: {\n      plugins: {\n        legend: {\n          position: 'right',\n          labels: {\n            textAlign: 'left'\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      width: 256,\n      height: 256\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.legend/label-textAlign/right.js",
    "content": "module.exports = {\n  config: {\n    type: 'pie',\n    data: {\n      labels: ['aaaa', 'bb', 'c'],\n      datasets: [\n        {\n          data: [1, 2, 3]\n        }\n      ]\n    },\n    options: {\n      plugins: {\n        legend: {\n          position: 'right',\n          labels: {\n            textAlign: 'right'\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      width: 256,\n      height: 256\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.legend/label-textAlign/rtl-center.js",
    "content": "module.exports = {\n  config: {\n    type: 'pie',\n    data: {\n      labels: ['aaaa', 'bb', 'c'],\n      datasets: [\n        {\n          data: [1, 2, 3]\n        }\n      ]\n    },\n    options: {\n      plugins: {\n        legend: {\n          position: 'right',\n          rtl: true,\n          labels: {\n            textAlign: 'center'\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      width: 256,\n      height: 256\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.legend/label-textAlign/rtl-left.js",
    "content": "module.exports = {\n  config: {\n    type: 'pie',\n    data: {\n      labels: ['aaaa', 'bb', 'c'],\n      datasets: [\n        {\n          data: [1, 2, 3]\n        }\n      ]\n    },\n    options: {\n      plugins: {\n        legend: {\n          position: 'right',\n          rtl: true,\n          labels: {\n            textAlign: 'left'\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      width: 256,\n      height: 256\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.legend/label-textAlign/rtl-right.js",
    "content": "module.exports = {\n  config: {\n    type: 'pie',\n    data: {\n      labels: ['aaaa', 'bb', 'c'],\n      datasets: [\n        {\n          data: [1, 2, 3]\n        }\n      ]\n    },\n    options: {\n      plugins: {\n        legend: {\n          rtl: true,\n          position: 'right',\n          labels: {\n            textAlign: 'right'\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      width: 256,\n      height: 256\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.legend/legend-doughnut-bottom-center-mulitiline.json",
    "content": "{\n\t\"config\": {\n\t\t\"type\": \"doughnut\",\n\t\t\"data\": {\n\t\t\t\"labels\": [\"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\"],\n\t\t\t\"datasets\": [{\n\t\t\t\t\"data\": [10, 20, 30, 40, 50, 60, 70, 10, 20, 30, 40, 50, 60, 70, 10, 20, 30, 20, 10],\n\t\t\t\t\"backgroundColor\": \"#00ff00\",\n\t\t\t\t\"borderWidth\": 0\n\t\t\t}]\n\t\t},\n\t\t\"options\": {\n\t\t\t\"plugins\": {\n\t\t\t\t\"legend\": {\n\t\t\t\t\t\"position\": \"bottom\",\n\t\t\t\t\t\"align\": \"center\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\t\"options\": {\n\t\t\"canvas\": {\n\t\t\t\"height\": 256,\n\t\t\t\"width\": 512\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "test/fixtures/plugin.legend/legend-doughnut-bottom-center-single.json",
    "content": "{\n\t\"config\": {\n\t\t\"type\": \"doughnut\",\n\t\t\"data\": {\n\t\t\t\"labels\": [\"\"],\n\t\t\t\"datasets\": [{\n\t\t\t\t\"data\": [10],\n\t\t\t\t\"backgroundColor\": \"#00ff00\",\n\t\t\t\t\"borderWidth\": 0\n\t\t\t}]\n\t\t},\n\t\t\"options\": {\n\t\t\t\"plugins\": {\n\t\t\t\t\"legend\": {\n\t\t\t\t\t\"position\": \"bottom\",\n\t\t\t\t\t\"align\": \"center\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\t\"options\": {\n\t\t\"canvas\": {\n\t\t\t\"height\": 256,\n\t\t\t\"width\": 512\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "test/fixtures/plugin.legend/legend-doughnut-bottom-end-mulitiline.json",
    "content": "{\n\t\"config\": {\n\t\t\"type\": \"doughnut\",\n\t\t\"data\": {\n\t\t\t\"labels\": [\"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\"],\n\t\t\t\"datasets\": [{\n\t\t\t\t\"data\": [10, 20, 30, 40, 50, 60, 70, 10, 20, 30, 40, 50, 60, 70, 10],\n\t\t\t\t\"backgroundColor\": \"#00ff00\",\n\t\t\t\t\"borderWidth\": 0\n\t\t\t}]\n\t\t},\n\t\t\"options\": {\n\t\t\t\"plugins\": {\n\t\t\t\t\"legend\": {\n\t\t\t\t\t\"position\": \"bottom\",\n\t\t\t\t\t\"align\": \"end\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\t\"options\": {\n\t\t\"canvas\": {\n\t\t\t\"height\": 256,\n\t\t\t\"width\": 512\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "test/fixtures/plugin.legend/legend-doughnut-bottom-start-mulitiline.json",
    "content": "{\n\t\"config\": {\n\t\t\"type\": \"doughnut\",\n\t\t\"data\": {\n\t\t\t\"labels\": [\"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\"],\n\t\t\t\"datasets\": [{\n\t\t\t\t\"data\": [10, 20, 30, 40, 50, 60, 70, 10, 20, 30, 40, 50, 60, 70, 10],\n\t\t\t\t\"backgroundColor\": \"#00ff00\",\n\t\t\t\t\"borderWidth\": 0\n\t\t\t}]\n\t\t},\n\t\t\"options\": {\n\t\t\t\"plugins\": {\n\t\t\t\t\"legend\": {\n\t\t\t\t\t\"position\": \"bottom\",\n\t\t\t\t\t\"align\": \"start\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\t\"options\": {\n\t\t\"canvas\": {\n\t\t\t\"height\": 256,\n\t\t\t\"width\": 512\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "test/fixtures/plugin.legend/legend-doughnut-left-center-mulitiline.json",
    "content": "{\n\t\"config\": {\n\t\t\"type\": \"doughnut\",\n\t\t\"data\": {\n\t\t\t\"labels\": [\"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\"],\n\t\t\t\"datasets\": [{\n\t\t\t\t\"data\": [10, 20, 30, 40, 50, 60, 70, 10, 20, 30, 40, 50, 60, 70, 10, 20, 30],\n\t\t\t\t\"backgroundColor\": \"#00ff00\",\n\t\t\t\t\"borderWidth\": 0\n\t\t\t}]\n\t\t},\n\t\t\"options\": {\n\t\t\t\"plugins\": {\n\t\t\t\t\"legend\": {\n\t\t\t\t\t\"position\": \"left\",\n\t\t\t\t\t\"align\": \"center\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\t\"options\": {\n\t\t\"canvas\": {\n\t\t\t\"height\": 256,\n\t\t\t\"width\": 512\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "test/fixtures/plugin.legend/legend-doughnut-left-center-single.json",
    "content": "{\n\t\"config\": {\n\t\t\"type\": \"doughnut\",\n\t\t\"data\": {\n\t\t\t\"labels\": [\"\"],\n\t\t\t\"datasets\": [{\n\t\t\t\t\"data\": [10],\n\t\t\t\t\"backgroundColor\": \"#00ff00\",\n\t\t\t\t\"borderWidth\": 0\n\t\t\t}]\n\t\t},\n\t\t\"options\": {\n\t\t\t\"plugins\": {\n\t\t\t\t\"legend\": {\n\t\t\t\t\t\"position\": \"left\",\n\t\t\t\t\t\"align\": \"center\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\t\"options\": {\n\t\t\"canvas\": {\n\t\t\t\"height\": 256,\n\t\t\t\"width\": 512\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "test/fixtures/plugin.legend/legend-doughnut-left-default-center.json",
    "content": "{\n\t\"config\": {\n\t\t\"type\": \"doughnut\",\n\t\t\"data\": {\n\t\t\t\"labels\": [\"\", \"\", \"\", \"\", \"\", \"\"],\n\t\t\t\"datasets\": [{\n\t\t\t\t\"data\": [10, 20, 30, 40, 50],\n\t\t\t\t\"backgroundColor\": \"#00ff00\",\n\t\t\t\t\"borderWidth\": 0\n\t\t\t}]\n\t\t},\n\t\t\"options\": {\n\t\t\t\"plugins\": {\n\t\t\t\t\"legend\": {\n\t\t\t\t\t\"position\": \"left\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\t\"options\": {\n\t\t\"canvas\": {\n\t\t\t\"height\": 256,\n\t\t\t\"width\": 512\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "test/fixtures/plugin.legend/legend-doughnut-left-end-mulitiline.json",
    "content": "{\n\t\"config\": {\n\t\t\"type\": \"doughnut\",\n\t\t\"data\": {\n\t\t\t\"labels\": [\"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\"],\n\t\t\t\"datasets\": [{\n\t\t\t\t\"data\": [10, 20, 30, 40, 50, 60, 70, 10, 20, 30, 40, 50, 60, 70, 10, 20, 30],\n\t\t\t\t\"backgroundColor\": \"#00ff00\",\n\t\t\t\t\"borderWidth\": 0\n\t\t\t}]\n\t\t},\n\t\t\"options\": {\n\t\t\t\"plugins\": {\n\t\t\t\t\"legend\": {\n\t\t\t\t\t\"position\": \"left\",\n\t\t\t\t\t\"align\": \"end\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\t\"options\": {\n\t\t\"canvas\": {\n\t\t\t\"height\": 256,\n\t\t\t\"width\": 512\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "test/fixtures/plugin.legend/legend-doughnut-left-start-mulitiline.json",
    "content": "{\n\t\"config\": {\n\t\t\"type\": \"doughnut\",\n\t\t\"data\": {\n\t\t\t\"labels\": [\"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\"],\n\t\t\t\"datasets\": [{\n\t\t\t\t\"data\": [10, 20, 30, 40, 50, 60, 70, 10, 20, 30, 40, 50, 60, 70, 10, 20, 30],\n\t\t\t\t\"backgroundColor\": \"#00ff00\",\n\t\t\t\t\"borderWidth\": 0\n\t\t\t}]\n\t\t},\n\t\t\"options\": {\n\t\t\t\"plugins\": {\n\t\t\t\t\"legend\": {\n\t\t\t\t\t\"position\": \"left\",\n\t\t\t\t\t\"align\": \"start\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\t\"options\": {\n\t\t\"canvas\": {\n\t\t\t\"height\": 256,\n\t\t\t\"width\": 512\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "test/fixtures/plugin.legend/legend-doughnut-point-style.json",
    "content": "{\n\t\"config\": {\n\t\t\"type\": \"doughnut\",\n\t\t\"data\": {\n\t\t\t\"labels\": [\"\"],\n\t\t\t\"datasets\": [{\n\t\t\t\t\"data\": [10],\n\t\t\t\t\"backgroundColor\": \"#00ff00\",\n\t\t\t\t\"borderWidth\": 0\n\t\t\t}]\n\t\t},\n\t\t\"options\": {\n\t\t\t\"plugins\": {\n\t\t\t\t\"legend\": {\n\t\t\t\t\t\"labels\": {\n\t\t\t\t\t\t\"pointStyle\": \"triangle\",\n\t\t\t\t\t\t\"usePointStyle\": true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\t\"options\": {\n\t\t\"canvas\": {\n\t\t\t\"height\": 256,\n\t\t\t\"width\": 512\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "test/fixtures/plugin.legend/legend-doughnut-right-center-mulitiline-labels.json",
    "content": "{\n\t\"config\": {\n\t\t\"type\": \"doughnut\",\n\t\t\"data\": {\n\t\t\t\"labels\": [\"Example Label\", [\"I like these colors\", \"Red\", \"Green\", \"Blue\", \"Yellow\"], \"Example Label\", \"Example Label\", \"Example Label\"],\n\t\t\t\"datasets\": [{\n\t\t\t\t\"data\": [10, 20, 30, 40, 50],\n\t\t\t\t\"backgroundColor\": \"#00ff00\",\n\t\t\t\t\"borderWidth\": 0\n\t\t\t}]\n\t\t},\n\t\t\"options\": {\n\t\t\t\"plugins\": {\n\t\t\t\t\"legend\": {\n\t\t\t\t\t\"position\": \"right\",\n\t\t\t\t\t\"align\": \"center\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\t\"options\": {\n    \"spriteText\": true,\n\t\t\"canvas\": {\n\t\t\t\"height\": 256,\n\t\t\t\"width\": 512\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "test/fixtures/plugin.legend/legend-doughnut-right-center-mulitiline.json",
    "content": "{\n\t\"config\": {\n\t\t\"type\": \"doughnut\",\n\t\t\"data\": {\n\t\t\t\"labels\": [\"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\"],\n\t\t\t\"datasets\": [{\n\t\t\t\t\"data\": [10, 20, 30, 40, 50, 60, 70, 10, 20, 30, 40, 50, 60, 70, 10, 20, 30],\n\t\t\t\t\"backgroundColor\": \"#00ff00\",\n\t\t\t\t\"borderWidth\": 0\n\t\t\t}]\n\t\t},\n\t\t\"options\": {\n\t\t\t\"plugins\": {\n\t\t\t\t\"legend\": {\n\t\t\t\t\t\"position\": \"right\",\n\t\t\t\t\t\"align\": \"center\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\t\"options\": {\n\t\t\"canvas\": {\n\t\t\t\"height\": 256,\n\t\t\t\"width\": 512\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "test/fixtures/plugin.legend/legend-doughnut-right-center-single.json",
    "content": "{\n\t\"config\": {\n\t\t\"type\": \"doughnut\",\n\t\t\"data\": {\n\t\t\t\"labels\": [\"\"],\n\t\t\t\"datasets\": [{\n\t\t\t\t\"data\": [10],\n\t\t\t\t\"backgroundColor\": \"#00ff00\",\n\t\t\t\t\"borderWidth\": 0\n\t\t\t}]\n\t\t},\n\t\t\"options\": {\n\t\t\t\"plugins\": {\n\t\t\t\t\"legend\": {\n\t\t\t\t\t\"position\": \"right\",\n\t\t\t\t\t\"align\": \"center\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\t\"options\": {\n\t\t\"canvas\": {\n\t\t\t\"height\": 256,\n\t\t\t\"width\": 512\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "test/fixtures/plugin.legend/legend-doughnut-right-default-center.json",
    "content": "{\n\t\"config\": {\n\t\t\"type\": \"doughnut\",\n\t\t\"data\": {\n\t\t\t\"labels\": [\"\", \"\", \"\", \"\", \"\", \"\"],\n\t\t\t\"datasets\": [{\n\t\t\t\t\"data\": [10, 20, 30, 40, 50],\n\t\t\t\t\"backgroundColor\": \"#00ff00\",\n\t\t\t\t\"borderWidth\": 0\n\t\t\t}]\n\t\t},\n\t\t\"options\": {\n\t\t\t\"plugins\": {\n\t\t\t\t\"legend\": {\n\t\t\t\t\t\"position\": \"right\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\t\"options\": {\n\t\t\"canvas\": {\n\t\t\t\"height\": 256,\n\t\t\t\"width\": 512\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "test/fixtures/plugin.legend/legend-doughnut-right-end-mulitiline.json",
    "content": "{\n\t\"config\": {\n\t\t\"type\": \"doughnut\",\n\t\t\"data\": {\n\t\t\t\"labels\": [\"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\"],\n\t\t\t\"datasets\": [{\n\t\t\t\t\"data\": [10, 20, 30, 40, 50, 60, 70, 10, 20, 30, 40, 50, 60, 70, 10, 20, 30],\n\t\t\t\t\"backgroundColor\": \"#00ff00\",\n\t\t\t\t\"borderWidth\": 0\n\t\t\t}]\n\t\t},\n\t\t\"options\": {\n\t\t\t\"plugins\": {\n\t\t\t\t\"legend\": {\n\t\t\t\t\t\"position\": \"right\",\n\t\t\t\t\t\"align\": \"end\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\t\"options\": {\n\t\t\"canvas\": {\n\t\t\t\"height\": 256,\n\t\t\t\"width\": 512\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "test/fixtures/plugin.legend/legend-doughnut-right-start-mulitiline.json",
    "content": "{\n\t\"config\": {\n\t\t\"type\": \"doughnut\",\n\t\t\"data\": {\n\t\t\t\"labels\": [\"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\"],\n\t\t\t\"datasets\": [{\n\t\t\t\t\"data\": [10, 20, 30, 40, 50, 60, 70, 10, 20, 30, 40, 50, 60, 70, 10, 20, 30],\n\t\t\t\t\"backgroundColor\": \"#00ff00\",\n\t\t\t\t\"borderWidth\": 0\n\t\t\t}]\n\t\t},\n\t\t\"options\": {\n\t\t\t\"plugins\": {\n\t\t\t\t\"legend\": {\n\t\t\t\t\t\"position\": \"right\",\n\t\t\t\t\t\"align\": \"start\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\t\"options\": {\n\t\t\"canvas\": {\n\t\t\t\"height\": 256,\n\t\t\t\"width\": 512\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "test/fixtures/plugin.legend/legend-doughnut-top-center-mulitiline.json",
    "content": "{\n\t\"config\": {\n\t\t\"type\": \"doughnut\",\n\t\t\"data\": {\n\t\t\t\"labels\": [\"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\"],\n\t\t\t\"datasets\": [{\n\t\t\t\t\"data\": [10, 20, 30, 40, 50, 60, 70, 10, 20, 30, 40, 50, 60, 70, 10, 20, 30, 20, 10],\n\t\t\t\t\"backgroundColor\": \"#00ff00\",\n\t\t\t\t\"borderWidth\": 0\n\t\t\t}]\n\t\t},\n\t\t\"options\": {\n\t\t\t\"plugins\": {\n\t\t\t\t\"legend\": {\n\t\t\t\t\t\"position\": \"top\",\n\t\t\t\t\t\"align\": \"center\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\t\"options\": {\n\t\t\"canvas\": {\n\t\t\t\"height\": 256,\n\t\t\t\"width\": 512\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "test/fixtures/plugin.legend/legend-doughnut-top-center-single.json",
    "content": "{\n\t\"config\": {\n\t\t\"type\": \"doughnut\",\n\t\t\"data\": {\n\t\t\t\"labels\": [\"\"],\n\t\t\t\"datasets\": [{\n\t\t\t\t\"data\": [10],\n\t\t\t\t\"backgroundColor\": \"#00ff00\",\n\t\t\t\t\"borderWidth\": 0\n\t\t\t}]\n\t\t},\n\t\t\"options\": {\n\t\t\t\"plugins\": {\n\t\t\t\t\"legend\": {\n\t\t\t\t\t\"position\": \"top\",\n\t\t\t\t\t\"align\": \"center\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\t\"options\": {\n\t\t\"canvas\": {\n\t\t\t\"height\": 256,\n\t\t\t\"width\": 512\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "test/fixtures/plugin.legend/legend-doughnut-top-end-mulitiline.json",
    "content": "{\n\t\"config\": {\n\t\t\"type\": \"doughnut\",\n\t\t\"data\": {\n\t\t\t\"labels\": [\"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\"],\n\t\t\t\"datasets\": [{\n\t\t\t\t\"data\": [10, 20, 30, 40, 50, 60, 70, 10, 20, 30, 40, 50, 60, 70, 10],\n\t\t\t\t\"backgroundColor\": \"#00ff00\",\n\t\t\t\t\"borderWidth\": 0\n\t\t\t}]\n\t\t},\n\t\t\"options\": {\n\t\t\t\"plugins\": {\n\t\t\t\t\"legend\": {\n\t\t\t\t\t\"position\": \"top\",\n\t\t\t\t\t\"align\": \"end\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\t\"options\": {\n\t\t\"canvas\": {\n\t\t\t\"height\": 256,\n\t\t\t\"width\": 512\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "test/fixtures/plugin.legend/legend-doughnut-top-start-mulitiline.json",
    "content": "{\n\t\"config\": {\n\t\t\"type\": \"doughnut\",\n\t\t\"data\": {\n\t\t\t\"labels\": [\"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\", \"\"],\n\t\t\t\"datasets\": [{\n\t\t\t\t\"data\": [10, 20, 30, 40, 50, 60, 70, 10, 20, 30, 40, 50, 60, 70, 10],\n\t\t\t\t\"backgroundColor\": \"#00ff00\",\n\t\t\t\t\"borderWidth\": 0\n\t\t\t}]\n\t\t},\n\t\t\"options\": {\n\t\t\t\"plugins\": {\n\t\t\t\t\"legend\": {\n\t\t\t\t\t\"position\": \"top\",\n\t\t\t\t\t\"align\": \"start\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\t\"options\": {\n\t\t\"canvas\": {\n\t\t\t\"height\": 256,\n\t\t\t\"width\": 512\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "test/fixtures/plugin.legend/legend-line-chart-area.json",
    "content": "{\n    \"config\": {\n        \"type\": \"line\",\n        \"data\": {\n            \"labels\": [\"A\", \"B\", \"C\", \"D\", \"E\"],\n            \"datasets\": [{\n                \"data\": [10, 20, 30, 40, 50],\n                \"backgroundColor\": \"#00ff00\",\n                \"borderWidth\": 0,\n                \"label\": \"\"\n            }]\n        },\n        \"options\": {\n            \"plugins\": {\n                \"legend\": {\n                    \"position\": \"chartArea\"\n                }\n            },\n            \"scales\": {\n                \"x\": {\n                    \"display\": false\n                },\n                \"y\": {\n                    \"display\": false\n                }\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 256,\n            \"width\": 512\n        }\n    }\n}"
  },
  {
    "path": "test/fixtures/plugin.legend/maxWidth/infinity.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],\n      datasets: [\n        {\n          label: '# of Votes',\n          data: [12, 19, 3, 5, 2, 3],\n          borderWidth: 1\n        },\n        {\n          label: '# of Points',\n          data: [7, 11, 5, 8, 3, 7],\n          borderWidth: 1\n        }\n      ]\n    },\n    options: {\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      },\n      plugins: {\n        title: false,\n        tooltip: false,\n        filler: false,\n        legend: {\n          position: 'left',\n          maxWidth: Infinity\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      width: 150,\n      height: 75\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.legend/maxWidth/undefined.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],\n      datasets: [\n        {\n          label: '# of Votes',\n          data: [12, 19, 3, 5, 2, 3],\n          borderWidth: 1\n        },\n        {\n          label: '# of Points',\n          data: [7, 11, 5, 8, 3, 7],\n          borderWidth: 1\n        }\n      ]\n    },\n    options: {\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      },\n      plugins: {\n        title: false,\n        tooltip: false,\n        filler: false,\n        legend: {\n          position: 'left',\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      width: 150,\n      height: 75\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.legend/maxWidth/value.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],\n      datasets: [\n        {\n          label: '# of Votes',\n          data: [12, 19, 3, 5, 2, 3],\n          borderWidth: 1\n        },\n        {\n          label: '# of Points',\n          data: [7, 11, 5, 8, 3, 7],\n          borderWidth: 1\n        }\n      ]\n    },\n    options: {\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      },\n      plugins: {\n        title: false,\n        tooltip: false,\n        filler: false,\n        legend: {\n          position: 'left',\n          maxWidth: 100\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      width: 150,\n      height: 75\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.legend/padding/2cols-with-padding.js",
    "content": "module.exports = {\n  description: 'https://github.com/chartjs/Chart.js/issues/9278',\n  config: {\n    type: 'pie',\n    data: {\n      labels: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k'],\n      datasets: [{\n        data: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],\n        backgroundColor: 'red'\n      }]\n    },\n    options: {\n      plugins: {\n        legend: {\n          position: 'left'\n        }\n      },\n      layout: {\n        padding: {\n          top: 50,\n          left: 30,\n          right: 30,\n          bottom: 50\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      width: 400,\n      height: 300\n    },\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.legend/padding/add-column.js",
    "content": "module.exports = {\n  description: 'https://github.com/chartjs/Chart.js/issues/9278',\n  config: {\n    type: 'pie',\n    data: {\n      labels: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'],\n      datasets: [{\n        data: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],\n        backgroundColor: 'red'\n      }]\n    },\n    options: {\n      plugins: {\n        legend: {\n          position: 'left'\n        }\n      },\n      layout: {\n        padding: {\n          top: 55,\n          left: 30,\n          right: 30\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      width: 400,\n      height: 300\n    },\n    run(chart) {\n      chart.data.labels.push('k');\n      chart.data.datasets[0].data.push(11);\n      chart.update();\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.legend/pointStyle-width/legend-pointStyle-width-default.json",
    "content": " {\n    \"config\": {\n        \"type\": \"line\",\n        \"data\": {\n            \"labels\": [\"A\", \"B\", \"C\"],\n            \"datasets\": [{\n                \"data\": [10, 10, 10],\n                \"backgroundColor\": \"#00ff00\",\n\t\t\t\t\"borderColor\": \"#ff0000\",\n                \"borderWidth\": 1,\n                \"label\": \"\",\n                \"pointStyle\": \"line\"\n            },\n\t\t\t{\n                \"data\": [15, 15, 15],\n                \"backgroundColor\": \"#00ff00\",\n\t\t\t\t\"borderColor\": \"#ff0000\",\n                \"borderWidth\": 1,\n                \"label\": \"\",\n                \"pointStyle\": \"triangle\"\n            },\n\t\t\t{\n                \"data\": [20, 20, 20],\n                \"backgroundColor\": \"#00ff00\",\n\t\t\t\t\"borderColor\": \"#ff0000\",\n                \"borderWidth\": 1,\n                \"label\": \"\",\n                \"pointStyle\": \"rectRounded\"\n            },\n\t\t\t{\n                \"data\": [30, 30, 30],\n                \"backgroundColor\": \"#00ff00\",\n\t\t\t\t\"borderColor\": \"#ff0000\",\n                \"borderWidth\": 1,\n                \"label\": \"\"\n            },\n\t\t\t{\n                \"data\": [40, 40, 40],\n                \"backgroundColor\": \"#00ff00\",\n\t\t\t\t\"borderColor\": \"#ff0000\",\n                \"borderWidth\": 1,\n                \"label\": \"\",\n                \"pointStyle\": \"rect\"\n            },\n\t\t\t{\n                \"data\": [25, 25, 25],\n                \"backgroundColor\": \"#00ff00\",\n\t\t\t\t\"borderColor\": \"#ff0000\",\n                \"borderWidth\": 1,\n                \"label\": \"\",\n                \"pointStyle\": \"rectRot\"\n            },\n\t\t\t{\n                \"data\": [35, 35, 35],\n                \"backgroundColor\": \"#00ff00\",\n\t\t\t\t\"borderColor\": \"#ff0000\",\n                \"borderWidth\": 1,\n                \"label\": \"\",\n                \"pointStyle\": \"crossRot\"\n            },\n\t\t\t{\n                \"data\": [45, 45, 45],\n                \"backgroundColor\": \"#00ff00\",\n\t\t\t\t\"borderColor\": \"#ff0000\",\n                \"borderWidth\": 1,\n                \"label\": \"\",\n                \"pointStyle\": \"cross\"\n            },\n\t\t\t{\n                \"data\": [50, 50, 50],\n                \"backgroundColor\": \"#00ff00\",\n\t\t\t\t\"borderColor\": \"#ff0000\",\n                \"borderWidth\": 1,\n                \"label\": \"\",\n                \"pointStyle\": \"star\"\n            },\n\t\t\t{\n                \"data\": [55, 55, 55],\n                \"backgroundColor\": \"#00ff00\",\n\t\t\t\t\"borderColor\": \"#ff0000\",\n                \"borderWidth\": 1,\n                \"label\": \"\",\n                \"pointStyle\": \"dash\"\n            }]\n        },\n        \"options\": {\n            \"plugins\": {\n                \"legend\": {\n                    \"display\": true,\n                    \"labels\": {\n                        \"usePointStyle\": true                        \n                    }\n                }\n            },\n            \"scales\": {\n                \"x\": {\n                    \"display\": false\n                },\n                \"y\": {\n                    \"display\": false\n                }\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 512,\n            \"width\": 1024\n        }\n    }\n}"
  },
  {
    "path": "test/fixtures/plugin.legend/pointStyle-width/legend-pointStyle-width.json",
    "content": " {\n    \"config\": {\n        \"type\": \"line\",\n        \"data\": {\n            \"labels\": [\"A\", \"B\", \"C\"],\n            \"datasets\": [{\n                \"data\": [10, 10, 10],\n                \"backgroundColor\": \"#00ff00\",\n\t\t\t\t\"borderColor\": \"#ff0000\",\n                \"borderWidth\": 1,\n                \"label\": \"\",\n                \"pointStyle\": \"line\"\n            },\n\t\t\t{\n                \"data\": [15, 15, 15],\n                \"backgroundColor\": \"#00ff00\",\n\t\t\t\t\"borderColor\": \"#ff0000\",\n                \"borderWidth\": 1,\n                \"label\": \"\",\n                \"pointStyle\": \"triangle\"\n            },\n\t\t\t{\n                \"data\": [20, 20, 20],\n                \"backgroundColor\": \"#00ff00\",\n\t\t\t\t\"borderColor\": \"#ff0000\",\n                \"borderWidth\": 1,\n                \"label\": \"\",\n                \"pointStyle\": \"rectRounded\"\n            },\n\t\t\t{\n                \"data\": [30, 30, 30],\n                \"backgroundColor\": \"#00ff00\",\n\t\t\t\t\"borderColor\": \"#ff0000\",\n                \"borderWidth\": 1,\n                \"label\": \"\"\n            },\n\t\t\t{\n                \"data\": [40, 40, 40],\n                \"backgroundColor\": \"#00ff00\",\n\t\t\t\t\"borderColor\": \"#ff0000\",\n                \"borderWidth\": 1,\n                \"label\": \"\",\n                \"pointStyle\": \"rect\"\n            },\n\t\t\t{\n                \"data\": [25, 25, 25],\n                \"backgroundColor\": \"#00ff00\",\n\t\t\t\t\"borderColor\": \"#ff0000\",\n                \"borderWidth\": 1,\n                \"label\": \"\",\n                \"pointStyle\": \"rectRot\"\n            },\n\t\t\t{\n                \"data\": [35, 35, 35],\n                \"backgroundColor\": \"#00ff00\",\n\t\t\t\t\"borderColor\": \"#ff0000\",\n                \"borderWidth\": 1,\n                \"label\": \"\",\n                \"pointStyle\": \"crossRot\"\n            },\n\t\t\t{\n                \"data\": [45, 45, 45],\n                \"backgroundColor\": \"#00ff00\",\n\t\t\t\t\"borderColor\": \"#ff0000\",\n                \"borderWidth\": 1,\n                \"label\": \"\",\n                \"pointStyle\": \"cross\"\n            },\n\t\t\t{\n                \"data\": [50, 50, 50],\n                \"backgroundColor\": \"#00ff00\",\n\t\t\t\t\"borderColor\": \"#ff0000\",\n                \"borderWidth\": 1,\n                \"label\": \"\",\n                \"pointStyle\": \"star\"\n            },\n\t\t\t{\n                \"data\": [55, 55, 55],\n                \"backgroundColor\": \"#00ff00\",\n\t\t\t\t\"borderColor\": \"#ff0000\",\n                \"borderWidth\": 1,\n                \"label\": \"\",\n                \"pointStyle\": \"dash\"\n            }]\n        },\n        \"options\": {\n            \"plugins\": {\n                \"legend\": {\n                    \"display\": true,\n                    \"labels\": {\n                        \"usePointStyle\": true,\n                        \"pointStyleWidth\": 75\n                    }\n                }\n            },\n            \"scales\": {\n                \"x\": {\n                    \"display\": false\n                },\n                \"y\": {\n                    \"display\": false\n                }\n            }\n        }\n    },\n    \"options\": {\n        \"canvas\": {\n            \"height\": 512,\n            \"width\": 1024\n        }\n    }\n}"
  },
  {
    "path": "test/fixtures/plugin.legend/title/bottom-center-center.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      datasets: [\n        {label: 'a', data: []},\n        {label: 'b', data: []},\n        {label: 'c', data: []}\n      ]\n    },\n    options: {\n      plugins: {\n        legend: {\n          position: 'bottom',\n          align: 'center',\n          title: {\n            display: true,\n            position: 'center',\n            text: 'title'\n          }\n        }\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 256,\n      width: 256\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.legend/title/bottom-end-end.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      datasets: [\n        {label: 'a', data: []},\n        {label: 'b', data: []},\n        {label: 'c', data: []}\n      ]\n    },\n    options: {\n      plugins: {\n        legend: {\n          position: 'bottom',\n          align: 'end',\n          title: {\n            display: true,\n            position: 'end',\n            text: 'title'\n          }\n        }\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 256,\n      width: 256\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.legend/title/bottom-start-start.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      datasets: [\n        {label: 'a', data: []},\n        {label: 'b', data: []},\n        {label: 'c', data: []}\n      ]\n    },\n    options: {\n      plugins: {\n        legend: {\n          position: 'bottom',\n          align: 'start',\n          title: {\n            display: true,\n            position: 'start',\n            text: 'title'\n          }\n        }\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 256,\n      width: 256\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.legend/title/left-center-center.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      datasets: [\n        {label: 'a', data: []},\n        {label: 'b', data: []},\n        {label: 'c', data: []}\n      ]\n    },\n    options: {\n      plugins: {\n        legend: {\n          position: 'left',\n          align: 'center',\n          title: {\n            display: true,\n            position: 'center',\n            text: 'title'\n          }\n        }\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 256,\n      width: 256\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.legend/title/left-end-end.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      datasets: [\n        {label: 'a', data: []},\n        {label: 'b', data: []},\n        {label: 'c', data: []}\n      ]\n    },\n    options: {\n      plugins: {\n        legend: {\n          position: 'left',\n          align: 'end',\n          title: {\n            display: true,\n            position: 'end',\n            text: 'title'\n          }\n        }\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 256,\n      width: 256\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.legend/title/left-start-start.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      datasets: [\n        {label: 'a', data: []},\n        {label: 'b', data: []},\n        {label: 'c', data: []}\n      ]\n    },\n    options: {\n      plugins: {\n        legend: {\n          position: 'left',\n          align: 'start',\n          title: {\n            display: true,\n            position: 'start',\n            text: 'title'\n          }\n        }\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 256,\n      width: 256\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.legend/title/right-center-center.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      datasets: [\n        {label: 'a', data: []},\n        {label: 'b', data: []},\n        {label: 'c', data: []}\n      ]\n    },\n    options: {\n      plugins: {\n        legend: {\n          position: 'right',\n          align: 'center',\n          title: {\n            display: true,\n            position: 'center',\n            text: 'title'\n          }\n        }\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 256,\n      width: 256\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.legend/title/right-end-end.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      datasets: [\n        {label: 'a', data: []},\n        {label: 'b', data: []},\n        {label: 'c', data: []}\n      ]\n    },\n    options: {\n      plugins: {\n        legend: {\n          position: 'right',\n          align: 'end',\n          title: {\n            display: true,\n            position: 'end',\n            text: 'title'\n          }\n        }\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 256,\n      width: 256\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.legend/title/right-start-start.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      datasets: [\n        {label: 'a', data: []},\n        {label: 'b', data: []},\n        {label: 'c', data: []}\n      ]\n    },\n    options: {\n      plugins: {\n        legend: {\n          position: 'right',\n          align: 'start',\n          title: {\n            display: true,\n            position: 'start',\n            text: 'title'\n          }\n        }\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 256,\n      width: 256\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.legend/title/top-center-center.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      datasets: [\n        {label: 'a', data: []},\n        {label: 'b', data: []},\n        {label: 'c', data: []}\n      ]\n    },\n    options: {\n      plugins: {\n        legend: {\n          position: 'top',\n          align: 'center',\n          title: {\n            display: true,\n            position: 'center',\n            text: 'title'\n          }\n        }\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 256,\n      width: 256\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.legend/title/top-end-end.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      datasets: [\n        {label: 'a', data: []},\n        {label: 'b', data: []},\n        {label: 'c', data: []}\n      ]\n    },\n    options: {\n      plugins: {\n        legend: {\n          position: 'top',\n          align: 'end',\n          title: {\n            display: true,\n            position: 'end',\n            text: 'title'\n          }\n        }\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 256,\n      width: 256\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.legend/title/top-start-start.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      datasets: [\n        {label: 'a', data: []},\n        {label: 'b', data: []},\n        {label: 'c', data: []}\n      ]\n    },\n    options: {\n      plugins: {\n        legend: {\n          position: 'top',\n          align: 'start',\n          title: {\n            display: true,\n            position: 'start',\n            text: 'title'\n          }\n        }\n      },\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 256,\n      width: 256\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.subtitle/basic.js",
    "content": "\nmodule.exports = {\n  config: {\n    type: 'scatter',\n    data: {\n      datasets: [{\n        data: [{x: 0, y: 0}, {x: 1, y: 1}, {x: 2, y: 2}],\n        backgroundColor: 'red',\n        radius: 1,\n        hoverRadius: 0\n      }],\n    },\n    options: {\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      },\n      plugins: {\n        legend: false,\n        title: {\n          display: true,\n          text: 'Title Text',\n        },\n        subtitle: {\n          display: true,\n          text: 'SubTitle Text',\n        },\n        filler: false,\n        tooltip: false\n      },\n    },\n\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 400,\n      width: 400\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.title/scriptable-options.js",
    "content": "const data = [];\nfor (let x = 0; x < 3; x++) {\n  for (let y = 0; y < 3; y++) {\n    data.push({x, y});\n  }\n}\n\nmodule.exports = {\n  config: {\n    type: 'scatter',\n    data: {\n      datasets: [{\n        data,\n        backgroundColor: 'red',\n        radius: 1,\n        hoverRadius: 0\n      }],\n    },\n    options: {\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      },\n      plugins: {\n        legend: false,\n        title: {\n          display: true,\n          text: () => 'Title Text',\n        },\n        filler: false,\n        tooltip: false\n      },\n    },\n\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 400,\n      width: 400\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.tooltip/box-padding.js",
    "content": "const data = [];\nfor (let x = 0; x < 3; x++) {\n  for (let y = 0; y < 3; y++) {\n    data.push({x, y});\n  }\n}\n\nmodule.exports = {\n  config: {\n    type: 'scatter',\n    data: {\n      datasets: [{\n        data,\n        backgroundColor: 'red',\n        radius: 1,\n        hoverRadius: 0\n      }],\n    },\n    options: {\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      },\n      plugins: {\n        legend: false,\n        title: false,\n        filler: false,\n        tooltip: {\n          mode: 'point',\n          intersect: true,\n          // spriteText: use white background to hide any gaps between fonts\n          backgroundColor: 'white',\n          borderColor: 'black',\n          borderWidth: 1,\n          callbacks: {\n            label: () => 'label',\n          },\n          boxPadding: 30\n        },\n      },\n    },\n    plugins: [{\n      afterDraw: function(chart) {\n        const canvas = chart.canvas;\n        const rect = canvas.getBoundingClientRect();\n        const meta = chart.getDatasetMeta(0);\n        let point, event;\n\n        for (let i = 0; i < data.length; i++) {\n          point = meta.data[i];\n          event = {\n            type: 'mousemove',\n            target: canvas,\n            clientX: rect.left + point.x,\n            clientY: rect.top + point.y\n          };\n          chart._handleEvent(event);\n          chart.tooltip.handleEvent(event);\n          chart.tooltip.draw(chart.ctx);\n        }\n      }\n    }]\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 400,\n      width: 500\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.tooltip/caret-position.js",
    "content": "const data = [];\nfor (let x = 1; x < 4; x++) {\n  for (let y = 1; y < 4; y++) {\n    data.push({x, y});\n  }\n}\n\nmodule.exports = {\n  config: {\n    type: 'scatter',\n    data: {\n      datasets: [{\n        data,\n        backgroundColor: 'red',\n        radius: 8,\n        hoverRadius: 0\n      }],\n    },\n    options: {\n      scales: {\n        x: {display: false, min: 0.96, max: 3.04},\n        y: {display: false, min: 1, max: 3}\n      },\n      plugins: {\n        legend: false,\n        title: false,\n        filler: false,\n        tooltip: {\n          mode: 'point',\n          intersect: true,\n          // spriteText: use white background to hide any gaps between fonts\n          backgroundColor: 'white',\n          borderColor: 'black',\n          borderWidth: 1,\n          callbacks: {\n            label: () => 'label',\n          }\n        },\n      },\n    },\n    plugins: [{\n      afterDraw: function(chart) {\n        const canvas = chart.canvas;\n        const rect = canvas.getBoundingClientRect();\n        const meta = chart.getDatasetMeta(0);\n        let point, event;\n\n        for (let i = 0; i < data.length; i++) {\n          point = meta.data[i];\n          event = {\n            type: 'mousemove',\n            target: canvas,\n            clientX: rect.left + point.x,\n            clientY: rect.top + point.y\n          };\n          chart._handleEvent(event);\n          chart.tooltip.handleEvent(event);\n          chart.tooltip.draw(chart.ctx);\n        }\n      }\n    }]\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 240,\n      width: 320\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.tooltip/color-box-border-dash.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      datasets: [{\n        data: [8, 7, 6, 5],\n        pointBorderColor: '#ff0000',\n        pointBackgroundColor: '#00ff00',\n        showLine: false\n      }],\n      labels: ['', '', '', '']\n    },\n    options: {\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      },\n      elements: {\n        line: {\n          fill: false\n        }\n      },\n      plugins: {\n        legend: false,\n        title: false,\n        filler: false,\n        tooltip: {\n          mode: 'nearest',\n          intersect: false,\n          callbacks: {\n            label: function() {\n              return '\\u200b';\n            },\n            labelColor: function(tooltipItem) {\n              const meta = tooltipItem.chart.getDatasetMeta(tooltipItem.datasetIndex);\n              const options = meta.controller.getStyle(tooltipItem.dataIndex);\n              return {\n                borderColor: options.borderColor,\n                backgroundColor: options.backgroundColor,\n                borderWidth: 2,\n                borderDash: [2, 2]\n              };\n            },\n          }\n        },\n      },\n\n      layout: {\n        padding: 15\n      }\n    },\n    plugins: [{\n      afterDraw: function(chart) {\n        const canvas = chart.canvas;\n        const rect = canvas.getBoundingClientRect();\n        const point = chart.getDatasetMeta(0).data[1];\n        const event = {\n          type: 'mousemove',\n          target: canvas,\n          clientX: rect.left + point.x,\n          clientY: rect.top + point.y\n        };\n        chart._handleEvent(event);\n        chart.tooltip.handleEvent(event);\n        chart.tooltip.draw(chart.ctx);\n      }\n    }]\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.tooltip/color-box-border-radius.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      datasets: [{\n        data: [8, 7, 6, 5],\n        pointBorderColor: '#ff0000',\n        pointBackgroundColor: '#00ff00',\n        showLine: false\n      }],\n      labels: ['', '', '', '']\n    },\n    options: {\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      },\n      elements: {\n        line: {\n          fill: false\n        }\n      },\n      plugins: {\n        legend: false,\n        title: false,\n        filler: false,\n        tooltip: {\n          mode: 'nearest',\n          intersect: false,\n          callbacks: {\n            label: function() {\n              return '\\u200b';\n            },\n            labelColor: function(tooltipItem) {\n              const meta = tooltipItem.chart.getDatasetMeta(tooltipItem.datasetIndex);\n              const options = meta.controller.getStyle(tooltipItem.dataIndex);\n              return {\n                borderColor: options.borderColor,\n                backgroundColor: options.backgroundColor,\n                borderWidth: 2,\n                borderRadius: {\n                  topRight: 5,\n                  bottomRight: 5,\n                },\n              };\n            },\n          }\n        },\n      },\n\n      layout: {\n        padding: 15\n      }\n    },\n    plugins: [{\n      afterDraw: function(chart) {\n        const canvas = chart.canvas;\n        const rect = canvas.getBoundingClientRect();\n        const point = chart.getDatasetMeta(0).data[1];\n        const event = {\n          type: 'mousemove',\n          target: canvas,\n          clientX: rect.left + point.x,\n          clientY: rect.top + point.y\n        };\n        chart._handleEvent(event);\n        chart.tooltip.handleEvent(event);\n        chart.tooltip.draw(chart.ctx);\n      }\n    }]\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.tooltip/corner-radius.js",
    "content": "const data = [];\nfor (let x = 0; x < 3; x++) {\n  for (let y = 0; y < 3; y++) {\n    data.push({x, y});\n  }\n}\n\nmodule.exports = {\n  config: {\n    type: 'scatter',\n    data: {\n      datasets: [{\n        data,\n        backgroundColor: 'red',\n        radius: 1,\n        hoverRadius: 0\n      }],\n    },\n    options: {\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      },\n      plugins: {\n        legend: false,\n        title: false,\n        filler: false,\n        tooltip: {\n          mode: 'point',\n          intersect: true,\n          // spriteText: use white background to hide any gaps between fonts\n          backgroundColor: 'white',\n          borderColor: 'black',\n          borderWidth: 1,\n          callbacks: {\n            beforeLabel: () => 'before label',\n            label: () => 'label',\n            afterLabel: () => 'after1\\nafter2\\nafter3\\nafter4\\nafter5'\n          },\n          cornerRadius: {\n            topLeft: 10,\n            topRight: 20,\n            bottomRight: 5,\n            bottomLeft: 0,\n          }\n        },\n      },\n    },\n    plugins: [{\n      afterDraw: function(chart) {\n        const canvas = chart.canvas;\n        const rect = canvas.getBoundingClientRect();\n        const meta = chart.getDatasetMeta(0);\n        let point, event;\n\n        for (let i = 0; i < data.length; i++) {\n          point = meta.data[i];\n          event = {\n            type: 'mousemove',\n            target: canvas,\n            clientX: rect.left + point.x,\n            clientY: rect.top + point.y\n          };\n          chart._handleEvent(event);\n          chart.tooltip.handleEvent(event);\n          chart.tooltip.draw(chart.ctx);\n        }\n      }\n    }]\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 400,\n      width: 500\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.tooltip/opacity.js",
    "content": "var patternCanvas = document.createElement('canvas');\nvar patternContext = patternCanvas.getContext('2d');\n\npatternCanvas.width = 6;\npatternCanvas.height = 6;\npatternContext.fillStyle = '#ff0000';\npatternContext.fillRect(0, 0, 6, 6);\npatternContext.fillStyle = '#ffff00';\npatternContext.fillRect(0, 0, 4, 4);\n\nvar pattern = patternContext.createPattern(patternCanvas, 'repeat');\n\nvar gradient;\n\nmodule.exports = {\n  config: {\n    type: 'line',\n    data: {\n      datasets: [{\n        data: [8, 8, 8, 8, 8, 8, 7, 8, 8, 8, 8],\n        pointBorderColor: '#ff0000',\n        pointBackgroundColor: '#00ff00',\n        showLine: false\n      }, {\n        label: '',\n        data: [4, 4, 4, 4, 4, 5, 3, 4, 4, 4, 4],\n        pointBorderColor: pattern,\n        pointBackgroundColor: pattern,\n        showLine: false\n      }, {\n        label: '',\n        data: [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0],\n        showLine: false\n      }],\n      labels: ['', '', '', '', '', '', '', '', '', '', '']\n    },\n    options: {\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      },\n      elements: {\n        line: {\n          fill: false\n        }\n      },\n      plugins: {\n        legend: false,\n        title: false,\n        filler: false,\n        tooltip: {\n          mode: 'nearest',\n          intersect: false,\n          callbacks: {\n            label: function() {\n              return '\\u200b';\n            },\n          }\n        },\n      },\n\n      layout: {\n        padding: 15\n      }\n    },\n    plugins: [{\n      beforeDatasetsUpdate: function(chart) {\n        if (!gradient) {\n          gradient = chart.ctx.createLinearGradient(0, 0, 512, 256);\n          gradient.addColorStop(0, '#ff0000');\n          gradient.addColorStop(1, '#0000ff');\n        }\n        chart.config.data.datasets[2].pointBorderColor = gradient;\n        chart.config.data.datasets[2].pointBackgroundColor = gradient;\n\n        return true;\n      },\n      afterDraw: function(chart) {\n        var canvas = chart.canvas;\n        var rect = canvas.getBoundingClientRect();\n        var point, event;\n\n        for (var i = 0; i < 3; ++i) {\n          for (var j = 0; j < 11; ++j) {\n            point = chart.getDatasetMeta(i).data[j];\n            event = {\n              type: 'mousemove',\n              target: canvas,\n              clientX: rect.left + point.x,\n              clientY: rect.top + point.y\n            };\n            chart._handleEvent(event);\n            chart.tooltip.handleEvent(event);\n            chart.tooltip.opacity = j / 10;\n            chart.tooltip.draw(chart.ctx);\n          }\n        }\n      }\n    }]\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.tooltip/point-style.js",
    "content": "const pointStyles = ['circle', 'cross', 'crossRot', 'dash', 'line', 'rect', 'rectRounded', 'rectRot', 'star', 'triangle', false];\n\nfunction newDataset(pointStyle, i) {\n  return {\n    label: '',\n    data: pointStyles.map(() => i),\n    pointStyle: pointStyle,\n    pointBackgroundColor: '#0000ff',\n    pointBorderColor: '#00ff00',\n    showLine: false\n  };\n}\nmodule.exports = {\n  config: {\n    type: 'line',\n    data: {\n      datasets: pointStyles.map((pointStyle, i) => newDataset(pointStyle, i)),\n      labels: pointStyles.map(() => '')\n    },\n    options: {\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      },\n      elements: {\n        line: {\n          fill: false\n        }\n      },\n      plugins: {\n        legend: false,\n        title: false,\n        filler: false,\n        tooltip: {\n          mode: 'nearest',\n          intersect: false,\n          padding: 5,\n          usePointStyle: true,\n          callbacks: {\n            label: function() {\n              return '\\u200b';\n            }\n          }\n        },\n      },\n      layout: {\n        padding: 15\n      }\n    },\n    plugins: [{\n      afterDraw: function(chart) {\n        var canvas = chart.canvas;\n        var rect = canvas.getBoundingClientRect();\n        var point, event;\n\n        for (var i = 0; i < pointStyles.length; ++i) {\n          point = chart.getDatasetMeta(i).data[i];\n          event = {\n            type: 'mousemove',\n            target: canvas,\n            clientX: rect.left + point.x,\n            clientY: rect.top + point.y\n          };\n          chart._handleEvent(event);\n          chart.tooltip.handleEvent(event);\n          chart.tooltip.draw(chart.ctx);\n        }\n      }\n    }]\n  },\n  options: {\n    canvas: {\n      height: 256,\n      width: 512\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/plugin.tooltip/positioning.js",
    "content": "const data = [];\nfor (let x = 0; x < 3; x++) {\n  for (let y = 0; y < 3; y++) {\n    data.push({x, y});\n  }\n}\n\nmodule.exports = {\n  config: {\n    type: 'scatter',\n    data: {\n      datasets: [{\n        data,\n        backgroundColor: 'red',\n        radius: 1,\n        hoverRadius: 0\n      }],\n    },\n    options: {\n      scales: {\n        x: {display: false},\n        y: {display: false}\n      },\n      plugins: {\n        legend: false,\n        title: false,\n        filler: false,\n        tooltip: {\n          mode: 'point',\n          intersect: true,\n          // spriteText: use white background to hide any gaps between fonts\n          backgroundColor: 'white',\n          borderColor: 'black',\n          borderWidth: 1,\n          callbacks: {\n            beforeLabel: () => 'before label',\n            label: () => 'label',\n            afterLabel: () => 'after1\\nafter2\\nafter3\\nafter4\\nafter5'\n          }\n        }\n      },\n    },\n    plugins: [{\n      afterDraw: function(chart) {\n        const canvas = chart.canvas;\n        const rect = canvas.getBoundingClientRect();\n        const meta = chart.getDatasetMeta(0);\n        let point, event;\n\n        for (let i = 0; i < data.length; i++) {\n          point = meta.data[i];\n          event = {\n            type: 'mousemove',\n            target: canvas,\n            clientX: rect.left + point.x,\n            clientY: rect.top + point.y\n          };\n          chart._handleEvent(event);\n          chart.tooltip.handleEvent(event);\n          chart.tooltip.draw(chart.ctx);\n        }\n      }\n    }]\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 400,\n      width: 500\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.category/invalid-data.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: ['a', 'b', 'c', 'd', 'e', 'f', 'g'],\n      datasets: [{\n        data: [\n          {x: 'a', y: 1},\n          {x: null, y: 1},\n          {x: 2, y: 1},\n          {x: undefined, y: 1},\n          {x: 4, y: 1},\n          {x: NaN, y: 1},\n          {x: 6, y: 1}\n        ],\n        backgroundColor: 'red',\n        borderColor: 'red',\n        borderWidth: 5\n      }]\n    },\n    options: {\n      scales: {\n        y: {\n          display: false\n        },\n        x: {\n          grid: {\n            display: false\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      width: 256,\n      height: 256\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.category/max-ticks-limit-a.js",
    "content": "const data = Array.from({length: 42}, (_, i) => i + 1);\nconst labels = data.map(v => 'tick' + v);\n\nmodule.exports = {\n  description: 'https://github.com/chartjs/Chart.js/issues/7302',\n  config: {\n    type: 'bar',\n    data: {\n      datasets: [{\n        data\n      }],\n      labels\n    },\n    options: {\n      scales: {\n        x: {\n          ticks: {\n            display: false,\n            maxTicksLimit: 7\n          },\n          grid: {\n            color: 'red'\n          }\n        },\n        y: {display: false}\n      },\n      layout: {\n        padding: {\n          right: 2\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.category/max-ticks-limit-b.js",
    "content": "const data = Array.from({length: 42}, (_, i) => i + 1);\nconst labels = data.map(v => 'tick' + v);\n\nmodule.exports = {\n  description: 'https://github.com/chartjs/Chart.js/issues/7302',\n  config: {\n    type: 'bar',\n    data: {\n      datasets: [{\n        data\n      }],\n      labels\n    },\n    options: {\n      scales: {\n        x: {\n          ticks: {\n            display: false,\n            maxTicksLimit: 6\n          },\n          grid: {\n            color: 'red'\n          }\n        },\n        y: {display: false}\n      },\n      layout: {\n        padding: {\n          right: 2\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.category/max-ticks-limit-norotation.js",
    "content": "const data = Array.from({length: 42}, (_, i) => i + 1);\nconst labels = data.map(v => 'tick' + v);\n\nmodule.exports = {\n  description: 'https://github.com/chartjs/Chart.js/issues/10856',\n  config: {\n    type: 'bar',\n    data: {\n      datasets: [{\n        data\n      }],\n      labels\n    },\n    options: {\n      scales: {\n        x: {\n          ticks: {\n            display: true,\n            maxTicksLimit: 6\n          },\n          grid: {\n            color: 'red'\n          }\n        },\n        y: {display: false}\n      },\n      layout: {\n        padding: {\n          right: 2\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.category/ticks-from-data.js",
    "content": "module.exports = {\n  threshold: 0.01,\n  config: {\n    type: 'bar',\n    data: {\n      datasets: [{\n        data: [10, 5, 0, 25, 78],\n        backgroundColor: 'transparent'\n      }],\n      labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5']\n    },\n    options: {\n      indexAxis: 'y',\n      scales: {\n        x: {display: false},\n        y: {display: true}\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      width: 128,\n      height: 256\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.linear/grace/grace-10%.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      labels: ['a', 'b'],\n      datasets: [{\n        data: [90, -10],\n      }],\n    },\n    options: {\n      indexAxis: 'y',\n      scales: {\n        y: {\n          display: false\n        },\n        x: {\n          grace: '10%'\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      width: 512,\n      height: 128\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.linear/grace/grace-beginAtZero.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      labels: ['a', 'b'],\n      datasets: [{\n        data: [100, 0],\n        backgroundColor: 'blue'\n      }, {\n        xAxisID: 'x2',\n        data: [0, 100],\n        backgroundColor: 'red'\n      }],\n    },\n    options: {\n      indexAxis: 'y',\n      scales: {\n        y: {\n          display: false\n        },\n        x: {\n          position: 'top',\n          beginAtZero: true,\n          grace: '10%',\n        },\n        x2: {\n          position: 'bottom',\n          type: 'linear',\n          beginAtZero: false,\n          grace: '10%',\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      width: 512,\n      height: 128\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.linear/grace/grace-neg.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      labels: ['a'],\n      datasets: [{\n        data: [-0.18],\n      }],\n    },\n    options: {\n      indexAxis: 'y',\n      scales: {\n        y: {\n          display: false\n        },\n        x: {\n          grace: '5%'\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      width: 512,\n      height: 128\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.linear/grace/grace-pos.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      labels: ['a'],\n      datasets: [{\n        data: [0.18],\n      }],\n    },\n    options: {\n      indexAxis: 'y',\n      scales: {\n        y: {\n          display: false\n        },\n        x: {\n          grace: '5%'\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      width: 512,\n      height: 128\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.linear/grace/grace.js",
    "content": "module.exports = {\n  config: {\n    type: 'bar',\n    data: {\n      labels: ['a', 'b'],\n      datasets: [{\n        data: [1.2, -0.2],\n      }],\n    },\n    options: {\n      indexAxis: 'y',\n      scales: {\n        y: {\n          display: false\n        },\n        x: {\n          grace: 0.3\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      width: 512,\n      height: 128\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.linear/grace/issue-8912.js",
    "content": "module.exports = {\n  description: 'https://github.com/chartjs/Chart.js/issues/8912',\n  config: {\n    type: 'bar',\n    data: {\n      labels: ['Red', 'Blue'],\n      datasets: [{\n        data: [10, -10]\n      }]\n    },\n    options: {\n      plugins: false,\n      scales: {\n        x: {\n          display: false,\n        },\n        y: {\n          grace: '100%'\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.linear/issue-8806.js",
    "content": "module.exports = {\n  description: 'https://github.com/chartjs/Chart.js/issues/8806',\n  config: {\n    type: 'bar',\n    data: {\n      labels: ['0', '1', '2', '3', '4', '5', '6'],\n      datasets: [{\n        label: '# of Votes',\n        data: [32, 46, 28, 21, 20, 13, 27]\n      }]\n    },\n    options: {\n      scales: {\n        x: {display: false},\n        y: {ticks: {maxTicksLimit: 4}, min: 0}\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      width: 256,\n      height: 256\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.linear/min-max-skip/edge-case-1.js",
    "content": "module.exports = {\n  description: 'https://github.com/chartjs/Chart.js/issues/8982',\n  config: {\n    type: 'scatter',\n    options: {\n      scales: {\n        y: {\n          max: 1069,\n          min: 230,\n          ticks: {\n            autoSkip: false\n          }\n        },\n        x: {\n          max: 1069,\n          min: 230,\n          ticks: {\n            autoSkip: false\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 196,\n      width: 407\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.linear/min-max-skip/edge-case-2.js",
    "content": "module.exports = {\n  description: 'https://github.com/chartjs/Chart.js/issues/8982',\n  config: {\n    type: 'scatter',\n    options: {\n      scales: {\n        y: {\n          max: 1069,\n          min: 230,\n          ticks: {\n            autoSkip: false\n          }\n        },\n        x: {\n          max: 1069,\n          min: 230,\n          ticks: {\n            autoSkip: false\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 197,\n      width: 420\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.linear/min-max-skip/edge-case-3.js",
    "content": "module.exports = {\n  description: 'https://github.com/chartjs/Chart.js/issues/8982',\n  config: {\n    type: 'scatter',\n    options: {\n      scales: {\n        y: {\n          max: 1069,\n          min: 230,\n          ticks: {\n            autoSkip: false\n          }\n        },\n        x: {\n          max: 1069,\n          min: 230,\n          ticks: {\n            autoSkip: false\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 199,\n      width: 556\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.linear/min-max-skip/edge-case-4.js",
    "content": "module.exports = {\n  description: 'https://github.com/chartjs/Chart.js/issues/8982',\n  config: {\n    type: 'scatter',\n    options: {\n      scales: {\n        y: {\n          max: 1069,\n          min: 230,\n          ticks: {\n            autoSkip: false\n          }\n        },\n        x: {\n          max: 1069,\n          min: 230,\n          ticks: {\n            autoSkip: false\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 200,\n      width: 557\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.linear/min-max-skip/includeBounds.js",
    "content": "module.exports = {\n  config: {\n    type: 'scatter',\n    options: {\n      scales: {\n        y: {\n          max: 1225.2,\n          min: 369.5,\n          ticks: {\n            includeBounds: false\n          }\n        },\n        x: {\n          min: 20,\n          max: 100,\n          ticks: {\n            includeBounds: false\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.linear/min-max-skip/min-max-skip.js",
    "content": "module.exports = {\n  description: 'https://github.com/chartjs/Chart.js/issues/7734',\n  config: {\n    type: 'line',\n    options: {\n      scales: {\n        y: {\n          max: 1225.2,\n          min: 369.5,\n        },\n        x: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.linear/min-max-skip/no-collision.js",
    "content": "module.exports = {\n  description: 'https://github.com/chartjs/Chart.js/issues/9025',\n  threshold: 0.2,\n  config: {\n    type: 'line',\n    data: {\n      datasets: [{\n        data: [\n          {x: 10000000, y: 65},\n          {x: 20000000, y: 12},\n          {x: 30000000, y: 23},\n          {x: 40000000, y: 51},\n          {x: 50000000, y: 17},\n          {x: 60000000, y: 23}\n        ]\n      }]\n    },\n    options: {\n      scales: {\n        x: {\n          type: 'linear',\n          min: 10000000,\n          max: 60000000,\n          ticks: {\n            minRotation: 45,\n            maxRotation: 45,\n            count: 6\n          }\n        }\n      }\n    }\n  },\n  options: {\n    canvas: {\n      width: 200,\n      height: 200\n    },\n    spriteText: true\n  }\n};\n\n"
  },
  {
    "path": "test/fixtures/scale.linear/min-max-skip/rotated-case-1.js",
    "content": "module.exports = {\n  description: 'https://github.com/chartjs/Chart.js/issues/9025',\n  threshold: 0.2,\n  config: {\n    type: 'scatter',\n    options: {\n      scales: {\n        y: {\n          max: 1069,\n          min: 230,\n          ticks: {\n            autoSkip: false,\n            minRotation: 22.5\n          }\n        },\n        x: {\n          max: 1069,\n          min: 230,\n          ticks: {\n            autoSkip: false,\n            minRotation: 67.5\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 231,\n      width: 221\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.linear/min-max-skip/rotated-case-2.js",
    "content": "module.exports = {\n  description: 'https://github.com/chartjs/Chart.js/issues/9025',\n  threshold: 0.2,\n  config: {\n    type: 'scatter',\n    options: {\n      scales: {\n        y: {\n          max: 1069,\n          min: 230,\n          ticks: {\n            autoSkip: false,\n            minRotation: 22.5\n          }\n        },\n        x: {\n          max: 1069,\n          min: 230,\n          ticks: {\n            autoSkip: false,\n            minRotation: 67.5\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 232,\n      width: 222\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.linear/min-max-skip/rotated-case-3.js",
    "content": "module.exports = {\n  description: 'https://github.com/chartjs/Chart.js/issues/9025',\n  threshold: 0.2,\n  config: {\n    type: 'scatter',\n    options: {\n      scales: {\n        y: {\n          max: 1069,\n          min: 230,\n          ticks: {\n            autoSkip: false,\n            minRotation: 22.5\n          }\n        },\n        x: {\n          max: 1069,\n          min: 230,\n          ticks: {\n            autoSkip: false,\n            minRotation: 67.5\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 234,\n      width: 224\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.linear/min-max-skip/rotated-case-4.js",
    "content": "module.exports = {\n  description: 'https://github.com/chartjs/Chart.js/issues/9025',\n  threshold: 0.2,\n  config: {\n    type: 'scatter',\n    options: {\n      scales: {\n        y: {\n          max: 1069,\n          min: 230,\n          ticks: {\n            autoSkip: false,\n            minRotation: 22.5\n          }\n        },\n        x: {\n          max: 1069,\n          min: 230,\n          ticks: {\n            autoSkip: false,\n            minRotation: 67.5\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 235,\n      width: 225\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.linear/rotated-45.js",
    "content": "module.exports = {\n  description: 'https://github.com/chartjs/Chart.js/issues/9025',\n  threshold: 0.2,\n  config: {\n    type: 'scatter',\n    options: {\n      scales: {\n        y: {\n          min: 1612781975085.5466,\n          max: 1620287255085.5466,\n          ticks: {\n            autoSkip: false,\n            minRotation: 45,\n            maxRotation: 45,\n            count: 13\n          }\n        },\n        x: {\n          min: 1612781975085.5466,\n          max: 1620287255085.5466,\n          ticks: {\n            autoSkip: false,\n            minRotation: 45,\n            maxRotation: 45,\n            count: 13\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 350,\n      width: 350\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.linear/rotated-5.js",
    "content": "module.exports = {\n  description: 'https://github.com/chartjs/Chart.js/issues/9025',\n  threshold: 0.2,\n  config: {\n    type: 'scatter',\n    options: {\n      scales: {\n        y: {\n          min: 0,\n          max: 500000,\n          ticks: {\n            minRotation: 5,\n            maxRotation: 5,\n          }\n        },\n        x: {\n          min: 0,\n          max: 500000,\n          ticks: {\n            minRotation: 5,\n            maxRotation: 5,\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 350,\n      width: 350\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.linear/rotated-85.js",
    "content": "module.exports = {\n  description: 'https://github.com/chartjs/Chart.js/issues/9025',\n  threshold: 0.2,\n  config: {\n    type: 'scatter',\n    options: {\n      scales: {\n        y: {\n          min: 0,\n          max: 500000,\n          ticks: {\n            minRotation: 85,\n            maxRotation: 85,\n          }\n        },\n        x: {\n          min: 0,\n          max: 500000,\n          ticks: {\n            minRotation: 85,\n            maxRotation: 85,\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      height: 350,\n      width: 350\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.linear/tick-count-data-limits.js",
    "content": "module.exports = {\n  description: 'https://github.com/chartjs/Chart.js/issues/4234',\n  config: {\n    type: 'line',\n    data: {\n      datasets: [{\n        data: [0, 2, 45, 30]\n      }],\n      labels: ['A', 'B', 'C', 'D']\n    },\n    options: {\n      scales: {\n        y: {\n          ticks: {\n            count: 21,\n            callback: (v) => v.toString(),\n          }\n        },\n        x: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.linear/tick-count-min-max-not-aligned.js",
    "content": "module.exports = {\n  description: 'https://github.com/chartjs/Chart.js/issues/4234',\n  config: {\n    type: 'line',\n    options: {\n      scales: {\n        y: {\n          max: 27,\n          min: -3,\n          ticks: {\n            count: 11,\n          }\n        },\n        x: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.linear/tick-count-min-max-not-int.js",
    "content": "module.exports = {\n  description: 'https://github.com/chartjs/Chart.js/issues/9078',\n  config: {\n    type: 'bar',\n    data: {\n      datasets: [{\n        data: [\n          {x: 1, y: 3.5},\n          {x: 2, y: 4.7},\n          {x: 3, y: 7.3},\n          {x: 4, y: 6.7}\n        ]\n      }]\n    },\n    options: {\n      scales: {\n        x: {\n          type: 'linear',\n          display: false,\n        },\n        y: {\n          min: 3.5,\n          max: 8.5,\n          ticks: {\n            count: 6,\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      width: 256,\n      height: 256\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.linear/tick-count-min-max.js",
    "content": "module.exports = {\n  description: 'https://github.com/chartjs/Chart.js/issues/4234',\n  config: {\n    type: 'line',\n    options: {\n      scales: {\n        y: {\n          max: 50,\n          min: 0,\n          ticks: {\n            count: 21,\n          }\n        },\n        x: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.linear/tick-step-min-max-step-fp.js",
    "content": "module.exports = {\n  description: 'https://github.com/chartjs/Chart.js/issues/9334',\n  config: {\n    type: 'line',\n    options: {\n      scales: {\n        y: {\n          display: false,\n        },\n        x: {\n          type: 'linear',\n          min: 7.2,\n          max: 21.6,\n          ticks: {\n            stepSize: 1.8\n          }\n        },\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.linear/tick-step-min-max.js",
    "content": "module.exports = {\n  description: 'https://github.com/chartjs/Chart.js/issues/4234',\n  config: {\n    type: 'line',\n    options: {\n      scales: {\n        y: {\n          max: 27,\n          min: -3,\n          ticks: {\n            stepSize: 3,\n          }\n        },\n        x: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.linear/tiny-numbers.js",
    "content": "// Should generate max and min that are not equal when data contains values that are very close to each other\n\nmodule.exports = {\n  config: {\n    type: 'scatter',\n    data: {\n      datasets: [{\n        data: [\n          {x: 1, y: 1.8548483304974972},\n          {x: 2, y: 1.8548483304974974},\n        ]\n      }],\n    },\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.logarithmic/large-range.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],\n      datasets: [{\n        backgroundColor: 'red',\n        borderColor: 'red',\n        fill: false,\n        data: [23, 21, 34, 52, 115, 3333, 5116]\n      }]\n    },\n    options: {\n      responsive: true,\n      scales: {\n        x: {\n          display: false,\n        },\n        y: {\n          type: 'logarithmic',\n          ticks: {\n            autoSkip: false\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.logarithmic/large-values-small-range.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],\n      datasets: [{\n        backgroundColor: 'red',\n        borderColor: 'red',\n        fill: false,\n        data: [5000.002, 5000.012, 5000.01, 5000.03, 5000.04, 5000.004, 5000.032]\n      }]\n    },\n    options: {\n      responsive: true,\n      scales: {\n        x: {\n          display: false,\n        },\n        y: {\n          type: 'logarithmic',\n          ticks: {\n            autoSkip: false\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.logarithmic/med-range.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],\n      datasets: [{\n        backgroundColor: 'red',\n        borderColor: 'red',\n        fill: false,\n        data: [25, 24, 27, 32, 45, 30, 28]\n      }]\n    },\n    options: {\n      responsive: true,\n      scales: {\n        x: {\n          display: false,\n        },\n        y: {\n          type: 'logarithmic',\n          ticks: {\n            autoSkip: false\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.logarithmic/min-max.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],\n      datasets: [{\n        backgroundColor: 'red',\n        borderColor: 'red',\n        fill: false,\n        data: [250, 240, 270, 320, 450, 300, 280]\n      }]\n    },\n    options: {\n      responsive: true,\n      scales: {\n        x: {\n          display: false,\n        },\n        y: {\n          type: 'logarithmic',\n          min: 233,\n          max: 471,\n          ticks: {\n            autoSkip: false\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.logarithmic/null-values.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],\n      datasets: [{\n        backgroundColor: 'red',\n        borderColor: 'red',\n        fill: false,\n        data: [\n          150,\n          null,\n          1500,\n          200,\n          9000,\n          3000,\n          8888\n        ],\n        spanGaps: true\n      }, {\n        backgroundColor: 'blue',\n        borderColor: 'blue',\n        fill: false,\n        data: [\n          1000,\n          5500,\n          800,\n          7777,\n          null,\n          6666,\n          5555\n        ],\n        spanGaps: false\n      }]\n    },\n    options: {\n      responsive: true,\n      scales: {\n        x: {\n          display: false,\n        },\n        y: {\n          display: false,\n          type: 'logarithmic',\n        }\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.logarithmic/small-range.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],\n      datasets: [{\n        backgroundColor: 'red',\n        borderColor: 'red',\n        fill: false,\n        data: [3, 1, 4, 2, 5, 3, 16]\n      }]\n    },\n    options: {\n      responsive: true,\n      scales: {\n        x: {\n          display: false,\n        },\n        y: {\n          type: 'logarithmic',\n          ticks: {\n            autoSkip: false\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.radialLinear/anglelines-disable.json",
    "content": "{\n  \"config\": {\n    \"type\": \"radar\",\n    \"data\": {\n      \"labels\": [\"A\", \"B\", \"C\", \"D\", \"E\"]\n    },\n    \"options\": {\n      \"responsive\": false,\n      \"scales\": {\n        \"r\": {\n          \"grid\": {\n            \"color\": \"rgb(0, 0, 0)\",\n            \"lineWidth\": 1\n          },\n          \"angleLines\": {\n            \"display\": false\n          },\n          \"pointLabels\": {\n            \"display\": false\n          },\n          \"ticks\": {\n            \"display\": false\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "test/fixtures/scale.radialLinear/anglelines-indexable.js",
    "content": "module.exports = {\n  config: {\n    type: 'radar',\n    data: {\n      labels: ['A', 'B', 'C', 'D', 'E']\n    },\n    options: {\n      responsive: false,\n      scales: {\n        r: {\n          grid: {\n            display: true,\n          },\n          angleLines: {\n            color: ['red', 'green'],\n            lineWidth: [1, 5]\n          },\n          pointLabels: {\n            display: false\n          },\n          ticks: {\n            display: false\n          }\n        }\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.radialLinear/anglelines-reverse-scale.js",
    "content": "module.exports = {\n  config: {\n    type: 'radar',\n    data: {\n      labels: ['A', 'B', 'C', 'D', 'E'],\n      datasets: [{\n        data: [1, 1, 2, 3, 5]\n      }]\n    },\n    options: {\n      responsive: false,\n      scales: {\n        r: {\n          reverse: true,\n          grid: {\n            display: true,\n          },\n          angleLines: {\n            color: 'red',\n            lineWidth: 5,\n          },\n          pointLabels: {\n            display: false\n          },\n          ticks: {\n            display: true,\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.radialLinear/anglelines-scriptable.js",
    "content": "module.exports = {\n  config: {\n    type: 'radar',\n    data: {\n      labels: ['A', 'B', 'C', 'D', 'E']\n    },\n    options: {\n      responsive: false,\n      scales: {\n        r: {\n          grid: {\n            display: true,\n          },\n          angleLines: {\n            color: function(context) {\n              return context.index % 2 === 0 ? 'red' : 'green';\n            },\n            lineWidth: function(context) {\n              return context.index % 2 === 0 ? 1 : 5;\n            },\n          },\n          pointLabels: {\n            display: false\n          },\n          ticks: {\n            display: false\n          }\n        }\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.radialLinear/backgroundColor.js",
    "content": "module.exports = {\n  threshold: 0.01,\n  config: {\n    type: 'radar',\n    data: {\n      labels: [1, 2, 3, 4, 5, 6],\n      datasets: [\n        {\n          data: [3, 2, 2, 1, 3, 1]\n        }\n      ]\n    },\n    options: {\n      plugins: {\n        legend: false,\n        tooltip: false,\n        filler: false\n      },\n      scales: {\n        r: {\n          backgroundColor: '#00FF00',\n          min: 0,\n          max: 3,\n          pointLabels: {\n            display: false\n          },\n          ticks: {\n            display: false,\n            stepSize: 1,\n          }\n        }\n      },\n      responsive: true,\n      maintainAspectRatio: false\n    }\n  },\n};\n"
  },
  {
    "path": "test/fixtures/scale.radialLinear/border-dash.json",
    "content": "{\n  \"config\": {\n    \"type\": \"radar\",\n    \"data\": {\n      \"labels\": [\"A\", \"B\", \"C\", \"D\", \"E\"]\n    },\n    \"options\": {\n      \"responsive\": false,\n      \"scales\": {\n        \"r\": {\n          \"grid\": {\n            \"color\": \"rgba(0, 0, 255, 0.5)\",\n            \"lineWidth\": 1\n          },\n          \"border\": {\n            \"dash\": [4, 2],\n            \"dashOffset\": 2\n          },\n          \"angleLines\": {\n            \"color\": \"rgba(0, 0, 255, 0.5)\",\n            \"lineWidth\": 1,\n            \"borderDash\": [4, 2],\n            \"borderDashOffset\": 2\n          },\n          \"pointLabels\": {\n            \"display\": false\n          },\n          \"ticks\": {\n            \"display\": false\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "test/fixtures/scale.radialLinear/circular-backgroundColor.js",
    "content": "module.exports = {\n  threshold: 0.05,\n  config: {\n    type: 'radar',\n    data: {\n      labels: [1, 2, 3, 4, 5, 6],\n      datasets: [\n        {\n          data: [3, 2, 2, 1, 3, 1]\n        }\n      ]\n    },\n    options: {\n      plugins: {\n        legend: false,\n        tooltip: false,\n        filler: false\n      },\n      scales: {\n        r: {\n          backgroundColor: '#00FF00',\n          min: 0,\n          max: 3,\n          grid: {\n            circular: true\n          },\n          pointLabels: {\n            display: false\n          },\n          ticks: {\n            display: false,\n            stepSize: 1,\n          }\n        }\n      },\n      responsive: true,\n      maintainAspectRatio: false\n    }\n  },\n};\n"
  },
  {
    "path": "test/fixtures/scale.radialLinear/circular-border-dash.json",
    "content": "{\n  \"config\": {\n    \"type\": \"radar\",\n    \"data\": {\n      \"labels\": [\"A\", \"B\", \"C\", \"D\", \"E\"]\n    },\n    \"options\": {\n      \"responsive\": false,\n      \"scales\": {\n        \"r\": {\n          \"border\": {\n            \"dash\": [4, 2],\n            \"dashOffset\": 2\n          },\n          \"grid\": {\n            \"circular\": true,\n            \"color\": \"rgba(0, 0, 255, 0.5)\",\n            \"lineWidth\": 1\n          },\n          \"angleLines\": {\n            \"color\": \"rgba(0, 0, 255, 0.5)\",\n            \"lineWidth\": 1,\n            \"borderDash\": [4, 2],\n            \"borderDashOffset\": 2\n          },\n          \"pointLabels\": {\n            \"display\": false\n          },\n          \"ticks\": {\n            \"display\": false\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "test/fixtures/scale.radialLinear/gridlines-disable.json",
    "content": "{\n  \"config\": {\n    \"type\": \"radar\",\n    \"data\": {\n      \"labels\": [\"A\", \"B\", \"C\", \"D\", \"E\"]\n    },\n    \"options\": {\n      \"responsive\": false,\n      \"scales\": {\n        \"r\": {\n          \"grid\": {\n            \"display\": false\n          },\n          \"angleLines\": {\n            \"color\": \"rgb(0, 0, 0)\",\n            \"lineWidth\": 1\n          },\n          \"pointLabels\": {\n            \"display\": false\n          },\n          \"ticks\": {\n            \"display\": false\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "test/fixtures/scale.radialLinear/gridlines-no-z.json",
    "content": "{\n  \"config\": {\n    \"type\": \"radar\",\n    \"data\": {\n      \"labels\": [\"A\", \"B\", \"C\", \"D\", \"E\"],\n      \"datasets\": [\n        {\n          \"backgroundColor\": \"rgba(255, 0, 0, 1)\",\n          \"data\": [1, 2, 3, 3, 3]\n        }\n      ]\n    },\n    \"options\": {\n      \"responsive\": false,\n      \"scales\": {\n        \"r\": {\n          \"grid\": {\n            \"color\": \"rgba(0, 0, 0, 1)\",\n            \"lineWidth\": 1\n          },\n          \"angleLines\": {\n            \"color\": \"rgba(0, 0, 255, 1)\",\n            \"lineWidth\": 1\n          },\n          \"pointLabels\": {\n            \"display\": false\n          },\n          \"ticks\": {\n            \"display\": false\n          }\n        }\n      },\n      \"plugins\": {\n        \"legend\": false,\n        \"title\": false,\n        \"tooltip\": false,\n        \"filler\": true\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "test/fixtures/scale.radialLinear/gridlines-scriptable.js",
    "content": "module.exports = {\n  config: {\n    type: 'radar',\n    data: {\n      labels: ['A', 'B', 'C', 'D', 'E']\n    },\n    options: {\n      responsive: false,\n      scales: {\n        r: {\n          grid: {\n            display: true,\n            color: function(context) {\n              return context.index % 2 === 0 ? 'green' : 'red';\n            },\n            lineWidth: function(context) {\n              return context.index % 2 === 0 ? 5 : 1;\n            },\n          },\n          angleLines: {\n            color: 'rgba(255, 255, 255, 0.5)',\n            lineWidth: 2\n          },\n          pointLabels: {\n            display: false\n          },\n          ticks: {\n            display: false\n          }\n        }\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.radialLinear/gridlines-z.json",
    "content": "{\n  \"config\": {\n    \"type\": \"radar\",\n    \"data\": {\n      \"labels\": [\"A\", \"B\", \"C\", \"D\", \"E\"],\n      \"datasets\": [\n        {\n          \"backgroundColor\": \"rgba(255, 0, 0, 1)\",\n          \"data\": [1, 2, 3, 3, 3]\n        }\n      ]\n    },\n    \"options\": {\n      \"responsive\": false,\n      \"scales\": {\n        \"r\": {\n          \"grid\": {\n            \"color\": \"rgba(0, 0, 0, 1)\",\n            \"lineWidth\": 1,\n            \"z\": 1\n          },\n          \"angleLines\": {\n            \"color\": \"rgba(0, 0, 255, 1)\",\n            \"lineWidth\": 1\n          },\n          \"pointLabels\": {\n            \"display\": false\n          },\n          \"ticks\": {\n            \"display\": false\n          }\n        }\n      },\n      \"plugins\": {\n        \"legend\": false,\n        \"title\": false,\n        \"tooltip\": false,\n        \"filler\": true\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "test/fixtures/scale.radialLinear/indexable-gridlines.json",
    "content": "{\n  \"config\": {\n    \"type\": \"radar\",\n    \"data\": {\n      \"labels\": [\"A\", \"B\", \"C\", \"D\", \"E\"]\n    },\n    \"options\": {\n      \"responsive\": false,\n      \"scales\": {\n        \"r\": {\n          \"grid\": {\n            \"display\": true,\n            \"color\": [\n              \"rgba(0, 0, 0, 0.5)\",\n              \"rgba(255, 255, 255, 0.5)\",\n              false,\n              \"\",\n              \"rgba(255, 0, 0, 0.5)\",\n              \"rgba(0, 255, 0, 0.5)\",\n              \"rgba(0, 0, 255, 0.5)\",\n              \"rgba(255, 255, 0, 0.5)\",\n              \"rgba(255, 0, 255, 0.5)\",\n              \"rgba(0, 255, 255, 0.5)\"\n            ],\n            \"lineWidth\": [false, 0, 1, 2, 1, 2, 1, 2, 1, 2]\n          },\n          \"angleLines\": {\n            \"color\": \"rgba(255, 255, 255, 0.5)\",\n            \"lineWidth\": 2\n          },\n          \"pointLabels\": {\n            \"display\": false\n          },\n          \"ticks\": {\n            \"display\": false\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "test/fixtures/scale.radialLinear/pointLabels/background.js",
    "content": "module.exports = {\n  tolerance: 0.01,\n  config: {\n    type: 'radar',\n    data: {\n      labels: [\n        ['VENTE ET', 'COMMERCIALISATION'],\n        ['GESTION', 'FINANCIÈRE'],\n        'NUMÉRIQUE',\n        ['ADMINISTRATION', 'ET OPÉRATION'],\n        ['RESSOURCES', 'HUMAINES'],\n        'INNOVATION'\n      ],\n      datasets: [\n        {\n          backgroundColor: '#E43E51',\n          label: 'Compétences entrepreunariales',\n          data: [3, 2, 2, 1, 3, 1]\n        }\n      ]\n    },\n    options: {\n      plugins: {\n        legend: false,\n        tooltip: false,\n        filler: false\n      },\n      scales: {\n        r: {\n          min: 0,\n          max: 3,\n          pointLabels: {\n            backdropColor: 'blue',\n            backdropPadding: {left: 5, right: 5, top: 2, bottom: 2},\n          },\n          ticks: {\n            display: false,\n            stepSize: 1,\n            maxTicksLimit: 1\n          }\n        }\n      },\n      responsive: true,\n      maintainAspectRatio: false\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.radialLinear/pointLabels/border-radius.js",
    "content": "module.exports = {\n  tolerance: 0.01,\n  config: {\n    type: 'radar',\n    data: {\n      labels: [\n        ['VENTE ET', 'COMMERCIALISATION'],\n        ['GESTION', 'FINANCIÈRE'],\n        'NUMÉRIQUE',\n        ['ADMINISTRATION', 'ET OPÉRATION'],\n        ['RESSOURCES', 'HUMAINES'],\n        'INNOVATION'\n      ],\n      datasets: [\n        {\n          backgroundColor: '#E43E51',\n          label: 'Compétences entrepreunariales',\n          data: [3, 2, 2, 1, 3, 1]\n        }\n      ]\n    },\n    options: {\n      plugins: {\n        legend: false,\n        tooltip: false,\n        filler: false\n      },\n      scales: {\n        r: {\n          min: 0,\n          max: 3,\n          pointLabels: {\n            backdropColor: 'blue',\n            backdropPadding: {left: 5, right: 5, top: 2, bottom: 2},\n            borderRadius: 10,\n          },\n          ticks: {\n            display: false,\n            stepSize: 1,\n            maxTicksLimit: 1\n          }\n        }\n      },\n      responsive: true,\n      maintainAspectRatio: false\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.radialLinear/pointLabels/no-more-than-half-radius.js",
    "content": "module.exports = {\n  config: {\n    type: 'radar',\n    data: {\n      labels: ['Too long label 1', 'Too long label 2', 'Too long label 3', 'Too long label 4'],\n      datasets: [\n        {\n          backgroundColor: '#E43E51',\n          data: [1, 1, 1, 1]\n        }\n      ]\n    },\n    options: {\n      scales: {\n        r: {\n          max: 1,\n          ticks: {\n            display: false,\n          },\n          grid: {\n            display: false\n          }\n        }\n      },\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {\n      width: 256,\n      height: 256\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.radialLinear/pointLabels/padding.js",
    "content": "module.exports = {\n  config: {\n    type: 'radar',\n    data: {\n      labels: [\n        ['VENTE ET', 'COMMERCIALISATION'],\n        ['GESTION', 'FINANCIÈRE'],\n        'NUMÉRIQUE',\n        ['ADMINISTRATION', 'ET OPÉRATION'],\n        ['RESSOURCES', 'HUMAINES'],\n        'INNOVATION'\n      ],\n      datasets: [\n        {\n          radius: 12,\n          backgroundColor: '#E43E51',\n          label: 'Compétences entrepreunariales',\n          data: [3, 2, 2, 1, 3, 1]\n        }\n      ]\n    },\n    options: {\n      plugins: {\n        legend: false,\n        tooltip: false,\n        filler: false\n      },\n      scales: {\n        r: {\n          min: 0,\n          max: 3,\n          pointLabels: {\n            padding: 30\n          },\n          ticks: {\n            display: false,\n            stepSize: 1,\n            maxTicksLimit: 1\n          }\n        }\n      },\n      responsive: true,\n      maintainAspectRatio: false\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.radialLinear/pointLabels/scriptable-color-small.js",
    "content": "module.exports = {\n  config: {\n    type: 'radar',\n    data: {\n      labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange', 'Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange', 'Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange', 'Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],\n      datasets: [{\n        label: '# of Votes',\n        data: [12, 19, 3, 5, 1, 3, 12, 19, 3, 5, 1, 3, 12, 19, 3, 5, 1, 3, 12, 19, 3, 5, 1, 3]\n      }]\n    },\n    options: {\n      scales: {\n        r: {\n          ticks: {\n            display: false,\n          },\n          angleLines: {\n            color: (ctx) => {\n              return ctx.index % 2 === 0 ? 'green' : 'red';\n            }\n          },\n          pointLabels: {\n            display: false,\n          }\n        }\n      },\n    }\n  },\n  options: {\n    spriteText: true,\n    width: 300,\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.radialLinear/ticks-below-zero.js",
    "content": "module.exports = {\n  config: {\n    type: 'radar',\n    data: {\n      labels: ['A', 'B', 'C', 'D', 'E']\n    },\n    options: {\n      responsive: false,\n      scales: {\n        r: {\n          min: -1,\n          max: 1,\n          grid: {\n            display: true,\n            color: 'blue',\n            lineWidth: 2\n          },\n          angleLines: {\n            color: 'rgba(255, 255, 255, 0.5)',\n            lineWidth: 2\n          },\n          pointLabels: {\n            display: false\n          },\n          ticks: {\n            display: true,\n            autoSkip: false,\n            stepSize: 0.2,\n            callback: function(value) {\n              if (value === 0.8) {\n                return 'Strong';\n              }\n              if (value === 0.4) {\n                return 'Weak';\n              }\n              if (value === 0) {\n                return 'No';\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.time/autoskip-major.js",
    "content": "var date = moment('Jan 01 1990', 'MMM DD YYYY');\nvar data = [];\nfor (var i = 0; i < 60; i++) {\n  data.push({x: date.valueOf(), y: i});\n  date = date.clone().add(1, 'month');\n}\n\nmodule.exports = {\n  threshold: 0.05,\n  config: {\n    type: 'line',\n    data: {\n      datasets: [{\n        xAxisID: 'x',\n        data: data,\n        fill: false\n      }],\n    },\n    options: {\n      scales: {\n        x: {\n          type: 'time',\n          ticks: {\n            major: {\n              enabled: true\n            },\n            source: 'data',\n            autoSkip: true,\n            maxRotation: 0\n          }\n        },\n        y: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.time/bar-large-gap-between-data.js",
    "content": "var date = moment('May 24 2020', 'MMM DD YYYY');\n\nmodule.exports = {\n  threshold: 0.05,\n  config: {\n    type: 'bar',\n    data: {\n      datasets: [{\n        backgroundColor: 'rgba(255, 0, 0, 0.5)',\n        data: [\n          {\n            x: date.clone().add(-2, 'day'),\n            y: 20,\n          },\n          {\n            x: date.clone().add(-1, 'day'),\n            y: 30,\n          },\n          {\n            x: date,\n            y: 40,\n          },\n          {\n            x: date.clone().add(1, 'day'),\n            y: 50,\n          },\n          {\n            x: date.clone().add(7, 'day'),\n            y: 10,\n          }\n        ]\n      }]\n    },\n    options: {\n      scales: {\n        x: {\n          display: false,\n          type: 'time',\n          ticks: {\n            source: 'auto'\n          },\n          time: {\n            unit: 'day'\n          }\n        },\n        y: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.time/custom-parser.js",
    "content": "module.exports = {\n  threshold: 0.01,\n  tolerance: 0.0025,\n  config: {\n    type: 'line',\n    data: {\n      labels: ['foo', 'bar'],\n      datasets: [{\n        data: [0, 1],\n        fill: false\n      }],\n    },\n    options: {\n      scales: {\n        x: {\n          type: 'time',\n          position: 'bottom',\n          time: {\n            unit: 'day',\n            round: true,\n            parser: function(label) {\n              return label === 'foo' ?\n                moment('2000/01/02', 'YYYY/MM/DD') :\n                moment('2016/05/08', 'YYYY/MM/DD');\n            }\n          },\n          ticks: {\n            source: 'labels'\n          }\n        },\n        y: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {width: 256, height: 128}\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.time/data-ty.js",
    "content": "function newDateFromRef(days) {\n  return moment('01/01/2015 12:00', 'DD/MM/YYYY HH:mm').add(days, 'd').toDate();\n}\n\nmodule.exports = {\n  threshold: 0.01,\n  tolerance: 0.003,\n  config: {\n    type: 'line',\n    data: {\n      datasets: [{\n        data: [{\n          t: newDateFromRef(0),\n          y: 1\n        }, {\n          t: newDateFromRef(1),\n          y: 10\n        }, {\n          t: newDateFromRef(2),\n          y: 0\n        }, {\n          t: newDateFromRef(4),\n          y: 5\n        }, {\n          t: newDateFromRef(6),\n          y: 77\n        }, {\n          t: newDateFromRef(7),\n          y: 9\n        }, {\n          t: newDateFromRef(9),\n          y: 5\n        }],\n        fill: false,\n        parsing: {\n          xAxisKey: 't'\n        }\n      }],\n    },\n    options: {\n      scales: {\n        x: {\n          type: 'time',\n          position: 'bottom',\n          ticks: {\n            maxRotation: 0\n          }\n        },\n        y: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {width: 800, height: 200}\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.time/data-xy.js",
    "content": "function newDateFromRef(days) {\n  return moment('01/01/2015 12:00', 'DD/MM/YYYY HH:mm').add(days, 'd').toDate();\n}\n\nmodule.exports = {\n  threshold: 0.01,\n  tolerance: 0.003,\n  config: {\n    type: 'line',\n    data: {\n      datasets: [{\n        data: [{\n          x: newDateFromRef(0),\n          y: 1\n        }, {\n          x: newDateFromRef(1),\n          y: 10\n        }, {\n          x: newDateFromRef(2),\n          y: 0\n        }, {\n          x: newDateFromRef(4),\n          y: 5\n        }, {\n          x: newDateFromRef(6),\n          y: 77\n        }, {\n          x: newDateFromRef(7),\n          y: 9\n        }, {\n          x: newDateFromRef(9),\n          y: 5\n        }],\n        fill: false\n      }],\n    },\n    options: {\n      scales: {\n        x: {\n          type: 'time',\n          position: 'bottom',\n          ticks: {\n            maxRotation: 0\n          }\n        },\n        y: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {width: 800, height: 200}\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.time/invalid-data.js",
    "content": "module.exports = {\n  description: 'Invalid data, https://github.com/chartjs/Chart.js/issues/5563',\n  config: {\n    type: 'line',\n    data: {\n      datasets: [{\n        data: [{\n          x: '14:45:00',\n          y: 20,\n        }, {\n          x: '20:30:00',\n          y: 10,\n        }, {\n          x: '25:15:00',\n          y: 15,\n        }, {\n          x: null,\n          y: 15,\n        }, {\n          x: undefined,\n          y: 15,\n        }, {\n          x: NaN,\n          y: 15,\n        }, {\n          x: 'monday',\n          y: 15,\n        }],\n      }]\n    },\n    options: {\n      scales: {\n        x: {\n          type: 'time',\n          time: {\n            parser: 'HH:mm:ss',\n            unit: 'hour'\n          },\n        },\n      },\n      layout: {\n        padding: 16\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {width: 1000, height: 200}\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.time/labels-date.js",
    "content": "function newDateFromRef(days) {\n  return moment('01/01/2015 12:00', 'DD/MM/YYYY HH:mm').add(days, 'd').toDate();\n}\n\nmodule.exports = {\n  threshold: 0.1,\n  tolerance: 0.002,\n  config: {\n    type: 'line',\n    data: {\n      labels: [newDateFromRef(0), newDateFromRef(1), newDateFromRef(2), newDateFromRef(4), newDateFromRef(6), newDateFromRef(7), newDateFromRef(9)],\n      fill: false\n    },\n    options: {\n      scales: {\n        x: {\n          type: 'time',\n        },\n        y: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {width: 1000, height: 200}\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.time/labels-strings.js",
    "content": "module.exports = {\n  threshold: 0.05,\n  tolerance: 0.002,\n  config: {\n    type: 'line',\n    data: {\n      labels: ['2015-01-01T12:00:00', '2015-01-02T21:00:00', '2015-01-03T22:00:00', '2015-01-05T23:00:00', '2015-01-07T03:00', '2015-01-08T10:00', '2015-01-10T12:00']\n    },\n    options: {\n      scales: {\n        x: {\n          type: 'time',\n        },\n        y: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {width: 1000, height: 200}\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.time/labels.js",
    "content": "var timeOpts = {\n  parser: 'YYYY',\n  unit: 'year',\n  displayFormats: {\n    year: 'YYYY'\n  }\n};\n\nmodule.exports = {\n  threshold: 0.01,\n  tolerance: 0.002,\n  config: {\n    type: 'line',\n    data: {\n      labels: ['1975', '1976', '1977'],\n      xLabels: ['1985', '1986', '1987'],\n      yLabels: ['1995', '1996', '1997']\n    },\n    options: {\n      scales: {\n        x: {\n          type: 'time',\n          labels: ['2015', '2016', '2017'],\n          time: timeOpts\n        },\n        x2: {\n          type: 'time',\n          position: 'bottom',\n          time: timeOpts\n        },\n        y: {\n          type: 'time',\n          time: timeOpts\n        },\n        y2: {\n          position: 'left',\n          type: 'time',\n          labels: ['2005', '2006', '2007'],\n          time: timeOpts\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.time/negative-times.js",
    "content": "module.exports = {\n  config: {\n    type: 'line',\n    data: {\n      datasets: [{\n        fill: true,\n        backgroundColor: 'red',\n        data: [\n          {x: -1000000, y: 1},\n          {x: 1000000000, y: 2}\n        ]\n      }]\n    },\n    options: {\n      scales: {\n        x: {\n          type: 'time',\n          time: {\n            unit: 'day'\n          },\n          ticks: {\n            display: false\n          }\n        },\n        y: {\n          ticks: {\n            display: false\n          }\n        }\n      },\n      plugins: {\n        legend: false,\n        title: false,\n        tooltip: false\n      }\n    }\n  },\n  options: {\n    canvas: {width: 1000, height: 200}\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.time/offset-auto-skip-ticks.js",
    "content": "const data = {\n  labels: [],\n  datasets: [{\n    label: 'Dataset',\n    borderColor: '#2f54eb',\n    data: [{\n      y: 3,\n      x: 1646345700000\n    }, {\n      y: 7,\n      x: 1646346600000\n    }, {\n      y: 9,\n      x: 1646347500000\n    }, {\n      y: 5,\n      x: 1646348400000\n    }, {\n      y: 5,\n      x: 1646349300000\n    }],\n  }]\n};\n\nmodule.exports = {\n  description: 'https://github.com/chartjs/Chart.js/issues/10215',\n  config: {\n    type: 'bar',\n    data,\n    options: {\n      maintainAspectRatio: false,\n      scales: {\n        x: {\n          type: 'time',\n          offset: true,\n          offsetAfterAutoskip: true,\n          axis: 'x',\n          grid: {\n            offset: true\n          },\n        },\n        y: {\n          display: false,\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {width: 600, height: 400}\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.time/offset-with-1-tick.js",
    "content": "const data = {\n  datasets: [\n    {\n      label: 6,\n      backgroundColor: 'red',\n      data: [\n        {\n          x: '2021-03-24',\n          y: 464\n        }\n      ]\n    },\n    {\n      label: 1,\n      backgroundColor: 'red',\n      data: [\n        {\n          x: '2021-03-24',\n          y: 464\n        }\n      ]\n    },\n    {\n      label: 17,\n      backgroundColor: 'blue',\n      data: [\n        {\n          x: '2021-03-24',\n          y: 390\n        }\n      ]\n    }\n  ]\n};\n\nmodule.exports = {\n  description: 'https://github.com/chartjs/Chart.js/issues/8718',\n  config: {\n    type: 'bar',\n    data,\n    options: {\n      scales: {\n        x: {\n          type: 'time',\n          time: {\n            unit: 'day',\n          },\n        },\n        y: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {width: 256, height: 128}\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.time/offset-with-2-ticks.js",
    "content": "const data = {\n  datasets: [\n    {\n      label: 1,\n      backgroundColor: 'orange',\n      data: [\n        {\n          x: '2021-03-24',\n          y: 464\n        }\n      ]\n    },\n    {\n      label: 2,\n      backgroundColor: 'red',\n      data: [\n        {\n          x: '2021-03-24',\n          y: 464\n        }\n      ]\n    },\n    {\n      label: 3,\n      backgroundColor: 'blue',\n      data: [\n        {\n          x: '2021-03-24',\n          y: 390\n        }\n      ]\n    },\n    {\n      label: 4,\n      backgroundColor: 'purple',\n      data: [\n        {\n          x: '2021-03-25',\n          y: 464\n        }\n      ]\n    },\n    {\n      label: 5,\n      backgroundColor: 'black',\n      data: [\n        {\n          x: '2021-03-25',\n          y: 464\n        }\n      ]\n    },\n    {\n      label: 6,\n      backgroundColor: 'cyan',\n      data: [\n        {\n          x: '2021-03-25',\n          y: 390\n        }\n      ]\n    }\n  ]\n};\n\nmodule.exports = {\n  description: 'https://github.com/chartjs/Chart.js/issues/8718',\n  config: {\n    type: 'bar',\n    data,\n    options: {\n      scales: {\n        x: {\n          type: 'time',\n          time: {\n            unit: 'day',\n          },\n        },\n        y: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {width: 256, height: 128}\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.time/offset-with-no-ticks.js",
    "content": "const data = {\n  datasets: [\n    {\n      data: [\n        {\n          x: moment('15/10/2020', 'DD/MM/YYYY').valueOf(),\n          y: 55\n        },\n        {\n          x: moment('18/10/2020', 'DD/MM/YYYY').valueOf(),\n          y: 10\n        },\n        {\n          x: moment('19/10/2020', 'DD/MM/YYYY').valueOf(),\n          y: 15\n        }\n      ],\n      backgroundColor: 'blue'\n    },\n    {\n      data: [\n        {\n          x: moment('15/10/2020', 'DD/MM/YYYY').valueOf(),\n          y: 6\n        },\n        {\n          x: moment('18/10/2020', 'DD/MM/YYYY').valueOf(),\n          y: 11\n        },\n        {\n          x: moment('19/10/2020', 'DD/MM/YYYY').valueOf(),\n          y: 16\n        }\n      ],\n      backgroundColor: 'green',\n    },\n    {\n      data: [\n        {\n          x: moment('15/10/2020', 'DD/MM/YYYY').valueOf(),\n          y: 7\n        },\n        {\n          x: moment('18/10/2020', 'DD/MM/YYYY').valueOf(),\n          y: 12\n        },\n        {\n          x: moment('19/10/2020', 'DD/MM/YYYY').valueOf(),\n          y: 17\n        }\n      ],\n      backgroundColor: 'red',\n    }\n  ]\n};\n\nmodule.exports = {\n  description: 'https://github.com/chartjs/Chart.js/issues/7991',\n  config: {\n    type: 'bar',\n    data,\n    options: {\n      scales: {\n        x: {\n          type: 'time',\n          time: {\n            unit: 'month',\n          },\n        },\n        y: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {width: 256, height: 128}\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.time/skip-null-gridlines.js",
    "content": "module.exports = {\n  threshold: 0.01,\n  tolerance: 0.0025,\n  config: {\n    type: 'line',\n    data: {\n      labels: ['2017', '2018', '2019', '2020', '2025'],\n      datasets: [{data: [0, 1, 2, 3, 4], fill: false}]\n    },\n    options: {\n      scales: {\n        x: {\n          type: 'time',\n          time: {\n            parser: 'YYYY',\n            unit: 'year'\n          },\n          ticks: {\n            source: 'auto',\n            callback: (tick, index) => index % 2 === 0 ? null : tick,\n          }\n        },\n        y: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.time/skip-undefined-gridlines.js",
    "content": "module.exports = {\n  threshold: 0.01,\n  tolerance: 0.0025,\n  config: {\n    type: 'line',\n    data: {\n      labels: ['2017', '2018', '2019', '2020', '2025'],\n      datasets: [{data: [0, 1, 2, 3, 4], fill: false}]\n    },\n    options: {\n      scales: {\n        x: {\n          type: 'time',\n          time: {\n            parser: 'YYYY',\n            unit: 'year'\n          },\n          ticks: {\n            source: 'auto',\n            callback: (tick, index) => index % 2 === 0 ? undefined : tick,\n          }\n        },\n        y: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.time/source-auto-linear.js",
    "content": "module.exports = {\n  threshold: 0.01,\n  tolerance: 0.0025,\n  config: {\n    type: 'line',\n    data: {\n      labels: ['2017', '2018', '2019', '2020', '2025'],\n      datasets: [{data: [0, 1, 2, 3, 4], fill: false}]\n    },\n    options: {\n      scales: {\n        x: {\n          type: 'time',\n          time: {\n            parser: 'YYYY',\n            unit: 'year'\n          },\n          ticks: {\n            source: 'auto'\n          }\n        },\n        y: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.time/source-data-linear.js",
    "content": "module.exports = {\n  threshold: 0.01,\n  tolerance: 0.0015,\n  config: {\n    type: 'line',\n    data: {\n      labels: ['2017', '2018', '2019', '2020', '2025'],\n      datasets: [{data: [0, 1, 2, 3, 4], fill: false}]\n    },\n    options: {\n      scales: {\n        x: {\n          type: 'time',\n          time: {\n            parser: 'YYYY',\n            unit: 'year'\n          },\n          ticks: {\n            source: 'data'\n          }\n        },\n        y: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.time/source-labels-linear-offset-min-max.js",
    "content": "module.exports = {\n  threshold: 0.01,\n  tolerance: 0.0015,\n  config: {\n    type: 'line',\n    data: {\n      labels: ['2017', '2019', '2020', '2025', '2042'],\n      datasets: [{data: [0, 1, 2, 3, 4, 5], fill: false}]\n    },\n    options: {\n      scales: {\n        x: {\n          type: 'time',\n          min: '2012',\n          max: '2051',\n          offset: true,\n          time: {\n            parser: 'YYYY',\n          },\n          ticks: {\n            source: 'labels'\n          }\n        },\n        y: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.time/source-labels-linear.js",
    "content": "module.exports = {\n  threshold: 0.01,\n  tolerance: 0.0015,\n  config: {\n    type: 'line',\n    data: {\n      labels: ['2017', '2018', '2019', '2020', '2025'],\n      datasets: [{data: [0, 1, 2, 3, 4], fill: false}]\n    },\n    options: {\n      scales: {\n        x: {\n          type: 'time',\n          time: {\n            parser: 'YYYY',\n            unit: 'year'\n          },\n          ticks: {\n            source: 'labels'\n          }\n        },\n        y: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.time/ticks-capacity.js",
    "content": "module.exports = {\n  threshold: 0.01,\n  tolerance: 0.002,\n  config: {\n    type: 'line',\n    data: {\n      labels: [\n        '2012-01-01', '2013-01-01', '2014-01-01', '2015-01-01',\n        '2016-01-01', '2017-01-01', '2018-01-01', '2019-01-01'\n      ]\n    },\n    options: {\n      scales: {\n        x: {\n          type: 'time',\n          time: {\n            unit: 'year'\n          }\n        },\n        y: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.time/ticks-minunit.js",
    "content": "module.exports = {\n  threshold: 0.01,\n  tolerance: 0.002,\n  config: {\n    type: 'line',\n    data: {\n      labels: ['2015-01-01T20:00:00', '2015-01-02T21:00:00'],\n    },\n    options: {\n      scales: {\n        x: {\n          type: 'time',\n          bounds: 'ticks',\n          time: {\n            minUnit: 'day'\n          }\n        },\n        y: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {width: 256, height: 128}\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.time/ticks-reverse-linear-min-max.js",
    "content": "module.exports = {\n  threshold: 0.01,\n  tolerance: 0.0015,\n  config: {\n    type: 'line',\n    data: {\n      labels: ['2017', '2019', '2020', '2025', '2042'],\n      datasets: [{data: [0, 1, 2, 3, 4, 5], fill: false}]\n    },\n    options: {\n      scales: {\n        x: {\n          type: 'time',\n          min: '2012',\n          max: '2050',\n          time: {\n            parser: 'YYYY'\n          },\n          reverse: true,\n          ticks: {\n            source: 'labels'\n          }\n        },\n        y: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.time/ticks-reverse-linear.js",
    "content": "module.exports = {\n  threshold: 0.01,\n  config: {\n    type: 'line',\n    data: {\n      labels: ['2017', '2019', '2020', '2025', '2042'],\n      datasets: [{data: [0, 1, 2, 3, 4], fill: false}]\n    },\n    options: {\n      scales: {\n        x: {\n          type: 'time',\n          time: {\n            parser: 'YYYY'\n          },\n          reverse: true,\n          ticks: {\n            source: 'labels'\n          }\n        },\n        y: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.time/ticks-reverse-offset.js",
    "content": "module.exports = {\n  threshold: 0.01,\n  tolerance: 0.002,\n  config: {\n    type: 'line',\n    data: {\n      labels: ['2017', '2018', '2019', '2020', '2021'],\n      datasets: [{data: [0, 1, 2, 3, 4], fill: false}]\n    },\n    options: {\n      scales: {\n        x: {\n          type: 'time',\n          reverse: true,\n          offset: true,\n          time: {\n            parser: 'YYYY',\n            unit: 'year'\n          },\n          ticks: {\n            source: 'labels',\n          },\n        },\n        y: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.time/ticks-reverse.js",
    "content": "module.exports = {\n  threshold: 0.01,\n  tolerance: 0.0015,\n  config: {\n    type: 'line',\n    data: {\n      labels: ['2017', '2018', '2019', '2020', '2021'],\n      datasets: [{data: [0, 1, 2, 3, 4], fill: false}]\n    },\n    options: {\n      scales: {\n        x: {\n          type: 'time',\n          reverse: true,\n          time: {\n            parser: 'YYYY',\n            unit: 'year'\n          },\n          ticks: {\n            source: 'labels',\n          },\n        },\n        y: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.time/ticks-round.js",
    "content": "module.exports = {\n  threshold: 0.05,\n  config: {\n    type: 'line',\n    data: {\n      labels: ['2015-01-01T20:00:00', '2015-02-02T21:00:00', '2015-02-21T01:00:00']\n    },\n    options: {\n      scales: {\n        x: {\n          type: 'time',\n          bounds: 'ticks',\n          time: {\n            unit: 'week',\n            round: 'week'\n          }\n        },\n        y: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {width: 512, height: 256}\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.time/ticks-stepsize.js",
    "content": "module.exports = {\n  threshold: 0.01,\n  config: {\n    type: 'line',\n    data: {\n      labels: ['2015-01-01T20:00:00', '2015-01-01T21:00:00']\n    },\n    options: {\n      scales: {\n        x: {\n          type: 'time',\n          bounds: 'ticks',\n          time: {\n            unit: 'hour',\n          },\n          ticks: {\n            stepSize: 2\n          }\n        },\n        y: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {width: 512, height: 128}\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.time/ticks-unit.js",
    "content": "module.exports = {\n  threshold: 0.05,\n  tolerance: 0.002,\n  config: {\n    type: 'line',\n    data: {\n      labels: ['2015-01-01T20:00:00', '2015-01-02T21:00:00'],\n    },\n    options: {\n      scales: {\n        x: {\n          type: 'time',\n          time: {\n            unit: 'hour',\n          }\n        },\n        y: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true,\n    canvas: {width: 1200, height: 200}\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.timeseries/data-timestamps.js",
    "content": "module.exports = {\n  threshold: 0.01,\n  tolerance: 0.0015,\n  config: {\n    type: 'line',\n    data: {\n      datasets: [{data: [\n        {x: 1687849697000, y: 904},\n        {x: 1687817063000, y: 905},\n        {x: 1687694268000, y: 913},\n        {x: 1687609438000, y: 914},\n        {x: 1687561387000, y: 916},\n        {x: 1686875127000, y: 918},\n        {x: 1686873138000, y: 920},\n        {x: 1686872777000, y: 928},\n        {x: 1686081641000, y: 915}\n      ], fill: false}, {data: [\n        {x: 1687816803000, y: 1105},\n        {x: 1686869490000, y: 1114},\n        {x: 1686869397000, y: 1103},\n        {x: 1686869225000, y: 1091},\n        {x: 1686556516000, y: 1078}\n      ]}]\n    },\n    options: {\n      scales: {\n        x: {\n          type: 'timeseries',\n          bounds: 'data',\n          time: {\n            unit: 'day'\n          },\n          ticks: {\n            source: 'auto'\n          }\n        },\n        y: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.timeseries/financial-daily.js",
    "content": "const data = [{x: 631180800000, y: 31.80}, {x: 631267200000, y: 30.20}, {x: 631353600000, y: 29.84}, {x: 631440000000, y: 29.72}, {x: 631526400000, y: 28.91}, {x: 631785600000, y: 29.55}, {x: 631872000000, y: 30.39}, {x: 631958400000, y: 29.54}, {x: 632044800000, y: 28.86}, {x: 632131200000, y: 30.75}, {x: 632390400000, y: 31.86}, {x: 632476800000, y: 33.59}, {x: 632563200000, y: 31.22}, {x: 632649600000, y: 30.12}, {x: 632736000000, y: 30.68}, {x: 632995200000, y: 31.46}, {x: 633081600000, y: 30.77}, {x: 633168000000, y: 30.27}, {x: 633254400000, y: 29.64}, {x: 633340800000, y: 30.53}, {x: 633600000000, y: 30.79}, {x: 633686400000, y: 30.27}, {x: 633772800000, y: 30.18}, {x: 633859200000, y: 27.72}, {x: 633945600000, y: 27.83}, {x: 634204800000, y: 27.82}, {x: 634291200000, y: 29.10}, {x: 634377600000, y: 28.34}, {x: 634464000000, y: 29.52}, {x: 634550400000, y: 28.69}, {x: 634809600000, y: 28.23}, {x: 634896000000, y: 27.45}, {x: 634982400000, y: 27.40}, {x: 635068800000, y: 28.39}, {x: 635155200000, y: 30.03}, {x: 635414400000, y: 31.19}, {x: 635500800000, y: 32.30}, {x: 635587200000, y: 33.84}, {x: 635673600000, y: 32.34}, {x: 635760000000, y: 31.96}, {x: 636019200000, y: 31.95}, {x: 636105600000, y: 32.84}, {x: 636192000000, y: 30.80}, {x: 636278400000, y: 31.54}, {x: 636364800000, y: 30.81}, {x: 636624000000, y: 32.99}, {x: 636710400000, y: 32.25}, {x: 636796800000, y: 33.87}, {x: 636883200000, y: 35.75}, {x: 636969600000, y: 35.71}, {x: 637228800000, y: 36.60}, {x: 637315200000, y: 35.65}, {x: 637401600000, y: 34.36}, {x: 637488000000, y: 33.61}, {x: 637574400000, y: 34.24}, {x: 637833600000, y: 32.79}, {x: 637920000000, y: 34.41}, {x: 638006400000, y: 34.11}, {x: 638092800000, y: 33.91}, {x: 638179200000, y: 33.33}, {x: 638438400000, y: 32.99}, {x: 638524800000, y: 34.17}, {x: 638611200000, y: 33.50}, {x: 638697600000, y: 35.64}, {x: 638784000000, y: 35.50}, {x: 639039600000, y: 33.11}, {x: 639126000000, y: 34.08}, {x: 639212400000, y: 35.69}, {x: 639298800000, y: 38.24}, {x: 639385200000, y: 40.86}, {x: 639644400000, y: 41.99}, {x: 639730800000, y: 44.45}, {x: 639817200000, y: 45.06}, {x: 639903600000, y: 44.32}, {x: 639990000000, y: 43.70}, {x: 640249200000, y: 44.97}, {x: 640335600000, y: 44.92}, {x: 640422000000, y: 44.11}, {x: 640508400000, y: 44.42}, {x: 640594800000, y: 43.90}, {x: 640854000000, y: 41.91}, {x: 640940400000, y: 41.60}, {x: 641026800000, y: 41.84}, {x: 641113200000, y: 42.55}, {x: 641199600000, y: 40.56}, {x: 641458800000, y: 39.99}, {x: 641545200000, y: 43.51}, {x: 641631600000, y: 43.17}, {x: 641718000000, y: 40.52}, {x: 641804400000, y: 41.06}, {x: 642063600000, y: 40.15}, {x: 642150000000, y: 43.82}, {x: 642236400000, y: 43.19}, {x: 642322800000, y: 40.99}, {x: 642409200000, y: 41.16}, {x: 642668400000, y: 41.02}, {x: 642754800000, y: 40.03}, {x: 642841200000, y: 36.46}, {x: 642927600000, y: 39.11}, {x: 643014000000, y: 41.10}, {x: 643273200000, y: 41.15}, {x: 643359600000, y: 39.01}, {x: 643446000000, y: 39.48}, {x: 643532400000, y: 41.89}, {x: 643618800000, y: 40.74}, {x: 643878000000, y: 38.88}, {x: 643964400000, y: 38.11}, {x: 644050800000, y: 40.39}, {x: 644137200000, y: 38.28}, {x: 644223600000, y: 39.96}, {x: 644482800000, y: 39.37}, {x: 644569200000, y: 39.39}, {x: 644655600000, y: 39.62}, {x: 644742000000, y: 38.99}, {x: 644828400000, y: 40.25}, {x: 645087600000, y: 42.85}, {x: 645174000000, y: 45.91}, {x: 645260400000, y: 46.66}, {x: 645346800000, y: 48.08}, {x: 645433200000, y: 51.00}, {x: 645692400000, y: 50.61}, {x: 645778800000, y: 54.55}, {x: 645865200000, y: 53.59}, {x: 645951600000, y: 53.39}, {x: 646038000000, y: 54.61}, {x: 646297200000, y: 55.02}, {x: 646383600000, y: 57.35}, {x: 646470000000, y: 56.95}, {x: 646556400000, y: 60.08}, {x: 646642800000, y: 59.80}, {x: 646902000000, y: 61.29}, {x: 646988400000, y: 63.45}, {x: 647074800000, y: 62.07}, {x: 647161200000, y: 59.01}, {x: 647247600000, y: 59.76}, {x: 647506800000, y: 60.08}, {x: 647593200000, y: 60.96}, {x: 647679600000, y: 60.56}, {x: 647766000000, y: 58.60}, {x: 647852400000, y: 57.40}, {x: 648111600000, y: 59.86}, {x: 648198000000, y: 58.76}, {x: 648284400000, y: 57.54}, {x: 648370800000, y: 57.78}, {x: 648457200000, y: 54.33}, {x: 648716400000, y: 54.57}, {x: 648802800000, y: 53.69}, {x: 648889200000, y: 57.02}, {x: 648975600000, y: 52.30}, {x: 649062000000, y: 49.79}, {x: 649321200000, y: 47.40}, {x: 649407600000, y: 45.44}, {x: 649494000000, y: 46.75}, {x: 649580400000, y: 44.19}, {x: 649666800000, y: 43.05}, {x: 649926000000, y: 43.99}, {x: 650012400000, y: 45.99}, {x: 650098800000, y: 42.15}, {x: 650185200000, y: 41.84}, {x: 650271600000, y: 43.30}, {x: 650530800000, y: 41.57}, {x: 650617200000, y: 42.13}, {x: 650703600000, y: 43.29}, {x: 650790000000, y: 43.98}, {x: 650876400000, y: 44.51}, {x: 651135600000, y: 45.50}, {x: 651222000000, y: 43.63}, {x: 651308400000, y: 41.93}, {x: 651394800000, y: 38.41}, {x: 651481200000, y: 41.01}, {x: 651740400000, y: 38.17}, {x: 651826800000, y: 38.32}, {x: 651913200000, y: 38.27}, {x: 651999600000, y: 36.10}, {x: 652086000000, y: 34.62}, {x: 652345200000, y: 33.91}, {x: 652431600000, y: 34.25}, {x: 652518000000, y: 33.97}, {x: 652604400000, y: 35.11}, {x: 652690800000, y: 35.05}, {x: 652950000000, y: 36.37}, {x: 653036400000, y: 35.54}, {x: 653122800000, y: 35.80}, {x: 653209200000, y: 36.75}, {x: 653295600000, y: 35.48}, {x: 653554800000, y: 36.78}, {x: 653641200000, y: 34.35}, {x: 653727600000, y: 32.62}, {x: 653814000000, y: 32.66}, {x: 653900400000, y: 31.45}, {x: 654159600000, y: 29.29}, {x: 654246000000, y: 31.18}, {x: 654332400000, y: 29.47}, {x: 654418800000, y: 28.40}, {x: 654505200000, y: 28.21}, {x: 654764400000, y: 27.73}, {x: 654850800000, y: 27.08}, {x: 654937200000, y: 25.32}, {x: 655023600000, y: 25.69}, {x: 655110000000, y: 27.28}, {x: 655369200000, y: 28.53}, {x: 655455600000, y: 27.88}, {x: 655542000000, y: 28.17}, {x: 655628400000, y: 26.22}, {x: 655714800000, y: 26.07}, {x: 655974000000, y: 28.42}, {x: 656060400000, y: 28.27}, {x: 656146800000, y: 29.76}, {x: 656233200000, y: 29.58}, {x: 656319600000, y: 29.41}, {x: 656578800000, y: 29.34}, {x: 656665200000, y: 29.45}, {x: 656751600000, y: 27.93}, {x: 656838000000, y: 27.68}, {x: 656924400000, y: 27.42}, {x: 657187200000, y: 25.79}, {x: 657273600000, y: 25.84}, {x: 657360000000, y: 26.00}, {x: 657446400000, y: 26.57}, {x: 657532800000, y: 26.66}, {x: 657792000000, y: 26.40}, {x: 657878400000, y: 28.06}, {x: 657964800000, y: 27.58}, {x: 658051200000, y: 27.18}, {x: 658137600000, y: 27.71}, {x: 658396800000, y: 26.37}, {x: 658483200000, y: 26.53}, {x: 658569600000, y: 26.19}, {x: 658656000000, y: 25.29}, {x: 658742400000, y: 27.33}, {x: 659001600000, y: 26.08}, {x: 659088000000, y: 26.26}, {x: 659174400000, y: 26.35}, {x: 659260800000, y: 24.88}, {x: 659347200000, y: 23.71}, {x: 659606400000, y: 25.77}, {x: 659692800000, y: 26.03}, {x: 659779200000, y: 27.38}, {x: 659865600000, y: 27.82}, {x: 659952000000, y: 27.61}, {x: 660211200000, y: 26.15}, {x: 660297600000, y: 26.79}, {x: 660384000000, y: 26.78}, {x: 660470400000, y: 28.69}, {x: 660556800000, y: 29.38}, {x: 660816000000, y: 30.16}, {x: 660902400000, y: 29.42}, {x: 660988800000, y: 29.06}, {x: 661075200000, y: 28.05}, {x: 661161600000, y: 29.48}, {x: 661420800000, y: 28.48}, {x: 661507200000, y: 28.67}, {x: 661593600000, y: 28.27}, {x: 661680000000, y: 27.29}, {x: 661766400000, y: 26.88}, {x: 662025600000, y: 27.12}, {x: 662112000000, y: 27.02}, {x: 662198400000, y: 27.08}, {x: 662284800000, y: 24.53}, {x: 662371200000, y: 25.19}, {x: 662630400000, y: 26.70}, {x: 662716800000, y: 27.23}, {x: 662803200000, y: 26.26}, {x: 662889600000, y: 26.46}, {x: 662976000000, y: 25.38}, {x: 663235200000, y: 25.23}, {x: 663321600000, y: 25.53}, {x: 663408000000, y: 25.71}, {x: 663494400000, y: 25.39}, {x: 663580800000, y: 24.35}, {x: 663840000000, y: 23.64}, {x: 663926400000, y: 22.98}, {x: 664012800000, y: 22.75}, {x: 664099200000, y: 22.70}, {x: 664185600000, y: 21.56}, {x: 664444800000, y: 22.65}, {x: 664531200000, y: 21.54}, {x: 664617600000, y: 20.68}, {x: 664704000000, y: 21.37}, {x: 664790400000, y: 22.44}, {x: 665049600000, y: 23.89}, {x: 665136000000, y: 25.02}, {x: 665222400000, y: 26.84}, {x: 665308800000, y: 26.11}, {x: 665395200000, y: 25.91}, {x: 665654400000, y: 27.21}, {x: 665740800000, y: 26.37}, {x: 665827200000, y: 26.81}, {x: 665913600000, y: 26.42}, {x: 666000000000, y: 26.73}, {x: 666259200000, y: 27.25}, {x: 666345600000, y: 25.01}, {x: 666432000000, y: 24.55}, {x: 666518400000, y: 25.34}, {x: 666604800000, y: 25.37}, {x: 666864000000, y: 27.51}, {x: 666950400000, y: 27.51}, {x: 667036800000, y: 28.65}, {x: 667123200000, y: 28.90}, {x: 667209600000, y: 29.22}, {x: 667468800000, y: 29.77}, {x: 667555200000, y: 29.21}, {x: 667641600000, y: 29.81}, {x: 667728000000, y: 27.75}, {x: 667814400000, y: 28.56}, {x: 668073600000, y: 28.06}, {x: 668160000000, y: 26.70}, {x: 668246400000, y: 26.39}, {x: 668332800000, y: 26.42}, {x: 668419200000, y: 29.05}, {x: 668678400000, y: 27.84}, {x: 668764800000, y: 27.67}, {x: 668851200000, y: 26.75}, {x: 668937600000, y: 26.20}, {x: 669024000000, y: 27.33}, {x: 669283200000, y: 27.55}, {x: 669369600000, y: 26.79}, {x: 669456000000, y: 25.29}, {x: 669542400000, y: 25.17}, {x: 669628800000, y: 25.55}, {x: 669888000000, y: 23.87}, {x: 669974400000, y: 22.92}, {x: 670060800000, y: 23.80}, {x: 670147200000, y: 24.18}, {x: 670233600000, y: 22.56}, {x: 670492800000, y: 21.93}, {x: 670579200000, y: 20.96}, {x: 670665600000, y: 21.94}, {x: 670752000000, y: 21.48}, {x: 670838400000, y: 22.17}, {x: 671094000000, y: 22.68}, {x: 671180400000, y: 20.56}, {x: 671266800000, y: 18.98}, {x: 671353200000, y: 19.93}, {x: 671439600000, y: 19.53}, {x: 671698800000, y: 18.93}, {x: 671785200000, y: 19.41}, {x: 671871600000, y: 18.61}, {x: 671958000000, y: 18.88}, {x: 672044400000, y: 18.70}, {x: 672303600000, y: 18.80}, {x: 672390000000, y: 17.72}, {x: 672476400000, y: 17.65}, {x: 672562800000, y: 17.99}, {x: 672649200000, y: 17.01}, {x: 672908400000, y: 17.05}, {x: 672994800000, y: 16.39}, {x: 673081200000, y: 15.96}, {x: 673167600000, y: 15.82}, {x: 673254000000, y: 16.26}, {x: 673513200000, y: 16.33}, {x: 673599600000, y: 15.73}, {x: 673686000000, y: 15.02}, {x: 673772400000, y: 14.51}, {x: 673858800000, y: 14.71}, {x: 674118000000, y: 15.29}, {x: 674204400000, y: 15.46}, {x: 674290800000, y: 15.30}, {x: 674377200000, y: 14.14}, {x: 674463600000, y: 13.94}, {x: 674722800000, y: 13.01}, {x: 674809200000, y: 13.59}, {x: 674895600000, y: 13.67}, {x: 674982000000, y: 13.28}, {x: 675068400000, y: 13.11}, {x: 675327600000, y: 13.52}, {x: 675414000000, y: 14.02}, {x: 675500400000, y: 14.53}, {x: 675586800000, y: 14.61}, {x: 675673200000, y: 14.53}, {x: 675932400000, y: 14.29}, {x: 676018800000, y: 14.46}, {x: 676105200000, y: 14.07}, {x: 676191600000, y: 13.91}, {x: 676278000000, y: 14.08}, {x: 676537200000, y: 13.63}, {x: 676623600000, y: 14.38}, {x: 676710000000, y: 14.86}, {x: 676796400000, y: 14.82}, {x: 676882800000, y: 14.04}, {x: 677142000000, y: 14.63}, {x: 677228400000, y: 14.83}, {x: 677314800000, y: 15.33}, {x: 677401200000, y: 14.67}, {x: 677487600000, y: 14.18}, {x: 677746800000, y: 14.40}, {x: 677833200000, y: 14.45}, {x: 677919600000, y: 14.78}, {x: 678006000000, y: 14.93}, {x: 678092400000, y: 14.09}, {x: 678351600000, y: 13.56}, {x: 678438000000, y: 14.26}, {x: 678524400000, y: 14.36}, {x: 678610800000, y: 14.82}, {x: 678697200000, y: 15.96}, {x: 678956400000, y: 15.83}, {x: 679042800000, y: 15.92}, {x: 679129200000, y: 15.29}, {x: 679215600000, y: 16.29}, {x: 679302000000, y: 15.31}, {x: 679561200000, y: 15.13}, {x: 679647600000, y: 15.59}, {x: 679734000000, y: 14.97}, {x: 679820400000, y: 15.81}, {x: 679906800000, y: 15.59}, {x: 680166000000, y: 14.83}, {x: 680252400000, y: 14.57}, {x: 680338800000, y: 14.24}, {x: 680425200000, y: 14.49}, {x: 680511600000, y: 13.80}, {x: 680770800000, y: 14.17}, {x: 680857200000, y: 14.40}, {x: 680943600000, y: 14.31}, {x: 681030000000, y: 13.89}, {x: 681116400000, y: 13.59}, {x: 681375600000, y: 13.36}, {x: 681462000000, y: 13.33}, {x: 681548400000, y: 13.26}, {x: 681634800000, y: 13.71}, {x: 681721200000, y: 13.67}, {x: 681980400000, y: 12.87}, {x: 682066800000, y: 14.03}, {x: 682153200000, y: 13.95}, {x: 682239600000, y: 13.11}, {x: 682326000000, y: 14.05}, {x: 682585200000, y: 14.47}, {x: 682671600000, y: 14.45}, {x: 682758000000, y: 15.14}, {x: 682844400000, y: 15.65}, {x: 682930800000, y: 15.15}, {x: 683190000000, y: 15.22}, {x: 683276400000, y: 15.38}, {x: 683362800000, y: 16.42}, {x: 683449200000, y: 16.26}, {x: 683535600000, y: 16.51}, {x: 683794800000, y: 15.66}, {x: 683881200000, y: 15.88}, {x: 683967600000, y: 16.36}, {x: 684054000000, y: 15.87}, {x: 684140400000, y: 15.61}, {x: 684399600000, y: 16.63}, {x: 684486000000, y: 15.88}, {x: 684572400000, y: 17.21}, {x: 684658800000, y: 18.46}, {x: 684745200000, y: 18.76}, {x: 685004400000, y: 18.39}, {x: 685090800000, y: 18.14}, {x: 685177200000, y: 17.31}, {x: 685263600000, y: 17.21}, {x: 685350000000, y: 17.17}, {x: 685609200000, y: 17.21}, {x: 685695600000, y: 16.86}, {x: 685782000000, y: 17.17}, {x: 685868400000, y: 16.20}, {x: 685954800000, y: 15.14}, {x: 686214000000, y: 15.05}, {x: 686300400000, y: 16.09}, {x: 686386800000, y: 16.40}, {x: 686473200000, y: 15.83}, {x: 686559600000, y: 16.53}, {x: 686818800000, y: 16.32}, {x: 686905200000, y: 16.47}, {x: 686991600000, y: 16.59}, {x: 687078000000, y: 16.51}, {x: 687164400000, y: 17.41}, {x: 687423600000, y: 18.17}, {x: 687510000000, y: 17.63}, {x: 687596400000, y: 17.62}, {x: 687682800000, y: 17.69}, {x: 687769200000, y: 17.54}, {x: 688028400000, y: 16.56}, {x: 688114800000, y: 16.83}, {x: 688201200000, y: 15.98}, {x: 688287600000, y: 16.52}, {x: 688374000000, y: 17.08}, {x: 688636800000, y: 17.27}, {x: 688723200000, y: 18.18}, {x: 688809600000, y: 18.67}, {x: 688896000000, y: 18.97}, {x: 688982400000, y: 20.31}, {x: 689241600000, y: 21.30}, {x: 689328000000, y: 20.96}, {x: 689414400000, y: 20.01}, {x: 689500800000, y: 21.13}, {x: 689587200000, y: 21.52}, {x: 689846400000, y: 22.08}, {x: 689932800000, y: 21.88}, {x: 690019200000, y: 21.18}, {x: 690105600000, y: 22.79}, {x: 690192000000, y: 22.51}, {x: 690451200000, y: 23.66}, {x: 690537600000, y: 23.43}, {x: 690624000000, y: 24.08}, {x: 690710400000, y: 24.83}, {x: 690796800000, y: 23.49}, {x: 691056000000, y: 23.43}, {x: 691142400000, y: 23.98}, {x: 691228800000, y: 24.52}, {x: 691315200000, y: 23.32}, {x: 691401600000, y: 23.63}, {x: 691660800000, y: 21.74}, {x: 691747200000, y: 20.03}, {x: 691833600000, y: 20.37}, {x: 691920000000, y: 21.09}, {x: 692006400000, y: 21.33}, {x: 692265600000, y: 20.48}, {x: 692352000000, y: 20.15}, {x: 692438400000, y: 20.33}, {x: 692524800000, y: 19.53}, {x: 692611200000, y: 19.34}, {x: 692870400000, y: 18.63}, {x: 692956800000, y: 18.42}, {x: 693043200000, y: 19.49}, {x: 693129600000, y: 18.75}, {x: 693216000000, y: 18.11}, {x: 693475200000, y: 17.40}, {x: 693561600000, y: 17.40}, {x: 693648000000, y: 17.73}, {x: 693734400000, y: 18.36}, {x: 693820800000, y: 18.14}, {x: 694080000000, y: 18.71}, {x: 694166400000, y: 17.97}, {x: 694252800000, y: 18.90}, {x: 694339200000, y: 18.31}, {x: 694425600000, y: 18.67}, {x: 694684800000, y: 18.78}, {x: 694771200000, y: 19.53}, {x: 694857600000, y: 19.41}, {x: 694944000000, y: 19.42}, {x: 695030400000, y: 20.29}, {x: 695289600000, y: 21.08}, {x: 695376000000, y: 20.69}, {x: 695462400000, y: 21.37}, {x: 695548800000, y: 20.67}, {x: 695635200000, y: 20.79}, {x: 695894400000, y: 20.39}, {x: 695980800000, y: 19.98}, {x: 696067200000, y: 19.35}, {x: 696153600000, y: 18.60}, {x: 696240000000, y: 18.67}, {x: 696499200000, y: 19.41}, {x: 696585600000, y: 20.62}, {x: 696672000000, y: 21.09}, {x: 696758400000, y: 21.43}, {x: 696844800000, y: 20.31}, {x: 697104000000, y: 19.40}, {x: 697190400000, y: 19.82}, {x: 697276800000, y: 19.55}, {x: 697363200000, y: 19.77}, {x: 697449600000, y: 19.33}, {x: 697708800000, y: 18.75}, {x: 697795200000, y: 18.50}, {x: 697881600000, y: 18.39}, {x: 697968000000, y: 19.19}, {x: 698054400000, y: 19.93}, {x: 698313600000, y: 20.15}, {x: 698400000000, y: 22.09}, {x: 698486400000, y: 20.29}, {x: 698572800000, y: 20.37}, {x: 698659200000, y: 19.06}, {x: 698918400000, y: 20.51}, {x: 699004800000, y: 20.06}, {x: 699091200000, y: 19.54}, {x: 699177600000, y: 17.89}, {x: 699264000000, y: 17.57}, {x: 699523200000, y: 16.88}, {x: 699609600000, y: 17.26}, {x: 699696000000, y: 17.15}, {x: 699782400000, y: 15.73}, {x: 699868800000, y: 15.08}, {x: 700128000000, y: 14.73}, {x: 700214400000, y: 14.58}, {x: 700300800000, y: 14.33}, {x: 700387200000, y: 14.76}, {x: 700473600000, y: 15.44}, {x: 700732800000, y: 16.63}, {x: 700819200000, y: 15.63}, {x: 700905600000, y: 15.61}, {x: 700992000000, y: 16.88}, {x: 701078400000, y: 16.26}, {x: 701337600000, y: 15.95}, {x: 701424000000, y: 15.41}, {x: 701510400000, y: 16.14}, {x: 701596800000, y: 15.77}, {x: 701683200000, y: 15.84}, {x: 701942400000, y: 14.41}, {x: 702028800000, y: 15.62}, {x: 702115200000, y: 15.62}, {x: 702201600000, y: 15.85}, {x: 702288000000, y: 17.18}, {x: 702543600000, y: 17.58}, {x: 702630000000, y: 19.25}, {x: 702716400000, y: 19.77}, {x: 702802800000, y: 20.66}, {x: 702889200000, y: 19.70}, {x: 703148400000, y: 20.01}, {x: 703234800000, y: 19.93}, {x: 703321200000, y: 19.94}, {x: 703407600000, y: 19.77}, {x: 703494000000, y: 19.83}];\n\nmodule.exports = {\n  threshold: 0.01,\n  tolerance: 0.0015,\n  config: {\n    data: {\n      datasets: [{\n        data,\n        type: 'line',\n        pointRadius: 0,\n        fill: false,\n        tension: 0,\n        borderWidth: 2\n      }]\n    },\n    options: {\n      animation: {\n        duration: 0\n      },\n      scales: {\n        x: {\n          type: 'timeseries',\n          offset: true,\n          ticks: {\n            major: {\n              enabled: true,\n            },\n            font: function(context) {\n              return context.tick && context.tick.major ? {weight: 'bold'} : undefined;\n            },\n            source: 'data',\n            autoSkip: true,\n            autoSkipPadding: 75,\n            maxRotation: 0,\n            sampleSize: 100,\n            maxTicksLimit: 3\n          },\n          // manually set major ticks so that test passes in all time zones with moment adapter\n          afterBuildTicks: function(scale) {\n            const ticks = scale.ticks;\n            const major = [0, 264, 522];\n            for (let i = 0; i < ticks.length; i++) {\n              ticks[i].major = major.indexOf(i) >= 0;\n            }\n          }\n        },\n        y: {\n          type: 'linear',\n          border: {\n            display: false\n          }\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.timeseries/normalize.js",
    "content": "module.exports = {\n  threshold: 0.01,\n  tolerance: 0.002,\n  config: {\n    type: 'line',\n    data: {\n      datasets: [{\n        data: [\n          {x: '2017', y: null},\n          {x: '2018', y: 1},\n          {x: '2019', y: 2},\n          {x: '2020', y: 3},\n          {x: '2021', y: 4}\n        ],\n        fill: false\n      }]\n    },\n    options: {\n      normalized: true,\n      scales: {\n        x: {\n          type: 'timeseries',\n          time: {\n            parser: 'YYYY'\n          },\n          ticks: {\n            source: 'data'\n          }\n        },\n        y: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.timeseries/source-auto.js",
    "content": "module.exports = {\n  threshold: 0.01,\n  tolerance: 0.0025,\n  config: {\n    type: 'line',\n    data: {\n      labels: ['2017', '2018', '2019', '2020', '2025'],\n      datasets: [{data: [0, 1, 2, 3, 4], fill: false}]\n    },\n    options: {\n      scales: {\n        x: {\n          type: 'timeseries',\n          time: {\n            parser: 'YYYY',\n            unit: 'year'\n          },\n          ticks: {\n            source: 'auto'\n          }\n        },\n        y: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.timeseries/source-data-offset-min-max.js",
    "content": "module.exports = {\n  threshold: 0.01,\n  tolerance: 0.002,\n  config: {\n    type: 'line',\n    data: {\n      labels: ['2017', '2019', '2020', '2025', '2042'],\n      datasets: [{data: [0, 1, 2, 3, 4], fill: false}]\n    },\n    options: {\n      scales: {\n        x: {\n          type: 'timeseries',\n          min: '2012',\n          max: '2051',\n          offset: true,\n          time: {\n            parser: 'YYYY',\n          },\n          ticks: {\n            source: 'data'\n          }\n        },\n        y: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.timeseries/source-data.js",
    "content": "module.exports = {\n  threshold: 0.01,\n  tolerance: 0.0015,\n  config: {\n    type: 'line',\n    data: {\n      labels: ['2017', '2018', '2019', '2020', '2025'],\n      datasets: [{data: [0, 1, 2, 3, 4], fill: false}]\n    },\n    options: {\n      scales: {\n        x: {\n          type: 'timeseries',\n          time: {\n            parser: 'YYYY',\n            unit: 'year'\n          },\n          ticks: {\n            source: 'data'\n          }\n        },\n        y: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.timeseries/source-labels-offset-min-max.js",
    "content": "module.exports = {\n  threshold: 0.01,\n  tolerance: 0.0015,\n  config: {\n    type: 'line',\n    data: {\n      labels: ['2017', '2019', '2020', '2025', '2042'],\n      datasets: [{data: [0, 1, 2, 3, 4], fill: false}]\n    },\n    options: {\n      scales: {\n        x: {\n          type: 'timeseries',\n          min: '2012',\n          max: '2051',\n          offset: true,\n          time: {\n            parser: 'YYYY',\n          },\n          ticks: {\n            source: 'labels'\n          }\n        },\n        y: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.timeseries/source-labels.js",
    "content": "module.exports = {\n  threshold: 0.01,\n  tolerance: 0.0015,\n  config: {\n    type: 'line',\n    data: {\n      labels: ['2017', '2018', '2019', '2020', '2025'],\n      datasets: [{data: [0, 1, 2, 3, 4], fill: false}]\n    },\n    options: {\n      scales: {\n        x: {\n          type: 'timeseries',\n          time: {\n            parser: 'YYYY',\n            unit: 'year'\n          },\n          ticks: {\n            source: 'labels'\n          }\n        },\n        y: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.timeseries/ticks-reverse-max.js",
    "content": "module.exports = {\n  threshold: 0.01,\n  config: {\n    type: 'line',\n    data: {\n      labels: ['2017', '2019', '2020', '2025', '2042'],\n      datasets: [{data: [0, 1, 2, 3, 4], fill: false}]\n    },\n    options: {\n      scales: {\n        x: {\n          type: 'timeseries',\n          max: '2050',\n          time: {\n            parser: 'YYYY'\n          },\n          reverse: true,\n          ticks: {\n            source: 'labels'\n          }\n        },\n        y: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.timeseries/ticks-reverse-min-max.js",
    "content": "module.exports = {\n  threshold: 0.01,\n  tolerance: 0.002,\n  config: {\n    type: 'line',\n    data: {\n      labels: ['2017', '2019', '2020', '2025', '2042'],\n      datasets: [{data: [0, 1, 2, 3, 4], fill: false}]\n    },\n    options: {\n      scales: {\n        x: {\n          type: 'timeseries',\n          min: '2012',\n          max: '2050',\n          time: {\n            parser: 'YYYY'\n          },\n          reverse: true,\n          ticks: {\n            source: 'labels'\n          }\n        },\n        y: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.timeseries/ticks-reverse-min.js",
    "content": "module.exports = {\n  threshold: 0.01,\n  config: {\n    type: 'line',\n    data: {\n      labels: ['2017', '2019', '2020', '2025', '2042'],\n      datasets: [{data: [0, 1, 2, 3, 4], fill: false}]\n    },\n    options: {\n      scales: {\n        x: {\n          type: 'timeseries',\n          min: '2012',\n          time: {\n            parser: 'YYYY'\n          },\n          reverse: true,\n          ticks: {\n            source: 'labels'\n          }\n        },\n        y: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/fixtures/scale.timeseries/ticks-reverse.js",
    "content": "module.exports = {\n  threshold: 0.01,\n  tolerance: 0.0015,\n  config: {\n    type: 'line',\n    data: {\n      labels: ['2017', '2019', '2020', '2025', '2042'],\n      datasets: [{data: [0, 1, 2, 3, 4], fill: false}]\n    },\n    options: {\n      scales: {\n        x: {\n          type: 'timeseries',\n          time: {\n            parser: 'YYYY'\n          },\n          reverse: true,\n          ticks: {\n            source: 'labels'\n          }\n        },\n        y: {\n          display: false\n        }\n      }\n    }\n  },\n  options: {\n    spriteText: true\n  }\n};\n"
  },
  {
    "path": "test/index.js",
    "content": "import {acquireChart, releaseChart, createMockContext, afterEvent, waitForResize, injectWrapperCSS, specsFromFixtures, triggerMouseEvent, addMatchers, releaseCharts} from 'chartjs-test-utils';\n\n// force ratio=1 for tests on high-res/retina devices\n// fixes https://github.com/chartjs/Chart.js/issues/4515\nwindow.devicePixelRatio = 1;\n\nwindow.acquireChart = acquireChart;\nwindow.afterEvent = afterEvent;\nwindow.releaseChart = releaseChart;\nwindow.waitForResize = waitForResize;\nwindow.createMockContext = createMockContext;\n\ninjectWrapperCSS();\n\njasmine.fixture = {\n  specs: specsFromFixtures\n};\n\njasmine.triggerMouseEvent = triggerMouseEvent;\n\n// Set a fixed time zone (and, in particular, disable Daylight Saving Time) for\n// more stable test results.\nwindow.moment.tz.setDefault('Etc/UTC');\n\nbeforeAll(() => {\n  // Disable colors plugin for tests.\n  window.Chart.defaults.plugins.colors.enabled = false;\n});\n\nbeforeEach(() => {\n  addMatchers();\n});\n\nafterEach(() => {\n  releaseCharts();\n});\n"
  },
  {
    "path": "test/integration/node/package.json",
    "content": "{\n  \"private\": true,\n  \"description\": \"chart.js should work in Node\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"test\": \"npm run test-mjs && npm run test-cjs\",\n    \"test-mjs\": \"node test.js\",\n    \"test-cjs\": \"node test.cjs\"\n  },\n  \"dependencies\": {\n    \"chart.js\": \"workspace:*\"\n  }\n}\n"
  },
  {
    "path": "test/integration/node/test.cjs",
    "content": "const {Chart} = require('chart.js');\nconst {valueOrDefault} = require('chart.js/helpers');\n\nChart.register({\n  id: 'TEST_PLUGIN',\n  dummyValue: valueOrDefault(0, 1)\n});\n"
  },
  {
    "path": "test/integration/node/test.js",
    "content": "import {Chart} from 'chart.js';\nimport {valueOrDefault} from 'chart.js/helpers';\n\nChart.register({\n  id: 'TEST_PLUGIN',\n  dummyValue: valueOrDefault(0, 1)\n});\n"
  },
  {
    "path": "test/integration/node-commonjs/package.json",
    "content": "{\n  \"private\": true,\n  \"description\": \"chart.js should work in Node\",\n  \"scripts\": {\n    \"test\": \"npm run test-index && npm run test-auto\",\n    \"test-index\": \"node test.js\",\n    \"test-auto\": \"node test-auto.js\"\n  },\n  \"dependencies\": {\n    \"chart.js\": \"workspace:*\"\n  }\n}\n"
  },
  {
    "path": "test/integration/node-commonjs/test-auto.js",
    "content": "const Chart = require('chart.js/auto');\nconst {valueOrDefault} = require('chart.js/helpers');\n\nChart.register({\n  id: 'TEST_PLUGIN',\n  dummyValue: valueOrDefault(0, 1)\n});\n"
  },
  {
    "path": "test/integration/node-commonjs/test.js",
    "content": "const {Chart} = require('chart.js');\nconst {valueOrDefault} = require('chart.js/helpers');\n\nChart.register({\n  id: 'TEST_PLUGIN',\n  dummyValue: valueOrDefault(0, 1)\n});\n"
  },
  {
    "path": "test/integration/react-browser/package.json",
    "content": "{\n  \"private\": true,\n  \"description\": \"chart.js should work in react-browser (Web)\",\n  \"dependencies\": {\n    \"@babel/core\": \"^7.0.0\",\n    \"@babel/plugin-syntax-flow\": \"^7.14.5\",\n    \"@babel/plugin-transform-react-jsx\": \"^7.14.9\",\n    \"@types/node\": \"^18.7.6\",\n    \"@types/react\": \"^18.0.17\",\n    \"@types/react-dom\": \"^18.0.6\",\n    \"chart.js\": \"workspace:*\",\n    \"react\": \"^18.2.0\",\n    \"react-dom\": \"^18.2.0\",\n    \"react-scripts\": \"5.0.1\",\n    \"typescript\": \"^4.7.4\",\n    \"web-vitals\": \"^2.1.4\"\n  },\n  \"scripts\": {\n    \"test\": \"react-scripts build\"\n  },\n  \"browserslist\": {\n    \"production\": [\n      \">0.2%\",\n      \"not dead\",\n      \"not op_mini all\"\n    ],\n    \"development\": [\n      \"last 1 chrome version\",\n      \"last 1 firefox version\",\n      \"last 1 safari version\"\n    ]\n  }\n}\n"
  },
  {
    "path": "test/integration/react-browser/public/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <meta name=\"theme-color\" content=\"#000000\" />\n    <meta\n      name=\"description\"\n      content=\"Web site created using create-react-app\"\n    />\n    <!--\n      Notice the use of %PUBLIC_URL% in the tags above.\n      It will be replaced with the URL of the `public` folder during the build.\n      Only files inside the `public` folder can be referenced from the HTML.\n\n      Unlike \"/favicon.ico\" or \"favicon.ico\", \"%PUBLIC_URL%/favicon.ico\" will\n      work correctly both with client-side routing and a non-root public URL.\n      Learn how to configure a non-root public URL by running `npm run build`.\n    -->\n    <title>Chartjs test React App</title>\n  </head>\n  <body>\n    <noscript>You need to enable JavaScript to run this app.</noscript>\n    <div id=\"root\"></div>\n    <!--\n      This HTML file is a template.\n      If you open it directly in the browser, you will see an empty page.\n\n      You can add webfonts, meta tags, or analytics to this file.\n      The build step will place the bundled scripts into the <body> tag.\n\n      To begin the development, run `npm start` or `yarn start`.\n      To create a production bundle, use `npm run build` or `yarn build`.\n    -->\n  </body>\n</html>\n"
  },
  {
    "path": "test/integration/react-browser/src/App.tsx",
    "content": "import React, {useEffect} from 'react';\nimport {Chart, DoughnutController, ArcElement} from 'chart.js';\nimport {merge} from 'chart.js/helpers';\n\nChart.register(DoughnutController, ArcElement);\n\nfunction App() {\n  useEffect(() => {\n    const c = Chart.getChart('myChart');\n    if (c) {\n      c.destroy();\n    }\n\n    merge({a: 1}, {b: 2});\n\n    // eslint-disable-next-line no-new\n    new Chart('myChart', {\n      type: 'doughnut',\n      data: {\n        labels: ['Chart', 'JS'],\n        datasets: [{\n          data: [2, 3]\n        }]\n      }\n    });\n  }, []);\n\n  return (\n    <div className=\"App\">\n      <canvas id=\"myChart\"></canvas>\n    </div>\n  );\n}\n\nexport default App;\n"
  },
  {
    "path": "test/integration/react-browser/src/AppAuto.tsx",
    "content": "import React, {useEffect} from 'react';\nimport Chart from 'chart.js/auto';\nimport {merge} from 'chart.js/helpers';\n\nfunction AppAuto() {\n  useEffect(() => {\n    const c = Chart.getChart('myChart');\n    if (c) {\n      c.destroy();\n    }\n\n    merge({a: 1}, {b: 2});\n\n    // eslint-disable-next-line no-new\n    new Chart('myChart', {\n      type: 'doughnut',\n      data: {\n        labels: ['Chart', 'JS'],\n        datasets: [{\n          data: [2, 3]\n        }]\n      }\n    });\n  }, []);\n\n  return (\n    <div className=\"App\">\n      <canvas id=\"myChart\"></canvas>\n    </div>\n  );\n}\n\nexport default AppAuto;\n"
  },
  {
    "path": "test/integration/react-browser/src/index.tsx",
    "content": "import React from 'react';\nimport {render} from 'react-dom';\nimport App from './App';\nimport AppAuto from './AppAuto';\n\nrender(\n  <React.StrictMode>\n    <App />\n    <AppAuto />\n  </React.StrictMode>,\n  document.getElementById('root')\n);\n"
  },
  {
    "path": "test/integration/react-browser/tsconfig.json",
    "content": "{\n    \"compilerOptions\": {\n      \"jsx\": \"react\",\n      \"target\": \"ES6\",\n      \"moduleResolution\": \"Node\",\n      \"allowSyntheticDefaultImports\": true,\n      \"alwaysStrict\": true,\n      \"strict\": true,\n      \"noEmit\": true\n    },\n    \"include\": [\n      \"./**/*.tsx\",\n    ]\n  }\n"
  },
  {
    "path": "test/integration/typescript-node/package.json",
    "content": "{\n  \"private\": true,\n  \"type\": \"module\",\n  \"description\": \"chart.js should work in node typescript project\",\n  \"dependencies\": {\n    \"chart.js\": \"workspace:*\",\n    \"typescript\": \"^4.7.4\"\n  },\n  \"scripts\": {\n    \"test\": \"tsc\"\n  },\n  \"devDependencies\": {\n    \"ts-expect\": \"^1.3.0\"\n  }\n}\n"
  },
  {
    "path": "test/integration/typescript-node/src/index.ts",
    "content": "/* eslint-disable @typescript-eslint/no-explicit-any, no-console */\nimport {Chart} from 'chart.js';\nimport AutoChart from 'chart.js/auto';\nimport {debounce} from 'chart.js/helpers';\nimport {TypeOf, expectType} from 'ts-expect';\n\nexpectType<TypeOf<typeof Chart.register, any>>(false);\nexpectType<TypeOf<typeof AutoChart.register, any>>(false);\nexpectType<TypeOf<typeof debounce, any>>(false);\n"
  },
  {
    "path": "test/integration/typescript-node/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES6\",\n    \"moduleResolution\": \"Node\",\n    \"noEmit\": true,\n    \"lib\": [\"es2018\", \"DOM\"]\n  },\n  \"include\": [\n    \"./src/**/*.ts\",\n  ]\n}\n"
  },
  {
    "path": "test/integration/typescript-node-next/package.json",
    "content": "{\n  \"private\": true,\n  \"type\": \"module\",\n  \"description\": \"chart.js should work in node next typescript project\",\n  \"dependencies\": {\n    \"chart.js\": \"workspace:*\",\n    \"typescript\": \"^4.7.4\"\n  },\n  \"scripts\": {\n    \"test\": \"tsc\"\n  },\n  \"devDependencies\": {\n    \"ts-expect\": \"^1.3.0\"\n  }\n}\n"
  },
  {
    "path": "test/integration/typescript-node-next/src/index.ts",
    "content": "/* eslint-disable @typescript-eslint/no-explicit-any, no-console */\nimport {Chart} from 'chart.js';\nimport AutoChart from 'chart.js/auto';\nimport {debounce} from 'chart.js/helpers';\nimport {TypeOf, expectType} from 'ts-expect';\n\nexpectType<TypeOf<typeof Chart.register, any>>(false);\nexpectType<TypeOf<typeof AutoChart.register, any>>(false);\nexpectType<TypeOf<typeof debounce, any>>(false);\n"
  },
  {
    "path": "test/integration/typescript-node-next/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES6\",\n    \"moduleResolution\": \"NodeNext\",\n    \"noEmit\": true,\n    \"lib\": [\"es2018\", \"DOM\"]\n  },\n  \"include\": [\n    \"./src/**/*.ts\",\n  ]\n}\n"
  },
  {
    "path": "test/seed-reporter.cjs",
    "content": "const SeedReporter = function(baseReporterDecorator) {\n  baseReporterDecorator(this);\n\n  this.onBrowserComplete = function(browser, result) {\n    if (result.order && result.order.random && result.order.seed) {\n      this.write('%s: Randomized with seed %s\\n', browser, result.order.seed);\n    }\n  };\n};\n\nmodule.exports = {\n  'reporter:jasmine-seed': ['type', SeedReporter]\n};\n"
  },
  {
    "path": "test/specs/controller.bar.tests.js",
    "content": "describe('Chart.controllers.bar', function() {\n  describe('auto', jasmine.fixture.specs('controller.bar'));\n\n  it('should be registered as dataset controller', function() {\n    expect(typeof Chart.controllers.bar).toBe('function');\n  });\n\n  it('should be constructed', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [\n          {data: []},\n          {data: []}\n        ],\n        labels: []\n      }\n    });\n\n    var meta = chart.getDatasetMeta(1);\n    expect(meta.type).toEqual('bar');\n    expect(meta.data).toEqual([]);\n    expect(meta.hidden).toBe(null);\n    expect(meta.controller).not.toBe(undefined);\n    expect(meta.controller.index).toBe(1);\n    expect(meta.xAxisID).not.toBe(null);\n    expect(meta.yAxisID).not.toBe(null);\n\n    meta.controller.updateIndex(0);\n    expect(meta.controller.index).toBe(0);\n  });\n\n  it('should set null bars to the reset state', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          data: [10, null, 0, -4],\n          label: 'dataset1',\n        }],\n        labels: ['label1', 'label2', 'label3', 'label4']\n      }\n    });\n\n    var meta = chart.getDatasetMeta(0);\n    var bar = meta.data[1];\n    var {x, y, base} = bar.getProps(['x', 'y', 'base'], true);\n    expect(isNaN(x)).toBe(false);\n    expect(isNaN(y)).toBe(false);\n    expect(isNaN(base)).toBe(false);\n  });\n\n  it('should use the first scale IDs if the dataset does not specify them', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [\n          {data: []},\n          {data: []}\n        ],\n        labels: []\n      },\n    });\n\n    var meta = chart.getDatasetMeta(1);\n    expect(meta.xAxisID).toBe('x');\n    expect(meta.yAxisID).toBe('y');\n  });\n\n  it('should correctly count the number of stacks ignoring datasets of other types and hidden datasets', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [\n          {data: [], type: 'line'},\n          {data: [], hidden: true},\n          {data: []},\n          {data: []}\n        ],\n        labels: []\n      }\n    });\n\n    var meta = chart.getDatasetMeta(1);\n    expect(meta.controller._getStackCount()).toBe(2);\n  });\n\n  it('should correctly count the number of stacks when a group is not specified', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [\n          {data: []},\n          {data: []},\n          {data: []},\n          {data: []}\n        ],\n        labels: []\n      }\n    });\n\n    var meta = chart.getDatasetMeta(1);\n    expect(meta.controller._getStackCount()).toBe(4);\n  });\n\n  it('should correctly count the number of stacks when a group is not specified and the scale is stacked', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [\n          {data: []},\n          {data: []},\n          {data: []},\n          {data: []}\n        ],\n        labels: []\n      },\n      options: {\n        scales: {\n          x: {\n            stacked: true\n          },\n          y: {\n            stacked: true\n          }\n        }\n      }\n    });\n\n    var meta = chart.getDatasetMeta(1);\n    expect(meta.controller._getStackCount()).toBe(1);\n  });\n\n  it('should correctly count the number of stacks when a group is not specified and the scale is not stacked', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [\n          {data: []},\n          {data: []},\n          {data: []},\n          {data: []}\n        ],\n        labels: []\n      },\n      options: {\n        scales: {\n          x: {\n            stacked: false\n          },\n          y: {\n            stacked: false\n          }\n        }\n      }\n    });\n\n    var meta = chart.getDatasetMeta(1);\n    expect(meta.controller._getStackCount()).toBe(4);\n  });\n\n  it('should correctly count the number of stacks when a group is specified for some', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [\n          {data: [], stack: 'stack1'},\n          {data: [], stack: 'stack1'},\n          {data: []},\n          {data: []}\n        ],\n        labels: []\n      }\n    });\n\n    var meta = chart.getDatasetMeta(3);\n    expect(meta.controller._getStackCount()).toBe(3);\n  });\n\n  it('should correctly count the number of stacks when a group is specified for some and the scale is stacked', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [\n          {data: [], stack: 'stack1'},\n          {data: [], stack: 'stack1'},\n          {data: []},\n          {data: []}\n        ],\n        labels: []\n      },\n      options: {\n        scales: {\n          x: {\n            stacked: true\n          },\n          y: {\n            stacked: true\n          }\n        }\n      }\n    });\n\n    var meta = chart.getDatasetMeta(3);\n    expect(meta.controller._getStackCount()).toBe(2);\n  });\n\n  it('should correctly count the number of stacks when a group is specified for some and the scale is not stacked', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [\n          {data: [], stack: 'stack1'},\n          {data: [], stack: 'stack1'},\n          {data: []},\n          {data: []}\n        ],\n        labels: []\n      },\n      options: {\n        scales: {\n          x: {\n            stacked: false\n          },\n          y: {\n            stacked: false\n          }\n        }\n      }\n    });\n\n    var meta = chart.getDatasetMeta(3);\n    expect(meta.controller._getStackCount()).toBe(4);\n  });\n\n  it('should correctly count the number of stacks when a group is specified for all', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [\n          {data: [], stack: 'stack1'},\n          {data: [], stack: 'stack1'},\n          {data: [], stack: 'stack2'},\n          {data: [], stack: 'stack2'}\n        ],\n        labels: []\n      }\n    });\n\n    var meta = chart.getDatasetMeta(3);\n    expect(meta.controller._getStackCount()).toBe(2);\n  });\n\n  it('should correctly count the number of stacks when a group is specified for all and the scale is stacked', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [\n          {data: [], stack: 'stack1'},\n          {data: [], stack: 'stack1'},\n          {data: [], stack: 'stack2'},\n          {data: [], stack: 'stack2'}\n        ],\n        labels: []\n      },\n      options: {\n        scales: {\n          x: {\n            stacked: true\n          },\n          y: {\n            stacked: true\n          }\n        }\n      }\n    });\n\n    var meta = chart.getDatasetMeta(3);\n    expect(meta.controller._getStackCount()).toBe(2);\n  });\n\n  it('should correctly count the number of stacks when a group is specified for all and the scale is not stacked', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [\n          {data: [], stack: 'stack1'},\n          {data: [], stack: 'stack1'},\n          {data: [], stack: 'stack2'},\n          {data: [], stack: 'stack2'}\n        ],\n        labels: []\n      },\n      options: {\n        scales: {\n          x: {\n            stacked: false\n          },\n          y: {\n            stacked: false\n          }\n        }\n      }\n    });\n\n    var meta = chart.getDatasetMeta(3);\n    expect(meta.controller._getStackCount()).toBe(4);\n  });\n\n  it('should correctly get the stack index accounting for datasets of other types and hidden datasets', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [\n          {data: []},\n          {data: [], hidden: true},\n          {data: [], type: 'line'},\n          {data: []}\n        ],\n        labels: []\n      }\n    });\n\n    var meta = chart.getDatasetMeta(1);\n    expect(meta.controller._getStackIndex(0)).toBe(0);\n    expect(meta.controller._getStackIndex(3)).toBe(1);\n  });\n\n  it('should correctly get the stack index when a group is not specified', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [\n          {data: []},\n          {data: []},\n          {data: []},\n          {data: []}\n        ],\n        labels: []\n      }\n    });\n\n    var meta = chart.getDatasetMeta(1);\n    expect(meta.controller._getStackIndex(0)).toBe(0);\n    expect(meta.controller._getStackIndex(1)).toBe(1);\n    expect(meta.controller._getStackIndex(2)).toBe(2);\n    expect(meta.controller._getStackIndex(3)).toBe(3);\n  });\n\n  it('should correctly get the stack index when a group is not specified and the scale is stacked', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [\n          {data: []},\n          {data: []},\n          {data: []},\n          {data: []}\n        ],\n        labels: []\n      },\n      options: {\n        scales: {\n          x: {\n            stacked: true\n          },\n          y: {\n            stacked: true\n          }\n        }\n      }\n    });\n\n    var meta = chart.getDatasetMeta(1);\n    expect(meta.controller._getStackIndex(0)).toBe(0);\n    expect(meta.controller._getStackIndex(1)).toBe(0);\n    expect(meta.controller._getStackIndex(2)).toBe(0);\n    expect(meta.controller._getStackIndex(3)).toBe(0);\n  });\n\n  it('should correctly get the stack index when a group is not specified and the scale is not stacked', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [\n          {data: []},\n          {data: []},\n          {data: []},\n          {data: []}\n        ],\n        labels: []\n      },\n      options: {\n        scales: {\n          x: {\n            stacked: false\n          },\n          y: {\n            stacked: false\n          }\n        }\n      }\n    });\n\n    var meta = chart.getDatasetMeta(1);\n    expect(meta.controller._getStackIndex(0)).toBe(0);\n    expect(meta.controller._getStackIndex(1)).toBe(1);\n    expect(meta.controller._getStackIndex(2)).toBe(2);\n    expect(meta.controller._getStackIndex(3)).toBe(3);\n  });\n\n  it('should correctly get the stack index when a group is specified for some', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [\n          {data: [], stack: 'stack1'},\n          {data: [], stack: 'stack1'},\n          {data: []},\n          {data: []}\n        ],\n        labels: []\n      }\n    });\n\n    var meta = chart.getDatasetMeta(1);\n    expect(meta.controller._getStackIndex(0)).toBe(0);\n    expect(meta.controller._getStackIndex(1)).toBe(0);\n    expect(meta.controller._getStackIndex(2)).toBe(1);\n    expect(meta.controller._getStackIndex(3)).toBe(2);\n  });\n\n  it('should correctly get the stack index when a group is specified for some and the scale is stacked', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [\n          {data: [], stack: 'stack1'},\n          {data: [], stack: 'stack1'},\n          {data: []},\n          {data: []}\n        ],\n        labels: []\n      },\n      options: {\n        scales: {\n          x: {\n            stacked: true\n          },\n          y: {\n            stacked: true\n          }\n        }\n      }\n    });\n\n    var meta = chart.getDatasetMeta(1);\n    expect(meta.controller._getStackIndex(0)).toBe(0);\n    expect(meta.controller._getStackIndex(1)).toBe(0);\n    expect(meta.controller._getStackIndex(2)).toBe(1);\n    expect(meta.controller._getStackIndex(3)).toBe(1);\n  });\n\n  it('should correctly get the stack index when a group is specified for some and the scale is not stacked', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [\n          {data: [], stack: 'stack1'},\n          {data: [], stack: 'stack1'},\n          {data: []},\n          {data: []}\n        ],\n        labels: []\n      },\n      options: {\n        scales: {\n          x: {\n            stacked: false\n          },\n          y: {\n            stacked: false\n          }\n        }\n      }\n    });\n\n    var meta = chart.getDatasetMeta(1);\n    expect(meta.controller._getStackIndex(0)).toBe(0);\n    expect(meta.controller._getStackIndex(1)).toBe(1);\n    expect(meta.controller._getStackIndex(2)).toBe(2);\n    expect(meta.controller._getStackIndex(3)).toBe(3);\n  });\n\n  it('should correctly get the stack index when a group is specified for all', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [\n          {data: [], stack: 'stack1'},\n          {data: [], stack: 'stack1'},\n          {data: [], stack: 'stack2'},\n          {data: [], stack: 'stack2'}\n        ],\n        labels: []\n      }\n    });\n\n    var meta = chart.getDatasetMeta(1);\n    expect(meta.controller._getStackIndex(0)).toBe(0);\n    expect(meta.controller._getStackIndex(1)).toBe(0);\n    expect(meta.controller._getStackIndex(2)).toBe(1);\n    expect(meta.controller._getStackIndex(3)).toBe(1);\n  });\n\n  it('should correctly get the stack index when a group is specified for all and the scale is stacked', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [\n          {data: [], stack: 'stack1'},\n          {data: [], stack: 'stack1'},\n          {data: [], stack: 'stack2'},\n          {data: [], stack: 'stack2'}\n        ],\n        labels: []\n      },\n      options: {\n        scales: {\n          x: {\n            stacked: true\n          },\n          y: {\n            stacked: true\n          }\n        }\n      }\n    });\n\n    var meta = chart.getDatasetMeta(1);\n    expect(meta.controller._getStackIndex(0)).toBe(0);\n    expect(meta.controller._getStackIndex(1)).toBe(0);\n    expect(meta.controller._getStackIndex(2)).toBe(1);\n    expect(meta.controller._getStackIndex(3)).toBe(1);\n  });\n\n  it('should correctly get the stack index when a group is specified for all and the scale is not stacked', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [\n          {data: [], stack: 'stack1'},\n          {data: [], stack: 'stack1'},\n          {data: [], stack: 'stack2'},\n          {data: [], stack: 'stack2'}\n        ],\n        labels: []\n      },\n      options: {\n        scales: {\n          x: {\n            stacked: false\n          },\n          y: {\n            stacked: false\n          }\n        }\n      }\n    });\n\n    var meta = chart.getDatasetMeta(1);\n    expect(meta.controller._getStackIndex(0)).toBe(0);\n    expect(meta.controller._getStackIndex(1)).toBe(1);\n    expect(meta.controller._getStackIndex(2)).toBe(2);\n    expect(meta.controller._getStackIndex(3)).toBe(3);\n  });\n\n  it('should create bar elements for each data item during initialization', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [\n          {data: []},\n          {data: [10, 15, 0, -4]}\n        ],\n        labels: []\n      }\n    });\n\n    var meta = chart.getDatasetMeta(1);\n    expect(meta.data.length).toBe(4); // 4 bars created\n    expect(meta.data[0] instanceof Chart.elements.BarElement).toBe(true);\n    expect(meta.data[1] instanceof Chart.elements.BarElement).toBe(true);\n    expect(meta.data[2] instanceof Chart.elements.BarElement).toBe(true);\n    expect(meta.data[3] instanceof Chart.elements.BarElement).toBe(true);\n  });\n\n  it('should update elements when modifying data', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          data: [1, 2],\n          label: 'dataset1'\n        }, {\n          data: [10, 15, 0, -4],\n          label: 'dataset2',\n          borderColor: 'blue'\n        }],\n        labels: ['label1', 'label2', 'label3', 'label4']\n      },\n      options: {\n        plugins: {\n          legend: false,\n          title: false\n        },\n        elements: {\n          bar: {\n            backgroundColor: 'red',\n            borderSkipped: 'top',\n            borderColor: 'green',\n            borderWidth: 2,\n          }\n        },\n        scales: {\n          x: {\n            type: 'category',\n            display: false\n          },\n          y: {\n            type: 'linear',\n            display: false,\n            beginAtZero: false\n          }\n        }\n      }\n    });\n\n    var meta = chart.getDatasetMeta(1);\n    expect(meta.data.length).toBe(4);\n\n    chart.data.datasets[1].data = [1, 2]; // remove 2 items\n    chart.data.datasets[1].borderWidth = 1;\n    chart.update();\n\n    expect(meta.data.length).toBe(2);\n    expect(meta._parsed.length).toBe(2);\n\n    [\n      {x: 89, y: 512},\n      {x: 217, y: 0}\n    ].forEach(function(expected, i) {\n      expect(meta.data[i].x).toBeCloseToPixel(expected.x);\n      expect(meta.data[i].y).toBeCloseToPixel(expected.y);\n      expect(meta.data[i].base).toBeCloseToPixel(1024);\n      expect(meta.data[i].width).toBeCloseToPixel(46);\n      expect(meta.data[i].options).toEqual(jasmine.objectContaining({\n        backgroundColor: 'red',\n        borderSkipped: 'top',\n        borderColor: 'blue',\n        borderWidth: 1\n      }));\n    });\n\n    chart.data.datasets[1].data = [1, 2, 3]; // add 1 items\n    chart.update();\n\n    expect(meta.data.length).toBe(3); // should add a new meta data item\n  });\n\n  it('should get the correct bar points when datasets of different types exist', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          data: [1, 2],\n          label: 'dataset1'\n        }, {\n          type: 'line',\n          data: [4, 6],\n          label: 'dataset2'\n        }, {\n          data: [8, 10],\n          label: 'dataset3'\n        }],\n        labels: ['label1', 'label2']\n      },\n      options: {\n        plugins: {\n          legend: false,\n          title: false\n        },\n        scales: {\n          x: {\n            type: 'category',\n            display: false\n          },\n          y: {\n            type: 'linear',\n            display: false,\n            beginAtZero: false\n          }\n        }\n      }\n    });\n\n    var meta = chart.getDatasetMeta(2);\n    expect(meta.data.length).toBe(2);\n\n    var bar1 = meta.data[0];\n    var bar2 = meta.data[1];\n\n    expect(bar1.x).toBeCloseToPixel(179);\n    expect(bar1.y).toBeCloseToPixel(117);\n    expect(bar2.x).toBeCloseToPixel(431);\n    expect(bar2.y).toBeCloseToPixel(4);\n  });\n\n  it('should get the bar points for hidden dataset', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          data: [1, 2],\n          label: 'dataset1',\n          hidden: true\n        }],\n        labels: ['label1', 'label2']\n      },\n      options: {\n        plugins: {\n          legend: false,\n          title: false\n        },\n        scales: {\n          x: {\n            type: 'category',\n            display: false\n          },\n          y: {\n            type: 'linear',\n            min: 0,\n            max: 2,\n            display: false\n          }\n        }\n      }\n    });\n\n    var meta = chart.getDatasetMeta(0);\n    expect(meta.data.length).toBe(2);\n\n    var bar1 = meta.data[0];\n    var bar2 = meta.data[1];\n\n    expect(bar1.x).toBeCloseToPixel(128);\n    expect(bar1.y).toBeCloseToPixel(256);\n    expect(bar2.x).toBeCloseToPixel(384);\n    expect(bar2.y).toBeCloseToPixel(0);\n  });\n\n\n  it('should update elements when the scales are stacked', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          data: [10, -10, 10, -10],\n          label: 'dataset1'\n        }, {\n          data: [10, 15, 0, -4],\n          label: 'dataset2'\n        }],\n        labels: ['label1', 'label2', 'label3', 'label4']\n      },\n      options: {\n        plugins: {\n          legend: false,\n          title: false\n        },\n        scales: {\n          x: {\n            type: 'category',\n            display: false\n          },\n          y: {\n            type: 'linear',\n            display: false,\n            stacked: true\n          }\n        }\n      }\n    });\n\n    var meta0 = chart.getDatasetMeta(0);\n\n    [\n      {b: 293, w: 92 / 2, x: 38, y: 146},\n      {b: 293, w: 92 / 2, x: 166, y: 439},\n      {b: 293, w: 92 / 2, x: 295, y: 146},\n      {b: 293, w: 92 / 2, x: 422, y: 439}\n    ].forEach(function(values, i) {\n      expect(meta0.data[i].base).toBeCloseToPixel(values.b);\n      expect(meta0.data[i].width).toBeCloseToPixel(values.w);\n      expect(meta0.data[i].x).toBeCloseToPixel(values.x);\n      expect(meta0.data[i].y).toBeCloseToPixel(values.y);\n    });\n\n    var meta1 = chart.getDatasetMeta(1);\n\n    [\n      {b: 146, w: 92 / 2, x: 89, y: 0},\n      {b: 293, w: 92 / 2, x: 217, y: 73},\n      {b: 146, w: 92 / 2, x: 345, y: 146},\n      {b: 439, w: 92 / 2, x: 473, y: 497}\n    ].forEach(function(values, i) {\n      expect(meta1.data[i].base).toBeCloseToPixel(values.b);\n      expect(meta1.data[i].width).toBeCloseToPixel(values.w);\n      expect(meta1.data[i].x).toBeCloseToPixel(values.x);\n      expect(meta1.data[i].y).toBeCloseToPixel(values.y);\n    });\n  });\n\n  it('should update elements when the scales are stacked and the y axis has a user defined minimum', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          data: [50, 20, 10, 100],\n          label: 'dataset1'\n        }, {\n          data: [50, 80, 90, 0],\n          label: 'dataset2'\n        }],\n        labels: ['label1', 'label2', 'label3', 'label4']\n      },\n      options: {\n        plugins: {\n          legend: false,\n          title: false\n        },\n        scales: {\n          x: {\n            type: 'category',\n            display: false\n          },\n          y: {\n            type: 'linear',\n            display: false,\n            stacked: true,\n            min: 50,\n            max: 100\n          }\n        }\n      }\n    });\n\n    var meta0 = chart.getDatasetMeta(0);\n\n    [\n      {b: 1024, w: 92 / 2, x: 38, y: 512},\n      {b: 1024, w: 92 / 2, x: 166, y: 819},\n      {b: 1024, w: 92 / 2, x: 294, y: 922},\n      {b: 1024, w: 92 / 2, x: 422.5, y: 0}\n    ].forEach(function(values, i) {\n      expect(meta0.data[i].base).toBeCloseToPixel(values.b);\n      expect(meta0.data[i].width).toBeCloseToPixel(values.w);\n      expect(meta0.data[i].x).toBeCloseToPixel(values.x);\n      expect(meta0.data[i].y).toBeCloseToPixel(values.y);\n    });\n\n    var meta1 = chart.getDatasetMeta(1);\n\n    [\n      {b: 512, w: 92 / 2, x: 89, y: 0},\n      {b: 819.2, w: 92 / 2, x: 217, y: 0},\n      {b: 921.6, w: 92 / 2, x: 345, y: 0},\n      {b: 0, w: 92 / 2, x: 473.5, y: 0}\n    ].forEach(function(values, i) {\n      expect(meta1.data[i].base).toBeCloseToPixel(values.b);\n      expect(meta1.data[i].width).toBeCloseToPixel(values.w);\n      expect(meta1.data[i].x).toBeCloseToPixel(values.x);\n      expect(meta1.data[i].y).toBeCloseToPixel(values.y);\n    });\n  });\n\n  it('should update elements when only the category scale is stacked', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          data: [20, -10, 10, -10],\n          label: 'dataset1'\n        }, {\n          data: [10, 15, 0, -14],\n          label: 'dataset2'\n        }],\n        labels: ['label1', 'label2', 'label3', 'label4']\n      },\n      options: {\n        plugins: {\n          legend: false,\n          title: false\n        },\n        scales: {\n          x: {\n            type: 'category',\n            display: false,\n            stacked: true\n          },\n          y: {\n            type: 'linear',\n            display: false\n          }\n        }\n      }\n    });\n\n    var meta0 = chart.getDatasetMeta(0);\n\n    [\n      {b: 293, w: 92, x: 64, y: 0},\n      {b: 293, w: 92, x: 192, y: 439},\n      {b: 293, w: 92, x: 320, y: 146},\n      {b: 293, w: 92, x: 448, y: 439}\n    ].forEach(function(values, i) {\n      expect(meta0.data[i].base).toBeCloseToPixel(values.b);\n      expect(meta0.data[i].width).toBeCloseToPixel(values.w);\n      expect(meta0.data[i].x).toBeCloseToPixel(values.x);\n      expect(meta0.data[i].y).toBeCloseToPixel(values.y);\n    });\n\n    var meta1 = chart.getDatasetMeta(1);\n\n    [\n      {b: 293, w: 92, x: 64, y: 146},\n      {b: 293, w: 92, x: 192, y: 73},\n      {b: 293, w: 92, x: 320, y: 293},\n      {b: 293, w: 92, x: 448, y: 497}\n    ].forEach(function(values, i) {\n      expect(meta1.data[i].base).toBeCloseToPixel(values.b);\n      expect(meta1.data[i].width).toBeCloseToPixel(values.w);\n      expect(meta1.data[i].x).toBeCloseToPixel(values.x);\n      expect(meta1.data[i].y).toBeCloseToPixel(values.y);\n    });\n  });\n\n  it('should update elements when the scales are stacked and data is strings', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          data: ['10', '-10', '10', '-10'],\n          label: 'dataset1'\n        }, {\n          data: ['10', '15', '0', '-4'],\n          label: 'dataset2'\n        }],\n        labels: ['label1', 'label2', 'label3', 'label4']\n      },\n      options: {\n        plugins: {\n          legend: false,\n          title: false\n        },\n        scales: {\n          x: {\n            type: 'category',\n            display: false\n          },\n          y: {\n            type: 'linear',\n            display: false,\n            stacked: true\n          }\n        }\n      }\n    });\n\n    var meta0 = chart.getDatasetMeta(0);\n\n    [\n      {b: 293, w: 92 / 2, x: 38, y: 146},\n      {b: 293, w: 92 / 2, x: 166, y: 439},\n      {b: 293, w: 92 / 2, x: 295, y: 146},\n      {b: 293, w: 92 / 2, x: 422, y: 439}\n    ].forEach(function(values, i) {\n      expect(meta0.data[i].base).toBeCloseToPixel(values.b);\n      expect(meta0.data[i].width).toBeCloseToPixel(values.w);\n      expect(meta0.data[i].x).toBeCloseToPixel(values.x);\n      expect(meta0.data[i].y).toBeCloseToPixel(values.y);\n    });\n\n    var meta1 = chart.getDatasetMeta(1);\n\n    [\n      {b: 146, w: 92 / 2, x: 89, y: 0},\n      {b: 293, w: 92 / 2, x: 217, y: 73},\n      {b: 146, w: 92 / 2, x: 345, y: 146},\n      {b: 439, w: 92 / 2, x: 473, y: 497}\n    ].forEach(function(values, i) {\n      expect(meta1.data[i].base).toBeCloseToPixel(values.b);\n      expect(meta1.data[i].width).toBeCloseToPixel(values.w);\n      expect(meta1.data[i].x).toBeCloseToPixel(values.x);\n      expect(meta1.data[i].y).toBeCloseToPixel(values.y);\n    });\n  });\n\n  it('should get the correct bar points for grouped stacked chart if the group name is same', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          data: [10, -10, 10, -10],\n          label: 'dataset1',\n          stack: 'stack1'\n        }, {\n          data: [10, 15, 0, -4],\n          label: 'dataset2',\n          stack: 'stack1'\n        }],\n        labels: ['label1', 'label2', 'label3', 'label4']\n      },\n      options: {\n        plugins: {\n          legend: false,\n          title: false\n        },\n        scales: {\n          x: {\n            type: 'category',\n            display: false\n          },\n          y: {\n            type: 'linear',\n            display: false,\n            stacked: true\n          }\n        }\n      }\n    });\n\n    var meta0 = chart.getDatasetMeta(0);\n\n    [\n      {b: 293, w: 92, x: 64, y: 146},\n      {b: 293, w: 92, x: 192, y: 439},\n      {b: 293, w: 92, x: 320, y: 146},\n      {b: 293, w: 92, x: 448, y: 439}\n    ].forEach(function(values, i) {\n      expect(meta0.data[i].base).toBeCloseToPixel(values.b);\n      expect(meta0.data[i].width).toBeCloseToPixel(values.w);\n      expect(meta0.data[i].x).toBeCloseToPixel(values.x);\n      expect(meta0.data[i].y).toBeCloseToPixel(values.y);\n    });\n\n    var meta = chart.getDatasetMeta(1);\n\n    [\n      {b: 146, w: 92, x: 64, y: 0},\n      {b: 293, w: 92, x: 192, y: 73},\n      {b: 146, w: 92, x: 320, y: 146},\n      {b: 439, w: 92, x: 448, y: 497}\n    ].forEach(function(values, i) {\n      expect(meta.data[i].base).toBeCloseToPixel(values.b);\n      expect(meta.data[i].width).toBeCloseToPixel(values.w);\n      expect(meta.data[i].x).toBeCloseToPixel(values.x);\n      expect(meta.data[i].y).toBeCloseToPixel(values.y);\n    });\n  });\n\n  it('should get the correct bar points for grouped stacked chart if the group name is different', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          data: [1, 2],\n          stack: 'stack1'\n        }, {\n          data: [1, 2],\n          stack: 'stack2'\n        }],\n        labels: ['label1', 'label2', 'label3', 'label4']\n      },\n      options: {\n        plugins: {\n          legend: false,\n          title: false\n        },\n        scales: {\n          x: {\n            type: 'category',\n            display: false\n          },\n          y: {\n            type: 'linear',\n            display: false,\n            stacked: true,\n          }\n        }\n      }\n    });\n\n    var meta = chart.getDatasetMeta(1);\n\n    [\n      {x: 89, y: 256},\n      {x: 217, y: 0}\n    ].forEach(function(values, i) {\n      expect(meta.data[i].base).toBeCloseToPixel(512);\n      expect(meta.data[i].width).toBeCloseToPixel(46);\n      expect(meta.data[i].x).toBeCloseToPixel(values.x);\n      expect(meta.data[i].y).toBeCloseToPixel(values.y);\n    });\n  });\n\n  it('should get the correct bar points for grouped stacked chart', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          data: [1, 2],\n          stack: 'stack1'\n        }, {\n          data: [0.5, 1],\n          stack: 'stack2'\n        }, {\n          data: [0.5, 1],\n          stack: 'stack2'\n        }],\n        labels: ['label1', 'label2', 'label3', 'label4']\n      },\n      options: {\n        plugins: {\n          legend: false,\n          title: false\n        },\n        scales: {\n          x: {\n            type: 'category',\n            display: false\n          },\n          y: {\n            type: 'linear',\n            display: false,\n            stacked: true\n          }\n        }\n      }\n    });\n\n    var meta = chart.getDatasetMeta(2);\n\n    [\n      {b: 384, x: 89, y: 256},\n      {b: 256, x: 217, y: 0}\n    ].forEach(function(values, i) {\n      expect(meta.data[i].base).toBeCloseToPixel(values.b);\n      expect(meta.data[i].width).toBeCloseToPixel(46);\n      expect(meta.data[i].x).toBeCloseToPixel(values.x);\n      expect(meta.data[i].y).toBeCloseToPixel(values.y);\n    });\n  });\n\n  it('should draw all bars', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          data: [],\n        }, {\n          data: [10, 15, 0, -4],\n          label: 'dataset2'\n        }],\n        labels: ['label1', 'label2', 'label3', 'label4']\n      }\n    });\n\n    var meta = chart.getDatasetMeta(1);\n\n    spyOn(meta.data[0], 'draw');\n    spyOn(meta.data[1], 'draw');\n    spyOn(meta.data[2], 'draw');\n    spyOn(meta.data[3], 'draw');\n\n    chart.update();\n\n    expect(meta.data[0].draw.calls.count()).toBe(1);\n    expect(meta.data[1].draw.calls.count()).toBe(1);\n    expect(meta.data[2].draw.calls.count()).toBe(1);\n    expect(meta.data[3].draw.calls.count()).toBe(1);\n  });\n\n  it('should set hover styles on bars', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          data: [],\n        }, {\n          data: [10, 15, 0, -4],\n          label: 'dataset2'\n        }],\n        labels: ['label1', 'label2', 'label3', 'label4']\n      },\n      options: {\n        elements: {\n          bar: {\n            backgroundColor: 'rgb(255, 0, 0)',\n            borderColor: 'rgb(0, 0, 255)',\n            borderWidth: 2,\n          }\n        }\n      }\n    });\n\n    var meta = chart.getDatasetMeta(1);\n    var bar = meta.data[0];\n\n    meta.controller.setHoverStyle(bar, 1, 0);\n    expect(bar.options.backgroundColor).toBe('#E60000');\n    expect(bar.options.borderColor).toBe('#0000E6');\n    expect(bar.options.borderWidth).toBe(2);\n\n    // Set a dataset style\n    chart.data.datasets[1].hoverBackgroundColor = 'rgb(128, 128, 128)';\n    chart.data.datasets[1].hoverBorderColor = 'rgb(0, 0, 0)';\n    chart.data.datasets[1].hoverBorderWidth = 5;\n    chart.update();\n\n    meta.controller.setHoverStyle(bar, 1, 0);\n    expect(bar.options.backgroundColor).toBe('rgb(128, 128, 128)');\n    expect(bar.options.borderColor).toBe('rgb(0, 0, 0)');\n    expect(bar.options.borderWidth).toBe(5);\n\n    // Should work with array styles so that we can set per bar\n    chart.data.datasets[1].hoverBackgroundColor = ['rgb(255, 255, 255)', 'rgb(128, 128, 128)'];\n    chart.data.datasets[1].hoverBorderColor = ['rgb(9, 9, 9)', 'rgb(0, 0, 0)'];\n    chart.data.datasets[1].hoverBorderWidth = [2.5, 5];\n    chart.update();\n\n    meta.controller.setHoverStyle(bar, 1, 0);\n    expect(bar.options.backgroundColor).toBe('rgb(255, 255, 255)');\n    expect(bar.options.borderColor).toBe('rgb(9, 9, 9)');\n    expect(bar.options.borderWidth).toBe(2.5);\n  });\n\n  it('should remove a hover style from a bar', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          data: [],\n        }, {\n          data: [10, 15, 0, -4],\n          label: 'dataset2'\n        }],\n        labels: ['label1', 'label2', 'label3', 'label4']\n      },\n      options: {\n        elements: {\n          bar: {\n            backgroundColor: 'rgb(255, 0, 0)',\n            borderColor: 'rgb(0, 0, 255)',\n            borderWidth: 2,\n          }\n        }\n      }\n    });\n\n    var meta = chart.getDatasetMeta(1);\n    var bar = meta.data[0];\n    var helpers = window.Chart.helpers;\n\n    // Change default\n    chart.options.elements.bar.backgroundColor = 'rgb(128, 128, 128)';\n    chart.options.elements.bar.borderColor = 'rgb(15, 15, 15)';\n    chart.options.elements.bar.borderWidth = 3.14;\n\n    chart.update();\n    expect(bar.options.backgroundColor).toBe('rgb(128, 128, 128)');\n    expect(bar.options.borderColor).toBe('rgb(15, 15, 15)');\n    expect(bar.options.borderWidth).toBe(3.14);\n    meta.controller.setHoverStyle(bar, 1, 0);\n    expect(bar.options.backgroundColor).toBe(helpers.getHoverColor('rgb(128, 128, 128)'));\n    expect(bar.options.borderColor).toBe(helpers.getHoverColor('rgb(15, 15, 15)'));\n    expect(bar.options.borderWidth).toBe(3.14);\n    meta.controller.removeHoverStyle(bar);\n    expect(bar.options.backgroundColor).toBe('rgb(128, 128, 128)');\n    expect(bar.options.borderColor).toBe('rgb(15, 15, 15)');\n    expect(bar.options.borderWidth).toBe(3.14);\n\n    // Should work with array styles so that we can set per bar\n    chart.data.datasets[1].backgroundColor = ['rgb(255, 255, 255)', 'rgb(128, 128, 128)'];\n    chart.data.datasets[1].borderColor = ['rgb(9, 9, 9)', 'rgb(0, 0, 0)'];\n    chart.data.datasets[1].borderWidth = [2.5, 5];\n\n    chart.update();\n    expect(bar.options.backgroundColor).toBe('rgb(255, 255, 255)');\n    expect(bar.options.borderColor).toBe('rgb(9, 9, 9)');\n    expect(bar.options.borderWidth).toBe(2.5);\n    meta.controller.setHoverStyle(bar, 1, 0);\n    expect(bar.options.backgroundColor).toBe(helpers.getHoverColor('rgb(255, 255, 255)'));\n    expect(bar.options.borderColor).toBe(helpers.getHoverColor('rgb(9, 9, 9)'));\n    expect(bar.options.borderWidth).toBe(2.5);\n    meta.controller.removeHoverStyle(bar);\n    expect(bar.options.backgroundColor).toBe('rgb(255, 255, 255)');\n    expect(bar.options.borderColor).toBe('rgb(9, 9, 9)');\n    expect(bar.options.borderWidth).toBe(2.5);\n  });\n\n  describe('Bar width', function() {\n    beforeEach(function() {\n      // 2 datasets\n      this.data = {\n        labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],\n        datasets: [{\n          data: [10, 20, 30, 40, 50, 60, 70],\n        }, {\n          data: [10, 20, 30, 40, 50, 60, 70],\n        }]\n      };\n    });\n\n    afterEach(function() {\n      var chart = window.acquireChart(this.config);\n      var meta = chart.getDatasetMeta(0);\n      var xScale = chart.scales[meta.xAxisID];\n      var options = Chart.defaults.datasets.bar;\n\n      var categoryPercentage = options.categoryPercentage;\n      var barPercentage = options.barPercentage;\n      var stacked = xScale.options.stacked;\n\n      var totalBarWidth = 0;\n      for (var i = 0; i < chart.data.datasets.length; i++) {\n        var bars = chart.getDatasetMeta(i).data;\n        for (var j = xScale.min; j <= xScale.max; j++) {\n          totalBarWidth += bars[j].width;\n        }\n        if (stacked) {\n          break;\n        }\n      }\n\n      var actualValue = totalBarWidth;\n      var expectedValue = xScale.width * categoryPercentage * barPercentage;\n      expect(actualValue).toBeCloseToPixel(expectedValue);\n\n    });\n\n    it('should correctly set bar width when min and max option is set.', function() {\n      this.config = {\n        type: 'bar',\n        data: this.data,\n        options: {\n          scales: {\n            x: {\n              min: 'March',\n              max: 'May',\n            }\n          }\n        }\n      };\n    });\n\n    it('should correctly set bar width when scale are stacked with min and max options.', function() {\n      this.config = {\n        type: 'bar',\n        data: this.data,\n        options: {\n          scales: {\n            x: {\n              min: 'March',\n              max: 'May',\n            },\n            y: {\n              stacked: true\n            }\n          }\n        }\n      };\n    });\n  });\n\n  describe('Bar height (horizontal type)', function() {\n    beforeEach(function() {\n      // 2 datasets\n      this.data = {\n        labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],\n        datasets: [{\n          data: [10, 20, 30, 40, 50, 60, 70],\n        }, {\n          data: [10, 20, 30, 40, 50, 60, 70],\n        }]\n      };\n    });\n\n    afterEach(function() {\n      var chart = window.acquireChart(this.config);\n      var meta = chart.getDatasetMeta(0);\n      var yScale = chart.scales[meta.yAxisID];\n\n      var config = meta.controller.options;\n      var categoryPercentage = config.categoryPercentage;\n      var barPercentage = config.barPercentage;\n      var stacked = yScale.options.stacked;\n\n      var totalBarHeight = 0;\n      for (var i = 0; i < chart.data.datasets.length; i++) {\n        var bars = chart.getDatasetMeta(i).data;\n        for (var j = yScale.min; j <= yScale.max; j++) {\n          totalBarHeight += bars[j].height;\n        }\n        if (stacked) {\n          break;\n        }\n      }\n\n      var actualValue = totalBarHeight;\n      var expectedValue = yScale.height * categoryPercentage * barPercentage;\n      expect(actualValue).toBeCloseToPixel(expectedValue);\n\n    });\n\n    it('should correctly set bar height when min and max option is set.', function() {\n      this.config = {\n        type: 'bar',\n        data: this.data,\n        options: {\n          indexAxis: 'y',\n          scales: {\n            y: {\n              min: 'March',\n              max: 'May',\n            }\n          }\n        }\n      };\n    });\n\n    it('should correctly set bar height when scale are stacked with min and max options.', function() {\n      this.config = {\n        type: 'bar',\n        data: this.data,\n        options: {\n          indexAxis: 'y',\n          scales: {\n            x: {\n              stacked: true\n            },\n            y: {\n              min: 'March',\n              max: 'May',\n            }\n          }\n        }\n      };\n    });\n  });\n\n  describe('Bar thickness with a category scale', function() {\n    [undefined, 20].forEach(function(barThickness) {\n      describe('When barThickness is ' + barThickness, function() {\n        beforeEach(function() {\n          this.chart = window.acquireChart({\n            type: 'bar',\n            data: {\n              datasets: [{\n                data: [1, 2]\n              }, {\n                data: [1, 2]\n              }],\n              labels: ['label1', 'label2', 'label3']\n            },\n            options: {\n              legend: false,\n              title: false,\n              datasets: {\n                bar: {\n                  barThickness: barThickness\n                }\n              },\n              scales: {\n                x: {\n                  id: 'x',\n                  type: 'category',\n                },\n                y: {\n                  type: 'linear',\n                }\n              }\n            }\n          });\n        });\n\n        it('should correctly set bar width', function() {\n          var chart = this.chart;\n          var expected, i, ilen, meta;\n\n          if (barThickness) {\n            expected = barThickness;\n          } else {\n            var scale = chart.scales.x;\n            var options = Chart.defaults.datasets.bar;\n            var categoryPercentage = options.categoryPercentage;\n            var barPercentage = options.barPercentage;\n            var tickInterval = scale.getPixelForTick(1) - scale.getPixelForTick(0);\n\n            expected = tickInterval * categoryPercentage / 2 * barPercentage;\n          }\n\n          for (i = 0, ilen = chart.data.datasets.length; i < ilen; ++i) {\n            meta = chart.getDatasetMeta(i);\n            expect(meta.data[0].width).toBeCloseToPixel(expected);\n            expect(meta.data[1].width).toBeCloseToPixel(expected);\n          }\n        });\n\n        it('should correctly set bar width if maxBarThickness is specified', function() {\n          var chart = this.chart;\n          var i, ilen, meta;\n\n          chart.data.datasets[0].maxBarThickness = 10;\n          chart.data.datasets[1].maxBarThickness = 10;\n          chart.update();\n\n          for (i = 0, ilen = chart.data.datasets.length; i < ilen; ++i) {\n            meta = chart.getDatasetMeta(i);\n            expect(meta.data[0].width).toBeCloseToPixel(10);\n            expect(meta.data[1].width).toBeCloseToPixel(10);\n          }\n        });\n      });\n    });\n  });\n\n  it('minBarLength settings should be used on Y axis on bar chart', function() {\n    var minBarLength = 4;\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          minBarLength: minBarLength,\n          data: [0.05, -0.05, 10, 15, 20, 25, 30, 35]\n        }]\n      }\n    });\n\n    var data = chart.getDatasetMeta(0).data;\n    var halfBaseLine = chart.scales.y.getLineWidthForValue(0) / 2;\n\n    expect(data[0].base - minBarLength + halfBaseLine).toEqual(data[0].y);\n    expect(data[1].base + minBarLength - halfBaseLine).toEqual(data[1].y);\n  });\n\n  it('minBarLength settings should be used on X axis on horizontal bar chart', function() {\n    var minBarLength = 4;\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          indexAxis: 'y',\n          minBarLength: minBarLength,\n          data: [0.05, -0.05, 10, 15, 20, 25, 30, 35]\n        }]\n      }\n    });\n\n    var data = chart.getDatasetMeta(0).data;\n    var halfBaseLine = chart.scales.x.getLineWidthForValue(0) / 2;\n\n    expect(data[0].base + minBarLength - halfBaseLine).toEqual(data[0].x);\n    expect(data[1].base - minBarLength + halfBaseLine).toEqual(data[1].x);\n  });\n\n  it('should respect the data visibility settings', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          data: [1, 2, 3, 4]\n        }],\n        labels: ['A', 'B', 'C', 'D']\n      },\n      options: {\n        plugins: {\n          legend: false,\n          title: false\n        },\n        scales: {\n          x: {\n            type: 'category',\n            display: false\n          },\n          y: {\n            type: 'linear',\n            display: false,\n          }\n        }\n      }\n    });\n\n    var data = chart.getDatasetMeta(0).data;\n    expect(data[0].base).toBeCloseToPixel(512);\n    expect(data[0].y).toBeCloseToPixel(384);\n\n    chart.toggleDataVisibility(0);\n    chart.update();\n\n    data = chart.getDatasetMeta(0).data;\n    expect(data[0].base).toBeCloseToPixel(512);\n    expect(data[0].y).toBeCloseToPixel(512);\n  });\n\n  it('should hide bar dataset beneath the chart for correct animations', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          data: [1, 2, 3, 4]\n        }, {\n          data: [1, 2, 3, 4]\n        }],\n        labels: ['A', 'B', 'C', 'D']\n      },\n      options: {\n        plugins: {\n          legend: false,\n          title: false\n        },\n        scales: {\n          x: {\n            type: 'category',\n            display: false,\n            stacked: true,\n          },\n          y: {\n            type: 'linear',\n            display: false,\n            stacked: true,\n          }\n        }\n      }\n    });\n\n    var data = chart.getDatasetMeta(0).data;\n    expect(data[0].base).toBeCloseToPixel(512);\n    expect(data[0].y).toBeCloseToPixel(448);\n\n    chart.setDatasetVisibility(0, false);\n    chart.update();\n\n    data = chart.getDatasetMeta(0).data;\n    expect(data[0].base).toBeCloseToPixel(640);\n    expect(data[0].y).toBeCloseToPixel(512);\n  });\n\n  describe('Float bar', function() {\n    it('Should return correct values from getMinMax', function() {\n      var chart = window.acquireChart({\n        type: 'bar',\n        data: {\n          labels: ['a'],\n          datasets: [{\n            data: [[10, -10]]\n          }]\n        }\n      });\n\n      expect(chart.scales.y.getMinMax()).toEqual({min: -10, max: 10});\n    });\n  });\n\n  describe('clip', function() {\n    it('Should not use ctx.clip when clip=false', function() {\n      var ctx = window.createMockContext();\n      ctx.resetTransform = function() {};\n\n      var chart = window.acquireChart({\n        type: 'bar',\n        data: {\n          labels: ['a', 'b', 'c'],\n          datasets: [{\n            data: [1, 2, 3],\n            clip: false\n          }]\n        }\n      });\n      var orig = chart.ctx;\n\n      // Draw on mock context\n      chart.ctx = ctx;\n      chart.draw();\n\n      chart.ctx = orig;\n\n      expect(ctx.getCalls().filter(x => x.name === 'clip').length).toEqual(0);\n    });\n  });\n\n  it('should not crash with skipNull and uneven datasets', function() {\n    function unevenChart() {\n      window.acquireChart({\n        type: 'bar',\n        data: {\n          labels: [1, 2],\n          datasets: [\n            {data: [1, 2]},\n            {data: [1, 2, 3]},\n          ]\n        },\n        options: {\n          skipNull: true,\n        }\n      });\n    }\n\n    expect(unevenChart).not.toThrow();\n  });\n\n  it('should correctly count the number of stacks when skipNull and different order datasets', function() {\n\n    const chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [\n          {\n            id: '1',\n            label: 'USA',\n            data: [\n              {\n                xScale: 'First',\n                Country: 'USA',\n                yScale: 524\n              },\n              {\n                xScale: 'Second',\n                Country: 'USA',\n                yScale: 325\n              }\n            ],\n\n            yAxisID: 'yScale',\n            xAxisID: 'xScale',\n\n            parsing: {\n              yAxisKey: 'yScale',\n              xAxisKey: 'xScale'\n            }\n          },\n          {\n            id: '2',\n            label: 'BRA',\n            data: [\n              {\n                xScale: 'Second',\n                Country: 'BRA',\n                yScale: 183\n              },\n              {\n                xScale: 'First',\n                Country: 'BRA',\n                yScale: 177\n              }\n            ],\n\n            yAxisID: 'yScale',\n            xAxisID: 'xScale',\n\n            parsing: {\n              yAxisKey: 'yScale',\n              xAxisKey: 'xScale'\n            }\n          },\n          {\n            id: '3',\n            label: 'DEU',\n            data: [\n              {\n                xScale: 'First',\n                Country: 'DEU',\n                yScale: 162\n              }\n            ],\n\n            yAxisID: 'yScale',\n            xAxisID: 'xScale',\n\n            parsing: {\n              yAxisKey: 'yScale',\n              xAxisKey: 'xScale'\n            }\n          }\n        ]\n      },\n      options: {\n        skipNull: true\n      }\n    });\n\n    var meta = chart.getDatasetMeta(0);\n    expect(meta.controller._getStackCount(0)).toBe(3);\n    expect(meta.controller._getStackCount(1)).toBe(2);\n\n  });\n\n  it('should not override tooltip title and label callbacks', async() => {\n    const chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        labels: ['Label 1', 'Label 2'],\n        datasets: [{\n          data: [21, 79],\n          label: 'Dataset 1'\n        }, {\n          data: [33, 67],\n          label: 'Dataset 2'\n        }]\n      },\n      options: {\n        responsive: true,\n        maintainAspectRatio: true,\n      }\n    });\n    const {tooltip} = chart;\n    const point = chart.getDatasetMeta(0).data[0];\n\n    await jasmine.triggerMouseEvent(chart, 'mousemove', point);\n\n    expect(tooltip.title).toEqual(['Label 1']);\n    expect(tooltip.body).toEqual([{\n      before: [],\n      lines: ['Dataset 1: 21'],\n      after: []\n    }]);\n\n    chart.options.plugins.tooltip = {mode: 'dataset'};\n    chart.update();\n    await jasmine.triggerMouseEvent(chart, 'mousemove', point);\n\n    expect(tooltip.title).toEqual(['Dataset 1']);\n    expect(tooltip.body).toEqual([{\n      before: [],\n      lines: ['Label 1: 21'],\n      after: []\n    }, {\n      before: [],\n      lines: ['Label 2: 79'],\n      after: []\n    }]);\n  });\n});\n"
  },
  {
    "path": "test/specs/controller.bubble.tests.js",
    "content": "describe('Chart.controllers.bubble', function() {\n  describe('auto', jasmine.fixture.specs('controller.bubble'));\n\n  it('should be registered as dataset controller', function() {\n    expect(typeof Chart.controllers.bubble).toBe('function');\n  });\n\n  it('should be constructed', function() {\n    var chart = window.acquireChart({\n      type: 'bubble',\n      data: {\n        datasets: [{\n          data: []\n        }]\n      }\n    });\n\n    var meta = chart.getDatasetMeta(0);\n    expect(meta.type).toBe('bubble');\n    expect(meta.controller).not.toBe(undefined);\n    expect(meta.controller.index).toBe(0);\n    expect(meta.data).toEqual([]);\n\n    meta.controller.updateIndex(1);\n    expect(meta.controller.index).toBe(1);\n  });\n\n  it('should use the first scale IDs if the dataset does not specify them', function() {\n    var chart = window.acquireChart({\n      type: 'bubble',\n      data: {\n        datasets: [{\n          data: []\n        }]\n      },\n    });\n\n    var meta = chart.getDatasetMeta(0);\n\n    expect(meta.xAxisID).toBe('x');\n    expect(meta.yAxisID).toBe('y');\n  });\n\n  it('should create point elements for each data item during initialization', function() {\n    var chart = window.acquireChart({\n      type: 'bubble',\n      data: {\n        datasets: [{\n          data: [10, 15, 0, -4]\n        }]\n      }\n    });\n\n    var meta = chart.getDatasetMeta(0);\n\n    expect(meta.data.length).toBe(4); // 4 points created\n    expect(meta.data[0] instanceof Chart.elements.PointElement).toBe(true);\n    expect(meta.data[1] instanceof Chart.elements.PointElement).toBe(true);\n    expect(meta.data[2] instanceof Chart.elements.PointElement).toBe(true);\n    expect(meta.data[3] instanceof Chart.elements.PointElement).toBe(true);\n  });\n\n  it('should draw all elements', function() {\n    var chart = window.acquireChart({\n      type: 'bubble',\n      data: {\n        datasets: [{\n          data: [10, 15, 0, -4]\n        }]\n      },\n      options: {\n        animation: false,\n        showLine: true\n      }\n    });\n\n    var meta = chart.getDatasetMeta(0);\n\n    spyOn(meta.data[0], 'draw');\n    spyOn(meta.data[1], 'draw');\n    spyOn(meta.data[2], 'draw');\n    spyOn(meta.data[3], 'draw');\n\n    chart.update();\n\n    expect(meta.data[0].draw.calls.count()).toBe(1);\n    expect(meta.data[1].draw.calls.count()).toBe(1);\n    expect(meta.data[2].draw.calls.count()).toBe(1);\n    expect(meta.data[3].draw.calls.count()).toBe(1);\n  });\n\n  it('should update elements when modifying style', function() {\n    var chart = window.acquireChart({\n      type: 'bubble',\n      data: {\n        datasets: [{\n          data: [{\n            x: 10,\n            y: 10,\n            r: 5\n          }, {\n            x: -15,\n            y: -10,\n            r: 1\n          }, {\n            x: 0,\n            y: -9,\n            r: 2\n          }, {\n            x: -4,\n            y: 10,\n            r: 1\n          }]\n        }],\n        labels: ['label1', 'label2', 'label3', 'label4']\n      },\n      options: {\n        plugins: {\n          legend: false,\n          title: false\n        },\n        scales: {\n          x: {\n            type: 'category',\n            display: false\n          },\n          y: {\n            type: 'linear',\n            display: false\n          }\n        }\n      }\n    });\n\n    var meta = chart.getDatasetMeta(0);\n\n    [\n      {r: 5, x: 5, y: 5},\n      {r: 1, x: 171, y: 507},\n      {r: 2, x: 341, y: 482},\n      {r: 1, x: 507, y: 5}\n    ].forEach(function(expected, i) {\n      expect(meta.data[i].x).toBeCloseToPixel(expected.x);\n      expect(meta.data[i].y).toBeCloseToPixel(expected.y);\n      expect(meta.data[i].options).toEqual(jasmine.objectContaining({\n        backgroundColor: Chart.defaults.backgroundColor,\n        borderColor: Chart.defaults.borderColor,\n        borderWidth: 1,\n        hitRadius: 1,\n        radius: expected.r\n      }));\n    });\n\n    // Use dataset level styles for lines & points\n    chart.data.datasets[0].backgroundColor = 'rgb(98, 98, 98)';\n    chart.data.datasets[0].borderColor = 'rgb(8, 8, 8)';\n    chart.data.datasets[0].borderWidth = 0.55;\n\n    // point styles\n    chart.data.datasets[0].radius = 22;\n    chart.data.datasets[0].hitRadius = 3.3;\n\n    chart.update();\n\n    for (var i = 0; i < 4; ++i) {\n      expect(meta.data[i].options).toEqual(jasmine.objectContaining({\n        backgroundColor: 'rgb(98, 98, 98)',\n        borderColor: 'rgb(8, 8, 8)',\n        borderWidth: 0.55,\n        hitRadius: 3.3\n      }));\n    }\n  });\n\n  it('should handle number of data point changes in update', function() {\n    var chart = window.acquireChart({\n      type: 'bubble',\n      data: {\n        datasets: [{\n          data: [{\n            x: 10,\n            y: 10,\n            r: 5\n          }, {\n            x: -15,\n            y: -10,\n            r: 1\n          }, {\n            x: 0,\n            y: -9,\n            r: 2\n          }, {\n            x: -4,\n            y: 10,\n            r: 1\n          }]\n        }],\n        labels: ['label1', 'label2', 'label3', 'label4']\n      }\n    });\n\n    var meta = chart.getDatasetMeta(0);\n\n    expect(meta.data.length).toBe(4);\n\n    chart.data.datasets[0].data = [{\n      x: 1,\n      y: 1,\n      r: 10\n    }, {\n      x: 10,\n      y: 5,\n      r: 2\n    }]; // remove 2 items\n\n    chart.update();\n\n    expect(meta.data.length).toBe(2);\n    expect(meta.data[0] instanceof Chart.elements.PointElement).toBe(true);\n    expect(meta.data[1] instanceof Chart.elements.PointElement).toBe(true);\n\n    chart.data.datasets[0].data = [{\n      x: 10,\n      y: 10,\n      r: 5\n    }, {\n      x: -15,\n      y: -10,\n      r: 1\n    }, {\n      x: 0,\n      y: -9,\n      r: 2\n    }, {\n      x: -4,\n      y: 10,\n      r: 1\n    }, {\n      x: -5,\n      y: 0,\n      r: 3\n    }]; // add 3 items\n\n    chart.update();\n\n    expect(meta.data.length).toBe(5);\n    expect(meta.data[0] instanceof Chart.elements.PointElement).toBe(true);\n    expect(meta.data[1] instanceof Chart.elements.PointElement).toBe(true);\n    expect(meta.data[2] instanceof Chart.elements.PointElement).toBe(true);\n    expect(meta.data[3] instanceof Chart.elements.PointElement).toBe(true);\n    expect(meta.data[4] instanceof Chart.elements.PointElement).toBe(true);\n  });\n\n  describe('Interactions', function() {\n    beforeEach(function() {\n      this.chart = window.acquireChart({\n        type: 'bubble',\n        data: {\n          labels: ['label1', 'label2', 'label3', 'label4'],\n          datasets: [{\n            data: [{\n              x: 5,\n              y: 5,\n              r: 20\n            }, {\n              x: -15,\n              y: -10,\n              r: 15\n            }, {\n              x: 15,\n              y: 10,\n              r: 10\n            }, {\n              x: -15,\n              y: 10,\n              r: 5\n            }]\n          }]\n        },\n        options: {\n          elements: {\n            point: {\n              backgroundColor: 'rgb(100, 150, 200)',\n              borderColor: 'rgb(50, 100, 150)',\n              borderWidth: 2,\n              radius: 3\n            }\n          }\n        }\n      });\n    });\n\n    it ('should handle default hover styles', async function() {\n      var chart = this.chart;\n      var point = chart.getDatasetMeta(0).data[0];\n\n      await jasmine.triggerMouseEvent(chart, 'mousemove', point);\n      expect(point.options.backgroundColor).toBe('#3187DD');\n      expect(point.options.borderColor).toBe('#175A9D');\n      expect(point.options.borderWidth).toBe(1);\n      expect(point.options.radius).toBe(20 + 4);\n\n      await jasmine.triggerMouseEvent(chart, 'mouseout', point);\n      expect(point.options.backgroundColor).toBe('rgb(100, 150, 200)');\n      expect(point.options.borderColor).toBe('rgb(50, 100, 150)');\n      expect(point.options.borderWidth).toBe(2);\n      expect(point.options.radius).toBe(20);\n    });\n\n    it ('should handle hover styles defined via dataset properties', async function() {\n      var chart = this.chart;\n      var point = chart.getDatasetMeta(0).data[0];\n\n      Chart.helpers.merge(chart.data.datasets[0], {\n        hoverBackgroundColor: 'rgb(200, 100, 150)',\n        hoverBorderColor: 'rgb(150, 50, 100)',\n        hoverBorderWidth: 8.4,\n        hoverRadius: 4.2\n      });\n\n      chart.update();\n\n      await jasmine.triggerMouseEvent(chart, 'mousemove', point);\n      expect(point.options.backgroundColor).toBe('rgb(200, 100, 150)');\n      expect(point.options.borderColor).toBe('rgb(150, 50, 100)');\n      expect(point.options.borderWidth).toBe(8.4);\n      expect(point.options.radius).toBe(20 + 4.2);\n\n      await jasmine.triggerMouseEvent(chart, 'mouseout', point);\n      expect(point.options.backgroundColor).toBe('rgb(100, 150, 200)');\n      expect(point.options.borderColor).toBe('rgb(50, 100, 150)');\n      expect(point.options.borderWidth).toBe(2);\n      expect(point.options.radius).toBe(20);\n    });\n\n    it ('should handle hover styles defined via element options', async function() {\n      var chart = this.chart;\n      var point = chart.getDatasetMeta(0).data[0];\n\n      Chart.helpers.merge(chart.options.elements.point, {\n        hoverBackgroundColor: 'rgb(200, 100, 150)',\n        hoverBorderColor: 'rgb(150, 50, 100)',\n        hoverBorderWidth: 8.4,\n        hoverRadius: 4.2\n      });\n\n      chart.update();\n\n      await jasmine.triggerMouseEvent(chart, 'mousemove', point);\n      expect(point.options.backgroundColor).toBe('rgb(200, 100, 150)');\n      expect(point.options.borderColor).toBe('rgb(150, 50, 100)');\n      expect(point.options.borderWidth).toBe(8.4);\n      expect(point.options.radius).toBe(20 + 4.2);\n\n      await jasmine.triggerMouseEvent(chart, 'mouseout', point);\n      expect(point.options.backgroundColor).toBe('rgb(100, 150, 200)');\n      expect(point.options.borderColor).toBe('rgb(50, 100, 150)');\n      expect(point.options.borderWidth).toBe(2);\n      expect(point.options.radius).toBe(20);\n    });\n  });\n\n  it('should not override tooltip title and label callbacks', async() => {\n    const chart = window.acquireChart({\n      type: 'bubble',\n      data: {\n        labels: ['Label 1', 'Label 2'],\n        datasets: [{\n          data: [{\n            x: 10,\n            y: 15,\n            r: 15\n          },\n          {\n            x: 12,\n            y: 10,\n            r: 10\n          }],\n          label: 'Dataset 1'\n        }, {\n          data: [{\n            x: 20,\n            y: 10,\n            r: 5\n          },\n          {\n            x: 4,\n            y: 8,\n            r: 30\n          }],\n          label: 'Dataset 2'\n        }]\n      },\n      options: {\n        responsive: true,\n        maintainAspectRatio: true,\n      }\n    });\n    const {tooltip} = chart;\n    const point = chart.getDatasetMeta(0).data[0];\n\n    await jasmine.triggerMouseEvent(chart, 'mousemove', point);\n\n    expect(tooltip.title).toEqual(['Label 1']);\n    expect(tooltip.body).toEqual([{\n      before: [],\n      lines: ['Dataset 1: (10, 15, 15)'],\n      after: []\n    }]);\n\n    chart.options.plugins.tooltip = {mode: 'dataset'};\n    chart.update();\n    await jasmine.triggerMouseEvent(chart, 'mousemove', point);\n\n    expect(tooltip.title).toEqual(['Dataset 1']);\n    expect(tooltip.body).toEqual([{\n      before: [],\n      lines: ['Label 1: (10, 15, 15)'],\n      after: []\n    }, {\n      before: [],\n      lines: ['Label 2: (12, 10, 10)'],\n      after: []\n    }]);\n  });\n});\n"
  },
  {
    "path": "test/specs/controller.doughnut.tests.js",
    "content": "describe('Chart.controllers.doughnut', function() {\n  describe('auto', jasmine.fixture.specs('controller.doughnut'));\n\n  it('should be registered as dataset controller', function() {\n    expect(typeof Chart.controllers.doughnut).toBe('function');\n    expect(typeof Chart.controllers.pie).toBe('function');\n  });\n\n  it('should be constructed', function() {\n    var chart = window.acquireChart({\n      type: 'doughnut',\n      data: {\n        datasets: [{\n          data: []\n        }],\n        labels: []\n      }\n    });\n\n    var meta = chart.getDatasetMeta(0);\n    expect(meta.type).toBe('doughnut');\n    expect(meta.controller).not.toBe(undefined);\n    expect(meta.controller.index).toBe(0);\n    expect(meta.data).toEqual([]);\n\n    meta.controller.updateIndex(1);\n    expect(meta.controller.index).toBe(1);\n  });\n\n  it('should create arc elements for each data item during initialization', function() {\n    var chart = window.acquireChart({\n      type: 'doughnut',\n      data: {\n        datasets: [{\n          data: [10, 15, 0, 4]\n        }],\n        labels: []\n      }\n    });\n\n    var meta = chart.getDatasetMeta(0);\n    expect(meta.data.length).toBe(4); // 4 arcs created\n    expect(meta.data[0] instanceof Chart.elements.ArcElement).toBe(true);\n    expect(meta.data[1] instanceof Chart.elements.ArcElement).toBe(true);\n    expect(meta.data[2] instanceof Chart.elements.ArcElement).toBe(true);\n    expect(meta.data[3] instanceof Chart.elements.ArcElement).toBe(true);\n  });\n\n  it ('should reset and update elements', function() {\n    var chart = window.acquireChart({\n      type: 'doughnut',\n      data: {\n        datasets: [{\n          data: [1, 2, 3, 4],\n          hidden: true\n        }, {\n          data: [5, 6, 0, 7]\n        }, {\n          data: [8, 9, 10, 11]\n        }],\n        labels: ['label0', 'label1', 'label2', 'label3']\n      },\n      options: {\n        plugins: {\n          legend: false,\n          title: false,\n        },\n        animation: {\n          duration: 0,\n          animateRotate: true,\n          animateScale: false\n        },\n        cutout: '50%',\n        rotation: 0,\n        circumference: 360,\n        elements: {\n          arc: {\n            backgroundColor: 'rgb(255, 0, 0)',\n            borderColor: 'rgb(0, 0, 255)',\n            borderWidth: 2\n          }\n        }\n      }\n    });\n\n    var meta = chart.getDatasetMeta(1);\n\n    meta.controller.reset(); // reset first\n\n    expect(meta.data.length).toBe(4);\n\n    [\n      {c: 0},\n      {c: 0},\n      {c: 0},\n      {c: 0}\n    ].forEach(function(expected, i) {\n      expect(meta.data[i].x).toBeCloseToPixel(256);\n      expect(meta.data[i].y).toBeCloseToPixel(256);\n      expect(meta.data[i].outerRadius).toBeCloseToPixel(256);\n      expect(meta.data[i].innerRadius).toBeCloseToPixel(192);\n      expect(meta.data[i].circumference).toBeCloseTo(expected.c, 8);\n      expect(meta.data[i].startAngle).toBeCloseToPixel(Math.PI * -0.5);\n      expect(meta.data[i].endAngle).toBeCloseToPixel(Math.PI * -0.5);\n      expect(meta.data[i].options).toEqual(jasmine.objectContaining({\n        backgroundColor: 'rgb(255, 0, 0)',\n        borderColor: 'rgb(0, 0, 255)',\n        borderWidth: 2\n      }));\n    });\n\n    chart.update();\n\n    [\n      {c: 1.7453292519, s: -1.5707963267, e: 0.1745329251},\n      {c: 2.0943951023, s: 0.1745329251, e: 2.2689280275},\n      {c: 0, s: 2.2689280275, e: 2.2689280275},\n      {c: 2.4434609527, s: 2.2689280275, e: 4.7123889803}\n    ].forEach(function(expected, i) {\n      expect(meta.data[i].x).toBeCloseToPixel(256);\n      expect(meta.data[i].y).toBeCloseToPixel(256);\n      expect(meta.data[i].outerRadius).toBeCloseToPixel(256);\n      expect(meta.data[i].innerRadius).toBeCloseToPixel(192);\n      expect(meta.data[i].circumference).toBeCloseTo(expected.c, 8);\n      expect(meta.data[i].startAngle).toBeCloseTo(expected.s, 8);\n      expect(meta.data[i].endAngle).toBeCloseTo(expected.e, 8);\n      expect(meta.data[i].options).toEqual(jasmine.objectContaining({\n        backgroundColor: 'rgb(255, 0, 0)',\n        borderColor: 'rgb(0, 0, 255)',\n        borderWidth: 2\n      }));\n    });\n\n    // Change the amount of data and ensure that arcs are updated accordingly\n    chart.data.datasets[1].data = [1, 2]; // remove 2 elements from dataset 0\n    chart.update();\n\n    expect(meta.data.length).toBe(2);\n    expect(meta.data[0] instanceof Chart.elements.ArcElement).toBe(true);\n    expect(meta.data[1] instanceof Chart.elements.ArcElement).toBe(true);\n\n    // Add data\n    chart.data.datasets[1].data = [1, 2, 3, 4];\n    chart.update();\n\n    expect(meta.data.length).toBe(4);\n    expect(meta.data[0] instanceof Chart.elements.ArcElement).toBe(true);\n    expect(meta.data[1] instanceof Chart.elements.ArcElement).toBe(true);\n    expect(meta.data[2] instanceof Chart.elements.ArcElement).toBe(true);\n    expect(meta.data[3] instanceof Chart.elements.ArcElement).toBe(true);\n  });\n\n  it ('should rotate and limit circumference', function() {\n    var chart = window.acquireChart({\n      type: 'doughnut',\n      data: {\n        datasets: [{\n          data: [2, 4],\n          hidden: true\n        }, {\n          data: [1, 3]\n        }, {\n          data: [1, 0]\n        }],\n        labels: ['label0', 'label1', 'label2']\n      },\n      options: {\n        plugins: {\n          legend: false,\n          title: false,\n        },\n        cutout: '50%',\n        rotation: 270,\n        circumference: 90,\n        elements: {\n          arc: {\n            backgroundColor: 'rgb(255, 0, 0)',\n            borderColor: 'rgb(0, 0, 255)',\n            borderWidth: 2\n          }\n        }\n      }\n    });\n\n    var meta = chart.getDatasetMeta(1);\n\n    expect(meta.data.length).toBe(2);\n\n    // Only startAngle, endAngle and circumference should be different.\n    [\n      {c: Math.PI / 8, s: Math.PI, e: Math.PI + Math.PI / 8},\n      {c: 3 * Math.PI / 8, s: Math.PI + Math.PI / 8, e: Math.PI + Math.PI / 2}\n    ].forEach(function(expected, i) {\n      expect(meta.data[i].x).toBeCloseToPixel(512);\n      expect(meta.data[i].y).toBeCloseToPixel(512);\n      expect(meta.data[i].outerRadius).toBeCloseToPixel(512);\n      expect(meta.data[i].innerRadius).toBeCloseToPixel(384);\n      expect(meta.data[i].circumference).toBeCloseTo(expected.c, 8);\n      expect(meta.data[i].startAngle).toBeCloseTo(expected.s, 8);\n      expect(meta.data[i].endAngle).toBeCloseTo(expected.e, 8);\n    });\n  });\n\n  it('should treat negative values as positive', function() {\n    var chart = window.acquireChart({\n      type: 'doughnut',\n      data: {\n        datasets: [{\n          data: [-1, -3]\n        }],\n        labels: ['label0', 'label1']\n      },\n      options: {\n        plugins: {\n          legend: false,\n          title: false\n        },\n        cutout: '50%',\n        rotation: 270,\n        circumference: 90,\n        elements: {\n          arc: {\n            backgroundColor: 'rgb(255, 0, 0)',\n            borderColor: 'rgb(0, 0, 255)',\n            borderWidth: 2\n          }\n        }\n      }\n    });\n\n    var meta = chart.getDatasetMeta(0);\n\n    expect(meta.data.length).toBe(2);\n\n    // Only startAngle, endAngle and circumference should be different.\n    [\n      {c: Math.PI / 8, s: Math.PI, e: Math.PI + Math.PI / 8},\n      {c: 3 * Math.PI / 8, s: Math.PI + Math.PI / 8, e: Math.PI + Math.PI / 2}\n    ].forEach(function(expected, i) {\n      expect(meta.data[i].circumference).toBeCloseTo(expected.c, 8);\n      expect(meta.data[i].startAngle).toBeCloseTo(expected.s, 8);\n      expect(meta.data[i].endAngle).toBeCloseTo(expected.e, 8);\n    });\n  });\n\n  it ('should draw all arcs', function() {\n    var chart = window.acquireChart({\n      type: 'doughnut',\n      data: {\n        datasets: [{\n          data: [10, 15, 0, 4]\n        }],\n        labels: ['label0', 'label1', 'label2', 'label3']\n      }\n    });\n\n    var meta = chart.getDatasetMeta(0);\n\n    spyOn(meta.data[0], 'draw');\n    spyOn(meta.data[1], 'draw');\n    spyOn(meta.data[2], 'draw');\n    spyOn(meta.data[3], 'draw');\n\n    chart.update();\n\n    expect(meta.data[0].draw.calls.count()).toBe(1);\n    expect(meta.data[1].draw.calls.count()).toBe(1);\n    expect(meta.data[2].draw.calls.count()).toBe(1);\n    expect(meta.data[3].draw.calls.count()).toBe(1);\n  });\n\n  it ('should calculate radiuses based on the border widths of the visible outermost dataset', function() {\n    var chart = window.acquireChart({\n      type: 'doughnut',\n      data: {\n        datasets: [{\n          data: [2, 4],\n          borderWidth: 4,\n          hidden: true\n        }, {\n          data: [1, 3],\n          borderWidth: 8\n        }, {\n          data: [1, 0],\n          borderWidth: 12\n        }],\n        labels: ['label0', 'label1']\n      },\n      options: {\n        plugins: {\n          legend: false,\n          title: false\n        }\n      }\n    });\n\n    chart.update();\n\n    var controller = chart.getDatasetMeta(0).controller;\n    expect(chart.chartArea.bottom - chart.chartArea.top).toBe(512);\n\n    expect(controller.getMaxBorderWidth()).toBe(8);\n    expect(controller.outerRadius).toBe(252);\n    expect(controller.innerRadius).toBe(189);\n\n    controller = chart.getDatasetMeta(1).controller;\n    expect(controller.getMaxBorderWidth()).toBe(8);\n    expect(controller.outerRadius).toBe(252);\n    expect(controller.innerRadius).toBe(189);\n\n    controller = chart.getDatasetMeta(2).controller;\n    expect(controller.getMaxBorderWidth()).toBe(8);\n    expect(controller.outerRadius).toBe(189);\n    expect(controller.innerRadius).toBe(126);\n  });\n\n  describe('Interactions', function() {\n    beforeEach(function() {\n      this.chart = window.acquireChart({\n        type: 'doughnut',\n        data: {\n          labels: ['label1', 'label2', 'label3', 'label4'],\n          datasets: [{\n            data: [10, 15, 0, 4]\n          }]\n        },\n        options: {\n          cutout: '50%',\n          elements: {\n            arc: {\n              backgroundColor: 'rgb(100, 150, 200)',\n              borderColor: 'rgb(50, 100, 150)',\n              borderWidth: 2,\n            }\n          }\n        }\n      });\n    });\n\n    it ('should handle default hover styles', async function() {\n      var chart = this.chart;\n      var arc = chart.getDatasetMeta(0).data[0];\n\n      await jasmine.triggerMouseEvent(chart, 'mousemove', arc);\n      expect(arc.options.backgroundColor).toBe('#3187DD');\n      expect(arc.options.borderColor).toBe('#175A9D');\n      expect(arc.options.borderWidth).toBe(2);\n\n      await jasmine.triggerMouseEvent(chart, 'mouseout', arc);\n      expect(arc.options.backgroundColor).toBe('rgb(100, 150, 200)');\n      expect(arc.options.borderColor).toBe('rgb(50, 100, 150)');\n      expect(arc.options.borderWidth).toBe(2);\n    });\n\n    it ('should handle hover styles defined via dataset properties', async function() {\n      var chart = this.chart;\n      var arc = chart.getDatasetMeta(0).data[0];\n\n      Chart.helpers.merge(chart.data.datasets[0], {\n        hoverBackgroundColor: 'rgb(200, 100, 150)',\n        hoverBorderColor: 'rgb(150, 50, 100)',\n        hoverBorderWidth: 8.4,\n      });\n\n      chart.update();\n\n      await jasmine.triggerMouseEvent(chart, 'mousemove', arc);\n      expect(arc.options.backgroundColor).toBe('rgb(200, 100, 150)');\n      expect(arc.options.borderColor).toBe('rgb(150, 50, 100)');\n      expect(arc.options.borderWidth).toBe(8.4);\n\n      await jasmine.triggerMouseEvent(chart, 'mouseout', arc);\n      expect(arc.options.backgroundColor).toBe('rgb(100, 150, 200)');\n      expect(arc.options.borderColor).toBe('rgb(50, 100, 150)');\n      expect(arc.options.borderWidth).toBe(2);\n    });\n\n    it ('should handle hover styles defined via element options', async function() {\n      var chart = this.chart;\n      var arc = chart.getDatasetMeta(0).data[0];\n\n      Chart.helpers.merge(chart.options.elements.arc, {\n        hoverBackgroundColor: 'rgb(200, 100, 150)',\n        hoverBorderColor: 'rgb(150, 50, 100)',\n        hoverBorderWidth: 8.4,\n      });\n\n      chart.update();\n\n      await jasmine.triggerMouseEvent(chart, 'mousemove', arc);\n      expect(arc.options.backgroundColor).toBe('rgb(200, 100, 150)');\n      expect(arc.options.borderColor).toBe('rgb(150, 50, 100)');\n      expect(arc.options.borderWidth).toBe(8.4);\n\n      await jasmine.triggerMouseEvent(chart, 'mouseout', arc);\n      expect(arc.options.backgroundColor).toBe('rgb(100, 150, 200)');\n      expect(arc.options.borderColor).toBe('rgb(50, 100, 150)');\n      expect(arc.options.borderWidth).toBe(2);\n    });\n  });\n\n  it('should not override tooltip title and label callbacks', async() => {\n    const chart = window.acquireChart({\n      type: 'doughnut',\n      data: {\n        labels: ['Label 1', 'Label 2'],\n        datasets: [{\n          data: [21, 79],\n          label: 'Dataset 1'\n        }, {\n          data: [33, 67],\n          label: 'Dataset 2'\n        }]\n      },\n      options: {\n        responsive: true,\n        maintainAspectRatio: true,\n      }\n    });\n    const {tooltip} = chart;\n    const point = chart.getDatasetMeta(0).data[0];\n\n    await jasmine.triggerMouseEvent(chart, 'mousemove', point);\n\n    expect(tooltip.title).toEqual(['Label 1']);\n    expect(tooltip.body).toEqual([{\n      before: [],\n      lines: ['Dataset 1: 21'],\n      after: []\n    }]);\n\n    chart.options.plugins.tooltip = {mode: 'dataset'};\n    chart.update();\n    await jasmine.triggerMouseEvent(chart, 'mousemove', point);\n\n    expect(tooltip.title).toEqual(['Dataset 1']);\n    expect(tooltip.body).toEqual([{\n      before: [],\n      lines: ['Label 1: 21'],\n      after: []\n    }, {\n      before: [],\n      lines: ['Label 2: 79'],\n      after: []\n    }]);\n  });\n});\n"
  },
  {
    "path": "test/specs/controller.line.tests.js",
    "content": "describe('Chart.controllers.line', function() {\n  describe('auto', jasmine.fixture.specs('controller.line'));\n\n  it('should be registered as dataset controller', function() {\n    expect(typeof Chart.controllers.line).toBe('function');\n  });\n\n  it('should be constructed', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          data: []\n        }],\n        labels: []\n      }\n    });\n\n    var meta = chart.getDatasetMeta(0);\n    expect(meta.type).toBe('line');\n    expect(meta.controller).not.toBe(undefined);\n    expect(meta.controller.index).toBe(0);\n    expect(meta.data).toEqual([]);\n\n    meta.controller.updateIndex(1);\n    expect(meta.controller.index).toBe(1);\n  });\n\n  it('Should use the first scale IDs if the dataset does not specify them', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          data: []\n        }],\n        labels: []\n      },\n    });\n\n    var meta = chart.getDatasetMeta(0);\n    expect(meta.xAxisID).toBe('x');\n    expect(meta.yAxisID).toBe('y');\n  });\n\n  it('Should not throw with empty dataset when tension is non-zero', function() {\n    // https://github.com/chartjs/Chart.js/issues/8676\n    function createChart() {\n      return window.acquireChart({\n        type: 'line',\n        data: {\n          datasets: [{\n            data: [],\n            tension: 0.5\n          }],\n          labels: []\n        },\n      });\n    }\n    expect(createChart).not.toThrow();\n  });\n\n  it('should find min and max for stacked chart', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          data: [10, 11, 12, 13]\n        }, {\n          data: [1, 2, 3, 4]\n        }],\n        labels: ['a', 'b', 'c', 'd']\n      },\n      options: {\n        scales: {\n          y: {\n            stacked: true\n          }\n        }\n      }\n    });\n    expect(chart.getDatasetMeta(0).controller.getMinMax(chart.scales.y, true)).toEqual({min: 10, max: 13});\n    expect(chart.getDatasetMeta(1).controller.getMinMax(chart.scales.y, true)).toEqual({min: 11, max: 17});\n    chart.hide(0);\n    expect(chart.getDatasetMeta(0).controller.getMinMax(chart.scales.y, true)).toEqual({min: 10, max: 13});\n    expect(chart.getDatasetMeta(1).controller.getMinMax(chart.scales.y, true)).toEqual({min: 1, max: 4});\n  });\n\n  it('Should create line elements and point elements for each data item during initialization', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          data: [10, 15, 0, -4],\n          label: 'dataset1'\n        }],\n        labels: ['label1', 'label2', 'label3', 'label4']\n      }\n    });\n\n    var meta = chart.getDatasetMeta(0);\n    expect(meta.data.length).toBe(4); // 4 points created\n    expect(meta.data[0] instanceof Chart.elements.PointElement).toBe(true);\n    expect(meta.data[1] instanceof Chart.elements.PointElement).toBe(true);\n    expect(meta.data[2] instanceof Chart.elements.PointElement).toBe(true);\n    expect(meta.data[3] instanceof Chart.elements.PointElement).toBe(true);\n    expect(meta.dataset instanceof Chart.elements.LineElement).toBe(true); // 1 line element\n  });\n\n  it('should draw all elements', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          data: [10, 15, 0, -4],\n          label: 'dataset1'\n        }],\n        labels: ['label1', 'label2', 'label3', 'label4']\n      },\n      options: {\n        showLine: true\n      }\n    });\n\n    var meta = chart.getDatasetMeta(0);\n    spyOn(meta.dataset, 'updateControlPoints');\n    spyOn(meta.dataset, 'draw');\n    spyOn(meta.data[0], 'draw');\n    spyOn(meta.data[1], 'draw');\n    spyOn(meta.data[2], 'draw');\n    spyOn(meta.data[3], 'draw');\n\n    chart.update();\n\n    expect(meta.dataset.updateControlPoints.calls.count()).toBeGreaterThanOrEqual(1);\n    expect(meta.dataset.draw.calls.count()).toBe(1);\n    expect(meta.data[0].draw.calls.count()).toBe(1);\n    expect(meta.data[1].draw.calls.count()).toBe(1);\n    expect(meta.data[2].draw.calls.count()).toBe(1);\n    expect(meta.data[3].draw.calls.count()).toBe(1);\n  });\n\n  it('should update elements when modifying data', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          data: [10, 15, 0, -4],\n          label: 'dataset',\n          xAxisID: 'x',\n          yAxisID: 'y'\n        }],\n        labels: ['label1', 'label2', 'label3', 'label4']\n      },\n      options: {\n        showLine: true,\n        plugins: {\n          legend: false,\n          title: false\n        },\n        elements: {\n          point: {\n            backgroundColor: 'red',\n            borderColor: 'blue',\n          }\n        },\n        scales: {\n          x: {\n            display: false\n          },\n          y: {\n            display: false\n          }\n        }\n      },\n    });\n\n    var meta = chart.getDatasetMeta(0);\n    expect(meta.data.length).toBe(4);\n\n    chart.data.datasets[0].data = [1, 2]; // remove 2 items\n    chart.data.datasets[0].borderWidth = 1;\n    chart.update();\n\n    expect(meta.data.length).toBe(2);\n    expect(meta._parsed.length).toBe(2);\n\n    [\n      {x: 5, y: 507},\n      {x: 171, y: 5}\n    ].forEach(function(expected, i) {\n      expect(meta.data[i].x).toBeCloseToPixel(expected.x);\n      expect(meta.data[i].y).toBeCloseToPixel(expected.y);\n      expect(meta.data[i].options).toEqual(jasmine.objectContaining({\n        backgroundColor: 'red',\n        borderColor: 'blue',\n      }));\n    });\n\n    chart.data.datasets[0].data = [1, 2, 3]; // add 1 items\n    chart.update();\n\n    expect(meta.data.length).toBe(3); // should add a new meta data item\n  });\n\n  it('should correctly calculate x scale for label and point', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        labels: ['One'],\n        datasets: [{\n          data: [1],\n        }]\n      },\n      options: {\n        plugins: {\n          legend: false,\n          title: false\n        },\n        hover: {\n          mode: 'nearest',\n          intersect: true\n        },\n        scales: {\n          x: {\n            display: false,\n          },\n          y: {\n            display: false,\n            beginAtZero: true\n          }\n        }\n      }\n    });\n\n    var meta = chart.getDatasetMeta(0);\n    // 1 point\n    var point = meta.data[0];\n    expect(point.x).toBeCloseToPixel(5);\n\n    // 2 points\n    chart.data.labels = ['One', 'Two'];\n    chart.data.datasets[0].data = [1, 2];\n    chart.update();\n\n    var points = meta.data;\n\n    expect(points[0].x).toBeCloseToPixel(5);\n    expect(points[1].x).toBeCloseToPixel(507);\n\n    // 3 points\n    chart.data.labels = ['One', 'Two', 'Three'];\n    chart.data.datasets[0].data = [1, 2, 3];\n    chart.update();\n\n    points = meta.data;\n\n    expect(points[0].x).toBeCloseToPixel(5);\n    expect(points[1].x).toBeCloseToPixel(256);\n    expect(points[2].x).toBeCloseToPixel(507);\n\n    // 4 points\n    chart.data.labels = ['One', 'Two', 'Three', 'Four'];\n    chart.data.datasets[0].data = [1, 2, 3, 4];\n    chart.update();\n\n    points = meta.data;\n\n    expect(points[0].x).toBeCloseToPixel(5);\n    expect(points[1].x).toBeCloseToPixel(171);\n    expect(points[2].x).toBeCloseToPixel(340);\n    expect(points[3].x).toBeCloseToPixel(507);\n  });\n\n  it('should update elements when the y scale is stacked', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          data: [10, -10, 10, -10],\n          label: 'dataset1'\n        }, {\n          data: [10, 15, 0, -4],\n          label: 'dataset2'\n        }],\n        labels: ['label1', 'label2', 'label3', 'label4']\n      },\n      options: {\n        plugins: {\n          legend: false,\n          title: false\n        },\n        scales: {\n          x: {\n            display: false,\n          },\n          y: {\n            display: false,\n            stacked: true\n          }\n        }\n      }\n    });\n\n    var meta0 = chart.getDatasetMeta(0);\n\n    [\n      {x: 5, y: 148},\n      {x: 171, y: 435},\n      {x: 341, y: 148},\n      {x: 507, y: 435}\n    ].forEach(function(values, i) {\n      expect(meta0.data[i].x).toBeCloseToPixel(values.x);\n      expect(meta0.data[i].y).toBeCloseToPixel(values.y);\n    });\n\n    var meta1 = chart.getDatasetMeta(1);\n\n    [\n      {x: 5, y: 5},\n      {x: 171, y: 76},\n      {x: 341, y: 148},\n      {x: 507, y: 492}\n    ].forEach(function(values, i) {\n      expect(meta1.data[i].x).toBeCloseToPixel(values.x);\n      expect(meta1.data[i].y).toBeCloseToPixel(values.y);\n    });\n\n  });\n\n  it('should update elements when the y scale is stacked with multiple axes', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          data: [10, -10, 10, -10],\n          label: 'dataset1'\n        }, {\n          data: [10, 15, 0, -4],\n          label: 'dataset2'\n        }, {\n          data: [10, 10, -10, -10],\n          label: 'dataset3',\n          yAxisID: 'y2'\n        }],\n        labels: ['label1', 'label2', 'label3', 'label4']\n      },\n      options: {\n        plugins: {\n          legend: false,\n          title: false,\n        },\n        scales: {\n          x: {\n            display: false,\n          },\n          y: {\n            display: false,\n            stacked: true\n          },\n          y2: {\n            type: 'linear',\n            position: 'right',\n            display: false\n          }\n        }\n      }\n    });\n\n    var meta0 = chart.getDatasetMeta(0);\n\n    [\n      {x: 5, y: 148},\n      {x: 171, y: 435},\n      {x: 341, y: 148},\n      {x: 507, y: 435}\n    ].forEach(function(values, i) {\n      expect(meta0.data[i].x).toBeCloseToPixel(values.x);\n      expect(meta0.data[i].y).toBeCloseToPixel(values.y);\n    });\n\n    var meta1 = chart.getDatasetMeta(1);\n\n    [\n      {x: 5, y: 5},\n      {x: 171, y: 76},\n      {x: 341, y: 148},\n      {x: 507, y: 492}\n    ].forEach(function(values, i) {\n      expect(meta1.data[i].x).toBeCloseToPixel(values.x);\n      expect(meta1.data[i].y).toBeCloseToPixel(values.y);\n    });\n\n  });\n\n  it('should update elements when the y scale is stacked and datasets is scatter data', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          data: [{\n            x: 0,\n            y: 10\n          }, {\n            x: 1,\n            y: -10\n          }, {\n            x: 2,\n            y: 10\n          }, {\n            x: 3,\n            y: -10\n          }],\n          label: 'dataset1'\n        }, {\n          data: [{\n            x: 0,\n            y: 10\n          }, {\n            x: 1,\n            y: 15\n          }, {\n            x: 2,\n            y: 0\n          }, {\n            x: 3,\n            y: -4\n          }],\n          label: 'dataset2'\n        }],\n        labels: ['label1', 'label2', 'label3', 'label4']\n      },\n      options: {\n        plugins: {\n          legend: false,\n          title: false\n        },\n        scales: {\n          x: {\n            display: false,\n          },\n          y: {\n            display: false,\n            stacked: true\n          }\n        }\n      }\n    });\n\n    var meta0 = chart.getDatasetMeta(0);\n\n    [\n      {x: 5, y: 148},\n      {x: 171, y: 435},\n      {x: 341, y: 148},\n      {x: 507, y: 435}\n    ].forEach(function(values, i) {\n      expect(meta0.data[i].x).toBeCloseToPixel(values.x);\n      expect(meta0.data[i].y).toBeCloseToPixel(values.y);\n    });\n\n    var meta1 = chart.getDatasetMeta(1);\n\n    [\n      {x: 5, y: 5},\n      {x: 171, y: 76},\n      {x: 341, y: 148},\n      {x: 507, y: 492}\n    ].forEach(function(values, i) {\n      expect(meta1.data[i].x).toBeCloseToPixel(values.x);\n      expect(meta1.data[i].y).toBeCloseToPixel(values.y);\n    });\n\n  });\n\n  it('should update elements when the y scale is stacked and data is strings', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          data: ['10', '-10', '10', '-10'],\n          label: 'dataset1'\n        }, {\n          data: ['10', '15', '0', '-4'],\n          label: 'dataset2'\n        }],\n        labels: ['label1', 'label2', 'label3', 'label4']\n      },\n      options: {\n        plugins: {\n          legend: false,\n          title: false\n        },\n        scales: {\n          x: {\n            display: false,\n          },\n          y: {\n            display: false,\n            stacked: true\n          }\n        }\n      }\n    });\n\n    var meta0 = chart.getDatasetMeta(0);\n\n    [\n      {x: 5, y: 148},\n      {x: 171, y: 435},\n      {x: 341, y: 148},\n      {x: 507, y: 435}\n    ].forEach(function(values, i) {\n      expect(meta0.data[i].x).toBeCloseToPixel(values.x);\n      expect(meta0.data[i].y).toBeCloseToPixel(values.y);\n    });\n\n    var meta1 = chart.getDatasetMeta(1);\n\n    [\n      {x: 5, y: 5},\n      {x: 171, y: 76},\n      {x: 341, y: 148},\n      {x: 507, y: 492}\n    ].forEach(function(values, i) {\n      expect(meta1.data[i].x).toBeCloseToPixel(values.x);\n      expect(meta1.data[i].y).toBeCloseToPixel(values.y);\n    });\n\n  });\n\n  it('should fall back to the line styles for points', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          data: [0, 0],\n          label: 'dataset1',\n\n          // line styles\n          backgroundColor: 'rgb(98, 98, 98)',\n          borderColor: 'rgb(8, 8, 8)',\n          borderWidth: 0.55,\n        }],\n        labels: ['label1', 'label2']\n      }\n    });\n\n    var meta = chart.getDatasetMeta(0);\n\n    expect(meta.dataset.options.backgroundColor).toBe('rgb(98, 98, 98)');\n    expect(meta.dataset.options.borderColor).toBe('rgb(8, 8, 8)');\n    expect(meta.dataset.options.borderWidth).toBe(0.55);\n  });\n\n  describe('dataset global defaults', function() {\n    beforeEach(function() {\n      this._defaults = Chart.helpers.clone(Chart.defaults.datasets.line);\n    });\n\n    afterEach(function() {\n      Chart.defaults.datasets.line = this._defaults;\n      delete this._defaults;\n    });\n\n    it('should utilize the dataset global default options', function() {\n      Chart.helpers.merge(Chart.defaults.datasets.line, {\n        spanGaps: true,\n        tension: 0.231,\n        backgroundColor: '#add',\n        borderWidth: '#daa',\n        borderColor: '#dad',\n        borderCapStyle: 'round',\n        borderDash: [0],\n        borderDashOffset: 0.871,\n        borderJoinStyle: 'miter',\n        fill: 'start',\n        cubicInterpolationMode: 'monotone'\n      });\n\n      var chart = window.acquireChart({\n        type: 'line',\n        data: {\n          datasets: [{\n            data: [0, 0],\n            label: 'dataset1'\n          }],\n          labels: ['label1', 'label2']\n        }\n      });\n\n      var options = chart.getDatasetMeta(0).dataset.options;\n\n      expect(options.spanGaps).toBe(true);\n      expect(options.tension).toBe(0.231);\n      expect(options.backgroundColor).toBe('#add');\n      expect(options.borderWidth).toBe('#daa');\n      expect(options.borderColor).toBe('#dad');\n      expect(options.borderCapStyle).toBe('round');\n      expect(options.borderDash).toEqual([0]);\n      expect(options.borderDashOffset).toBe(0.871);\n      expect(options.borderJoinStyle).toBe('miter');\n      expect(options.fill).toBe('start');\n      expect(options.cubicInterpolationMode).toBe('monotone');\n    });\n\n    it('should be overridden by user-supplied values', function() {\n      Chart.helpers.merge(Chart.defaults.datasets.line, {\n        spanGaps: true,\n        tension: 0.231\n      });\n\n      var chart = window.acquireChart({\n        type: 'line',\n        data: {\n          datasets: [{\n            data: [0, 0],\n            label: 'dataset1',\n            spanGaps: true,\n            backgroundColor: '#dad'\n          }],\n          labels: ['label1', 'label2']\n        },\n        options: {\n          datasets: {\n            line: {\n              tension: 0.345,\n              backgroundColor: '#add'\n            }\n          }\n        }\n      });\n\n      var options = chart.getDatasetMeta(0).dataset.options;\n\n      // dataset-level option overrides global default\n      expect(options.spanGaps).toBe(true);\n      // chart-level default overrides global default\n      expect(options.tension).toBe(0.345);\n      // dataset-level option overrides chart-level default\n      expect(options.backgroundColor).toBe('#dad');\n    });\n  });\n\n  it('should obey the chart-level dataset options', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          data: [0, 0],\n          label: 'dataset1'\n        }],\n        labels: ['label1', 'label2']\n      },\n      options: {\n        datasets: {\n          line: {\n            spanGaps: true,\n            tension: 0.231,\n            backgroundColor: '#add',\n            borderWidth: '#daa',\n            borderColor: '#dad',\n            borderCapStyle: 'round',\n            borderDash: [0],\n            borderDashOffset: 0.871,\n            borderJoinStyle: 'miter',\n            fill: 'start',\n            cubicInterpolationMode: 'monotone'\n          }\n        }\n      }\n    });\n\n    var options = chart.getDatasetMeta(0).dataset.options;\n\n    expect(options.spanGaps).toBe(true);\n    expect(options.tension).toBe(0.231);\n    expect(options.backgroundColor).toBe('#add');\n    expect(options.borderWidth).toBe('#daa');\n    expect(options.borderColor).toBe('#dad');\n    expect(options.borderCapStyle).toBe('round');\n    expect(options.borderDash).toEqual([0]);\n    expect(options.borderDashOffset).toBe(0.871);\n    expect(options.borderJoinStyle).toBe('miter');\n    expect(options.fill).toBe('start');\n    expect(options.cubicInterpolationMode).toBe('monotone');\n  });\n\n  it('should obey the dataset options', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          data: [0, 0],\n          label: 'dataset1',\n          spanGaps: true,\n          tension: 0.231,\n          backgroundColor: '#add',\n          borderWidth: '#daa',\n          borderColor: '#dad',\n          borderCapStyle: 'round',\n          borderDash: [0],\n          borderDashOffset: 0.871,\n          borderJoinStyle: 'miter',\n          fill: 'start',\n          cubicInterpolationMode: 'monotone'\n        }],\n        labels: ['label1', 'label2']\n      }\n    });\n\n    var options = chart.getDatasetMeta(0).dataset.options;\n\n    expect(options.spanGaps).toBe(true);\n    expect(options.tension).toBe(0.231);\n    expect(options.backgroundColor).toBe('#add');\n    expect(options.borderWidth).toBe('#daa');\n    expect(options.borderColor).toBe('#dad');\n    expect(options.borderCapStyle).toBe('round');\n    expect(options.borderDash).toEqual([0]);\n    expect(options.borderDashOffset).toBe(0.871);\n    expect(options.borderJoinStyle).toBe('miter');\n    expect(options.fill).toBe('start');\n    expect(options.cubicInterpolationMode).toBe('monotone');\n  });\n\n  it('should handle number of data point changes in update', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          data: [10, 15, 0, -4],\n          label: 'dataset1',\n        }],\n        labels: ['label1', 'label2', 'label3', 'label4']\n      }\n    });\n\n    var meta = chart.getDatasetMeta(0);\n\n    chart.data.datasets[0].data = [1, 2]; // remove 2 items\n    chart.update();\n    expect(meta.data.length).toBe(2);\n    expect(meta.data[0] instanceof Chart.elements.PointElement).toBe(true);\n    expect(meta.data[1] instanceof Chart.elements.PointElement).toBe(true);\n\n    chart.data.datasets[0].data = [1, 2, 3, 4, 5]; // add 3 items\n    chart.update();\n    expect(meta.data.length).toBe(5);\n    expect(meta.data[0] instanceof Chart.elements.PointElement).toBe(true);\n    expect(meta.data[1] instanceof Chart.elements.PointElement).toBe(true);\n    expect(meta.data[2] instanceof Chart.elements.PointElement).toBe(true);\n    expect(meta.data[3] instanceof Chart.elements.PointElement).toBe(true);\n    expect(meta.data[4] instanceof Chart.elements.PointElement).toBe(true);\n  });\n\n  describe('Interactions', function() {\n    beforeEach(function() {\n      this.chart = window.acquireChart({\n        type: 'line',\n        data: {\n          labels: ['label1', 'label2', 'label3', 'label4'],\n          datasets: [{\n            data: [10, 15, 0, -4]\n          }]\n        },\n        options: {\n          scales: {\n            x: {\n              offset: true\n            }\n          },\n          elements: {\n            point: {\n              backgroundColor: 'rgb(100, 150, 200)',\n              borderColor: 'rgb(50, 100, 150)',\n              borderWidth: 2,\n              radius: 3\n            }\n          }\n        }\n      });\n    });\n\n    it ('should handle default hover styles', async function() {\n      var chart = this.chart;\n      var point = chart.getDatasetMeta(0).data[0];\n\n      await jasmine.triggerMouseEvent(chart, 'mousemove', point);\n      expect(point.options.backgroundColor).toBe('#3187DD');\n      expect(point.options.borderColor).toBe('#175A9D');\n      expect(point.options.borderWidth).toBe(1);\n      expect(point.options.radius).toBe(4);\n\n      await jasmine.triggerMouseEvent(chart, 'mouseout', point);\n      expect(point.options.backgroundColor).toBe('rgb(100, 150, 200)');\n      expect(point.options.borderColor).toBe('rgb(50, 100, 150)');\n      expect(point.options.borderWidth).toBe(2);\n      expect(point.options.radius).toBe(3);\n    });\n\n    it ('should handle hover styles defined via dataset properties', async function() {\n      var chart = this.chart;\n      var point = chart.getDatasetMeta(0).data[0];\n\n      Chart.helpers.merge(chart.data.datasets[0], {\n        hoverBackgroundColor: 'rgb(200, 100, 150)',\n        hoverBorderColor: 'rgb(150, 50, 100)',\n        hoverBorderWidth: 8.4,\n        hoverRadius: 4.2\n      });\n\n      chart.update();\n\n      await jasmine.triggerMouseEvent(chart, 'mousemove', point);\n      expect(point.options.backgroundColor).toBe('rgb(200, 100, 150)');\n      expect(point.options.borderColor).toBe('rgb(150, 50, 100)');\n      expect(point.options.borderWidth).toBe(8.4);\n      expect(point.options.radius).toBe(4.2);\n\n      await jasmine.triggerMouseEvent(chart, 'mouseout', point);\n      expect(point.options.backgroundColor).toBe('rgb(100, 150, 200)');\n      expect(point.options.borderColor).toBe('rgb(50, 100, 150)');\n      expect(point.options.borderWidth).toBe(2);\n      expect(point.options.radius).toBe(3);\n    });\n\n    it('should handle hover styles defined via element options', async function() {\n      var chart = this.chart;\n      var point = chart.getDatasetMeta(0).data[0];\n\n      Chart.helpers.merge(chart.options.elements.point, {\n        hoverBackgroundColor: 'rgb(200, 100, 150)',\n        hoverBorderColor: 'rgb(150, 50, 100)',\n        hoverBorderWidth: 8.4,\n        hoverRadius: 4.2\n      });\n\n      chart.update();\n\n      await jasmine.triggerMouseEvent(chart, 'mousemove', point);\n      expect(point.options.backgroundColor).toBe('rgb(200, 100, 150)');\n      expect(point.options.borderColor).toBe('rgb(150, 50, 100)');\n      expect(point.options.borderWidth).toBe(8.4);\n      expect(point.options.radius).toBe(4.2);\n\n      await jasmine.triggerMouseEvent(chart, 'mouseout', point);\n      expect(point.options.backgroundColor).toBe('rgb(100, 150, 200)');\n      expect(point.options.borderColor).toBe('rgb(50, 100, 150)');\n      expect(point.options.borderWidth).toBe(2);\n      expect(point.options.radius).toBe(3);\n    });\n\n    it('should handle dataset hover styles defined via dataset properties', async function() {\n      var chart = this.chart;\n      var point = chart.getDatasetMeta(0).data[0];\n      var dataset = chart.getDatasetMeta(0).dataset;\n\n      Chart.helpers.merge(chart.data.datasets[0], {\n        backgroundColor: '#AAA',\n        borderColor: '#BBB',\n        borderWidth: 6,\n        hoverBackgroundColor: '#000',\n        hoverBorderColor: '#111',\n        hoverBorderWidth: 12\n      });\n\n      chart.options.hover = {mode: 'dataset'};\n      chart.update();\n\n      await jasmine.triggerMouseEvent(chart, 'mousemove', point);\n      expect(dataset.options.backgroundColor).toBe('#000');\n      expect(dataset.options.borderColor).toBe('#111');\n      expect(dataset.options.borderWidth).toBe(12);\n\n      await jasmine.triggerMouseEvent(chart, 'mouseout', point);\n      expect(dataset.options.backgroundColor).toBe('#AAA');\n      expect(dataset.options.borderColor).toBe('#BBB');\n      expect(dataset.options.borderWidth).toBe(6);\n    });\n  });\n\n  it('should allow 0 as a point border width', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          data: [10, 15, 0, -4],\n          label: 'dataset1',\n          pointBorderWidth: 0\n        }],\n        labels: ['label1', 'label2', 'label3', 'label4']\n      }\n    });\n\n    var meta = chart.getDatasetMeta(0);\n    var point = meta.data[0];\n\n    expect(point.options.borderWidth).toBe(0);\n  });\n\n  it('should allow an array as the point border width setting', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          data: [10, 15, 0, -4],\n          label: 'dataset1',\n          pointBorderWidth: [1, 2, 3, 4]\n        }],\n        labels: ['label1', 'label2', 'label3', 'label4']\n      }\n    });\n\n    var meta = chart.getDatasetMeta(0);\n    expect(meta.data[0].options.borderWidth).toBe(1);\n    expect(meta.data[1].options.borderWidth).toBe(2);\n    expect(meta.data[2].options.borderWidth).toBe(3);\n    expect(meta.data[3].options.borderWidth).toBe(4);\n  });\n\n  it('should render a million points', function() {\n    var data = [];\n    for (let x = 0; x < 1e6; x++) {\n      data.push({x, y: Math.sin(x / 10000)});\n    }\n    function createChart() {\n      window.acquireChart({\n        type: 'line',\n        data: {\n          datasets: [{\n            data,\n            borderWidth: 1,\n            radius: 0\n          }],\n        },\n        options: {\n          scales: {\n            x: {type: 'linear'},\n            y: {type: 'linear'}\n          }\n        }\n      });\n    }\n    expect(createChart).not.toThrow();\n  });\n\n  it('should set skipped points to the reset state', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          data: [10, null, 0, -4],\n          label: 'dataset1',\n          pointBorderWidth: [1, 2, 3, 4]\n        }],\n        labels: ['label1', 'label2', 'label3', 'label4']\n      }\n    });\n\n    var meta = chart.getDatasetMeta(0);\n    var point = meta.data[1];\n    var {x, y} = point.getProps(['x', 'y'], true);\n    expect(point.skip).toBe(true);\n    expect(isNaN(x)).toBe(false);\n    expect(isNaN(y)).toBe(false);\n  });\n\n  it('should honor spangap interval forwards', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          spanGaps: 10,\n          data: [{x: 10, y: 123}, {x: 15, y: 124}, {x: 26, y: 125}, {x: 30, y: 126}, {x: 35, y: 127}],\n          label: 'dataset1',\n        }],\n      },\n      options: {\n        scales: {\n          x: {\n            type: 'linear',\n          }\n        }\n      }\n    });\n\n    var meta = chart.getDatasetMeta(0);\n    for (var i = 0; i < meta.data.length; ++i) {\n      var point = meta.data[i];\n      expect(point.stop).toBe(i === 2);\n    }\n  });\n\n  it('should honor spangap interval backwards', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          spanGaps: 10,\n          data: [{x: 35, y: 123}, {x: 30, y: 124}, {x: 26, y: 125}, {x: 15, y: 126}, {x: 10, y: 127}],\n          label: 'dataset1',\n        }],\n      },\n      options: {\n        scales: {\n          x: {\n            type: 'linear',\n          }\n        }\n      }\n    });\n\n    var meta = chart.getDatasetMeta(0);\n    for (var i = 0; i < meta.data.length; ++i) {\n      var point = meta.data[i];\n      expect(point.stop).toBe(i === 3);\n    }\n  });\n\n  it('should correctly calc visible points on update', async() => {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          data: [\n            {x: 10, y: 20},\n            {x: 15, y: 19},\n          ]\n        }],\n      },\n      options: {\n        scales: {\n          y: {\n            type: 'linear',\n            min: 0,\n            max: 25,\n          },\n          x: {\n            type: 'linear',\n            min: 0,\n            max: 50\n          },\n        }\n      }\n    });\n\n    chart.data.datasets[0].data = [\n      {x: 10, y: 20},\n      {x: 15, y: 19},\n      {x: 17, y: 12},\n      {x: 50, y: 9},\n      {x: 50, y: 9},\n      {x: 50, y: 9},\n      {x: 51, y: 9},\n      {x: 52, y: 9},\n      {x: 52, y: 9},\n    ];\n    chart.update();\n\n    var point = chart.getDatasetMeta(0).data[0];\n    var event = {\n      type: 'mousemove',\n      native: true,\n      ...point\n    };\n\n    chart._handleEvent(event, false, true);\n\n    const visiblePoints = chart.getSortedVisibleDatasetMetas()[0].data.filter(_ => !_.skip);\n\n    expect(visiblePoints.length).toBe(6);\n  }, 500);\n\n  it('should correctly calc _drawStart and _drawCount when first points beyond scale limits are null and spanGaps=true', async() => {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        labels: [0, 10, 20, 30, 40, 50],\n        datasets: [{\n          data: [3, null, 2, 3, null, 1.5],\n          spanGaps: true,\n          tension: 0.4\n        }]\n      },\n      options: {\n        scales: {\n          x: {\n            type: 'linear',\n            min: 11,\n            max: 40,\n          }\n        }\n      }\n    });\n\n    chart.update();\n    var controller = chart.getDatasetMeta(0).controller;\n\n    expect(controller._drawStart).toBe(0);\n    expect(controller._drawCount).toBe(6);\n  }, 500);\n\n  it('should correctly calc _drawStart and _drawCount when all points beyond scale limits are null and spanGaps=true', async() => {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        labels: [0, 10, 20, 30, 40, 50],\n        datasets: [{\n          data: [null, null, 2, 3, null, null],\n          spanGaps: true,\n          tension: 0.4\n        }]\n      },\n      options: {\n        scales: {\n          x: {\n            type: 'linear',\n            min: 11,\n            max: 40,\n          }\n        }\n      }\n    });\n\n    chart.update();\n    var controller = chart.getDatasetMeta(0).controller;\n\n    expect(controller._drawStart).toBe(1);\n    expect(controller._drawCount).toBe(4);\n  }, 500);\n\n  it('should correctly calc _drawStart and _drawCount when spanGaps=false', async() => {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        labels: [0, 10, 20, 30, 40, 50],\n        datasets: [{\n          data: [3, null, 2, 3, null, 1.5],\n          spanGaps: false,\n          tension: 0.4\n        }]\n      },\n      options: {\n        scales: {\n          x: {\n            type: 'linear',\n            min: 11,\n            max: 40,\n          }\n        }\n      }\n    });\n\n    chart.update();\n    var controller = chart.getDatasetMeta(0).controller;\n\n    expect(controller._drawStart).toBe(1);\n    expect(controller._drawCount).toBe(4);\n  }, 500);\n\n  it('should not override tooltip title and label callbacks', async() => {\n    const chart = window.acquireChart({\n      type: 'line',\n      data: {\n        labels: ['Label 1', 'Label 2'],\n        datasets: [{\n          data: [21, 79],\n          label: 'Dataset 1'\n        }, {\n          data: [33, 67],\n          label: 'Dataset 2'\n        }]\n      },\n      options: {\n        responsive: true,\n        maintainAspectRatio: true,\n      }\n    });\n    const {tooltip} = chart;\n    const point = chart.getDatasetMeta(0).data[0];\n\n    await jasmine.triggerMouseEvent(chart, 'mousemove', point);\n\n    expect(tooltip.title).toEqual(['Label 1']);\n    expect(tooltip.body).toEqual([{\n      before: [],\n      lines: ['Dataset 1: 21'],\n      after: []\n    }]);\n\n    chart.options.plugins.tooltip = {mode: 'dataset'};\n    chart.update();\n    await jasmine.triggerMouseEvent(chart, 'mousemove', point);\n\n    expect(tooltip.title).toEqual(['Dataset 1']);\n    expect(tooltip.body).toEqual([{\n      before: [],\n      lines: ['Label 1: 21'],\n      after: []\n    }, {\n      before: [],\n      lines: ['Label 2: 79'],\n      after: []\n    }]);\n  });\n});\n"
  },
  {
    "path": "test/specs/controller.polarArea.tests.js",
    "content": "describe('Chart.controllers.polarArea', function() {\n  describe('auto', jasmine.fixture.specs('controller.polarArea'));\n\n  it('should update the scale correctly when data visibility is changed', function() {\n    var expectedScaleMax = 1;\n    var chart = window.acquireChart({\n      type: 'polarArea',\n      data: {\n        datasets: [\n          {data: [100]}\n        ],\n        labels: ['x']\n      }\n    });\n\n    chart.toggleDataVisibility(0);\n    chart.update();\n\n    expect(chart.scales.r.max).toBe(expectedScaleMax);\n  });\n\n  it('should be registered as dataset controller', function() {\n    expect(typeof Chart.controllers.polarArea).toBe('function');\n  });\n\n  it('should be constructed', function() {\n    var chart = window.acquireChart({\n      type: 'polarArea',\n      data: {\n        datasets: [\n          {data: []},\n          {data: []}\n        ],\n        labels: []\n      }\n    });\n\n    var meta = chart.getDatasetMeta(1);\n    expect(meta.type).toEqual('polarArea');\n    expect(meta.data).toEqual([]);\n    expect(meta.hidden).toBe(null);\n    expect(meta.controller).not.toBe(undefined);\n    expect(meta.controller.index).toBe(1);\n\n    meta.controller.updateIndex(0);\n    expect(meta.controller.index).toBe(0);\n  });\n\n  it('should create arc elements for each data item during initialization', function() {\n    var chart = window.acquireChart({\n      type: 'polarArea',\n      data: {\n        datasets: [\n          {data: []},\n          {data: [10, 15, 0, -4]}\n        ],\n        labels: []\n      }\n    });\n\n    var meta = chart.getDatasetMeta(1);\n    expect(meta.data.length).toBe(4); // 4 arcs created\n    expect(meta.data[0] instanceof Chart.elements.ArcElement).toBe(true);\n    expect(meta.data[1] instanceof Chart.elements.ArcElement).toBe(true);\n    expect(meta.data[2] instanceof Chart.elements.ArcElement).toBe(true);\n    expect(meta.data[3] instanceof Chart.elements.ArcElement).toBe(true);\n  });\n\n  it('should draw all elements', function() {\n    var chart = window.acquireChart({\n      type: 'polarArea',\n      data: {\n        datasets: [{\n          data: [10, 15, 0, -4],\n          label: 'dataset2'\n        }],\n        labels: ['label1', 'label2', 'label3', 'label4']\n      }\n    });\n\n    var meta = chart.getDatasetMeta(0);\n\n    spyOn(meta.data[0], 'draw');\n    spyOn(meta.data[1], 'draw');\n    spyOn(meta.data[2], 'draw');\n    spyOn(meta.data[3], 'draw');\n\n    chart.update();\n\n    expect(meta.data[0].draw.calls.count()).toBe(1);\n    expect(meta.data[1].draw.calls.count()).toBe(1);\n    expect(meta.data[2].draw.calls.count()).toBe(1);\n    expect(meta.data[3].draw.calls.count()).toBe(1);\n  });\n\n  it('should update elements when modifying data', function() {\n    var chart = window.acquireChart({\n      type: 'polarArea',\n      data: {\n        datasets: [{\n          data: [10, 15, 0, -4],\n          label: 'dataset2'\n        }],\n        labels: ['label1', 'label2', 'label3', 'label4']\n      },\n      options: {\n        showLine: true,\n        plugins: {\n          legend: false,\n          title: false\n        },\n        elements: {\n          arc: {\n            backgroundColor: 'rgb(255, 0, 0)',\n            borderColor: 'rgb(0, 255, 0)',\n            borderWidth: 1.2\n          }\n        }\n      }\n    });\n\n    var meta = chart.getDatasetMeta(0);\n    expect(meta.data.length).toBe(4);\n\n    [\n      {o: 174, s: -0.5 * Math.PI, e: 0},\n      {o: 236, s: 0, e: 0.5 * Math.PI},\n      {o: 51, s: 0.5 * Math.PI, e: Math.PI},\n      {o: 0, s: Math.PI, e: 1.5 * Math.PI}\n    ].forEach(function(expected, i) {\n      expect(meta.data[i].x).withContext(i).toBeCloseToPixel(256);\n      expect(meta.data[i].y).withContext(i).toBeCloseToPixel(256);\n      expect(meta.data[i].innerRadius).withContext(i).toBeCloseToPixel(0);\n      expect(meta.data[i].outerRadius).withContext(i).toBeCloseToPixel(expected.o);\n      expect(meta.data[i].startAngle).withContext(i).toBe(expected.s);\n      expect(meta.data[i].endAngle).withContext(i).toBe(expected.e);\n      expect(meta.data[i].options).withContext(i).toEqual(jasmine.objectContaining({\n        backgroundColor: 'rgb(255, 0, 0)',\n        borderColor: 'rgb(0, 255, 0)',\n        borderWidth: 1.2\n      }));\n    });\n\n    // arc styles\n    chart.data.datasets[0].backgroundColor = 'rgb(128, 129, 130)';\n    chart.data.datasets[0].borderColor = 'rgb(56, 57, 58)';\n    chart.data.datasets[0].borderWidth = 1.123;\n\n    chart.update();\n\n    for (var i = 0; i < 4; ++i) {\n      expect(meta.data[i].options.backgroundColor).toBe('rgb(128, 129, 130)');\n      expect(meta.data[i].options.borderColor).toBe('rgb(56, 57, 58)');\n      expect(meta.data[i].options.borderWidth).toBe(1.123);\n    }\n\n    chart.update();\n\n    expect(meta.data[0].x).toBeCloseToPixel(256);\n    expect(meta.data[0].y).toBeCloseToPixel(256);\n    expect(meta.data[0].innerRadius).toBeCloseToPixel(0);\n    expect(meta.data[0].outerRadius).toBeCloseToPixel(174);\n  });\n\n  it('should update elements with start angle from options', function() {\n    var chart = window.acquireChart({\n      type: 'polarArea',\n      data: {\n        datasets: [{\n          data: [10, 15, 0, -4],\n          label: 'dataset2'\n        }],\n        labels: ['label1', 'label2', 'label3', 'label4']\n      },\n      options: {\n        showLine: true,\n        plugins: {\n          legend: false,\n          title: false,\n        },\n        scales: {\n          r: {\n            startAngle: 90, // default is 0\n          }\n        },\n        elements: {\n          arc: {\n            backgroundColor: 'rgb(255, 0, 0)',\n            borderColor: 'rgb(0, 255, 0)',\n            borderWidth: 1.2\n          }\n        }\n      }\n    });\n\n    var meta = chart.getDatasetMeta(0);\n    expect(meta.data.length).toBe(4);\n\n    [\n      {o: 174, s: 0, e: 0.5 * Math.PI},\n      {o: 236, s: 0.5 * Math.PI, e: Math.PI},\n      {o: 51, s: Math.PI, e: 1.5 * Math.PI},\n      {o: 0, s: 1.5 * Math.PI, e: 2.0 * Math.PI}\n    ].forEach(function(expected, i) {\n      expect(meta.data[i].x).withContext(i).toBeCloseToPixel(256);\n      expect(meta.data[i].y).withContext(i).toBeCloseToPixel(256);\n      expect(meta.data[i].innerRadius).withContext(i).toBeCloseToPixel(0);\n      expect(meta.data[i].outerRadius).withContext(i).toBeCloseToPixel(expected.o);\n      expect(meta.data[i].startAngle).withContext(i).toBe(expected.s);\n      expect(meta.data[i].endAngle).withContext(i).toBe(expected.e);\n      expect(meta.data[i].options).withContext(i).toEqual(jasmine.objectContaining({\n        backgroundColor: 'rgb(255, 0, 0)',\n        borderColor: 'rgb(0, 255, 0)',\n        borderWidth: 1.2\n      }));\n    });\n  });\n\n  it('should handle number of data point changes in update', function() {\n    var chart = window.acquireChart({\n      type: 'polarArea',\n      data: {\n        datasets: [{\n          data: [10, 15, 0, -4],\n          label: 'dataset2'\n        }],\n        labels: ['label1', 'label2', 'label3', 'label4']\n      },\n      options: {\n        showLine: true,\n        elements: {\n          arc: {\n            backgroundColor: 'rgb(255, 0, 0)',\n            borderColor: 'rgb(0, 255, 0)',\n            borderWidth: 1.2\n          }\n        }\n      }\n    });\n\n    var meta = chart.getDatasetMeta(0);\n    expect(meta.data.length).toBe(4);\n\n    // remove 2 items\n    chart.data.labels = ['label1', 'label2'];\n    chart.data.datasets[0].data = [1, 2];\n    chart.update();\n\n    expect(meta.data.length).toBe(2);\n    expect(meta.data[0] instanceof Chart.elements.ArcElement).toBe(true);\n    expect(meta.data[1] instanceof Chart.elements.ArcElement).toBe(true);\n\n    // add 3 items\n    chart.data.labels = ['label1', 'label2', 'label3', 'label4', 'label5'];\n    chart.data.datasets[0].data = [1, 2, 3, 4, 5];\n    chart.update();\n\n    expect(meta.data.length).toBe(5);\n    expect(meta.data[0] instanceof Chart.elements.ArcElement).toBe(true);\n    expect(meta.data[1] instanceof Chart.elements.ArcElement).toBe(true);\n    expect(meta.data[2] instanceof Chart.elements.ArcElement).toBe(true);\n    expect(meta.data[3] instanceof Chart.elements.ArcElement).toBe(true);\n    expect(meta.data[4] instanceof Chart.elements.ArcElement).toBe(true);\n  });\n\n  describe('Interactions', function() {\n    beforeEach(function() {\n      this.chart = window.acquireChart({\n        type: 'polarArea',\n        data: {\n          labels: ['label1', 'label2', 'label3', 'label4'],\n          datasets: [{\n            data: [10, 15, 0, 4]\n          }]\n        },\n        options: {\n          cutoutPercentage: 0,\n          elements: {\n            arc: {\n              backgroundColor: 'rgb(100, 150, 200)',\n              borderColor: 'rgb(50, 100, 150)',\n              borderWidth: 2,\n            }\n          }\n        }\n      });\n    });\n\n    it('should handle default hover styles', async function() {\n      var chart = this.chart;\n      var arc = chart.getDatasetMeta(0).data[0];\n\n      await jasmine.triggerMouseEvent(chart, 'mousemove', arc);\n      expect(arc.options.backgroundColor).toBe('#3187DD');\n      expect(arc.options.borderColor).toBe('#175A9D');\n      expect(arc.options.borderWidth).toBe(2);\n\n      await jasmine.triggerMouseEvent(chart, 'mouseout', arc);\n      expect(arc.options.backgroundColor).toBe('rgb(100, 150, 200)');\n      expect(arc.options.borderColor).toBe('rgb(50, 100, 150)');\n      expect(arc.options.borderWidth).toBe(2);\n    });\n\n    it('should handle hover styles defined via dataset properties', async function() {\n      var chart = this.chart;\n      var arc = chart.getDatasetMeta(0).data[0];\n\n      Chart.helpers.merge(chart.data.datasets[0], {\n        hoverBackgroundColor: 'rgb(200, 100, 150)',\n        hoverBorderColor: 'rgb(150, 50, 100)',\n        hoverBorderWidth: 8.4,\n      });\n\n      chart.update();\n\n      await jasmine.triggerMouseEvent(chart, 'mousemove', arc);\n      expect(arc.options.backgroundColor).toBe('rgb(200, 100, 150)');\n      expect(arc.options.borderColor).toBe('rgb(150, 50, 100)');\n      expect(arc.options.borderWidth).toBe(8.4);\n\n      await jasmine.triggerMouseEvent(chart, 'mouseout', arc);\n      expect(arc.options.backgroundColor).toBe('rgb(100, 150, 200)');\n      expect(arc.options.borderColor).toBe('rgb(50, 100, 150)');\n      expect(arc.options.borderWidth).toBe(2);\n    });\n\n    it('should handle hover styles defined via element options', async function() {\n      var chart = this.chart;\n      var arc = chart.getDatasetMeta(0).data[0];\n\n      Chart.helpers.merge(chart.options.elements.arc, {\n        hoverBackgroundColor: 'rgb(200, 100, 150)',\n        hoverBorderColor: 'rgb(150, 50, 100)',\n        hoverBorderWidth: 8.4,\n      });\n\n      chart.update();\n\n      await jasmine.triggerMouseEvent(chart, 'mousemove', arc);\n      expect(arc.options.backgroundColor).toBe('rgb(200, 100, 150)');\n      expect(arc.options.borderColor).toBe('rgb(150, 50, 100)');\n      expect(arc.options.borderWidth).toBe(8.4);\n\n      await jasmine.triggerMouseEvent(chart, 'mouseout', arc);\n      expect(arc.options.backgroundColor).toBe('rgb(100, 150, 200)');\n      expect(arc.options.borderColor).toBe('rgb(50, 100, 150)');\n      expect(arc.options.borderWidth).toBe(2);\n    });\n  });\n\n  it('should not override tooltip title and label callbacks', async() => {\n    const chart = window.acquireChart({\n      type: 'polarArea',\n      data: {\n        labels: ['Label 1', 'Label 2'],\n        datasets: [{\n          data: [21, 79],\n          label: 'Dataset 1'\n        }, {\n          data: [33, 67],\n          label: 'Dataset 2'\n        }]\n      },\n      options: {\n        responsive: true,\n        maintainAspectRatio: true,\n      }\n    });\n    const {tooltip} = chart;\n    const point = chart.getDatasetMeta(0).data[0];\n\n    await jasmine.triggerMouseEvent(chart, 'mousemove', point);\n\n    expect(tooltip.title).toEqual(['Label 1']);\n    expect(tooltip.body).toEqual([{\n      before: [],\n      lines: ['Dataset 1: 21'],\n      after: []\n    }]);\n\n    chart.options.plugins.tooltip = {mode: 'dataset'};\n    chart.update();\n    await jasmine.triggerMouseEvent(chart, 'mousemove', point);\n\n    expect(tooltip.title).toEqual(['Dataset 1']);\n    expect(tooltip.body).toEqual([{\n      before: [],\n      lines: ['Label 1: 21'],\n      after: []\n    }, {\n      before: [],\n      lines: ['Label 2: 79'],\n      after: []\n    }]);\n  });\n});\n"
  },
  {
    "path": "test/specs/controller.radar.tests.js",
    "content": "describe('Chart.controllers.radar', function() {\n  describe('auto', jasmine.fixture.specs('controller.radar'));\n\n  it('should be registered as dataset controller', function() {\n    expect(typeof Chart.controllers.radar).toBe('function');\n  });\n\n  it('Should be constructed', function() {\n    var chart = window.acquireChart({\n      type: 'radar',\n      data: {\n        datasets: [{\n          data: []\n        }],\n        labels: []\n      }\n    });\n\n    var meta = chart.getDatasetMeta(0);\n    expect(meta.type).toBe('radar');\n    expect(meta.controller).not.toBe(undefined);\n    expect(meta.controller.index).toBe(0);\n    expect(meta.data).toEqual([]);\n\n    meta.controller.updateIndex(1);\n    expect(meta.controller.index).toBe(1);\n  });\n\n  it('Should create arc elements for each data item during initialization', function() {\n    var chart = window.acquireChart({\n      type: 'radar',\n      data: {\n        datasets: [{\n          data: [10, 15, 0, 4]\n        }],\n        labels: ['label1', 'label2', 'label3', 'label4']\n      }\n    });\n\n    var meta = chart.getDatasetMeta(0);\n    expect(meta.dataset instanceof Chart.elements.LineElement).toBe(true); // line element\n    expect(meta.data.length).toBe(4); // 4 points created\n    expect(meta.data[0] instanceof Chart.elements.PointElement).toBe(true);\n    expect(meta.data[1] instanceof Chart.elements.PointElement).toBe(true);\n    expect(meta.data[2] instanceof Chart.elements.PointElement).toBe(true);\n    expect(meta.data[3] instanceof Chart.elements.PointElement).toBe(true);\n  });\n\n  it('should draw all elements', function() {\n    var chart = window.acquireChart({\n      type: 'radar',\n      data: {\n        datasets: [{\n          data: [10, 15, 0, 4]\n        }],\n        labels: ['label1', 'label2', 'label3', 'label4']\n      }\n    });\n\n    var meta = chart.getDatasetMeta(0);\n\n    spyOn(meta.dataset, 'draw');\n    spyOn(meta.data[0], 'draw');\n    spyOn(meta.data[1], 'draw');\n    spyOn(meta.data[2], 'draw');\n    spyOn(meta.data[3], 'draw');\n\n    chart.update();\n\n    expect(meta.dataset.draw.calls.count()).toBe(1);\n    expect(meta.data[0].draw.calls.count()).toBe(1);\n    expect(meta.data[1].draw.calls.count()).toBe(1);\n    expect(meta.data[2].draw.calls.count()).toBe(1);\n    expect(meta.data[3].draw.calls.count()).toBe(1);\n  });\n\n  it('should draw all elements with object notation and default key', function() {\n    var chart = window.acquireChart({\n      type: 'radar',\n      data: {\n        datasets: [{\n          data: [{r: 10}, {r: 20}, {r: 15}]\n        }],\n        labels: ['label1', 'label2', 'label3']\n      }\n    });\n\n    var meta = chart.getDatasetMeta(0);\n\n    spyOn(meta.dataset, 'draw');\n    spyOn(meta.data[0], 'draw');\n    spyOn(meta.data[1], 'draw');\n    spyOn(meta.data[2], 'draw');\n\n    chart.update();\n\n    expect(meta.dataset.draw.calls.count()).toBe(1);\n    expect(meta.data[0].draw.calls.count()).toBe(1);\n    expect(meta.data[1].draw.calls.count()).toBe(1);\n    expect(meta.data[2].draw.calls.count()).toBe(1);\n  });\n\n  it('should update elements', function() {\n    var chart = window.acquireChart({\n      type: 'radar',\n      data: {\n        datasets: [{\n          data: [10, 15, 0, 4]\n        }],\n        labels: ['label1', 'label2', 'label3', 'label4']\n      },\n      options: {\n        showLine: true,\n        plugins: {\n          legend: false,\n          title: false,\n        },\n        elements: {\n          line: {\n            backgroundColor: 'rgb(255, 0, 0)',\n            borderCapStyle: 'round',\n            borderColor: 'rgb(0, 255, 0)',\n            borderDash: [],\n            borderDashOffset: 0.1,\n            borderJoinStyle: 'bevel',\n            borderWidth: 1.2,\n            fill: true,\n            tension: 0.1,\n          },\n          point: {\n            backgroundColor: Chart.defaults.backgroundColor,\n            borderWidth: 1,\n            borderColor: Chart.defaults.borderColor,\n            hitRadius: 1,\n            hoverRadius: 4,\n            hoverBorderWidth: 1,\n            radius: 3,\n            pointStyle: 'circle'\n          }\n        }\n      }\n    });\n\n    var meta = chart.getDatasetMeta(0);\n\n    chart.reset(); // reset first\n\n    // Line element\n    expect(meta.dataset.options).toEqual(jasmine.objectContaining({\n      backgroundColor: 'rgb(255, 0, 0)',\n      borderCapStyle: 'round',\n      borderColor: 'rgb(0, 255, 0)',\n      borderDash: [],\n      borderDashOffset: 0.1,\n      borderJoinStyle: 'bevel',\n      borderWidth: 1.2,\n      fill: true,\n      tension: 0.1,\n    }));\n\n    [\n      {x: 256, y: 256},\n      {x: 256, y: 256},\n      {x: 256, y: 256},\n      {x: 256, y: 256},\n    ].forEach(function(expected, i) {\n      expect(meta.data[i].x).withContext(i).toBeCloseToPixel(expected.x);\n      expect(meta.data[i].y).withContext(i).toBeCloseToPixel(expected.y);\n      expect(meta.data[i].options).withContext(i).toEqual(jasmine.objectContaining({\n        backgroundColor: Chart.defaults.backgroundColor,\n        borderWidth: 1,\n        borderColor: Chart.defaults.borderColor,\n        hitRadius: 1,\n        radius: 3,\n        pointStyle: 'circle',\n      }));\n    });\n\n    chart.update();\n\n    [\n      {x: 256, y: 122, cppx: 246, cppy: 122, cpnx: 272, cpny: 122},\n      {x: 457, y: 256, cppx: 457, cppy: 249, cpnx: 457, cpny: 262},\n      {x: 256, y: 256, cppx: 277, cppy: 256, cpnx: 250, cpny: 256},\n      {x: 202, y: 256, cppx: 202, cppy: 260, cpnx: 202, cpny: 246},\n    ].forEach(function(expected, i) {\n      expect(meta.data[i].x).withContext(i).toBeCloseToPixel(expected.x);\n      expect(meta.data[i].y).withContext(i).toBeCloseToPixel(expected.y);\n      expect(meta.data[i].cp1x).withContext(i).toBeCloseToPixel(expected.cppx);\n      expect(meta.data[i].cp1y).withContext(i).toBeCloseToPixel(expected.cppy);\n      expect(meta.data[i].cp2x).withContext(i).toBeCloseToPixel(expected.cpnx);\n      expect(meta.data[i].cp2y).withContext(i).toBeCloseToPixel(expected.cpny);\n      expect(meta.data[i].options).withContext(i).toEqual(jasmine.objectContaining({\n        backgroundColor: Chart.defaults.backgroundColor,\n        borderWidth: 1,\n        borderColor: Chart.defaults.borderColor,\n        hitRadius: 1,\n        radius: 3,\n        pointStyle: 'circle',\n      }));\n    });\n\n    // Use dataset level styles for lines & points\n    chart.data.datasets[0].tension = 0;\n    chart.data.datasets[0].backgroundColor = 'rgb(98, 98, 98)';\n    chart.data.datasets[0].borderColor = 'rgb(8, 8, 8)';\n    chart.data.datasets[0].borderWidth = 0.55;\n    chart.data.datasets[0].borderCapStyle = 'butt';\n    chart.data.datasets[0].borderDash = [2, 3];\n    chart.data.datasets[0].borderDashOffset = 7;\n    chart.data.datasets[0].borderJoinStyle = 'miter';\n    chart.data.datasets[0].fill = false;\n\n    // point styles\n    chart.data.datasets[0].pointRadius = 22;\n    chart.data.datasets[0].hitRadius = 3.3;\n    chart.data.datasets[0].pointBackgroundColor = 'rgb(128, 129, 130)';\n    chart.data.datasets[0].pointBorderColor = 'rgb(56, 57, 58)';\n    chart.data.datasets[0].pointBorderWidth = 1.123;\n\n    chart.update();\n\n    expect(meta.dataset.options).toEqual(jasmine.objectContaining({\n      backgroundColor: 'rgb(98, 98, 98)',\n      borderCapStyle: 'butt',\n      borderColor: 'rgb(8, 8, 8)',\n      borderDash: [2, 3],\n      borderDashOffset: 7,\n      borderJoinStyle: 'miter',\n      borderWidth: 0.55,\n      fill: false,\n      tension: 0,\n    }));\n\n    // Since tension is now 0, we don't care about the control points\n    [\n      {x: 256, y: 122},\n      {x: 457, y: 256},\n      {x: 256, y: 256},\n      {x: 202, y: 256},\n    ].forEach(function(expected, i) {\n      expect(meta.data[i].x).withContext(i).toBeCloseToPixel(expected.x);\n      expect(meta.data[i].y).withContext(i).toBeCloseToPixel(expected.y);\n      expect(meta.data[i].options).withContext(i).toEqual(jasmine.objectContaining({\n        backgroundColor: 'rgb(128, 129, 130)',\n        borderWidth: 1.123,\n        borderColor: 'rgb(56, 57, 58)',\n        hitRadius: 3.3,\n        radius: 22,\n        pointStyle: 'circle'\n      }));\n    });\n  });\n\n  describe('Interactions', function() {\n    beforeEach(function() {\n      this.chart = window.acquireChart({\n        type: 'radar',\n        data: {\n          labels: ['label1', 'label2', 'label3', 'label4'],\n          datasets: [{\n            data: [10, 15, 0, 4]\n          }]\n        },\n        options: {\n          elements: {\n            point: {\n              backgroundColor: 'rgb(100, 150, 200)',\n              borderColor: 'rgb(50, 100, 150)',\n              borderWidth: 2,\n              radius: 3\n            }\n          }\n        }\n      });\n    });\n\n    it('should handle default hover styles', async function() {\n      var chart = this.chart;\n      var point = chart.getDatasetMeta(0).data[0];\n\n      await jasmine.triggerMouseEvent(chart, 'mousemove', point);\n      expect(point.options.backgroundColor).toBe('#3187DD');\n      expect(point.options.borderColor).toBe('#175A9D');\n      expect(point.options.borderWidth).toBe(1);\n      expect(point.options.radius).toBe(4);\n\n      await jasmine.triggerMouseEvent(chart, 'mouseout', point);\n      expect(point.options.backgroundColor).toBe('rgb(100, 150, 200)');\n      expect(point.options.borderColor).toBe('rgb(50, 100, 150)');\n      expect(point.options.borderWidth).toBe(2);\n      expect(point.options.radius).toBe(3);\n    });\n\n    it('should handle hover styles defined via dataset properties', async function() {\n      var chart = this.chart;\n      var point = chart.getDatasetMeta(0).data[0];\n\n      Chart.helpers.merge(chart.data.datasets[0], {\n        hoverBackgroundColor: 'rgb(200, 100, 150)',\n        hoverBorderColor: 'rgb(150, 50, 100)',\n        hoverBorderWidth: 8.4,\n        hoverRadius: 4.2\n      });\n\n      chart.update();\n\n      await jasmine.triggerMouseEvent(chart, 'mousemove', point);\n      expect(point.options.backgroundColor).toBe('rgb(200, 100, 150)');\n      expect(point.options.borderColor).toBe('rgb(150, 50, 100)');\n      expect(point.options.borderWidth).toBe(8.4);\n      expect(point.options.radius).toBe(4.2);\n\n      await jasmine.triggerMouseEvent(chart, 'mouseout', point);\n      expect(point.options.backgroundColor).toBe('rgb(100, 150, 200)');\n      expect(point.options.borderColor).toBe('rgb(50, 100, 150)');\n      expect(point.options.borderWidth).toBe(2);\n      expect(point.options.radius).toBe(3);\n    });\n\n    it('should handle hover styles defined via element options', async function() {\n      var chart = this.chart;\n      var point = chart.getDatasetMeta(0).data[0];\n\n      Chart.helpers.merge(chart.options.elements.point, {\n        hoverBackgroundColor: 'rgb(200, 100, 150)',\n        hoverBorderColor: 'rgb(150, 50, 100)',\n        hoverBorderWidth: 8.4,\n        hoverRadius: 4.2\n      });\n\n      chart.update();\n\n      await jasmine.triggerMouseEvent(chart, 'mousemove', point);\n      expect(point.options.backgroundColor).toBe('rgb(200, 100, 150)');\n      expect(point.options.borderColor).toBe('rgb(150, 50, 100)');\n      expect(point.options.borderWidth).toBe(8.4);\n      expect(point.options.radius).toBe(4.2);\n\n      await jasmine.triggerMouseEvent(chart, 'mouseout', point);\n      expect(point.options.backgroundColor).toBe('rgb(100, 150, 200)');\n      expect(point.options.borderColor).toBe('rgb(50, 100, 150)');\n      expect(point.options.borderWidth).toBe(2);\n      expect(point.options.radius).toBe(3);\n    });\n  });\n\n  it('should allow pointBorderWidth to be set to 0', function() {\n    var chart = window.acquireChart({\n      type: 'radar',\n      data: {\n        datasets: [{\n          data: [10, 15, 0, 4],\n          pointBorderWidth: 0\n        }],\n        labels: ['label1', 'label2', 'label3', 'label4']\n      }\n    });\n\n    var meta = chart.getDatasetMeta(0);\n    var point = meta.data[0];\n    expect(point.options.borderWidth).toBe(0);\n  });\n\n  it('should use the pointRadius setting over the radius setting', function() {\n    var chart = window.acquireChart({\n      type: 'radar',\n      data: {\n        datasets: [{\n          data: [10, 15, 0, 4],\n          pointRadius: 10,\n          radius: 15,\n        }, {\n          data: [20, 20, 20, 20],\n          radius: 20\n        }],\n        labels: ['label1', 'label2', 'label3', 'label4']\n      }\n    });\n\n    var meta0 = chart.getDatasetMeta(0);\n    var meta1 = chart.getDatasetMeta(1);\n    expect(meta0.data[0].options.radius).toBe(10);\n    expect(meta1.data[0].options.radius).toBe(20);\n  });\n\n  it('should return id for value scale', function() {\n    var chart = window.acquireChart({\n      type: 'radar',\n      data: {\n        datasets: [{\n          data: [10, 15, 0, 4],\n          pointBorderWidth: 0\n        }],\n        labels: ['label1', 'label2', 'label3', 'label4']\n      },\n      options: {\n        scales: {\n          test: {\n            axis: 'r'\n          }\n        }\n      }\n    });\n\n    var meta = chart.getDatasetMeta(0);\n    expect(meta.vScale.id).toBe('test');\n  });\n\n  it('should not override tooltip title and label callbacks', async() => {\n    const chart = window.acquireChart({\n      type: 'radar',\n      data: {\n        labels: ['Label 1', 'Label 2'],\n        datasets: [{\n          data: [21, 79],\n          label: 'Dataset 1'\n        }, {\n          data: [33, 67],\n          label: 'Dataset 2'\n        }]\n      },\n      options: {\n        responsive: true,\n        maintainAspectRatio: true,\n      }\n    });\n    const {tooltip} = chart;\n    const point = chart.getDatasetMeta(0).data[0];\n\n    await jasmine.triggerMouseEvent(chart, 'mousemove', point);\n\n    expect(tooltip.title).toEqual(['Label 1']);\n    expect(tooltip.body).toEqual([{\n      before: [],\n      lines: ['Dataset 1: 21'],\n      after: []\n    }]);\n\n    chart.options.plugins.tooltip = {mode: 'dataset'};\n    chart.update();\n    await jasmine.triggerMouseEvent(chart, 'mousemove', point);\n\n    expect(tooltip.title).toEqual(['Dataset 1']);\n    expect(tooltip.body).toEqual([{\n      before: [],\n      lines: ['Label 1: 21'],\n      after: []\n    }, {\n      before: [],\n      lines: ['Label 2: 79'],\n      after: []\n    }]);\n  });\n});\n"
  },
  {
    "path": "test/specs/controller.scatter.tests.js",
    "content": "describe('Chart.controllers.scatter', function() {\n  describe('auto', jasmine.fixture.specs('controller.scatter'));\n\n  it('should be registered as dataset controller', function() {\n    expect(typeof Chart.controllers.scatter).toBe('function');\n  });\n\n  it('should only show a single point in the tooltip on multiple datasets', async function() {\n    var chart = window.acquireChart({\n      type: 'scatter',\n      data: {\n        datasets: [{\n          data: [{\n            x: 10,\n            y: 15\n          },\n          {\n            x: 12,\n            y: 10\n          }],\n          label: 'dataset1'\n        },\n        {\n          data: [{\n            x: 20,\n            y: 10\n          },\n          {\n            x: 4,\n            y: 8\n          }],\n          label: 'dataset2'\n        }]\n      },\n      options: {}\n    });\n    var point = chart.getDatasetMeta(0).data[1];\n\n    await jasmine.triggerMouseEvent(chart, 'mousemove', point);\n    expect(chart.tooltip.body.length).toEqual(1);\n  });\n\n  it('should not create line element by default', function() {\n    var chart = window.acquireChart({\n      type: 'scatter',\n      data: {\n        datasets: [{\n          data: [{\n            x: 10,\n            y: 15\n          },\n          {\n            x: 12,\n            y: 10\n          }],\n          label: 'dataset1'\n        },\n        {\n          data: [{\n            x: 20,\n            y: 10\n          },\n          {\n            x: 4,\n            y: 8\n          }],\n          label: 'dataset2'\n        }]\n      },\n    });\n\n    var meta = chart.getDatasetMeta(0);\n    expect(meta.dataset instanceof Chart.elements.LineElement).toBe(false);\n  });\n\n  it('should create line element if showline is true at datasets options', function() {\n    var chart = window.acquireChart({\n      type: 'scatter',\n      data: {\n        datasets: [{\n          showLine: true,\n          data: [{\n            x: 10,\n            y: 15\n          },\n          {\n            x: 12,\n            y: 10\n          }],\n          label: 'dataset1'\n        },\n        {\n          data: [{\n            x: 20,\n            y: 10\n          },\n          {\n            x: 4,\n            y: 8\n          }],\n          label: 'dataset2'\n        }]\n      },\n    });\n\n    var meta = chart.getDatasetMeta(0);\n    expect(meta.dataset instanceof Chart.elements.LineElement).toBe(true);\n  });\n\n  it('should create line element if showline is true at root options', function() {\n    var chart = window.acquireChart({\n      type: 'scatter',\n      data: {\n        datasets: [{\n          data: [{\n            x: 10,\n            y: 15\n          },\n          {\n            x: 12,\n            y: 10\n          }],\n          label: 'dataset1'\n        },\n        {\n          data: [{\n            x: 20,\n            y: 10\n          },\n          {\n            x: 4,\n            y: 8\n          }],\n          label: 'dataset2'\n        }]\n      },\n      options: {\n        showLine: true\n      }\n    });\n\n    var meta = chart.getDatasetMeta(0);\n    expect(meta.dataset instanceof Chart.elements.LineElement).toBe(true);\n  });\n\n  it('should not override tooltip title and label callbacks', async() => {\n    const chart = window.acquireChart({\n      type: 'scatter',\n      data: {\n        labels: ['Label 1', 'Label 2'],\n        datasets: [{\n          data: [{\n            x: 10,\n            y: 15\n          },\n          {\n            x: 12,\n            y: 10\n          }],\n          label: 'Dataset 1'\n        }, {\n          data: [{\n            x: 20,\n            y: 10\n          },\n          {\n            x: 4,\n            y: 8\n          }],\n          label: 'Dataset 2'\n        }]\n      },\n      options: {\n        responsive: true,\n        maintainAspectRatio: true,\n      }\n    });\n    const {tooltip} = chart;\n    const point = chart.getDatasetMeta(0).data[0];\n\n    await jasmine.triggerMouseEvent(chart, 'mousemove', point);\n\n    expect(tooltip.title).toEqual(['Label 1']);\n    expect(tooltip.body).toEqual([{\n      before: [],\n      lines: ['Dataset 1: (10, 15)'],\n      after: []\n    }]);\n\n    chart.options.plugins.tooltip = {mode: 'dataset'};\n    chart.update();\n    await jasmine.triggerMouseEvent(chart, 'mousemove', point);\n\n    expect(tooltip.title).toEqual(['Dataset 1']);\n    expect(tooltip.body).toEqual([{\n      before: [],\n      lines: ['Label 1: (10, 15)'],\n      after: []\n    }, {\n      before: [],\n      lines: ['Label 2: (12, 10)'],\n      after: []\n    }]);\n  });\n});\n"
  },
  {
    "path": "test/specs/core.animation.tests.js",
    "content": "describe('Chart.Animation', function() {\n  it('should animate boolean', function() {\n    const target = {prop: false};\n    const anim = new Chart.Animation({duration: 1000}, target, 'prop', true);\n    expect(anim.active()).toBeTrue();\n\n    anim.tick(anim._start + 500);\n    expect(anim.active()).toBeTrue();\n    expect(target.prop).toBeFalse();\n\n    anim.tick(anim._start + 501);\n    expect(anim.active()).toBeTrue();\n    expect(target.prop).toBeTrue();\n\n    anim.tick(anim._start - 100);\n    expect(anim.active()).toBeTrue();\n    expect(target.prop).toBeFalse();\n\n    anim.tick(anim._start + 1000);\n    expect(anim.active()).toBeFalse();\n    expect(target.prop).toBeTrue();\n  });\n\n  describe('color', function() {\n    it('should fall back to transparent', function() {\n      const target = {};\n      const anim = new Chart.Animation({duration: 1000, type: 'color'}, target, 'color', 'red');\n      anim._from = undefined;\n      anim.tick(anim._start + 500);\n      expect(target.color).toEqual('#FF000080');\n\n      anim._from = 'blue';\n      anim._to = undefined;\n      anim.tick(anim._start + 500);\n      expect(target.color).toEqual('#0000FF80');\n    });\n\n    it('should not try to mix invalid color', function() {\n      const target = {color: 'blue'};\n      const anim = new Chart.Animation({duration: 1000, type: 'color'}, target, 'color', 'invalid');\n      anim.tick(anim._start + 500);\n      expect(target.color).toEqual('invalid');\n    });\n  });\n\n  it('should loop', function() {\n    const target = {value: 0};\n    const anim = new Chart.Animation({duration: 100, loop: true}, target, 'value', 10);\n    anim.tick(anim._start + 50);\n    expect(target.value).toEqual(5);\n    anim.tick(anim._start + 100);\n    expect(target.value).toEqual(10);\n    anim.tick(anim._start + 150);\n    expect(target.value).toEqual(5);\n    anim.tick(anim._start + 400);\n    expect(target.value).toEqual(0);\n  });\n\n  it('should update', function() {\n    const target = {testColor: 'transparent'};\n    const anim = new Chart.Animation({duration: 100, type: 'color'}, target, 'testColor', 'red');\n\n    anim.tick(anim._start + 50);\n    expect(target.testColor).toEqual('#FF000080');\n\n    anim.update({duration: 500}, 'blue', Date.now());\n    anim.tick(anim._start + 250);\n    expect(target.testColor).toEqual('#4000BFBF');\n\n    anim.tick(anim._start + 500);\n    expect(target.testColor).toEqual('blue');\n  });\n\n  it('should not update when finished', function() {\n    const target = {testColor: 'transparent'};\n    const anim = new Chart.Animation({duration: 100, type: 'color'}, target, 'testColor', 'red');\n\n    anim.tick(anim._start + 100);\n    expect(target.testColor).toEqual('red');\n    expect(anim.active()).toBeFalse();\n\n    anim.update({duration: 500}, 'blue', Date.now());\n    expect(anim._duration).toEqual(100);\n    expect(anim._to).toEqual('red');\n  });\n});\n"
  },
  {
    "path": "test/specs/core.animations.tests.js",
    "content": "describe('Chart.animations', function() {\n  it('should override property collection with property', function() {\n    const chart = {};\n    const anims = new Chart.Animations(chart, {\n      collection1: {\n        properties: ['property1', 'property2'],\n        duration: 1000\n      },\n      property2: {\n        duration: 2000\n      }\n    });\n    expect(anims._properties.get('property1')).toEqual(jasmine.objectContaining({duration: 1000}));\n    expect(anims._properties.get('property2')).toEqual(jasmine.objectContaining({duration: 2000}));\n  });\n\n  it('should ignore duplicate definitions from collections', function() {\n    const chart = {};\n    const anims = new Chart.Animations(chart, {\n      collection1: {\n        properties: ['property1'],\n        duration: 1000\n      },\n      collection2: {\n        properties: ['property1', 'property2'],\n        duration: 2000\n      }\n    });\n    expect(anims._properties.get('property1')).toEqual(jasmine.objectContaining({duration: 1000}));\n    expect(anims._properties.get('property2')).toEqual(jasmine.objectContaining({duration: 2000}));\n  });\n\n  it('should not animate undefined options key', function() {\n    const chart = {};\n    const anims = new Chart.Animations(chart, {value: {duration: 100}, option: {duration: 200}});\n    const target = {\n      value: 1,\n      options: {\n        option: 2\n      }\n    };\n    expect(anims.update(target, {\n      options: undefined\n    })).toBeUndefined();\n  });\n\n  it('should assign options directly, if target does not have previous options', function() {\n    const chart = {};\n    const anims = new Chart.Animations(chart, {option: {duration: 200}});\n    const target = {};\n    expect(anims.update(target, {options: {option: 1}})).toBeUndefined();\n  });\n\n  it('should clone the target options, if those are shared and new options are not', function() {\n    const chart = {options: {}};\n    const anims = new Chart.Animations(chart, {option: {duration: 200}});\n    const options = {option: 0, $shared: true};\n    const target = {options};\n    expect(anims.update(target, {options: {option: 1}})).toBeTrue();\n    expect(target.options.$shared).not.toBeTrue();\n    expect(target.options !== options).toBeTrue();\n  });\n\n  it('should assign shared options to target after animations complete', function(done) {\n    const chart = {\n      draw: function() {},\n      options: {}\n    };\n    const anims = new Chart.Animations(chart, {value: {duration: 100}, option: {duration: 200}});\n\n    const target = {\n      value: 1,\n      options: {\n        option: 2\n      }\n    };\n    const sharedOpts = {option: 10, $shared: true};\n\n    expect(anims.update(target, {\n      options: sharedOpts\n    })).toBeTrue();\n\n    expect(target.options !== sharedOpts).toBeTrue();\n\n    Chart.animator.start(chart);\n\n    setTimeout(function() {\n      expect(Chart.animator.running(chart)).toBeFalse();\n      expect(target.options === sharedOpts).toBeTrue();\n\n      Chart.animator.remove(chart);\n      done();\n    }, 300);\n  });\n\n  it('should not assign shared options to target when animations are cancelled', function(done) {\n    const chart = {\n      draw: function() {},\n      options: {}\n    };\n    const anims = new Chart.Animations(chart, {value: {duration: 100}, option: {duration: 200}});\n\n    const target = {\n      value: 1,\n      options: {\n        option: 2\n      }\n    };\n    const sharedOpts = {option: 10, $shared: true};\n\n    expect(anims.update(target, {\n      options: sharedOpts\n    })).toBeTrue();\n\n    expect(target.options !== sharedOpts).toBeTrue();\n\n    Chart.animator.start(chart);\n\n    setTimeout(function() {\n      expect(Chart.animator.running(chart)).toBeTrue();\n      Chart.animator.stop(chart);\n      expect(Chart.animator.running(chart)).toBeFalse();\n\n      setTimeout(function() {\n        expect(target.options === sharedOpts).toBeFalse();\n\n        Chart.animator.remove(chart);\n        done();\n      }, 250);\n    }, 50);\n  });\n\n  it('should assign final shared options to target after animations complete', function(done) {\n    const chart = {\n      draw: function() {},\n      options: {}\n    };\n    const anims = new Chart.Animations(chart, {value: {duration: 100}, option: {duration: 200}});\n\n    const origOpts = {option: 2};\n    const target = {\n      value: 1,\n      options: origOpts\n    };\n    const sharedOpts = {option: 10, $shared: true};\n    const sharedOpts2 = {option: 20, $shared: true};\n\n    expect(anims.update(target, {\n      options: sharedOpts\n    })).toBeTrue();\n\n    expect(target.options !== sharedOpts).toBeTrue();\n\n    Chart.animator.start(chart);\n\n    setTimeout(function() {\n      expect(Chart.animator.running(chart)).toBeTrue();\n\n      expect(target.options === origOpts).toBeTrue();\n\n      expect(anims.update(target, {\n        options: sharedOpts2\n      })).toBeUndefined();\n\n      expect(target.options === origOpts).toBeTrue();\n\n      setTimeout(function() {\n        expect(target.options === sharedOpts2).toBeTrue();\n\n        Chart.animator.remove(chart);\n        done();\n      }, 250);\n    }, 50);\n  });\n});\n"
  },
  {
    "path": "test/specs/core.animator.tests.js",
    "content": "describe('Chart.animator', function() {\n  it('should fire onProgress for each draw', function(done) {\n    let count = 0;\n    let drawCount = 0;\n    const progress = (animation) => {\n      count++;\n      expect(animation.numSteps).toEqual(250);\n      expect(animation.currentStep <= 250).toBeTrue();\n    };\n    acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [\n          {data: [10, 5, 0, 25, 78, -10]}\n        ],\n        labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5', 'tick6']\n      },\n      options: {\n        animation: {\n          duration: 250,\n          onProgress: progress,\n          onComplete: function() {\n            expect(count).toEqual(drawCount);\n            done();\n          }\n        }\n      },\n      plugins: [{\n        afterDraw() {\n          drawCount++;\n        }\n      }]\n    }, {\n      canvas: {\n        height: 150,\n        width: 250\n      },\n    });\n  });\n\n  it('should not fail when adding no items', function() {\n    const chart = {};\n    Chart.animator.add(chart, undefined);\n    Chart.animator.add(chart, []);\n    Chart.animator.start(chart);\n    expect(Chart.animator.running(chart)).toBeFalse();\n  });\n});\n"
  },
  {
    "path": "test/specs/core.controller.tests.js",
    "content": "describe('Chart', function() {\n\n  const overrides = Chart.overrides;\n\n  // https://github.com/chartjs/Chart.js/issues/2481\n  // See global.deprecations.tests.js for backward compatibility\n  it('should be defined and prototype of chart instances', function() {\n    var chart = acquireChart({});\n    expect(Chart).toBeDefined();\n    expect(Chart instanceof Object).toBeTruthy();\n    expect(chart.constructor).toBe(Chart);\n    expect(chart instanceof Chart).toBeTruthy();\n  });\n\n  it('should throw an error if the canvas is already in use', function() {\n    var config = {\n      type: 'line',\n      data: {\n        datasets: [{\n          data: [1, 2, 3, 4]\n        }],\n        labels: ['A', 'B', 'C', 'D']\n      }\n    };\n    var chart = acquireChart(config);\n    var canvas = chart.canvas;\n\n    function createChart() {\n      return new Chart(canvas, config);\n    }\n\n    expect(createChart).toThrow(new Error(\n      'Canvas is already in use. ' +\n\t\t\t'Chart with ID \\'' + chart.id + '\\'' +\n\t\t\t' must be destroyed before the canvas with ID \\'' + chart.canvas.id + '\\' can be reused.'\n    ));\n\n    chart.destroy();\n    expect(createChart).not.toThrow();\n  });\n\n  describe('config initialization', function() {\n    it('should create missing config.data properties', function() {\n      var chart = acquireChart({});\n      var data = chart.data;\n\n      expect(data instanceof Object).toBeTruthy();\n      expect(data.labels instanceof Array).toBeTruthy();\n      expect(data.labels.length).toBe(0);\n      expect(data.datasets instanceof Array).toBeTruthy();\n      expect(data.datasets.length).toBe(0);\n    });\n\n    it('should not alter config.data references', function() {\n      var ds0 = {data: [10, 11, 12, 13]};\n      var ds1 = {data: [20, 21, 22, 23]};\n      var datasets = [ds0, ds1];\n      var labels = [0, 1, 2, 3];\n      var data = {labels: labels, datasets: datasets};\n\n      var chart = acquireChart({\n        type: 'line',\n        data: data\n      });\n\n      expect(chart.data).toBe(data);\n      expect(chart.data.labels).toBe(labels);\n      expect(chart.data.datasets).toBe(datasets);\n      expect(chart.data.datasets[0]).toBe(ds0);\n      expect(chart.data.datasets[1]).toBe(ds1);\n      expect(chart.data.datasets[0].data).toBe(ds0.data);\n      expect(chart.data.datasets[1].data).toBe(ds1.data);\n    });\n\n    it('should define chart.data as an alias for config.data', function() {\n      var config = {data: {labels: [], datasets: []}};\n      var chart = acquireChart(config);\n\n      expect(chart.data).toBe(config.data);\n\n      chart.data = {labels: [1, 2, 3], datasets: [{data: [4, 5, 6]}]};\n\n      expect(config.data).toBe(chart.data);\n      expect(config.data.labels).toEqual([1, 2, 3]);\n      expect(config.data.datasets[0].data).toEqual([4, 5, 6]);\n\n      config.data = {labels: [7, 8, 9], datasets: [{data: [10, 11, 12]}]};\n\n      expect(chart.data).toBe(config.data);\n      expect(chart.data.labels).toEqual([7, 8, 9]);\n      expect(chart.data.datasets[0].data).toEqual([10, 11, 12]);\n    });\n\n    it('should initialize config with default interaction options', function() {\n      var callback = function() {};\n      var defaults = Chart.defaults;\n\n      defaults.onHover = callback;\n      overrides.line.interaction = {\n        mode: 'test'\n      };\n\n      var chart = acquireChart({\n        type: 'line'\n      });\n\n      var options = chart.options;\n      expect(options.font.size).toBe(defaults.font.size);\n      expect(options.onHover).toBe(callback);\n      expect(options.hover.mode).toBe('test');\n\n      defaults.onHover = null;\n      delete overrides.line.interaction;\n    });\n\n    it('should initialize config with default hover options', function() {\n      var callback = function() {};\n      var defaults = Chart.defaults;\n\n      defaults.onHover = callback;\n      overrides.line.hover = {\n        mode: 'test'\n      };\n\n      var chart = acquireChart({\n        type: 'line'\n      });\n\n      var options = chart.options;\n      expect(options.font.size).toBe(defaults.font.size);\n      expect(options.onHover).toBe(callback);\n      expect(options.hover.mode).toBe('test');\n\n      defaults.onHover = null;\n      delete overrides.line.hover;\n    });\n\n    it('should override default options', function() {\n      var callback = function() {};\n      var defaults = Chart.defaults;\n      var defaultSpanGaps = defaults.datasets.line.spanGaps;\n\n      defaults.onHover = callback;\n      overrides.line.hover = {\n        mode: 'x-axis'\n      };\n      defaults.datasets.line.spanGaps = true;\n\n      var chart = acquireChart({\n        type: 'line',\n        options: {\n          spanGaps: false,\n          hover: {\n            mode: 'dataset',\n          },\n          plugins: {\n            title: {\n              position: 'bottom'\n            }\n          }\n        }\n      });\n\n      var options = chart.options;\n      expect(options.spanGaps).toBe(false);\n      expect(options.hover.mode).toBe('dataset');\n      expect(options.plugins.title.position).toBe('bottom');\n\n      defaults.onHover = null;\n      delete overrides.line.hover;\n      defaults.datasets.line.spanGaps = defaultSpanGaps;\n    });\n\n    it('should initialize config with default dataset options', function() {\n      var defaults = Chart.defaults.datasets.pie;\n\n      var chart = acquireChart({\n        type: 'pie'\n      });\n\n      var options = chart.options;\n      expect(options.circumference).toBe(defaults.circumference);\n    });\n\n    it('should override axis positions that are incorrect', function() {\n      var chart = acquireChart({\n        type: 'line',\n        options: {\n          scales: {\n            x: {\n              position: 'left',\n            },\n            y: {\n              position: 'bottom'\n            }\n          }\n        }\n      });\n\n      var scaleOptions = chart.options.scales;\n      expect(scaleOptions.x.position).toBe('bottom');\n      expect(scaleOptions.y.position).toBe('left');\n    });\n\n    it('should throw an error if the chart type is incorrect', function() {\n      function createChart() {\n        acquireChart({\n          type: 'area',\n          data: {\n            datasets: [{\n              label: 'first',\n              data: [10, 20]\n            }],\n            labels: ['0', '1'],\n          },\n          options: {\n            scales: {\n              x: {\n                type: 'linear',\n                position: 'left',\n              },\n              y: {\n                type: 'category',\n                position: 'bottom'\n              }\n            }\n          }\n        });\n      }\n      expect(createChart).toThrow(new Error('\"area\" is not a registered controller.'));\n    });\n\n    it('should initialize the data object', function() {\n      const chart = acquireChart({type: 'bar'});\n      expect(chart.data).toEqual(jasmine.objectContaining({labels: [], datasets: []}));\n      chart.data = {};\n      expect(chart.data).toEqual(jasmine.objectContaining({labels: [], datasets: []}));\n      chart.data = null;\n      expect(chart.data).toEqual(jasmine.objectContaining({labels: [], datasets: []}));\n      chart.data = undefined;\n      expect(chart.data).toEqual(jasmine.objectContaining({labels: [], datasets: []}));\n    });\n\n    describe('should disable hover', function() {\n      it('when options.hover=false', function() {\n        var chart = acquireChart({\n          type: 'line',\n          options: {\n            hover: false\n          }\n        });\n        expect(chart.options.hover).toBeFalse();\n      });\n\n      it('when options.interaction=false and options.hover is not defined', function() {\n        var chart = acquireChart({\n          type: 'line',\n          options: {\n            interaction: false\n          }\n        });\n        expect(chart.options.hover).toBeFalse();\n      });\n\n      it('when options.interaction=false and options.hover is defined', function() {\n        var chart = acquireChart({\n          type: 'line',\n          options: {\n            interaction: false,\n            hover: {mode: 'nearest'}\n          }\n        });\n        expect(chart.options.hover).toBeFalse();\n      });\n    });\n\n    it('should activate element on hover', async function() {\n      var chart = acquireChart({\n        type: 'line',\n        data: {\n          labels: ['A', 'B', 'C', 'D'],\n          datasets: [{\n            data: [10, 20, 30, 100]\n          }]\n        }\n      });\n\n      var point = chart.getDatasetMeta(0).data[1];\n\n      await jasmine.triggerMouseEvent(chart, 'mousemove', point);\n      expect(chart.getActiveElements()).toEqual([{datasetIndex: 0, index: 1, element: point}]);\n    });\n\n    it('should handle changing the events at runtime', async function() {\n      var chart = acquireChart({\n        type: 'line',\n        data: {\n          labels: ['A', 'B', 'C', 'D'],\n          datasets: [{\n            data: [10, 20, 30, 100]\n          }]\n        },\n        options: {\n          events: ['click']\n        }\n      });\n\n      var point1 = chart.getDatasetMeta(0).data[1];\n      var point2 = chart.getDatasetMeta(0).data[2];\n\n      await jasmine.triggerMouseEvent(chart, 'click', point1);\n      expect(chart.getActiveElements()).toEqual([{datasetIndex: 0, index: 1, element: point1}]);\n\n      chart.options.events = ['mousemove'];\n      chart.update();\n\n      await jasmine.triggerMouseEvent(chart, 'mousemove', point2);\n      expect(chart.getActiveElements()).toEqual([{datasetIndex: 0, index: 2, element: point2}]);\n    });\n\n    it('should activate element on hover when minPadding pixels outside chart area', async function() {\n      var chart = acquireChart({\n        type: 'line',\n        data: {\n          labels: ['A', 'B', 'C', 'D'],\n          datasets: [{\n            data: [10, 20, 30, 100],\n            hoverRadius: 0\n          }],\n        },\n        options: {\n          scales: {\n            x: {display: false},\n            y: {display: false}\n          }\n        }\n      });\n\n      var point = chart.getDatasetMeta(0).data[0];\n\n      await jasmine.triggerMouseEvent(chart, 'mousemove', {x: 1, y: point.y});\n      expect(chart.getActiveElements()).toEqual([{datasetIndex: 0, index: 0, element: point}]);\n    });\n\n    it('should not activate elements when hover is disabled', async function() {\n      var chart = acquireChart({\n        type: 'line',\n        data: {\n          labels: ['A', 'B', 'C', 'D'],\n          datasets: [{\n            data: [10, 20, 30, 100]\n          }]\n        },\n        options: {\n          hover: false\n        }\n      });\n\n      var point = chart.getDatasetMeta(0).data[1];\n\n      await jasmine.triggerMouseEvent(chart, 'mousemove', point);\n      expect(chart.getActiveElements()).toEqual([]);\n    });\n\n    it('should not change the active elements when outside chartArea, except for mouseout', async function() {\n      var chart = acquireChart({\n        type: 'line',\n        data: {\n          labels: ['A', 'B', 'C', 'D'],\n          datasets: [{\n            data: [10, 20, 30, 100],\n            hoverRadius: 0\n          }],\n        },\n        options: {\n          scales: {\n            x: {display: false},\n            y: {display: false}\n          },\n          layout: {\n            padding: 5\n          }\n        }\n      });\n\n      var point = chart.getDatasetMeta(0).data[0];\n\n      await jasmine.triggerMouseEvent(chart, 'mousemove', {x: point.x, y: point.y});\n      expect(chart.getActiveElements()).toEqual([{datasetIndex: 0, index: 0, element: point}]);\n\n      await jasmine.triggerMouseEvent(chart, 'mousemove', {x: 1, y: 1});\n      expect(chart.getActiveElements()).toEqual([{datasetIndex: 0, index: 0, element: point}]);\n\n      await jasmine.triggerMouseEvent(chart, 'mouseout', {x: 1, y: 1});\n      expect(chart.tooltip.getActiveElements()).toEqual([]);\n    });\n  });\n\n  describe('when merging scale options', function() {\n    beforeEach(function() {\n      Chart.helpers.merge(Chart.defaults.scale, {\n        _jasmineCheckA: 'a0',\n        _jasmineCheckB: 'b0',\n        _jasmineCheckC: 'c0'\n      });\n\n      Chart.helpers.merge(Chart.defaults.scales.logarithmic, {\n        _jasmineCheckB: 'b1',\n        _jasmineCheckC: 'c1',\n      });\n    });\n\n    afterEach(function() {\n      delete Chart.defaults.scale._jasmineCheckA;\n      delete Chart.defaults.scale._jasmineCheckB;\n      delete Chart.defaults.scale._jasmineCheckC;\n      delete Chart.defaults.scales.logarithmic._jasmineCheckB;\n      delete Chart.defaults.scales.logarithmic._jasmineCheckC;\n    });\n\n    it('should default to \"category\" for x scales and \"linear\" for y scales', function() {\n      var chart = acquireChart({\n        type: 'line',\n        options: {\n          scales: {\n            xFoo0: {},\n            xFoo1: {},\n            yBar0: {},\n            yBar1: {},\n          }\n        }\n      });\n\n      expect(chart.scales.xFoo0.type).toBe('category');\n      expect(chart.scales.xFoo1.type).toBe('category');\n      expect(chart.scales.yBar0.type).toBe('linear');\n      expect(chart.scales.yBar1.type).toBe('linear');\n    });\n\n    it('should correctly apply defaults on central scale', function() {\n      var chart = acquireChart({\n        type: 'line',\n        options: {\n          scales: {\n            foo: {\n              axis: 'x',\n              type: 'logarithmic',\n              _jasmineCheckC: 'c2',\n              _jasmineCheckD: 'd2'\n            }\n          }\n        }\n      });\n\n      // let's check a few values from the user options and defaults\n\n      expect(chart.scales.foo.type).toBe('logarithmic');\n      expect(chart.scales.foo.options).toEqual(chart.options.scales.foo);\n      expect(chart.scales.foo.options).toEqual(\n        jasmine.objectContaining({\n          _jasmineCheckA: 'a0',\n          _jasmineCheckB: 'b1',\n          _jasmineCheckC: 'c2',\n          _jasmineCheckD: 'd2'\n        }));\n    });\n\n    it('should correctly apply defaults on xy scales', function() {\n      var chart = acquireChart({\n        type: 'line',\n        options: {\n          scales: {\n            x: {\n              type: 'logarithmic',\n              _jasmineCheckC: 'c2',\n              _jasmineCheckD: 'd2'\n            },\n            y: {\n              type: 'time',\n              _jasmineCheckC: 'c2',\n              _jasmineCheckE: 'e2'\n            }\n          }\n        }\n      });\n\n      expect(chart.scales.x.type).toBe('logarithmic');\n      expect(chart.scales.x.options).toEqual(chart.options.scales.x);\n      expect(chart.scales.x.options).toEqual(\n        jasmine.objectContaining({\n          _jasmineCheckA: 'a0',\n          _jasmineCheckB: 'b1',\n          _jasmineCheckC: 'c2',\n          _jasmineCheckD: 'd2'\n        }));\n\n      expect(chart.scales.y.type).toBe('time');\n      expect(chart.scales.y.options).toEqual(chart.options.scales.y);\n      expect(chart.scales.y.options).toEqual(\n        jasmine.objectContaining({\n          _jasmineCheckA: 'a0',\n          _jasmineCheckB: 'b0',\n          _jasmineCheckC: 'c2',\n          _jasmineCheckE: 'e2'\n        }));\n    });\n\n    it('should not alter defaults when merging config', function() {\n      var chart = acquireChart({\n        type: 'line',\n        options: {\n          _jasmineCheck: 42,\n          scales: {\n            x: {\n              type: 'linear',\n              _jasmineCheck: 42,\n            },\n            y: {\n              type: 'category',\n              _jasmineCheck: 42,\n            }\n          }\n        }\n      });\n\n      expect(chart.options._jasmineCheck).toBeDefined();\n      expect(chart.scales.x.options._jasmineCheck).toBeDefined();\n      expect(chart.scales.y.options._jasmineCheck).toBeDefined();\n\n      expect(Chart.overrides.line._jasmineCheck).not.toBeDefined();\n      expect(Chart.defaults._jasmineCheck).not.toBeDefined();\n      expect(Chart.defaults.scales.linear._jasmineCheck).not.toBeDefined();\n      expect(Chart.defaults.scales.category._jasmineCheck).not.toBeDefined();\n    });\n\n    it('should ignore proxy passed as scale options', function() {\n      let failure = false;\n      const chart = acquireChart({\n        type: 'line',\n        data: [],\n        options: {\n          scales: {\n            x: {\n              grid: {\n                color: ctx => {\n                  if (!ctx.tick) {\n                    failure = true;\n                  }\n                }\n              }\n            }\n          }\n        }\n      });\n      chart.options.scales = {\n        x: chart.options.scales.x,\n        y: {\n          type: 'linear',\n          position: 'right'\n        }\n      };\n      chart.update();\n      expect(failure).toEqual(false);\n    });\n\n    it('should ignore array passed as scale options', function() {\n      const chart = acquireChart({\n        type: 'line',\n        data: [],\n        options: {\n          scales: {\n            xAxes: [{id: 'xAxes', type: 'category'}]\n          }\n        }\n      });\n      expect(chart.scales.xAxes).not.toBeDefined();\n    });\n  });\n\n  describe('Updating options', function() {\n    it('update should result to same set of options as construct', function() {\n      var chart = acquireChart({\n        type: 'line',\n        data: [],\n        options: {\n          animation: false,\n          locale: 'en-US',\n          responsive: false\n        }\n      });\n      const options = chart.options;\n      chart.options = {\n        animation: false,\n        locale: 'en-US',\n        responsive: false\n      };\n      chart.update();\n      expect(chart.options).toEqualOptions(options);\n    });\n  });\n\n  describe('config.options.responsive: true (maintainAspectRatio: false)', function() {\n    it('should fill parent width and height', function() {\n      var chart = acquireChart({\n        options: {\n          responsive: true,\n          maintainAspectRatio: false\n        }\n      }, {\n        canvas: {\n          style: 'width: 150px; height: 245px'\n        },\n        wrapper: {\n          style: 'width: 300px; height: 350px'\n        }\n      });\n\n      expect(chart).toBeChartOfSize({\n        dw: 300, dh: 350,\n        rw: 300, rh: 350,\n      });\n    });\n\n    it('should call onResize with correct arguments and context', function() {\n      let count = 0;\n      let correctThis = false;\n      let size = {\n        width: 0,\n        height: 0\n      };\n      acquireChart({\n        options: {\n          responsive: true,\n          maintainAspectRatio: false,\n          onResize(chart, newSize) {\n            count++;\n            correctThis = this === chart;\n            size.width = newSize.width;\n            size.height = newSize.height;\n          }\n        }\n      }, {\n        canvas: {\n          style: 'width: 150px; height: 245px'\n        },\n        wrapper: {\n          style: 'width: 300px; height: 350px'\n        }\n      });\n\n      expect(count).toEqual(1);\n      expect(correctThis).toBeTrue();\n      expect(size).toEqual({width: 300, height: 350});\n    });\n\n\n    it('should resize the canvas when parent width changes', function(done) {\n      var chart = acquireChart({\n        options: {\n          responsive: true,\n          maintainAspectRatio: false\n        }\n      }, {\n        canvas: {\n          style: ''\n        },\n        wrapper: {\n          style: 'width: 300px; height: 350px; position: relative'\n        }\n      });\n\n      expect(chart).toBeChartOfSize({\n        dw: 300, dh: 350,\n        rw: 300, rh: 350,\n      });\n\n      var wrapper = chart.canvas.parentNode;\n      waitForResize(chart, function() {\n        expect(chart).toBeChartOfSize({\n          dw: 455, dh: 350,\n          rw: 455, rh: 350,\n        });\n\n        waitForResize(chart, function() {\n          expect(chart).toBeChartOfSize({\n            dw: 150, dh: 350,\n            rw: 150, rh: 350,\n          });\n\n          done();\n        });\n        wrapper.style.width = '150px';\n      });\n      wrapper.style.width = '455px';\n    });\n\n    it('should restore the original size when parent became invisible', function(done) {\n      var chart = acquireChart({\n        options: {\n          responsive: true,\n          maintainAspectRatio: false\n        }\n      }, {\n        canvas: {\n          style: ''\n        },\n        wrapper: {\n          style: 'width: 300px; height: 350px; position: relative'\n        }\n      });\n\n      waitForResize(chart, function() {\n        expect(chart).toBeChartOfSize({\n          dw: 300, dh: 350,\n          rw: 300, rh: 350,\n        });\n\n        var original = chart.resize;\n        chart.resize = function() {\n          fail('resize should not have been called');\n        };\n\n        var wrapper = chart.canvas.parentNode;\n        wrapper.style.display = 'none';\n\n        setTimeout(function() {\n          expect(wrapper.clientWidth).toEqual(0);\n          expect(wrapper.clientHeight).toEqual(0);\n\n          expect(chart).toBeChartOfSize({\n            dw: 300, dh: 350,\n            rw: 300, rh: 350,\n          });\n\n          chart.resize = original;\n\n          waitForResize(chart, function() {\n            expect(chart).toBeChartOfSize({\n              dw: 300, dh: 350,\n              rw: 300, rh: 350,\n            });\n\n            done();\n          });\n          wrapper.style.display = 'block';\n        }, 200);\n      });\n    });\n\n    it('should resize the canvas when parent is RTL and width changes', function(done) {\n      var chart = acquireChart({\n        options: {\n          responsive: true,\n          maintainAspectRatio: false\n        }\n      }, {\n        canvas: {\n          style: ''\n        },\n        wrapper: {\n          style: 'width: 300px; height: 350px; position: relative; direction: rtl'\n        }\n      });\n\n      expect(chart).toBeChartOfSize({\n        dw: 300, dh: 350,\n        rw: 300, rh: 350,\n      });\n\n      var wrapper = chart.canvas.parentNode;\n      waitForResize(chart, function() {\n        expect(chart).toBeChartOfSize({\n          dw: 455, dh: 350,\n          rw: 455, rh: 350,\n        });\n\n        waitForResize(chart, function() {\n          expect(chart).toBeChartOfSize({\n            dw: 150, dh: 350,\n            rw: 150, rh: 350,\n          });\n\n          done();\n        });\n        wrapper.style.width = '150px';\n      });\n      wrapper.style.width = '455px';\n    });\n\n    it('should resize the canvas when parent height changes', function(done) {\n      var chart = acquireChart({\n        options: {\n          responsive: true,\n          maintainAspectRatio: false\n        }\n      }, {\n        canvas: {\n          style: ''\n        },\n        wrapper: {\n          style: 'width: 300px; height: 350px; position: relative'\n        }\n      });\n\n      expect(chart).toBeChartOfSize({\n        dw: 300, dh: 350,\n        rw: 300, rh: 350,\n      });\n\n      var wrapper = chart.canvas.parentNode;\n      waitForResize(chart, function() {\n        expect(chart).toBeChartOfSize({\n          dw: 300, dh: 455,\n          rw: 300, rh: 455,\n        });\n\n        waitForResize(chart, function() {\n          expect(chart).toBeChartOfSize({\n            dw: 300, dh: 150,\n            rw: 300, rh: 150,\n          });\n\n          done();\n        });\n        wrapper.style.height = '150px';\n      });\n      wrapper.style.height = '455px';\n    });\n\n    it('should not include parent padding when resizing the canvas', function(done) {\n      var chart = acquireChart({\n        type: 'line',\n        options: {\n          responsive: true,\n          maintainAspectRatio: false\n        }\n      }, {\n        canvas: {\n          style: ''\n        },\n        wrapper: {\n          style: 'padding: 50px; width: 320px; height: 350px; position: relative'\n        }\n      });\n\n      expect(chart).toBeChartOfSize({\n        dw: 320, dh: 350,\n        rw: 320, rh: 350,\n      });\n\n      var wrapper = chart.canvas.parentNode;\n      waitForResize(chart, function() {\n        expect(chart).toBeChartOfSize({\n          dw: 455, dh: 355,\n          rw: 455, rh: 355,\n        });\n\n        done();\n      });\n      wrapper.style.height = '355px';\n      wrapper.style.width = '455px';\n    });\n\n    it('should resize the canvas when the canvas display style changes from \"none\" to \"block\"', function(done) {\n      var chart = acquireChart({\n        options: {\n          responsive: true,\n          maintainAspectRatio: false\n        }\n      }, {\n        canvas: {\n          style: 'display: none;'\n        },\n        wrapper: {\n          style: 'width: 320px; height: 350px'\n        }\n      });\n\n      var canvas = chart.canvas;\n      waitForResize(chart, function() {\n        expect(chart).toBeChartOfSize({\n          dw: 320, dh: 350,\n          rw: 320, rh: 350,\n        });\n\n        done();\n      });\n      canvas.style.display = 'block';\n    });\n\n    it('should resize the canvas when the wrapper display style changes from \"none\" to \"block\"', function(done) {\n      var chart = acquireChart({\n        options: {\n          responsive: true,\n          maintainAspectRatio: false\n        }\n      }, {\n        canvas: {\n          style: ''\n        },\n        wrapper: {\n          style: 'display: none; width: 460px; height: 380px'\n        }\n      });\n\n      var wrapper = chart.canvas.parentNode;\n      waitForResize(chart, function() {\n        expect(chart).toBeChartOfSize({\n          dw: 460, dh: 380,\n          rw: 460, rh: 380,\n        });\n\n        done();\n      });\n      wrapper.style.display = 'block';\n    });\n\n    it('should resize the canvas when the wrapper has display style changes from \"none\" to \"block\"', function(done) {\n      // https://github.com/chartjs/Chart.js/issues/4659\n      var chart = acquireChart({\n        options: {\n          responsive: true,\n          maintainAspectRatio: false\n        }\n      }, {\n        canvas: {\n          style: ''\n        },\n        wrapper: {\n          style: 'display: none; max-width: 600px; max-height: 400px;'\n        }\n      });\n\n      var wrapper = chart.canvas.parentNode;\n      waitForResize(chart, function() {\n        expect(chart).toBeChartOfSize({\n          dw: 600, dh: 300,\n          rw: 600, rh: 300,\n        });\n\n        done();\n      });\n      wrapper.style.display = 'block';\n    });\n\n    // https://github.com/chartjs/Chart.js/issues/5485\n    it('should resize the canvas when the devicePixelRatio changes', function(done) {\n      var chart = acquireChart({\n        options: {\n          responsive: true,\n          maintainAspectRatio: false,\n          devicePixelRatio: 1\n        }\n      }, {\n        canvas: {\n          style: ''\n        },\n        wrapper: {\n          style: 'width: 400px; height: 200px; position: relative'\n        }\n      });\n\n      expect(chart).toBeChartOfSize({\n        dw: 400, dh: 200,\n        rw: 400, rh: 200,\n      });\n\n      waitForResize(chart, function() {\n        expect(chart).toBeChartOfSize({\n          dw: 400, dh: 200,\n          rw: 800, rh: 400,\n        });\n\n        done();\n      });\n      chart.options.devicePixelRatio = 2;\n      chart.resize();\n    });\n\n    // https://github.com/chartjs/Chart.js/issues/3790\n    it('should resize the canvas if attached to the DOM after construction', function(done) {\n      var canvas = document.createElement('canvas');\n      var wrapper = document.createElement('div');\n      var body = window.document.body;\n      var chart = new Chart(canvas, {\n        type: 'line',\n        options: {\n          responsive: true,\n          maintainAspectRatio: false\n        }\n      });\n\n      expect(chart).toBeChartOfSize({\n        dw: 0, dh: 0,\n        rw: 0, rh: 0,\n      });\n      expect(chart.chartArea).toBeUndefined();\n\n      waitForResize(chart, function() {\n        expect(chart).toBeChartOfSize({\n          dw: 455, dh: 355,\n          rw: 455, rh: 355,\n        });\n\n        expect(chart.chartArea).not.toBeUndefined();\n\n        body.removeChild(wrapper);\n        chart.destroy();\n        done();\n      });\n\n      wrapper.style.cssText = 'width: 455px; height: 355px';\n      wrapper.appendChild(canvas);\n      body.appendChild(wrapper);\n    });\n\n    it('should resize the canvas when attached to a different parent', function(done) {\n      var canvas = document.createElement('canvas');\n      var wrapper = document.createElement('div');\n      var body = window.document.body;\n      var chart = new Chart(canvas, {\n        type: 'line',\n        options: {\n          responsive: true,\n          maintainAspectRatio: false\n        }\n      });\n\n      expect(chart).toBeChartOfSize({\n        dw: 0, dh: 0,\n        rw: 0, rh: 0,\n      });\n\n      waitForResize(chart, function() {\n        expect(chart).toBeChartOfSize({\n          dw: 455, dh: 355,\n          rw: 455, rh: 355,\n        });\n\n        var target = document.createElement('div');\n\n        waitForResize(chart, function() {\n          expect(chart).toBeChartOfSize({\n            dw: 640, dh: 480,\n            rw: 640, rh: 480,\n          });\n\n          body.removeChild(wrapper);\n          body.removeChild(target);\n          chart.destroy();\n          done();\n        });\n\n        target.style.cssText = 'width: 640px; height: 480px';\n        target.appendChild(canvas);\n        body.appendChild(target);\n      });\n\n      wrapper.style.cssText = 'width: 455px; height: 355px';\n      wrapper.appendChild(canvas);\n      body.appendChild(wrapper);\n    });\n\n    // https://github.com/chartjs/Chart.js/issues/3521\n    it('should resize the canvas after the wrapper has been re-attached to the DOM', function(done) {\n      var chart = acquireChart({\n        options: {\n          responsive: true,\n          maintainAspectRatio: false\n        }\n      }, {\n        canvas: {\n          style: ''\n        },\n        wrapper: {\n          style: 'width: 320px; height: 350px'\n        }\n      });\n\n      expect(chart).toBeChartOfSize({\n        dw: 320, dh: 350,\n        rw: 320, rh: 350,\n      });\n\n      var wrapper = chart.canvas.parentNode;\n      var parent = wrapper.parentNode;\n\n      waitForResize(chart, function() {\n        expect(chart).toBeChartOfSize({\n          dw: 320, dh: 355,\n          rw: 320, rh: 355,\n        });\n\n        waitForResize(chart, function() {\n          expect(chart).toBeChartOfSize({\n            dw: 455, dh: 355,\n            rw: 455, rh: 355,\n          });\n\n          done();\n        });\n\n        parent.removeChild(wrapper);\n        wrapper.style.width = '455px';\n        parent.appendChild(wrapper);\n      });\n\n      parent.removeChild(wrapper);\n      setTimeout(() => {\n        parent.appendChild(wrapper);\n        wrapper.style.height = '355px';\n      }, 0);\n    });\n\n    // https://github.com/chartjs/Chart.js/issues/9875\n    it('should detect detach/attach in series', function(done) {\n      var chart = acquireChart({\n        options: {\n          responsive: true,\n          maintainAspectRatio: false\n        }\n      }, {\n        canvas: {\n          style: ''\n        },\n        wrapper: {\n          style: 'width: 320px; height: 350px'\n        }\n      });\n\n      var wrapper = chart.canvas.parentNode;\n      var parent = wrapper.parentNode;\n\n      waitForResize(chart, function() {\n        expect(chart).toBeChartOfSize({\n          dw: 320, dh: 350,\n          rw: 320, rh: 350,\n        });\n\n        done();\n      });\n\n      parent.removeChild(wrapper);\n      parent.appendChild(wrapper);\n    });\n\n    it('should detect detach/attach/detach in series', function(done) {\n      var chart = acquireChart({\n        options: {\n          responsive: true,\n          maintainAspectRatio: false\n        }\n      }, {\n        canvas: {\n          style: ''\n        },\n        wrapper: {\n          style: 'width: 320px; height: 350px'\n        }\n      });\n\n      var wrapper = chart.canvas.parentNode;\n      var parent = wrapper.parentNode;\n\n      waitForResize(chart, function() {\n        fail();\n      });\n\n      parent.removeChild(wrapper);\n      parent.appendChild(wrapper);\n      parent.removeChild(wrapper);\n\n      setTimeout(function() {\n        expect(chart.attached).toBeFalse();\n        done();\n      }, 100);\n    });\n\n    it('should detect attach/detach in series', function(done) {\n      var chart = acquireChart({\n        options: {\n          responsive: true,\n          maintainAspectRatio: false\n        }\n      }, {\n        canvas: {\n          style: ''\n        },\n        wrapper: {\n          style: 'width: 320px; height: 350px'\n        }\n      });\n\n      var wrapper = chart.canvas.parentNode;\n      var parent = wrapper.parentNode;\n\n      parent.removeChild(wrapper);\n\n      setTimeout(function() {\n        expect(chart.attached).toBeFalse();\n\n        waitForResize(chart, function() {\n          fail();\n        });\n\n        parent.appendChild(wrapper);\n        parent.removeChild(wrapper);\n\n        setTimeout(function() {\n          expect(chart.attached).toBeFalse();\n\n          done();\n        }, 100);\n      }, 100);\n    });\n\n    // https://github.com/chartjs/Chart.js/issues/4737\n    it('should resize the canvas when re-creating the chart', function(done) {\n      var chart = acquireChart({\n        options: {\n          responsive: true\n        }\n      }, {\n        wrapper: {\n          style: 'width: 320px'\n        }\n      });\n\n      var wrapper = chart.canvas.parentNode;\n\n      waitForResize(chart, function() {\n        var canvas = chart.canvas;\n        expect(chart).toBeChartOfSize({\n          dw: 320, dh: 320,\n          rw: 320, rh: 320,\n        });\n\n        chart.destroy();\n        chart = new Chart(canvas, {\n          type: 'line',\n          options: {\n            responsive: true\n          }\n        });\n\n        waitForResize(chart, function() {\n          expect(chart).toBeChartOfSize({\n            dw: 455, dh: 455,\n            rw: 455, rh: 455,\n          });\n\n          chart.destroy();\n          window.document.body.removeChild(wrapper);\n          done();\n        });\n        canvas.parentNode.style.width = '455px';\n        canvas.parentNode.style.height = '455px';\n      });\n    });\n\n    it('should resize the canvas if attached to the DOM after construction with multiple parents', function(done) {\n      var canvas = document.createElement('canvas');\n      var wrapper = document.createElement('div');\n      var wrapper2 = document.createElement('div');\n      var wrapper3 = document.createElement('div');\n      var body = window.document.body;\n\n      var chart = new Chart(canvas, {\n        type: 'line',\n        options: {\n          responsive: true,\n          maintainAspectRatio: false\n        }\n      });\n\n      expect(chart).toBeChartOfSize({\n        dw: 0, dh: 0,\n        rw: 0, rh: 0,\n      });\n      expect(chart.chartArea).toBeUndefined();\n\n      waitForResize(chart, function() {\n        expect(chart).toBeChartOfSize({\n          dw: 455, dh: 355,\n          rw: 455, rh: 355,\n        });\n\n        expect(chart.chartArea).not.toBeUndefined();\n\n        body.removeChild(wrapper3);\n        chart.destroy();\n        done();\n      });\n\n      wrapper3.appendChild(wrapper2);\n      wrapper2.appendChild(wrapper);\n      wrapper.style.cssText = 'width: 455px; height: 355px';\n      wrapper.appendChild(canvas);\n      body.appendChild(wrapper3);\n    });\n  });\n\n  describe('config.options.responsive: true (maintainAspectRatio: true)', function() {\n    it('should resize the canvas with correct aspect ratio when parent width changes', function(done) {\n      var chart = acquireChart({\n        type: 'line', // AR == 2\n        options: {\n          responsive: true,\n          maintainAspectRatio: true\n        }\n      }, {\n        canvas: {\n          style: ''\n        },\n        wrapper: {\n          style: 'width: 300px; height: 350px; position: relative'\n        }\n      });\n\n      expect(chart).toBeChartOfSize({\n        dw: 300, dh: 150,\n        rw: 300, rh: 150,\n      });\n\n      var wrapper = chart.canvas.parentNode;\n      waitForResize(chart, function() {\n        expect(chart).toBeChartOfSize({\n          dw: 450, dh: 225,\n          rw: 450, rh: 225,\n        });\n\n        waitForResize(chart, function() {\n          expect(chart).toBeChartOfSize({\n            dw: 150, dh: 75,\n            rw: 150, rh: 75,\n          });\n\n          done();\n        });\n        wrapper.style.width = '150px';\n      });\n      wrapper.style.width = '450px';\n    });\n\n    it('should maintain aspect ratio when parent height changes', function(done) {\n      var chart = acquireChart({\n        options: {\n          responsive: true,\n          maintainAspectRatio: true\n        }\n      }, {\n        canvas: {\n          style: ''\n        },\n        wrapper: {\n          style: 'width: 320px; height: 350px; position: relative'\n        }\n      });\n\n      expect(chart).toBeChartOfSize({\n        dw: 320, dh: 160,\n        rw: 320, rh: 160,\n      });\n\n      var wrapper = chart.canvas.parentNode;\n      waitForResize(chart, function() {\n        expect(chart).toBeChartOfSize({\n          dw: 320, dh: 160,\n          rw: 320, rh: 160,\n        });\n\n        waitForResize(chart, function() {\n          expect(chart).toBeChartOfSize({\n            dw: 300, dh: 150,\n            rw: 300, rh: 150,\n          });\n\n          done();\n        });\n        wrapper.style.height = '150px';\n      });\n      wrapper.style.height = '455px';\n    });\n  });\n\n  describe('Retina scale (a.k.a. device pixel ratio)', function() {\n    beforeEach(function() {\n      this.devicePixelRatio = window.devicePixelRatio;\n      window.devicePixelRatio = 3;\n    });\n\n    afterEach(function() {\n      window.devicePixelRatio = this.devicePixelRatio;\n    });\n\n    // see https://github.com/chartjs/Chart.js/issues/3575\n    it ('should scale the render size but not the \"implicit\" display size', function() {\n      var chart = acquireChart({\n        options: {\n          responsive: false\n        }\n      }, {\n        canvas: {\n          width: 320,\n          height: 240,\n        }\n      });\n\n      expect(chart).toBeChartOfSize({\n        dw: 320, dh: 240,\n        rw: 960, rh: 720,\n      });\n    });\n\n    it ('should scale the render size but not the \"explicit\" display size', function() {\n      var chart = acquireChart({\n        options: {\n          responsive: false\n        }\n      }, {\n        canvas: {\n          style: 'width: 320px; height: 240px'\n        }\n      });\n\n      expect(chart).toBeChartOfSize({\n        dw: 320, dh: 240,\n        rw: 960, rh: 720,\n      });\n    });\n  });\n\n  describe('config.options.devicePixelRatio', function() {\n    beforeEach(function() {\n      this.devicePixelRatio = window.devicePixelRatio;\n      window.devicePixelRatio = 1;\n    });\n\n    afterEach(function() {\n      window.devicePixelRatio = this.devicePixelRatio;\n    });\n\n    // see https://github.com/chartjs/Chart.js/issues/3575\n    it ('should scale the render size but not the \"implicit\" display size', function() {\n      var chart = acquireChart({\n        options: {\n          responsive: false,\n          devicePixelRatio: 3\n        }\n      }, {\n        canvas: {\n          width: 320,\n          height: 240,\n        }\n      });\n\n      expect(chart).toBeChartOfSize({\n        dw: 320, dh: 240,\n        rw: 960, rh: 720,\n      });\n    });\n\n    it ('should scale the render size but not the \"explicit\" display size', function() {\n      var chart = acquireChart({\n        options: {\n          responsive: false,\n          devicePixelRatio: 3\n        }\n      }, {\n        canvas: {\n          style: 'width: 320px; height: 240px'\n        }\n      });\n\n      expect(chart).toBeChartOfSize({\n        dw: 320, dh: 240,\n        rw: 960, rh: 720,\n      });\n    });\n  });\n\n  describe('config.options.aspectRatio', function() {\n    it('should resize the canvas when the aspectRatio option changes', function(done) {\n      var chart = acquireChart({\n        options: {\n          responsive: true,\n          aspectRatio: 1,\n        }\n      }, {\n        canvas: {\n          style: '',\n          width: 400,\n        },\n      });\n\n      expect(chart).toBeChartOfSize({\n        dw: 400, dh: 400,\n        rw: 400, rh: 400,\n      });\n\n      waitForResize(chart, function() {\n        expect(chart).toBeChartOfSize({\n          dw: 400, dh: 200,\n          rw: 400, rh: 200,\n        });\n\n        done();\n      });\n      chart.options.aspectRatio = 2;\n      chart.resize();\n    });\n  });\n\n  describe('controller.reset', function() {\n    it('should reset the chart elements', function() {\n      var chart = acquireChart({\n        type: 'line',\n        data: {\n          labels: ['A', 'B', 'C', 'D'],\n          datasets: [{\n            data: [10, 20, 30, 0]\n          }]\n        },\n        options: {\n          responsive: true\n        }\n      });\n\n      var meta = chart.getDatasetMeta(0);\n\n      // Verify that points are at their initial correct location,\n      // then we will reset and see that they moved\n      expect(meta.data[0].y).toBeCloseToPixel(333);\n      expect(meta.data[1].y).toBeCloseToPixel(183);\n      expect(meta.data[2].y).toBeCloseToPixel(32);\n      expect(meta.data[3].y).toBeCloseToPixel(482);\n\n      chart.reset();\n\n      // For a line chart, the animation state is the bottom\n      expect(meta.data[0].y).toBeCloseToPixel(482);\n      expect(meta.data[1].y).toBeCloseToPixel(482);\n      expect(meta.data[2].y).toBeCloseToPixel(482);\n      expect(meta.data[3].y).toBeCloseToPixel(482);\n    });\n  });\n\n  describe('config update', function() {\n    it ('should update options', function() {\n      var chart = acquireChart({\n        type: 'line',\n        data: {\n          labels: ['A', 'B', 'C', 'D'],\n          datasets: [{\n            data: [10, 20, 30, 100]\n          }]\n        },\n        options: {\n          responsive: true\n        }\n      });\n\n      chart.options = {\n        responsive: false,\n        scales: {\n          y: {\n            min: 0,\n            max: 10\n          }\n        }\n      };\n      chart.update();\n\n      var yScale = chart.scales.y;\n      expect(yScale.options.min).toBe(0);\n      expect(yScale.options.max).toBe(10);\n    });\n\n    it ('should update scales options', function() {\n      var chart = acquireChart({\n        type: 'line',\n        data: {\n          labels: ['A', 'B', 'C', 'D'],\n          datasets: [{\n            data: [10, 20, 30, 100]\n          }]\n        },\n        options: {\n          responsive: true\n        }\n      });\n\n      chart.options.scales.y.min = 0;\n      chart.options.scales.y.max = 10;\n      chart.update();\n\n      var yScale = chart.scales.y;\n      expect(yScale.options.min).toBe(0);\n      expect(yScale.options.max).toBe(10);\n    });\n\n    it ('should update scales options from new object', function() {\n      var chart = acquireChart({\n        type: 'line',\n        data: {\n          labels: ['A', 'B', 'C', 'D'],\n          datasets: [{\n            data: [10, 20, 30, 100]\n          }]\n        },\n        options: {\n          responsive: true\n        }\n      });\n\n      var newScalesConfig = {\n        y: {\n          min: 0,\n          max: 10\n        }\n      };\n      chart.options.scales = newScalesConfig;\n\n      chart.update();\n\n      var yScale = chart.scales.y;\n      expect(yScale.options.min).toBe(0);\n      expect(yScale.options.max).toBe(10);\n    });\n\n    it ('should remove discarded scale', function() {\n      var chart = acquireChart({\n        type: 'line',\n        data: {\n          labels: ['A', 'B', 'C', 'D'],\n          datasets: [{\n            data: [10, 20, 30, 100]\n          }]\n        },\n        options: {\n          responsive: true,\n          scales: {\n            yAxis0: {\n              min: 0,\n              max: 10\n            }\n          }\n        }\n      });\n\n      var newScalesConfig = {\n        y: {\n          min: 0,\n          max: 10\n        }\n      };\n      chart.options.scales = newScalesConfig;\n\n      chart.update();\n\n      var yScale = chart.scales.yAxis0;\n      expect(yScale).toBeUndefined();\n      var newyScale = chart.scales.y;\n      expect(newyScale.options.min).toBe(0);\n      expect(newyScale.options.max).toBe(10);\n    });\n\n    it ('should update tooltip options', function() {\n      var chart = acquireChart({\n        type: 'line',\n        data: {\n          labels: ['A', 'B', 'C', 'D'],\n          datasets: [{\n            data: [10, 20, 30, 100]\n          }]\n        },\n        options: {\n          responsive: true\n        }\n      });\n\n      var newTooltipConfig = {\n        mode: 'dataset',\n        intersect: false\n      };\n      chart.options.plugins.tooltip = newTooltipConfig;\n\n      chart.update();\n      expect(chart.tooltip.options).toEqualOptions(newTooltipConfig);\n    });\n\n    it ('should update the tooltip on update', async function() {\n      var chart = acquireChart({\n        type: 'line',\n        data: {\n          labels: ['A', 'B', 'C', 'D'],\n          datasets: [{\n            data: [10, 20, 30, 100]\n          }]\n        },\n        options: {\n          responsive: true,\n          tooltip: {\n            mode: 'nearest'\n          }\n        }\n      });\n\n      // Trigger an event over top of a point to\n      // put an item into the tooltip\n      var meta = chart.getDatasetMeta(0);\n      var point = meta.data[1];\n\n      await jasmine.triggerMouseEvent(chart, 'mousemove', point);\n      // Check and see if tooltip was displayed\n      var tooltip = chart.tooltip;\n\n      expect(chart._active[0].element).toEqual(point);\n      expect(tooltip._active[0].element).toEqual(point);\n\n      // Update and confirm tooltip is updated\n      chart.update();\n      expect(chart._active[0].element).toEqual(point);\n      expect(tooltip._active[0].element).toEqual(point);\n    });\n\n    it ('should update the metadata', function() {\n      var cfg = {\n        data: {\n          labels: ['A', 'B', 'C', 'D'],\n          datasets: [{\n            type: 'line',\n            data: [10, 20, 30, 0]\n          }]\n        },\n        options: {\n          responsive: true,\n          scales: {\n            x: {\n              type: 'category'\n            },\n            y: {\n              type: 'linear',\n              title: {\n                display: true,\n                text: 'Value'\n              }\n            }\n          }\n        }\n      };\n      var chart = acquireChart(cfg);\n      var meta = chart.getDatasetMeta(0);\n      expect(meta.type).toBe('line');\n\n      // change the dataset to bar and check that meta was updated\n      chart.config.data.datasets[0].type = 'bar';\n      chart.update();\n      meta = chart.getDatasetMeta(0);\n      expect(meta.type).toBe('bar');\n    });\n  });\n\n  describe('plugin.extensions', function() {\n    var hooks = {\n      install: ['install'],\n      uninstall: ['uninstall'],\n      init: [\n        'beforeInit',\n        'resize',\n        'afterInit'\n      ],\n      start: ['start'],\n      stop: ['stop'],\n      update: [\n        'beforeUpdate',\n        'beforeLayout',\n        'beforeDataLimits', // y-axis fit\n        'afterDataLimits',\n        'beforeBuildTicks',\n        'afterBuildTicks',\n        'beforeDataLimits', // x-axis fit\n        'afterDataLimits',\n        'beforeBuildTicks',\n        'afterBuildTicks',\n        // 'beforeBuildTicks', // y-axis re-fit\n        // 'afterBuildTicks',\n        'afterLayout',\n        'beforeDatasetsUpdate',\n        'beforeDatasetUpdate',\n        'afterDatasetUpdate',\n        'afterDatasetsUpdate',\n        'afterUpdate',\n      ],\n      render: [\n        'beforeRender',\n        'beforeDraw',\n        'beforeDatasetsDraw',\n        'beforeDatasetDraw',\n        'afterDatasetDraw',\n        'afterDatasetsDraw',\n        // 'beforeTooltipDraw',\n        // 'afterTooltipDraw',\n        'afterDraw',\n        'afterRender',\n      ],\n      resize: [\n        'resize'\n      ],\n      destroy: [\n        'beforeDestroy',\n        'afterDestroy'\n      ]\n    };\n\n    it ('should notify plugin in correct order', function(done) {\n      var plugin = this.plugin = {};\n      var sequence = [];\n\n      Object.keys(hooks).forEach(function(group) {\n        hooks[group].forEach(function(name) {\n          plugin[name] = function() {\n            sequence.push(name);\n          };\n        });\n      });\n\n      var chart = window.acquireChart({\n        type: 'line',\n        data: {datasets: [{}]},\n        plugins: [plugin],\n        options: {\n          responsive: true\n        }\n      }, {\n        wrapper: {\n          style: 'width: 300px'\n        }\n      });\n\n      waitForResize(chart, function() {\n        chart.destroy();\n\n        expect(sequence).toEqual([].concat(\n          hooks.install,\n          hooks.start,\n          hooks.init,\n          hooks.update,\n          hooks.render,\n          hooks.resize,\n          hooks.update,\n          hooks.render,\n          hooks.destroy,\n          hooks.stop,\n          hooks.uninstall\n        ));\n\n        done();\n      });\n      chart.canvas.parentNode.style.width = '400px';\n      chart.canvas.parentNode.style.height = '400px';\n    });\n\n    it ('should notify initially disabled plugin in correct order', function() {\n      var plugin = this.plugin = {id: 'plugin'};\n      var sequence = [];\n\n      Object.keys(hooks).forEach(function(group) {\n        hooks[group].forEach(function(name) {\n          plugin[name] = function() {\n            sequence.push(name);\n          };\n        });\n      });\n\n      var chart = window.acquireChart({\n        type: 'line',\n        data: {datasets: [{}]},\n        plugins: [plugin],\n        options: {\n          plugins: {\n            plugin: false\n          }\n        }\n      });\n\n      expect(sequence).toEqual([].concat(\n        hooks.install\n      ));\n\n      sequence = [];\n      chart.options.plugins.plugin = true;\n      chart.update();\n\n      expect(sequence).toEqual([].concat(\n        hooks.start,\n        hooks.update,\n        hooks.render\n      ));\n\n      sequence = [];\n      chart.options.plugins.plugin = false;\n      chart.update();\n\n      expect(sequence).toEqual(hooks.stop);\n\n      sequence = [];\n      chart.destroy();\n\n      expect(sequence).toEqual(hooks.uninstall);\n    });\n\n    it('should not notify before/afterDatasetDraw if dataset is hidden', function() {\n      var sequence = [];\n      var plugin = this.plugin = {\n        beforeDatasetDraw: function(chart, args) {\n          sequence.push('before-' + args.index);\n        },\n        afterDatasetDraw: function(chart, args) {\n          sequence.push('after-' + args.index);\n        }\n      };\n\n      window.acquireChart({\n        type: 'line',\n        data: {datasets: [{}, {hidden: true}, {}]},\n        plugins: [plugin]\n      });\n\n      expect(sequence).toEqual([\n        'before-2', 'after-2',\n        'before-0', 'after-0'\n      ]);\n    });\n\n    it('should not crash when accessing options of a blank inline plugin', function() {\n      var chart = window.acquireChart({\n        type: 'line',\n        data: {datasets: [{}]},\n        plugins: [{}],\n      });\n\n      function iterateOptions() {\n        for (const plugin of chart._plugins._init) {\n          // triggering bug https://github.com/chartjs/Chart.js/issues/9368\n          expect(Object.getPrototypeOf(plugin.options)).toBeNull();\n        }\n      }\n\n      expect(iterateOptions).not.toThrow();\n    });\n  });\n\n  describe('metasets', function() {\n    beforeEach(function() {\n      this.chart = acquireChart({\n        type: 'line',\n        data: {\n          datasets: [\n            {label: '1', order: 2},\n            {label: '2', order: 1},\n            {label: '3', order: 4},\n            {label: '4', order: 3},\n          ]\n        }\n      });\n    });\n    afterEach(function() {\n      const metasets = this.chart._metasets;\n      expect(metasets.length).toEqual(this.chart.data.datasets.length);\n      for (let i = 0; i < metasets.length; i++) {\n        expect(metasets[i].index).toEqual(i);\n        expect(metasets[i]._dataset).toEqual(this.chart.data.datasets[i]);\n      }\n    });\n    it('should build metasets array in order', function() {\n      const metasets = this.chart._metasets;\n      expect(metasets[0].order).toEqual(2);\n      expect(metasets[1].order).toEqual(1);\n      expect(metasets[2].order).toEqual(4);\n      expect(metasets[3].order).toEqual(3);\n    });\n    it('should build sorted metasets array in correct order', function() {\n      const metasets = this.chart._sortedMetasets;\n      expect(metasets[0].order).toEqual(1);\n      expect(metasets[1].order).toEqual(2);\n      expect(metasets[2].order).toEqual(3);\n      expect(metasets[3].order).toEqual(4);\n    });\n    it('should be moved when datasets are removed from beginning', function() {\n      this.chart.data.datasets.splice(0, 2);\n      this.chart.update();\n      const metasets = this.chart._metasets;\n      expect(metasets[0].order).toEqual(4);\n      expect(metasets[1].order).toEqual(3);\n    });\n    it('should be moved when datasets are removed from middle', function() {\n      this.chart.data.datasets.splice(1, 2);\n      this.chart.update();\n      const metasets = this.chart._metasets;\n      expect(metasets[0].order).toEqual(2);\n      expect(metasets[1].order).toEqual(3);\n    });\n    it('should be moved when datasets are inserted', function() {\n      this.chart.data.datasets.splice(1, 0, {label: '1.5', order: 5});\n      this.chart.update();\n      const metasets = this.chart._metasets;\n      expect(metasets[0].order).toEqual(2);\n      expect(metasets[1].order).toEqual(5);\n      expect(metasets[2].order).toEqual(1);\n      expect(metasets[3].order).toEqual(4);\n      expect(metasets[4].order).toEqual(3);\n    });\n    it('should be replaced when dataset is replaced', function() {\n      this.chart.data.datasets.splice(1, 1, {label: '1.5', order: 5});\n      this.chart.update();\n      const metasets = this.chart._metasets;\n      expect(metasets[0].order).toEqual(2);\n      expect(metasets[1].order).toEqual(5);\n      expect(metasets[2].order).toEqual(4);\n      expect(metasets[3].order).toEqual(3);\n    });\n    it('should update properly when dataset locations are swapped', function() {\n      const orig = this.chart.data.datasets;\n      this.chart.data.datasets = [orig[0], orig[2], orig[1], orig[3]];\n      this.chart.update();\n      let metasets = this.chart._metasets;\n      expect(metasets[0].label).toEqual('1');\n      expect(metasets[1].label).toEqual('3');\n      expect(metasets[2].label).toEqual('2');\n      expect(metasets[3].label).toEqual('4');\n\n      this.chart.data.datasets = [{label: 'new', order: 10}, orig[3], orig[2], orig[1], orig[0]];\n      this.chart.update();\n      metasets = this.chart._metasets;\n      expect(metasets[0].label).toEqual('new');\n      expect(metasets[1].label).toEqual('4');\n      expect(metasets[2].label).toEqual('3');\n      expect(metasets[3].label).toEqual('2');\n      expect(metasets[4].label).toEqual('1');\n\n      this.chart.data.datasets = [orig[3], orig[2], orig[1], {label: 'new', order: 10}];\n      this.chart.update();\n      metasets = this.chart._metasets;\n      expect(metasets[0].label).toEqual('4');\n      expect(metasets[1].label).toEqual('3');\n      expect(metasets[2].label).toEqual('2');\n      expect(metasets[3].label).toEqual('new');\n    });\n  });\n\n  describe('_destroyDatasetMeta', function() {\n    beforeEach(function() {\n      this.chart = acquireChart({\n        type: 'line',\n        data: {\n          datasets: [\n            {label: '1', order: 2},\n            {label: '2', order: 1},\n            {label: '3', order: 4},\n            {label: '4', order: 3},\n          ]\n        }\n      });\n    });\n    it('cleans up metasets when the chart is destroyed', function() {\n      this.chart.destroy();\n      expect(this.chart._metasets).toEqual([undefined, undefined, undefined, undefined]);\n    });\n  });\n\n  describe('data visibility', function() {\n    it('should hide a dataset', function() {\n      var chart = acquireChart({\n        type: 'line',\n        data: {\n          datasets: [{\n            data: [0, 1, 2]\n          }],\n          labels: ['a', 'b', 'c']\n        }\n      });\n\n      chart.setDatasetVisibility(0, false);\n\n      var meta = chart.getDatasetMeta(0);\n      expect(meta.hidden).toBe(true);\n    });\n\n    it('should toggle data visibility by index', function() {\n      var chart = acquireChart({\n        type: 'pie',\n        data: {\n          datasets: [{\n            data: [1, 2, 3]\n          }]\n        }\n      });\n\n      expect(chart.getDataVisibility(1)).toBe(true);\n\n      chart.toggleDataVisibility(1);\n      expect(chart.getDataVisibility(1)).toBe(false);\n\n      chart.update();\n      expect(chart.getDataVisibility(1)).toBe(false);\n    });\n\n    it('should maintain data visibility indices when data changes', function() {\n      var chart = acquireChart({\n        type: 'pie',\n        data: {\n          labels: ['0', '1', '2', '3'],\n          datasets: [{\n            data: [0, 1, 2, 3]\n          }, {\n            data: [0, 1, 2, 3]\n          }]\n        }\n      });\n\n      chart.toggleDataVisibility(3);\n\n      chart.data.labels.splice(1, 1);\n      chart.data.datasets[0].data.splice(1, 1);\n      chart.data.datasets[1].data.splice(1, 1);\n      chart.update();\n\n      expect(chart.getDataVisibility(0)).toBe(true);\n      expect(chart.getDataVisibility(1)).toBe(true);\n      expect(chart.getDataVisibility(2)).toBe(false);\n\n      chart.data.labels.unshift('-1', '-2');\n      chart.data.datasets[0].data.unshift(-1, -2);\n      chart.data.datasets[1].data.unshift(-1, -2);\n      chart.update();\n\n      expect(chart.getDataVisibility(0)).toBe(true);\n      expect(chart.getDataVisibility(1)).toBe(true);\n      expect(chart.getDataVisibility(2)).toBe(true);\n      expect(chart.getDataVisibility(3)).toBe(true);\n      expect(chart.getDataVisibility(4)).toBe(false);\n\n      chart.data.labels.shift();\n      chart.data.datasets[0].data.shift();\n      chart.data.datasets[1].data.shift();\n      chart.update();\n\n      expect(chart.getDataVisibility(0)).toBe(true);\n      expect(chart.getDataVisibility(1)).toBe(true);\n      expect(chart.getDataVisibility(2)).toBe(true);\n      expect(chart.getDataVisibility(3)).toBe(false);\n\n      chart.data.labels.pop();\n      chart.data.datasets[0].data.pop();\n      chart.data.datasets[1].data.pop();\n      chart.update();\n\n      expect(chart.getDataVisibility(0)).toBe(true);\n      expect(chart.getDataVisibility(1)).toBe(true);\n      expect(chart.getDataVisibility(2)).toBe(true);\n      expect(chart.getDataVisibility(3)).toBe(true);\n\n      chart.toggleDataVisibility(1);\n      chart.data.labels.splice(1, 0, 'b');\n      chart.data.datasets[0].data.splice(1, 0, 1);\n      chart.data.datasets[1].data.splice(1, 0, 1);\n      chart.update();\n\n      expect(chart.getDataVisibility(0)).toBe(true);\n      expect(chart.getDataVisibility(1)).toBe(true);\n      expect(chart.getDataVisibility(2)).toBe(false);\n      expect(chart.getDataVisibility(3)).toBe(true);\n    });\n\n    it('should leave data visibility indices intact when data changes in non-uniform way', function() {\n      var chart = acquireChart({\n        type: 'pie',\n        data: {\n          labels: ['0', '1', '2', '3'],\n          datasets: [{\n            data: [0, 1, 2, 3]\n          }, {\n            data: [0, 1, 2, 3]\n          }]\n        }\n      });\n\n      chart.toggleDataVisibility(0);\n\n      chart.data.labels.push('a');\n      chart.data.datasets[0].data.pop();\n      chart.data.datasets[1].data.push(5);\n      chart.update();\n\n      expect(chart.getDataVisibility(0)).toBe(false);\n      expect(chart.getDataVisibility(1)).toBe(true);\n      expect(chart.getDataVisibility(2)).toBe(true);\n      expect(chart.getDataVisibility(3)).toBe(true);\n    });\n  });\n\n  describe('isDatasetVisible', function() {\n    it('should return false if index is out of bounds', function() {\n      var chart = acquireChart({\n        type: 'line',\n        data: {\n          datasets: [{\n            data: [0, 1, 2]\n          }],\n          labels: ['a', 'b', 'c']\n        }\n      });\n\n      expect(chart.isDatasetVisible(1)).toBe(false);\n    });\n  });\n\n  describe('getChart', function() {\n    it('should get the chart from the canvas ID', function() {\n      var chart = acquireChart({\n        type: 'pie',\n        data: {\n          datasets: [{\n            data: [1, 2, 3]\n          }]\n        }\n      });\n      chart.canvas.id = 'myID';\n\n      expect(Chart.getChart('myID')).toBe(chart);\n    });\n\n    it('should get the chart from an HTMLCanvasElement', function() {\n      var chart = acquireChart({\n        type: 'pie',\n        data: {\n          datasets: [{\n            data: [1, 2, 3]\n          }]\n        }\n      });\n      expect(Chart.getChart(chart.canvas)).toBe(chart);\n    });\n\n    it('should get the chart from an CanvasRenderingContext2D', function() {\n      var chart = acquireChart({\n        type: 'pie',\n        data: {\n          datasets: [{\n            data: [1, 2, 3]\n          }]\n        }\n      });\n      expect(Chart.getChart(chart.ctx)).toBe(chart);\n    });\n\n    it('should return undefined when a chart is not found or bad data is provided', function() {\n      expect(Chart.getChart(1)).toBeUndefined();\n    });\n  });\n\n  describe('active elements', function() {\n    it('should set the active elements', function() {\n      var chart = acquireChart({\n        type: 'pie',\n        data: {\n          datasets: [{\n            data: [1, 2, 3],\n            borderColor: 'red',\n            hoverBorderColor: 'blue',\n          }]\n        }\n      });\n\n      const meta = chart.getDatasetMeta(0);\n      let props = meta.data[0].getProps(['borderColor']);\n      expect(props.options.borderColor).toEqual('red');\n\n      chart.setActiveElements([{\n        datasetIndex: 0,\n        index: 0,\n      }]);\n\n      props = meta.data[0].getProps(['borderColor']);\n      expect(props.options.borderColor).toEqual('blue');\n\n      const active = chart.getActiveElements();\n      expect(active.length).toEqual(1);\n      expect(active[0].element).toBe(meta.data[0]);\n    });\n  });\n\n  it('should not replace the user set active elements by event replay', async function() {\n    var chart = acquireChart({\n      type: 'line',\n      data: {\n        labels: [1, 2, 3],\n        datasets: [{\n          data: [1, 2, 3],\n          borderColor: 'red',\n          hoverBorderColor: 'blue',\n        }]\n      }\n    });\n\n    const meta = chart.getDatasetMeta(0);\n    const point0 = meta.data[0];\n    const point1 = meta.data[1];\n\n    let props = meta.data[0].getProps(['borderColor']);\n    expect(props.options.borderColor).toEqual('red');\n\n    await jasmine.triggerMouseEvent(chart, 'mousemove', {x: point0.x, y: point0.y});\n    expect(chart.getActiveElements()).toEqual([{datasetIndex: 0, index: 0, element: point0}]);\n    expect(point0.options.borderColor).toEqual('blue');\n    expect(point1.options.borderColor).toEqual('red');\n\n    chart.setActiveElements([{datasetIndex: 0, index: 1}]);\n    expect(chart.getActiveElements()).toEqual([{datasetIndex: 0, index: 1, element: point1}]);\n    expect(point0.options.borderColor).toEqual('red');\n    expect(point1.options.borderColor).toEqual('blue');\n\n    chart.update();\n    expect(chart.getActiveElements()).toEqual([{datasetIndex: 0, index: 1, element: point1}]);\n    expect(point0.options.borderColor).toEqual('red');\n    expect(point1.options.borderColor).toEqual('blue');\n  });\n\n  describe('platform', function() {\n    it('should use the platform constructor provided in config', function() {\n      const chart = acquireChart({\n        platform: Chart.platforms.BasicPlatform,\n        type: 'line',\n      });\n      expect(chart.platform).toBeInstanceOf(Chart.platforms.BasicPlatform);\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/core.datasetController.tests.js",
    "content": "describe('Chart.DatasetController', function() {\n  describe('auto', jasmine.fixture.specs('core.datasetController'));\n\n  it('should listen for dataset data insertions or removals', function() {\n    var data = [0, 1, 2, 3, 4, 5];\n    var chart = acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          data: data\n        }]\n      }\n    });\n\n    var controller = chart.getDatasetMeta(0).controller;\n    var methods = [\n      '_onDataPush',\n      '_onDataPop',\n      '_onDataShift',\n      '_onDataSplice',\n      '_onDataUnshift'\n    ];\n\n    methods.forEach(function(method) {\n      spyOn(controller, method);\n    });\n\n    data.push(6, 7, 8);\n    data.push(9);\n    data.pop();\n    data.shift();\n    data.shift();\n    data.shift();\n    data.splice(1, 4, 10, 11);\n    data.unshift(12, 13, 14, 15);\n    data.unshift(16, 17);\n\n    [2, 1, 3, 1, 2].forEach(function(expected, index) {\n      expect(controller[methods[index]].calls.count()).toBe(expected);\n    });\n  });\n\n  it('should not try to delete non existent stacks', function() {\n    function createAndUpdateChart() {\n      var chart = acquireChart({\n        data: {\n          labels: ['q'],\n          datasets: [\n            {\n              id: 'dismissed',\n              label: 'Test before',\n              yAxisID: 'count',\n              data: [816],\n              type: 'bar',\n              stack: 'stack'\n            }\n          ]\n        },\n        options: {\n          scales: {\n            count: {\n              axis: 'y',\n              type: 'linear'\n            }\n          }\n        }\n      });\n\n      chart.data = {\n        datasets: [\n          {\n            id: 'tests',\n            yAxisID: 'count',\n            label: 'Test after',\n            data: [38300],\n            type: 'bar'\n          }\n        ],\n        labels: ['q']\n      };\n\n      chart.update();\n    }\n\n    expect(createAndUpdateChart).not.toThrow();\n  });\n\n  describe('inextensible data', function() {\n    it('should handle a frozen data object', function() {\n      function createChart() {\n        var data = Object.freeze([0, 1, 2, 3, 4, 5]);\n        expect(Object.isExtensible(data)).toBeFalsy();\n\n        var chart = acquireChart({\n          type: 'line',\n          data: {\n            datasets: [{\n              data: data\n            }]\n          }\n        });\n\n        var dataset = chart.data.datasets[0];\n        dataset.data = Object.freeze([5, 4, 3, 2, 1, 0]);\n        expect(Object.isExtensible(dataset.data)).toBeFalsy();\n        chart.update();\n\n        // Tests that the unlisten path also works for frozen objects\n        chart.destroy();\n      }\n\n      expect(createChart).not.toThrow();\n    });\n\n    it('should handle a sealed data object', function() {\n      function createChart() {\n        var data = Object.seal([0, 1, 2, 3, 4, 5]);\n        expect(Object.isExtensible(data)).toBeFalsy();\n\n        var chart = acquireChart({\n          type: 'line',\n          data: {\n            datasets: [{\n              data: data\n            }]\n          }\n        });\n\n        var dataset = chart.data.datasets[0];\n        dataset.data = Object.seal([5, 4, 3, 2, 1, 0]);\n        expect(Object.isExtensible(dataset.data)).toBeFalsy();\n        chart.update();\n\n        // Tests that the unlisten path also works for frozen objects\n        chart.destroy();\n      }\n\n      expect(createChart).not.toThrow();\n    });\n\n    it('should handle an unextendable data object', function() {\n      function createChart() {\n        var data = Object.preventExtensions([0, 1, 2, 3, 4, 5]);\n        expect(Object.isExtensible(data)).toBeFalsy();\n\n        var chart = acquireChart({\n          type: 'line',\n          data: {\n            datasets: [{\n              data: data\n            }]\n          }\n        });\n\n        var dataset = chart.data.datasets[0];\n        dataset.data = Object.preventExtensions([5, 4, 3, 2, 1, 0]);\n        expect(Object.isExtensible(dataset.data)).toBeFalsy();\n        chart.update();\n\n        // Tests that the unlisten path also works for frozen objects\n        chart.destroy();\n      }\n\n      expect(createChart).not.toThrow();\n    });\n  });\n\n  it('should parse data using correct scales', function() {\n    const data1 = [0, 1, 2, 3, 4, 5];\n    const data2 = ['a', 'b', 'c', 'd', 'a'];\n    const chart = acquireChart({\n      type: 'line',\n      data: {\n        datasets: [\n          {data: data1},\n          {data: data2, xAxisID: 'x2', yAxisID: 'y2'}\n        ]\n      },\n      options: {\n        scales: {\n          x: {\n            type: 'category',\n            labels: ['one', 'two', 'three', 'four', 'five', 'six']\n          },\n          x2: {\n            type: 'logarithmic',\n            labels: ['1', '10', '100', '1000', '2000']\n          },\n          y: {\n            type: 'linear'\n          },\n          y2: {\n            type: 'category',\n            labels: ['a', 'b', 'c', 'd', 'e']\n          }\n        }\n      }\n    });\n\n    const meta1 = chart.getDatasetMeta(0);\n    const parsedXValues1 = meta1._parsed.map(p => p.x);\n    const parsedYValues1 = meta1._parsed.map(p => p.y);\n\n    expect(meta1.data.length).toBe(6);\n    expect(parsedXValues1).toEqual([0, 1, 2, 3, 4, 5]); // label indices\n    expect(parsedYValues1).toEqual(data1);\n\n    const meta2 = chart.getDatasetMeta(1);\n    const parsedXValues2 = meta2._parsed.map(p => p.x);\n    const parsedYValues2 = meta2._parsed.map(p => p.y);\n\n    expect(meta2.data.length).toBe(5);\n    expect(parsedXValues2).toEqual([1, 10, 100, 1000, 2000]); // logarithmic scale labels\n    expect(parsedYValues2).toEqual([0, 1, 2, 3, 0]); // label indices\n  });\n\n  it('should parse using provided keys', function() {\n    const chart = acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          data: [\n            {x: 1, data: {key: 'one', value: 20}},\n            {data: {key: 'two', value: 30}}\n          ]\n        }]\n      },\n      options: {\n        parsing: {\n          xAxisKey: 'data.key',\n          yAxisKey: 'data.value'\n        },\n        scales: {\n          x: {\n            type: 'category',\n            labels: ['one', 'two']\n          },\n          y: {\n            type: 'linear'\n          },\n        }\n      }\n    });\n\n    const meta = chart.getDatasetMeta(0);\n    const parsedXValues = meta._parsed.map(p => p.x);\n    const parsedYValues = meta._parsed.map(p => p.y);\n\n    expect(meta.data.length).toBe(2);\n    expect(parsedXValues).toEqual([0, 1]); // label indices\n    expect(parsedYValues).toEqual([20, 30]);\n  });\n\n  describe('labels array synchronization', function() {\n    const data1 = [\n      {x: 'One', name: 'One', y: 1, value: 1},\n      {x: 'Two', name: 'Two', y: 2, value: 2}\n    ];\n    const data2 = [\n      {x: 'Three', name: 'Three', y: 3, value: 3},\n      {x: 'Four', name: 'Four', y: 4, value: 4},\n      {x: 'Five', name: 'Five', y: 5, value: 5}\n    ];\n    [\n      true,\n      false,\n      {\n        xAxisKey: 'name',\n        yAxisKey: 'value'\n      }\n    ].forEach(function(parsing) {\n      describe('when parsing is ' + JSON.stringify(parsing), function() {\n        it('should remove old labels when data is updated', function() {\n          const chart = acquireChart({\n            type: 'line',\n            data: {\n              datasets: [{\n                data: data1\n              }]\n            },\n            options: {\n              parsing\n            }\n          });\n\n          chart.data.datasets[0].data = data2;\n          chart.update();\n\n          const meta = chart.getDatasetMeta(0);\n          const labels = meta.iScale.getLabels();\n          expect(labels).toEqual(data2.map(n => n.x));\n        });\n\n        it('should not remove any user added labels', function() {\n          const chart = acquireChart({\n            type: 'line',\n            data: {\n              datasets: [{\n                data: data1\n              }]\n            },\n            options: {\n              parsing\n            }\n          });\n\n          chart.data.labels.push('user-added');\n          chart.data.datasets[0].data = [];\n          chart.update();\n\n          const meta = chart.getDatasetMeta(0);\n          const labels = meta.iScale.getLabels();\n          expect(labels).toEqual(['user-added']);\n        });\n\n        it('should not remove any user defined labels', function() {\n          const chart = acquireChart({\n            type: 'line',\n            data: {\n              datasets: [{\n                data: data1\n              }],\n              labels: ['user1', 'user2']\n            },\n            options: {\n              parsing\n            }\n          });\n\n          const meta = chart.getDatasetMeta(0);\n\n          expect(meta.iScale.getLabels()).toEqual(['user1', 'user2'].concat(data1.map(n => n.x)));\n\n          chart.data.datasets[0].data = data2;\n          chart.update();\n\n          expect(meta.iScale.getLabels()).toEqual(['user1', 'user2'].concat(data2.map(n => n.x)));\n        });\n\n        it('should keep up with multiple datasets', function() {\n          const chart = acquireChart({\n            type: 'line',\n            data: {\n              datasets: [{\n                data: data1\n              }, {\n                data: data2\n              }],\n              labels: ['One', 'Three']\n            },\n            options: {\n              parsing\n            }\n          });\n\n          const scale = chart.scales.x;\n\n          expect(scale.getLabels()).toEqual(['One', 'Three', 'Two', 'Four', 'Five']);\n\n          chart.data.datasets[0].data = data2;\n          chart.data.datasets[1].data = data1;\n          chart.update();\n\n          expect(scale.getLabels()).toEqual(['One', 'Three', 'Four', 'Five', 'Two']);\n        });\n\n      });\n    });\n  });\n\n\n  it('should synchronize metadata when data are inserted or removed and parsing is on', function() {\n    const data = [0, 1, 2, 3, 4, 5];\n    const chart = acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          data: data\n        }]\n      }\n    });\n\n    const meta = chart.getDatasetMeta(0);\n    const parsedYValues = () => meta._parsed.map(p => p.y);\n    let first, second, last;\n\n    first = meta.data[0];\n    last = meta.data[5];\n    data.push(6, 7, 8);\n    data.push(9);\n    chart.update();\n    expect(meta.data.length).toBe(10);\n    expect(meta.data[0]).toBe(first);\n    expect(meta.data[5]).toBe(last);\n    expect(parsedYValues()).toEqual(data);\n\n    last = meta.data[9];\n    data.pop();\n    chart.update();\n    expect(meta.data.length).toBe(9);\n    expect(meta.data[0]).toBe(first);\n    expect(meta.data.indexOf(last)).toBe(-1);\n    expect(parsedYValues()).toEqual(data);\n\n    last = meta.data[8];\n    data.shift();\n    data.shift();\n    data.shift();\n    chart.update();\n    expect(meta.data.length).toBe(6);\n    expect(meta.data.indexOf(first)).toBe(-1);\n    expect(meta.data[5]).toBe(last);\n    expect(parsedYValues()).toEqual(data);\n\n    first = meta.data[0];\n    second = meta.data[1];\n    last = meta.data[5];\n    data.splice(1, 4, 10, 11);\n    chart.update();\n    expect(meta.data.length).toBe(4);\n    expect(meta.data[0]).toBe(first);\n    expect(meta.data[3]).toBe(last);\n    expect(meta.data.indexOf(second)).toBe(-1);\n    expect(parsedYValues()).toEqual(data);\n\n    data.unshift(12, 13, 14, 15);\n    data.unshift(16, 17);\n    chart.update();\n    expect(meta.data.length).toBe(10);\n    expect(meta.data[6]).toBe(first);\n    expect(meta.data[9]).toBe(last);\n    expect(parsedYValues()).toEqual(data);\n  });\n\n  it('should synchronize metadata when data are inserted or removed and parsing is off', function() {\n    var data = [{x: 0, y: 0}, {x: 1, y: 1}, {x: 2, y: 2}, {x: 3, y: 3}, {x: 4, y: 4}, {x: 5, y: 5}];\n    var chart = acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          data: data\n        }]\n      },\n      options: {\n        parsing: false,\n        scales: {\n          x: {type: 'linear'},\n          y: {type: 'linear'}\n        }\n      }\n    });\n\n    var meta = chart.getDatasetMeta(0);\n    var controller = meta.controller;\n    var first, last;\n\n    first = controller.getParsed(0);\n    last = controller.getParsed(5);\n    data.push({x: 6, y: 6}, {x: 7, y: 7}, {x: 8, y: 8});\n    data.push({x: 9, y: 9});\n    chart.update();\n    expect(meta.data.length).toBe(10);\n    expect(controller.getParsed(0)).toBe(first);\n    expect(controller.getParsed(5)).toBe(last);\n\n    last = controller.getParsed(9);\n    data.pop();\n    chart.update();\n    expect(meta.data.length).toBe(9);\n    expect(controller.getParsed(0)).toBe(first);\n    expect(controller.getParsed(9)).toBe(undefined);\n    expect(controller.getParsed(8)).toEqual({x: 8, y: 8});\n\n    last = controller.getParsed(8);\n    data.shift();\n    data.shift();\n    data.shift();\n    chart.update();\n    expect(meta.data.length).toBe(6);\n    expect(controller.getParsed(5)).toBe(last);\n\n    first = controller.getParsed(0);\n    last = controller.getParsed(5);\n    data.splice(1, 4, {x: 10, y: 10}, {x: 11, y: 11});\n    chart.update();\n    expect(meta.data.length).toBe(4);\n    expect(controller.getParsed(0)).toBe(first);\n    expect(controller.getParsed(3)).toBe(last);\n    expect(controller.getParsed(1)).toEqual({x: 10, y: 10});\n\n    data.unshift({x: 12, y: 12}, {x: 13, y: 13}, {x: 14, y: 14}, {x: 15, y: 15});\n    data.unshift({x: 16, y: 16}, {x: 17, y: 17});\n    chart.update();\n    expect(meta.data.length).toBe(10);\n    expect(controller.getParsed(6)).toBe(first);\n    expect(controller.getParsed(9)).toBe(last);\n  });\n\n  it('should synchronize insert before removal when parsing is off', function() {\n    // https://github.com/chartjs/Chart.js/issues/9511\n    const data = [{x: 0, y: 1}, {x: 2, y: 7}, {x: 3, y: 5}];\n    var chart = acquireChart({\n      type: 'scatter',\n      data: {\n        datasets: [{\n          data: data,\n        }],\n      },\n      options: {\n        parsing: false,\n        scales: {\n          x: {\n            type: 'linear',\n            min: 0,\n            max: 10,\n          },\n          y: {\n            type: 'linear',\n            min: 0,\n            max: 10,\n          },\n        },\n      },\n    });\n\n    var meta = chart.getDatasetMeta(0);\n    var controller = meta.controller;\n\n    data.push({\n      x: 10,\n      y: 6\n    });\n    data.splice(0, 1);\n    chart.update();\n\n    expect(meta.data.length).toBe(3);\n    expect(controller.getParsed(0)).toBe(data[0]);\n    expect(controller.getParsed(2)).toBe(data[2]);\n  });\n\n  it('should re-synchronize metadata when the data object reference changes', function() {\n    var data0 = [0, 1, 2, 3, 4, 5];\n    var data1 = [6, 7, 8];\n    var data2 = [1, 2, 3, 4, 5, 6, 7, 8];\n\n    var chart = acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          data: data0\n        }]\n      }\n    });\n\n    var meta = chart.getDatasetMeta(0);\n\n    expect(meta.data.length).toBe(6);\n    expect(meta._parsed.map(p => p.y)).toEqual(data0);\n    const point0 = meta.data[0];\n\n    chart.data.datasets[0].data = data1;\n    chart.update();\n\n    expect(meta.data.length).toBe(3);\n    expect(meta._parsed.map(p => p.y)).toEqual(data1);\n    expect(meta.data[0]).toEqual(point0);\n\n    data1.push(9);\n    chart.update();\n    expect(meta.data.length).toBe(4);\n\n    chart.data.datasets[0].data = data0;\n    chart.update();\n\n    expect(meta.data.length).toBe(6);\n    expect(meta._parsed.map(p => p.y)).toEqual(data0);\n\n    chart.data.datasets[0].data = data2;\n    chart.update();\n\n    expect(meta.data.length).toBe(8);\n    expect(meta._parsed.map(p => p.y)).toEqual(data2);\n  });\n\n  it('should re-synchronize metadata when the data object reference changes, with animation', function() {\n    var data0 = [0, 1, 2, 3, 4, 5];\n    var data1 = [6, 7, 8];\n    var data2 = [1, 2, 3, 4, 5, 6, 7, 8];\n\n    var chart = acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          data: data0\n        }]\n      },\n      options: {\n        animation: true\n      }\n    });\n\n    var meta = chart.getDatasetMeta(0);\n\n    expect(meta.data.length).toBe(6);\n    expect(meta._parsed.map(p => p.y)).toEqual(data0);\n    const point0 = meta.data[0];\n\n    chart.data.datasets[0].data = data1;\n    chart.update();\n\n    expect(meta.data.length).toBe(3);\n    expect(meta._parsed.map(p => p.y)).toEqual(data1);\n    expect(meta.data[0]).toEqual(point0);\n\n    data1.push(9);\n    chart.update();\n    expect(meta.data.length).toBe(4);\n\n    chart.data.datasets[0].data = data0;\n    chart.update();\n\n    expect(meta.data.length).toBe(6);\n    expect(meta._parsed.map(p => p.y)).toEqual(data0);\n\n    chart.data.datasets[0].data = data2;\n    chart.update();\n\n    expect(meta.data.length).toBe(8);\n    expect(meta._parsed.map(p => p.y)).toEqual(data2);\n  });\n\n  it('should re-synchronize metadata when data are unusually altered', function() {\n    var data = [0, 1, 2, 3, 4, 5];\n    var chart = acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          data: data\n        }]\n      }\n    });\n\n    var meta = chart.getDatasetMeta(0);\n\n    expect(meta.data.length).toBe(6);\n\n    data.length = 2;\n    chart.update();\n\n    expect(meta.data.length).toBe(2);\n\n    data.length = 42;\n    chart.update();\n\n    expect(meta.data.length).toBe(42);\n  });\n\n  // https://github.com/chartjs/Chart.js/issues/7243\n  it('should re-synchronize metadata when data is moved and values are equal', function() {\n    var data = [10, 10, 10, 10, 10, 10];\n    var chart = acquireChart({\n      type: 'line',\n      data: {\n        labels: ['a', 'b', 'c', 'd', 'e', 'f'],\n        datasets: [{\n          data,\n          fill: true\n        }]\n      }\n    });\n\n    var meta = chart.getDatasetMeta(0);\n\n    expect(meta.data.length).toBe(6);\n    const firstX = meta.data[0].x;\n\n    data.push(data.shift());\n    chart.update();\n\n    expect(meta.data.length).toBe(6);\n    expect(meta.data[0].x).toEqual(firstX);\n  });\n\n  // https://github.com/chartjs/Chart.js/issues/7445\n  it('should re-synchronize metadata when data is objects and directly altered', function() {\n    var data = [{x: 'a', y: 1}, {x: 'b', y: 2}, {x: 'c', y: 3}];\n    var chart = acquireChart({\n      type: 'line',\n      data: {\n        labels: ['a', 'b', 'c'],\n        datasets: [{\n          data,\n          fill: true\n        }]\n      }\n    });\n\n    var meta = chart.getDatasetMeta(0);\n\n    expect(meta.data.length).toBe(3);\n    const y3 = meta.data[2].y;\n\n    data[0].y = 3;\n    chart.update();\n    expect(meta.data[0].y).toEqual(y3);\n  });\n\n  it('should re-synchronize metadata when scaleID changes', function() {\n    var chart = acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          data: [],\n          xAxisID: 'firstXScaleID',\n          yAxisID: 'firstYScaleID',\n        }]\n      },\n      options: {\n        scales: {\n          firstXScaleID: {\n            type: 'category',\n            position: 'bottom'\n          },\n          secondXScaleID: {\n            type: 'category',\n            position: 'bottom'\n          },\n          firstYScaleID: {\n            type: 'linear',\n            position: 'left'\n          },\n          secondYScaleID: {\n            type: 'linear',\n            position: 'left'\n          },\n        }\n      }\n    });\n\n    var meta = chart.getDatasetMeta(0);\n\n    expect(meta.xAxisID).toBe('firstXScaleID');\n    expect(meta.yAxisID).toBe('firstYScaleID');\n\n    chart.data.datasets[0].xAxisID = 'secondXScaleID';\n    chart.data.datasets[0].yAxisID = 'secondYScaleID';\n    chart.update();\n\n    expect(meta.xAxisID).toBe('secondXScaleID');\n    expect(meta.yAxisID).toBe('secondYScaleID');\n  });\n\n  it('should re-synchronize stacks when stack is changed', function() {\n    var chart = acquireChart({\n      type: 'bar',\n      data: {\n        labels: ['a', 'b'],\n        datasets: [{\n          data: [1, 10],\n          stack: '1'\n        }, {\n          data: [2, 20],\n          stack: '2'\n        }, {\n          data: [3, 30],\n          stack: '1'\n        }]\n      }\n    });\n\n    expect(chart._stacks).toEqual({\n      'x.y.1': {\n        0: {0: 1, 2: 3, _top: 2, _bottom: null, _visualValues: {0: 1, 2: 3}},\n        1: {0: 10, 2: 30, _top: 2, _bottom: null, _visualValues: {0: 10, 2: 30}}\n      },\n      'x.y.2': {\n        0: {1: 2, _top: 1, _bottom: null, _visualValues: {1: 2}},\n        1: {1: 20, _top: 1, _bottom: null, _visualValues: {1: 20}}\n      }\n    });\n\n    chart.data.datasets[2].stack = '2';\n    chart.update();\n\n    expect(chart._stacks).toEqual({\n      'x.y.1': {\n        0: {0: 1, _top: 2, _bottom: null, _visualValues: {0: 1}},\n        1: {0: 10, _top: 2, _bottom: null, _visualValues: {0: 10}}\n      },\n      'x.y.2': {\n        0: {1: 2, 2: 3, _top: 2, _bottom: null, _visualValues: {1: 2, 2: 3}},\n        1: {1: 20, 2: 30, _top: 2, _bottom: null, _visualValues: {1: 20, 2: 30}}\n      }\n    });\n  });\n\n  it('should re-synchronize stacks when data is removed', function() {\n    var chart = acquireChart({\n      type: 'bar',\n      data: {\n        labels: ['a', 'b'],\n        datasets: [{\n          data: [1, 10],\n          stack: '1'\n        }, {\n          data: [2, 20],\n          stack: '2'\n        }, {\n          data: [3, 30],\n          stack: '1'\n        }]\n      }\n    });\n\n    expect(chart._stacks).toEqual({\n      'x.y.1': {\n        0: {0: 1, 2: 3, _top: 2, _bottom: null, _visualValues: {0: 1, 2: 3}},\n        1: {0: 10, 2: 30, _top: 2, _bottom: null, _visualValues: {0: 10, 2: 30}}\n      },\n      'x.y.2': {\n        0: {1: 2, _top: 1, _bottom: null, _visualValues: {1: 2}},\n        1: {1: 20, _top: 1, _bottom: null, _visualValues: {1: 20}}\n      }\n    });\n\n    chart.data.datasets[2].data = [4];\n    chart.update();\n\n    expect(chart._stacks).toEqual({\n      'x.y.1': {\n        0: {0: 1, 2: 4, _top: 2, _bottom: null, _visualValues: {0: 1, 2: 4}},\n        1: {0: 10, _top: 2, _bottom: null, _visualValues: {0: 10}}\n      },\n      'x.y.2': {\n        0: {1: 2, _top: 1, _bottom: null, _visualValues: {1: 2}},\n        1: {1: 20, _top: 1, _bottom: null, _visualValues: {1: 20}}\n      }\n    });\n  });\n\n  it('should cleanup attached properties when the reference changes or when the chart is destroyed', function() {\n    var data0 = [0, 1, 2, 3, 4, 5];\n    var data1 = [6, 7, 8];\n    var chart = acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          data: data0\n        }]\n      }\n    });\n\n    var hooks = ['push', 'pop', 'shift', 'splice', 'unshift'];\n\n    expect(data0._chartjs).toBeDefined();\n    hooks.forEach(function(hook) {\n      expect(data0[hook]).not.toBe(Array.prototype[hook]);\n    });\n\n    expect(data1._chartjs).not.toBeDefined();\n    hooks.forEach(function(hook) {\n      expect(data1[hook]).toBe(Array.prototype[hook]);\n    });\n\n    chart.data.datasets[0].data = data1;\n    chart.update();\n\n    expect(data0._chartjs).not.toBeDefined();\n    hooks.forEach(function(hook) {\n      expect(data0[hook]).toBe(Array.prototype[hook]);\n    });\n\n    expect(data1._chartjs).toBeDefined();\n    hooks.forEach(function(hook) {\n      expect(data1[hook]).not.toBe(Array.prototype[hook]);\n    });\n\n    chart.destroy();\n\n    expect(data1._chartjs).not.toBeDefined();\n    hooks.forEach(function(hook) {\n      expect(data1[hook]).toBe(Array.prototype[hook]);\n    });\n  });\n\n  it('should resolve data element options to the default color', function() {\n    var data0 = [0, 1, 2, 3, 4, 5];\n    var oldColor = Chart.defaults.borderColor;\n    Chart.defaults.borderColor = 'red';\n    var chart = acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          data: data0\n        }]\n      }\n    });\n\n    var meta = chart.getDatasetMeta(0);\n    expect(meta.dataset.options.borderColor).toBe('red');\n    expect(meta.data[0].options.borderColor).toBe('red');\n\n    // Reset old shared state\n    Chart.defaults.borderColor = oldColor;\n  });\n\n  it('should read parsing from options when default is false', function() {\n    const originalDefault = Chart.defaults.parsing;\n    Chart.defaults.parsing = false;\n\n    var chart = acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          data: [{t: 1, y: 0}]\n        }]\n      },\n      options: {\n        parsing: {\n          xAxisKey: 't'\n        }\n      }\n    });\n\n    var meta = chart.getDatasetMeta(0);\n    expect(meta.data[0].x).not.toBeNaN();\n\n    // Reset old shared state\n    Chart.defaults.parsing = originalDefault;\n  });\n\n  it('should not fail to produce stacks when parsing is off', function() {\n    var chart = acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          data: [{x: 1, y: 10}]\n        }, {\n          data: [{x: 1, y: 20}]\n        }]\n      },\n      options: {\n        parsing: false,\n        scales: {\n          x: {stacked: true},\n          y: {stacked: true}\n        }\n      }\n    });\n\n    var meta = chart.getDatasetMeta(0);\n    expect(meta._parsed[0]._stacks).toEqual(jasmine.objectContaining({y: {0: 10, 1: 20, _top: 1, _bottom: null, _visualValues: {0: 10, 1: 20}}}));\n  });\n\n  describe('resolveDataElementOptions', function() {\n    it('should cache options when possible', function() {\n      const chart = acquireChart({\n        type: 'line',\n        data: {\n          datasets: [{\n            data: [1, 2, 3],\n          }]\n        },\n      });\n\n      const controller = chart.getDatasetMeta(0).controller;\n\n      expect(controller.enableOptionSharing).toBeTrue();\n\n      const opts0 = controller.resolveDataElementOptions(0);\n      const opts1 = controller.resolveDataElementOptions(1);\n\n      expect(opts0 === opts1).toBeTrue();\n      expect(opts0.$shared).toBeTrue();\n      expect(Object.isFrozen(opts0)).toBeTrue();\n    });\n\n    it('should not cache options when option sharing is disabled', function() {\n      const chart = acquireChart({\n        type: 'radar',\n        data: {\n          datasets: [{\n            data: [1, 2, 3],\n          }]\n        },\n      });\n\n      const controller = chart.getDatasetMeta(0).controller;\n\n      expect(controller.enableOptionSharing).toBeFalse();\n\n      const opts0 = controller.resolveDataElementOptions(0);\n      const opts1 = controller.resolveDataElementOptions(1);\n\n      expect(opts0 === opts1).toBeFalse();\n      expect(opts0.$shared).not.toBeTrue();\n      expect(Object.isFrozen(opts0)).toBeFalse();\n    });\n\n    it('should not cache options when functions are used', function() {\n      const chart = acquireChart({\n        type: 'line',\n        data: {\n          datasets: [{\n            data: [1, 2, 3],\n            backgroundColor: () => 'red'\n          }]\n        },\n      });\n\n      const controller = chart.getDatasetMeta(0).controller;\n\n      const opts0 = controller.resolveDataElementOptions(0);\n      const opts1 = controller.resolveDataElementOptions(1);\n\n      expect(opts0 === opts1).toBeFalse();\n      expect(opts0.$shared).not.toBeTrue();\n      expect(Object.isFrozen(opts0)).toBeFalse();\n    });\n\n    it('should support nested scriptable options', function() {\n      const chart = acquireChart({\n        type: 'line',\n        data: {\n          datasets: [{\n            data: [100, 120, 130],\n            fill: {\n              value: (ctx) => ctx.type === 'dataset' ? 75 : 0\n            }\n          }]\n        },\n      });\n\n      const controller = chart.getDatasetMeta(0).controller;\n      const opts = controller.resolveDatasetElementOptions();\n      expect(opts).toEqualOptions({\n        fill: {\n          value: 75\n        }\n      });\n    });\n\n    it('should support nested scriptable defaults', function() {\n      Chart.defaults.datasets.line.fill = {\n        value: (ctx) => ctx.type === 'dataset' ? 75 : 0\n      };\n      const chart = acquireChart({\n        type: 'line',\n        data: {\n          datasets: [{\n            data: [100, 120, 130],\n          }]\n        },\n      });\n\n      const controller = chart.getDatasetMeta(0).controller;\n      const opts = controller.resolveDatasetElementOptions();\n      expect(opts).toEqualOptions({\n        fill: {\n          value: 75\n        }\n      });\n      delete Chart.defaults.datasets.line.fill;\n    });\n\n  });\n\n  describe('_resolveAnimations', function() {\n    function animationsExpectations(anims, props) {\n      for (const [prop, opts] of Object.entries(props)) {\n        const anim = anims._properties.get(prop);\n        expect(anim).withContext(prop).toBeInstanceOf(Object);\n        if (anim) {\n          for (const [name, value] of Object.entries(opts)) {\n            expect(anim[name]).withContext('\"' + name + '\" of ' + JSON.stringify(anim)).toEqual(value);\n          }\n        }\n      }\n    }\n\n    it('should resolve to empty Animations when globally disabled', function() {\n      const chart = acquireChart({\n        type: 'line',\n        data: {\n          datasets: [{\n            data: [1],\n            animation: {\n              test: {duration: 10}\n            }\n          }]\n        },\n        options: {\n          animation: false\n        }\n      });\n\n      const controller = chart.getDatasetMeta(0).controller;\n\n      expect(controller._resolveAnimations(0)._properties.size).toEqual(0);\n    });\n\n    it('should resolve to empty Animations when disabled at dataset level', function() {\n      const chart = acquireChart({\n        type: 'line',\n        data: {\n          datasets: [{\n            data: [1],\n            animation: false\n          }]\n        }\n      });\n\n      const controller = chart.getDatasetMeta(0).controller;\n\n      expect(controller._resolveAnimations(0)._properties.size).toEqual(0);\n    });\n\n    it('should fallback properly', function() {\n      const chart = acquireChart({\n        type: 'line',\n        data: {\n          datasets: [{\n            data: [1],\n            animation: {\n              duration: 200\n            }\n          }, {\n            type: 'bar',\n            data: [2]\n          }]\n        },\n        options: {\n          animation: {\n            delay: 100\n          },\n          animations: {\n            x: {\n              delay: 200\n            }\n          },\n          transitions: {\n            show: {\n              x: {\n                delay: 300\n              }\n            }\n          },\n          datasets: {\n            bar: {\n              animation: {\n                duration: 500\n              }\n            }\n          }\n        }\n      });\n      const controller = chart.getDatasetMeta(0).controller;\n\n      expect(Chart.defaults.animation.duration).toEqual(1000);\n\n      const def0 = controller._resolveAnimations(0, 'default', false);\n      animationsExpectations(def0, {\n        x: {\n          delay: 200,\n          duration: 200\n        },\n        y: {\n          delay: 100,\n          duration: 200\n        }\n      });\n\n      const controller2 = chart.getDatasetMeta(1).controller;\n      const def1 = controller2._resolveAnimations(0, 'default', false);\n      animationsExpectations(def1, {\n        x: {\n          delay: 200,\n          duration: 500\n        }\n      });\n    });\n  });\n\n  describe('getContext', function() {\n    it('should reflect updated data', function() {\n      var chart = acquireChart({\n        type: 'scatter',\n        data: {\n          datasets: [{\n            data: [{x: 1, y: 0}, {x: 2, y: '1'}]\n          }]\n        },\n      });\n      let meta = chart.getDatasetMeta(0);\n\n      expect(meta.controller.getContext(undefined, true, 'test')).toEqual(jasmine.objectContaining({\n        active: true,\n        datasetIndex: 0,\n        dataset: chart.data.datasets[0],\n        index: 0,\n        mode: 'test'\n      }));\n      expect(meta.controller.getContext(1, false, 'datatest')).toEqual(jasmine.objectContaining({\n        active: false,\n        datasetIndex: 0,\n        dataset: chart.data.datasets[0],\n        dataIndex: 1,\n        element: meta.data[1],\n        index: 1,\n        parsed: {x: 2, y: 1},\n        raw: {x: 2, y: '1'},\n        mode: 'datatest'\n      }));\n\n      chart.data.datasets[0].data[1].y = 5;\n      chart.update();\n\n      expect(meta.controller.getContext(1, false, 'datatest')).toEqual(jasmine.objectContaining({\n        active: false,\n        datasetIndex: 0,\n        dataset: chart.data.datasets[0],\n        dataIndex: 1,\n        element: meta.data[1],\n        index: 1,\n        parsed: {x: 2, y: 5},\n        raw: {x: 2, y: 5},\n        mode: 'datatest'\n      }));\n\n      chart.data.datasets = [{\n        data: [{x: 0, y: 0}, {x: 1, y: 1}]\n      }];\n      chart.update();\n      // meta is re-created when dataset is replaced\n      meta = chart.getDatasetMeta(0);\n\n      expect(meta.controller.getContext(undefined, false, 'test2')).toEqual(jasmine.objectContaining({\n        active: false,\n        datasetIndex: 0,\n        dataset: chart.data.datasets[0],\n        index: 0,\n        mode: 'test2'\n      }));\n      expect(meta.controller.getContext(1, true, 'datatest2')).toEqual(jasmine.objectContaining({\n        active: true,\n        datasetIndex: 0,\n        dataset: chart.data.datasets[0],\n        dataIndex: 1,\n        element: meta.data[1],\n        index: 1,\n        parsed: {x: 1, y: 1},\n        raw: {x: 1, y: 1},\n        mode: 'datatest2'\n      }));\n\n      chart.data.datasets[0].data.unshift({x: -1, y: -1});\n      chart.update();\n      expect(meta.controller.getContext(0, true, 'unshift')).toEqual(jasmine.objectContaining({\n        active: true,\n        datasetIndex: 0,\n        dataset: chart.data.datasets[0],\n        dataIndex: 0,\n        element: meta.data[0],\n        index: 0,\n        parsed: {x: -1, y: -1},\n        raw: {x: -1, y: -1},\n        mode: 'unshift'\n      }));\n      expect(meta.controller.getContext(2, true, 'unshift2')).toEqual(jasmine.objectContaining({\n        active: true,\n        datasetIndex: 0,\n        dataset: chart.data.datasets[0],\n        dataIndex: 2,\n        element: meta.data[2],\n        index: 2,\n        parsed: {x: 1, y: 1},\n        raw: {x: 1, y: 1},\n        mode: 'unshift2'\n      }));\n\n      chart.data.datasets.unshift({data: [{x: 10, y: 20}]});\n      chart.update();\n      meta = chart.getDatasetMeta(0);\n      expect(meta.controller.getContext(0, true, 'unshift3')).toEqual(jasmine.objectContaining({\n        active: true,\n        datasetIndex: 0,\n        dataset: chart.data.datasets[0],\n        dataIndex: 0,\n        element: meta.data[0],\n        index: 0,\n        parsed: {x: 10, y: 20},\n        raw: {x: 10, y: 20},\n        mode: 'unshift3'\n      }));\n\n      meta = chart.getDatasetMeta(1);\n      expect(meta.controller.getContext(2, true, 'unshift4')).toEqual(jasmine.objectContaining({\n        active: true,\n        datasetIndex: 1,\n        dataset: chart.data.datasets[1],\n        dataIndex: 2,\n        element: meta.data[2],\n        index: 2,\n        parsed: {x: 1, y: 1},\n        raw: {x: 1, y: 1},\n        mode: 'unshift4'\n      }));\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/core.defaults.tests.js",
    "content": "describe('Chart.defaults', function() {\n  describe('.set', function() {\n    it('Should set defaults directly to root when scope is not provided', function() {\n      expect(Chart.defaults.test).toBeUndefined();\n      Chart.defaults.set({test: true});\n      expect(Chart.defaults.test).toEqual(true);\n      delete Chart.defaults.test;\n    });\n\n    it('Should create scope when it does not exist', function() {\n      expect(Chart.defaults.test).toBeUndefined();\n      Chart.defaults.set('test', {value: true});\n      expect(Chart.defaults.test.value).toEqual(true);\n      delete Chart.defaults.test;\n    });\n  });\n\n  describe('.route', function() {\n    it('Should read the source, but not change it', function() {\n      expect(Chart.defaults.testscope).toBeUndefined();\n\n      Chart.defaults.set('testscope', {test: true});\n      Chart.defaults.route('testscope', 'test2', 'testscope', 'test');\n\n      expect(Chart.defaults.testscope.test).toEqual(true);\n      expect(Chart.defaults.testscope.test2).toEqual(true);\n\n      Chart.defaults.set('testscope', {test2: false});\n      expect(Chart.defaults.testscope.test).toEqual(true);\n      expect(Chart.defaults.testscope.test2).toEqual(false);\n\n      Chart.defaults.set('testscope', {test2: undefined});\n      expect(Chart.defaults.testscope.test2).toEqual(true);\n\n      delete Chart.defaults.testscope;\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/core.element.tests.js",
    "content": "describe('Chart.element', function() {\n  describe('getProps', function() {\n    it('should return requested properties', function() {\n      const elem = new Chart.Element();\n      elem.x = 10;\n      elem.y = 1.5;\n\n      expect(elem.getProps(['x', 'y'])).toEqual(jasmine.objectContaining({x: 10, y: 1.5}));\n      expect(elem.getProps(['x', 'y'], true)).toEqual(jasmine.objectContaining({x: 10, y: 1.5}));\n\n      elem.$animations = {x: {active: () => true, _to: 20}};\n      expect(elem.getProps(['x', 'y'])).toEqual(jasmine.objectContaining({x: 10, y: 1.5}));\n      expect(elem.getProps(['x', 'y'], true)).toEqual(jasmine.objectContaining({x: 20, y: 1.5}));\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/core.helpers.tests.js",
    "content": "describe('Core helper tests', function() {\n\n  var helpers;\n\n  beforeAll(function() {\n    helpers = window.Chart.helpers;\n  });\n\n  it('should generate integer ids', function() {\n    var uid = helpers.uid();\n    expect(uid).toEqual(jasmine.any(Number));\n    expect(helpers.uid()).toBe(uid + 1);\n    expect(helpers.uid()).toBe(uid + 2);\n    expect(helpers.uid()).toBe(uid + 3);\n  });\n\n  describe('clone', function() {\n    it('should not allow prototype pollution', function() {\n      const test = helpers.clone(JSON.parse('{\"__proto__\":{\"polluted\": true}}'));\n      expect(test.prototype).toBeUndefined();\n      expect(Object.prototype.polluted).toBeUndefined();\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/core.interaction.tests.js",
    "content": "describe('Core.Interaction', function() {\n  describe('auto', jasmine.fixture.specs('core.interaction'));\n\n  describe('point mode', function() {\n    beforeEach(function() {\n      this.chart = window.acquireChart({\n        type: 'line',\n        data: {\n          datasets: [{\n            label: 'Dataset 1',\n            data: [10, 20, 30],\n            pointHoverBorderColor: 'rgb(255, 0, 0)',\n            pointHoverBackgroundColor: 'rgb(0, 255, 0)'\n          }, {\n            label: 'Dataset 2',\n            data: [40, 20, 40],\n            pointHoverBorderColor: 'rgb(0, 0, 255)',\n            pointHoverBackgroundColor: 'rgb(0, 255, 255)'\n          }],\n          labels: ['Point 1', 'Point 2', 'Point 3']\n        }\n      });\n    });\n\n    it ('should return all items under the point', function() {\n      var chart = this.chart;\n      var meta0 = chart.getDatasetMeta(0);\n      var meta1 = chart.getDatasetMeta(1);\n      var point = meta0.data[1];\n\n      var evt = {\n        type: 'click',\n        chart: chart,\n        native: true, // needed otherwise things its a DOM event\n        x: point.x,\n        y: point.y,\n      };\n\n      var elements = Chart.Interaction.modes.point(chart, evt, {}).map(item => item.element);\n      expect(elements).toEqual([point, meta1.data[1]]);\n    });\n\n    it ('should return an empty array when no items are found', function() {\n      var chart = this.chart;\n      var evt = {\n        type: 'click',\n        chart: chart,\n        native: true, // needed otherwise things its a DOM event\n        x: 0,\n        y: 0\n      };\n\n      var elements = Chart.Interaction.modes.point(chart, evt, {}).map(item => item.element);\n      expect(elements).toEqual([]);\n    });\n  });\n\n  describe('index mode', function() {\n    describe('intersect: true', function() {\n      beforeEach(function() {\n        this.chart = window.acquireChart({\n          type: 'line',\n          data: {\n            datasets: [{\n              label: 'Dataset 1',\n              data: [10, 20, 30],\n              pointHoverBorderColor: 'rgb(255, 0, 0)',\n              pointHoverBackgroundColor: 'rgb(0, 255, 0)'\n            }, {\n              label: 'Dataset 2',\n              data: [40, 40, 40],\n              pointHoverBorderColor: 'rgb(0, 0, 255)',\n              pointHoverBackgroundColor: 'rgb(0, 255, 255)'\n            }],\n            labels: ['Point 1', 'Point 2', 'Point 3']\n          }\n        });\n      });\n\n      it ('gets correct items', function() {\n        var chart = this.chart;\n        var meta0 = chart.getDatasetMeta(0);\n        var meta1 = chart.getDatasetMeta(1);\n        var point = meta0.data[1];\n\n        var evt = {\n          type: 'click',\n          chart: chart,\n          native: true, // needed otherwise things its a DOM event\n          x: point.x,\n          y: point.y,\n        };\n\n        var elements = Chart.Interaction.modes.index(chart, evt, {intersect: true}).map(item => item.element);\n        expect(elements).toEqual([point, meta1.data[1]]);\n      });\n\n      it ('returns empty array when nothing found', function() {\n        var chart = this.chart;\n        var evt = {\n          type: 'click',\n          chart: chart,\n          native: true, // needed otherwise things its a DOM event\n          x: 0,\n          y: 0,\n        };\n\n        var elements = Chart.Interaction.modes.index(chart, evt, {intersect: true}).map(item => item.element);\n        expect(elements).toEqual([]);\n      });\n    });\n\n    describe ('intersect: false', function() {\n      var data = {\n        datasets: [{\n          label: 'Dataset 1',\n          data: [10, 20, 30],\n          pointHoverBorderColor: 'rgb(255, 0, 0)',\n          pointHoverBackgroundColor: 'rgb(0, 255, 0)'\n        }, {\n          label: 'Dataset 2',\n          data: [40, 40, 40],\n          pointHoverBorderColor: 'rgb(0, 0, 255)',\n          pointHoverBackgroundColor: 'rgb(0, 255, 255)'\n        }],\n        labels: ['Point 1', 'Point 2', 'Point 3']\n      };\n\n      beforeEach(function() {\n        this.chart = window.acquireChart({\n          type: 'line',\n          data: data\n        });\n      });\n\n      it ('axis: x gets correct items', function() {\n        var chart = this.chart;\n        var meta0 = chart.getDatasetMeta(0);\n        var meta1 = chart.getDatasetMeta(1);\n\n        var evt = {\n          type: 'click',\n          chart: chart,\n          native: true, // needed otherwise things its a DOM event\n          x: chart.chartArea.left,\n          y: chart.chartArea.top\n        };\n\n        var elements = Chart.Interaction.modes.index(chart, evt, {intersect: false}).map(item => item.element);\n        expect(elements).toEqual([meta0.data[0], meta1.data[0]]);\n      });\n\n      it ('axis: y gets correct items', function() {\n        var chart = window.acquireChart({\n          type: 'bar',\n          data: data,\n          options: {\n            indexAxis: 'y',\n          }\n        });\n\n        var meta0 = chart.getDatasetMeta(0);\n        var meta1 = chart.getDatasetMeta(1);\n        var center = meta0.data[0].getCenterPoint();\n\n        var evt = {\n          type: 'click',\n          chart: chart,\n          native: true, // needed otherwise things its a DOM event\n          x: center.x,\n          y: center.y + 30,\n        };\n\n        var elements = Chart.Interaction.modes.index(chart, evt, {axis: 'y', intersect: false}).map(item => item.element);\n        expect(elements).toEqual([meta0.data[0], meta1.data[0]]);\n      });\n\n      it ('axis: xy gets correct items', function() {\n        var chart = this.chart;\n        var meta0 = chart.getDatasetMeta(0);\n        var meta1 = chart.getDatasetMeta(1);\n\n        var evt = {\n          type: 'click',\n          chart: chart,\n          native: true, // needed otherwise things its a DOM event\n          x: chart.chartArea.left,\n          y: chart.chartArea.top\n        };\n\n        var elements = Chart.Interaction.modes.index(chart, evt, {axis: 'xy', intersect: false}).map(item => item.element);\n        expect(elements).toEqual([meta0.data[0], meta1.data[0]]);\n      });\n    });\n  });\n\n  describe('dataset mode', function() {\n    describe('intersect: true', function() {\n      beforeEach(function() {\n        this.chart = window.acquireChart({\n          type: 'line',\n          data: {\n            datasets: [{\n              label: 'Dataset 1',\n              data: [10, 20, 30],\n              pointHoverBorderColor: 'rgb(255, 0, 0)',\n              pointHoverBackgroundColor: 'rgb(0, 255, 0)'\n            }, {\n              label: 'Dataset 2',\n              data: [40, 40, 40],\n              pointHoverBorderColor: 'rgb(0, 0, 255)',\n              pointHoverBackgroundColor: 'rgb(0, 255, 255)'\n            }],\n            labels: ['Point 1', 'Point 2', 'Point 3']\n          }\n        });\n      });\n\n      it ('should return all items in the dataset of the first item found', function() {\n        var chart = this.chart;\n        var meta = chart.getDatasetMeta(0);\n        var point = meta.data[1];\n\n        var evt = {\n          type: 'click',\n          chart: chart,\n          native: true, // needed otherwise things its a DOM event\n          x: point.x,\n          y: point.y\n        };\n\n        var elements = Chart.Interaction.modes.dataset(chart, evt, {intersect: true}).map(item => item.element);\n        expect(elements).toEqual(meta.data);\n      });\n\n      it ('should return an empty array if nothing found', function() {\n        var chart = this.chart;\n        var evt = {\n          type: 'click',\n          chart: chart,\n          native: true, // needed otherwise things its a DOM event\n          x: 0,\n          y: 0\n        };\n\n        var elements = Chart.Interaction.modes.dataset(chart, evt, {intersect: true});\n        expect(elements).toEqual([]);\n      });\n    });\n\n    describe('intersect: false', function() {\n      var data = {\n        datasets: [{\n          label: 'Dataset 1',\n          data: [10, 20, 30],\n          pointHoverBorderColor: 'rgb(255, 0, 0)',\n          pointHoverBackgroundColor: 'rgb(0, 255, 0)'\n        }, {\n          label: 'Dataset 2',\n          data: [40, 40, 40],\n          pointHoverBorderColor: 'rgb(0, 0, 255)',\n          pointHoverBackgroundColor: 'rgb(0, 255, 255)'\n        }],\n        labels: ['Point 1', 'Point 2', 'Point 3']\n      };\n\n      beforeEach(function() {\n        this.chart = window.acquireChart({\n          type: 'line',\n          data: data\n        });\n      });\n\n      it ('axis: x gets correct items', function() {\n        var chart = window.acquireChart({\n          type: 'bar',\n          data: data,\n          options: {\n            indexAxis: 'y',\n          }\n        });\n\n        var evt = {\n          type: 'click',\n          chart: chart,\n          native: true, // needed otherwise things its a DOM event\n          x: chart.chartArea.left,\n          y: chart.chartArea.top\n        };\n\n        var elements = Chart.Interaction.modes.dataset(chart, evt, {axis: 'x', intersect: false}).map(item => item.element);\n        expect(elements).toEqual(chart.getDatasetMeta(0).data);\n      });\n\n      it ('axis: y gets correct items', function() {\n        var chart = this.chart;\n        var evt = {\n          type: 'click',\n          chart: chart,\n          native: true, // needed otherwise things its a DOM event\n          x: chart.chartArea.left,\n          y: chart.chartArea.top\n        };\n\n        var elements = Chart.Interaction.modes.dataset(chart, evt, {axis: 'y', intersect: false}).map(item => item.element);\n        expect(elements).toEqual(chart.getDatasetMeta(1).data);\n      });\n\n      it ('axis: xy gets correct items', function() {\n        var chart = this.chart;\n        var evt = {\n          type: 'click',\n          chart: chart,\n          native: true, // needed otherwise things its a DOM event\n          x: chart.chartArea.left,\n          y: chart.chartArea.top\n        };\n\n        var elements = Chart.Interaction.modes.dataset(chart, evt, {intersect: false}).map(item => item.element);\n        expect(elements).toEqual(chart.getDatasetMeta(1).data);\n      });\n    });\n  });\n\n  describe('nearest mode', function() {\n    describe('intersect: false', function() {\n      beforeEach(function() {\n        this.lineChart = window.acquireChart({\n          type: 'line',\n          data: {\n            datasets: [{\n              label: 'Dataset 1',\n              data: [10, 40, 30],\n              pointRadius: [5, 5, 5],\n              pointHoverBorderColor: 'rgb(255, 0, 0)',\n              pointHoverBackgroundColor: 'rgb(0, 255, 0)'\n            }, {\n              label: 'Dataset 2',\n              data: [40, 40, 40],\n              pointRadius: [10, 10, 10],\n              pointHoverBorderColor: 'rgb(0, 0, 255)',\n              pointHoverBackgroundColor: 'rgb(0, 255, 255)'\n            }],\n            labels: ['Point 1', 'Point 2', 'Point 3']\n          }\n        });\n        this.polarChart = window.acquireChart({\n          type: 'polarArea',\n          data: {\n            datasets: [{\n              data: [1, 9, 5]\n            }],\n            labels: ['Point 1', 'Point 2', 'Point 3']\n          },\n          options: {\n            plugins: {\n              legend: {\n                display: false\n              },\n            },\n          }\n        });\n      });\n\n      describe('axis: xy', function() {\n        it ('should return the nearest item', function() {\n          var chart = this.lineChart;\n          var evt = {\n            type: 'click',\n            chart: chart,\n            native: true, // needed otherwise things its a DOM event\n            x: chart.chartArea.left,\n            y: chart.chartArea.top\n          };\n\n          // Nearest to 0,0 (top left) will be first point of dataset 2\n          var elements = Chart.Interaction.modes.nearest(chart, evt, {intersect: false}).map(item => item.element);\n          var meta = chart.getDatasetMeta(1);\n          expect(elements).toEqual([meta.data[0]]);\n        });\n\n        it ('should return all items at the same nearest distance', function() {\n          var chart = this.lineChart;\n          var meta0 = chart.getDatasetMeta(0);\n          var meta1 = chart.getDatasetMeta(1);\n\n          // Halfway between 2 mid points\n          var pt = {\n            x: meta0.data[1].x,\n            y: (meta0.data[1].y + meta1.data[1].y) / 2\n          };\n\n          var evt = {\n            type: 'click',\n            chart: chart,\n            native: true, // needed otherwise things its a DOM event\n            x: pt.x,\n            y: pt.y\n          };\n\n          // Both points are nearest\n          var elements = Chart.Interaction.modes.nearest(chart, evt, {intersect: false}).map(item => item.element);\n          expect(elements).toEqual([meta0.data[1], meta1.data[1]]);\n        });\n      });\n\n      describe('axis: x', function() {\n        it ('should return all items at current x', function() {\n          var chart = this.lineChart;\n          var meta0 = chart.getDatasetMeta(0);\n          var meta1 = chart.getDatasetMeta(1);\n\n          // At 'Point 2', 10\n          var pt = {\n            x: meta0.data[1].x,\n            y: meta0.data[0].y\n          };\n\n          var evt = {\n            type: 'click',\n            chart: chart,\n            native: true, // needed otherwise things its a DOM event\n            x: pt.x,\n            y: pt.y\n          };\n\n          // Middle point from both series are nearest\n          var elements = Chart.Interaction.modes.nearest(chart, evt, {axis: 'x', intersect: false}).map(item => item.element);\n          expect(elements).toEqual([meta0.data[1], meta1.data[1]]);\n        });\n\n        it ('should return all items at nearest x-distance', function() {\n          var chart = this.lineChart;\n          var meta0 = chart.getDatasetMeta(0);\n          var meta1 = chart.getDatasetMeta(1);\n\n          // Haflway between 'Point 1' and 'Point 2', y=10\n          var pt = {\n            x: (meta0.data[0].x + meta0.data[1].x) / 2,\n            y: meta0.data[0].y\n          };\n\n          var evt = {\n            type: 'click',\n            chart: chart,\n            native: true, // needed otherwise things its a DOM event\n            x: pt.x,\n            y: pt.y\n          };\n\n          // Should return all (4) points from 'Point 1' and 'Point 2'\n          var elements = Chart.Interaction.modes.nearest(chart, evt, {axis: 'x', intersect: false}).map(item => item.element);\n          expect(elements).toEqual([meta0.data[0], meta0.data[1], meta1.data[0], meta1.data[1]]);\n        });\n      });\n\n      describe('axis: y', function() {\n        it ('should return item with value 30', function() {\n          var chart = this.lineChart;\n          var meta0 = chart.getDatasetMeta(0);\n\n          // 'Point 1', y = 30\n          var pt = {\n            x: meta0.data[0].x,\n            y: meta0.data[2].y\n          };\n\n          var evt = {\n            type: 'click',\n            chart: chart,\n            native: true, // needed otherwise things its a DOM event\n            x: pt.x,\n            y: pt.y\n          };\n\n          // Middle point from both series are nearest\n          var elements = Chart.Interaction.modes.nearest(chart, evt, {axis: 'y', intersect: false}).map(item => item.element);\n          expect(elements).toEqual([meta0.data[2]]);\n        });\n\n        it ('should return all items at value 40', function() {\n          var chart = this.lineChart;\n          var meta0 = chart.getDatasetMeta(0);\n          var meta1 = chart.getDatasetMeta(1);\n\n          // 'Point 1', y = 40\n          var pt = {\n            x: meta0.data[0].x,\n            y: meta0.data[1].y\n          };\n\n          var evt = {\n            type: 'click',\n            chart: chart,\n            native: true, // needed otherwise things its a DOM event\n            x: pt.x,\n            y: pt.y\n          };\n\n          // Should return points with value 40\n          var elements = Chart.Interaction.modes.nearest(chart, evt, {axis: 'y', intersect: false}).map(item => item.element);\n          expect(elements).toEqual([meta0.data[1], meta1.data[0], meta1.data[1], meta1.data[2]]);\n        });\n      });\n\n      describe('axis: r', function() {\n        it ('should return item with value 9', function() {\n          var chart = this.polarChart;\n          var meta0 = chart.getDatasetMeta(0);\n\n          var evt = {\n            type: 'click',\n            chart: chart,\n            native: true, // Needed, otherwise assumed to be a DOM event\n            x: chart.width / 2,\n            y: chart.height / 2 + 5,\n          };\n\n          var elements = Chart.Interaction.modes.nearest(chart, evt, {axis: 'r'}).map(item => item.element);\n          expect(elements).toEqual([meta0.data[1]]);\n        });\n\n        it ('should return item with value 1 when clicked outside of it', function() {\n          var chart = this.polarChart;\n          var meta0 = chart.getDatasetMeta(0);\n\n          var evt = {\n            type: 'click',\n            chart: chart,\n            native: true, // Needed, otherwise assumed to be a DOM event\n            x: chart.width,\n            y: 0,\n          };\n\n          var elements = Chart.Interaction.modes.nearest(chart, evt, {axis: 'r', intersect: false}).map(item => item.element);\n          expect(elements).toEqual([meta0.data[0]]);\n        });\n      });\n    });\n\n    describe('intersect: true', function() {\n      beforeEach(function() {\n        this.chart = window.acquireChart({\n          type: 'line',\n          data: {\n            datasets: [{\n              label: 'Dataset 1',\n              data: [10, 20, 30],\n              pointHoverBorderColor: 'rgb(255, 0, 0)',\n              pointHoverBackgroundColor: 'rgb(0, 255, 0)'\n            }, {\n              label: 'Dataset 2',\n              data: [40, 40, 40],\n              pointHoverBorderColor: 'rgb(0, 0, 255)',\n              pointHoverBackgroundColor: 'rgb(0, 255, 255)'\n            }],\n            labels: ['Point 1', 'Point 2', 'Point 3']\n          }\n        });\n      });\n\n      describe('axis=xy', function() {\n        it ('should return the nearest item', function() {\n          var chart = this.chart;\n          var meta = chart.getDatasetMeta(1);\n          var point = meta.data[1];\n\n          var evt = {\n            type: 'click',\n            chart: chart,\n            native: true, // needed otherwise things its a DOM event\n            x: point.x + 15,\n            y: point.y\n          };\n\n          // Nothing intersects so find nothing\n          var elements = Chart.Interaction.modes.nearest(chart, evt, {intersect: true}).map(item => item.element);\n          expect(elements).toEqual([]);\n\n          evt = {\n            type: 'click',\n            chart: chart,\n            native: true,\n            x: point.x,\n            y: point.y\n          };\n          elements = Chart.Interaction.modes.nearest(chart, evt, {intersect: true}).map(item => item.element);\n          expect(elements).toEqual([point]);\n        });\n\n        it ('should return the nearest item even if 2 intersect', function() {\n          var chart = this.chart;\n          chart.data.datasets[0].pointRadius = [5, 30, 5];\n          chart.data.datasets[0].data[1] = 39;\n\n          chart.data.datasets[1].pointRadius = [10, 10, 10];\n\n          chart.update();\n\n          // Trigger an event over top of the\n          var meta0 = chart.getDatasetMeta(0);\n\n          // Halfway between 2 mid points\n          var pt = {\n            x: meta0.data[1].x,\n            y: meta0.data[1].y\n          };\n\n          var evt = {\n            type: 'click',\n            chart: chart,\n            native: true, // needed otherwise things its a DOM event\n            x: pt.x,\n            y: pt.y\n          };\n\n          var elements = Chart.Interaction.modes.nearest(chart, evt, {intersect: true}).map(item => item.element);\n          expect(elements).toEqual([meta0.data[1]]);\n        });\n\n        it ('should return the all items if more than 1 are at the same distance', function() {\n          var chart = this.chart;\n          chart.data.datasets[0].pointRadius = [5, 5, 5];\n          chart.data.datasets[0].data[1] = 40;\n\n          chart.data.datasets[1].pointRadius = [10, 10, 10];\n\n          chart.update();\n\n          var meta0 = chart.getDatasetMeta(0);\n          var meta1 = chart.getDatasetMeta(1);\n\n          // Halfway between 2 mid points\n          var pt = {\n            x: meta0.data[1].x,\n            y: meta0.data[1].y\n          };\n\n          var evt = {\n            type: 'click',\n            chart: chart,\n            native: true, // needed otherwise things its a DOM event\n            x: pt.x,\n            y: pt.y\n          };\n\n          var elements = Chart.Interaction.modes.nearest(chart, evt, {intersect: true}).map(item => item.element);\n          expect(elements).toEqual([meta0.data[1], meta1.data[1]]);\n        });\n      });\n    });\n  });\n\n  describe('x mode', function() {\n    beforeEach(function() {\n      this.chart = window.acquireChart({\n        type: 'line',\n        data: {\n          datasets: [{\n            label: 'Dataset 1',\n            data: [10, 40, 30],\n            pointRadius: [5, 10, 5],\n            pointHoverBorderColor: 'rgb(255, 0, 0)',\n            pointHoverBackgroundColor: 'rgb(0, 255, 0)'\n          }, {\n            label: 'Dataset 2',\n            data: [40, 40, 40],\n            pointRadius: [10, 10, 10],\n            pointHoverBorderColor: 'rgb(0, 0, 255)',\n            pointHoverBackgroundColor: 'rgb(0, 255, 255)'\n          }],\n          labels: ['Point 1', 'Point 2', 'Point 3']\n        }\n      });\n    });\n\n    it('should return items at the same x value when intersect is false', function() {\n      var chart = this.chart;\n      var meta0 = chart.getDatasetMeta(0);\n      var meta1 = chart.getDatasetMeta(1);\n\n      // Halfway between 2 mid points\n      var pt = {\n        x: meta0.data[1].x,\n        y: meta0.data[1].y\n      };\n\n      var evt = {\n        type: 'click',\n        chart: chart,\n        native: true, // needed otherwise things its a DOM event\n        x: pt.x,\n        y: 0\n      };\n\n      var elements = Chart.Interaction.modes.x(chart, evt, {intersect: false}).map(item => item.element);\n      expect(elements).toEqual([meta0.data[1], meta1.data[1]]);\n\n      evt = {\n        type: 'click',\n        chart: chart,\n        native: true, // needed otherwise things its a DOM event\n        x: pt.x + 20,\n        y: 0\n      };\n\n      elements = Chart.Interaction.modes.x(chart, evt, {intersect: false}).map(item => item.element);\n      expect(elements).toEqual([]);\n    });\n\n    it('should return items at the same x value when intersect is true', function() {\n      var chart = this.chart;\n      var meta0 = chart.getDatasetMeta(0);\n      var meta1 = chart.getDatasetMeta(1);\n\n      // Halfway between 2 mid points\n      var pt = {\n        x: meta0.data[1].x,\n        y: meta0.data[1].y\n      };\n\n      var evt = {\n        type: 'click',\n        chart: chart,\n        native: true, // needed otherwise things its a DOM event\n        x: pt.x,\n        y: 0\n      };\n\n      var elements = Chart.Interaction.modes.x(chart, evt, {intersect: true}).map(item => item.element);\n      expect(elements).toEqual([]); // we don't intersect anything\n\n      evt = {\n        type: 'click',\n        chart: chart,\n        native: true, // needed otherwise things its a DOM event\n        x: pt.x,\n        y: pt.y\n      };\n\n      elements = Chart.Interaction.modes.x(chart, evt, {intersect: true}).map(item => item.element);\n      expect(elements).toEqual([meta0.data[1], meta1.data[1]]);\n    });\n  });\n\n  describe('y mode', function() {\n    beforeEach(function() {\n      this.chart = window.acquireChart({\n        type: 'line',\n        data: {\n          datasets: [{\n            label: 'Dataset 1',\n            data: [10, 40, 30],\n            pointRadius: [5, 10, 5],\n            pointHoverBorderColor: 'rgb(255, 0, 0)',\n            pointHoverBackgroundColor: 'rgb(0, 255, 0)'\n          }, {\n            label: 'Dataset 2',\n            data: [40, 40, 40],\n            pointRadius: [10, 10, 10],\n            pointHoverBorderColor: 'rgb(0, 0, 255)',\n            pointHoverBackgroundColor: 'rgb(0, 255, 255)'\n          }],\n          labels: ['Point 1', 'Point 2', 'Point 3']\n        }\n      });\n    });\n\n    it('should return items at the same y value when intersect is false', function() {\n      var chart = this.chart;\n      var meta0 = chart.getDatasetMeta(0);\n      var meta1 = chart.getDatasetMeta(1);\n\n      // Halfway between 2 mid points\n      var pt = {\n        x: meta0.data[1].x,\n        y: meta0.data[1].y\n      };\n\n      var evt = {\n        type: 'click',\n        chart: chart,\n        native: true,\n        x: 0,\n        y: pt.y,\n      };\n\n      var elements = Chart.Interaction.modes.y(chart, evt, {intersect: false}).map(item => item.element);\n      expect(elements).toEqual([meta0.data[1], meta1.data[0], meta1.data[1], meta1.data[2]]);\n\n      evt = {\n        type: 'click',\n        chart: chart,\n        native: true,\n        x: pt.x,\n        y: pt.y + 20, // out of range\n      };\n\n      elements = Chart.Interaction.modes.y(chart, evt, {intersect: false}).map(item => item.element);\n      expect(elements).toEqual([]);\n    });\n\n    it('should return items at the same y value when intersect is true', function() {\n      var chart = this.chart;\n      var meta0 = chart.getDatasetMeta(0);\n      var meta1 = chart.getDatasetMeta(1);\n\n      // Halfway between 2 mid points\n      var pt = {\n        x: meta0.data[1].x,\n        y: meta0.data[1].y\n      };\n\n      var evt = {\n        type: 'click',\n        chart: chart,\n        native: true,\n        x: 0,\n        y: pt.y\n      };\n\n      var elements = Chart.Interaction.modes.y(chart, evt, {intersect: true}).map(item => item.element);\n      expect(elements).toEqual([]); // we don't intersect anything\n\n      evt = {\n        type: 'click',\n        chart: chart,\n        native: true,\n        x: pt.x,\n        y: pt.y,\n      };\n\n      elements = Chart.Interaction.modes.y(chart, evt, {intersect: true}).map(item => item.element);\n      expect(elements).toEqual([meta0.data[1], meta1.data[0], meta1.data[1], meta1.data[2]]);\n    });\n  });\n\n  describe('tooltip element of scatter chart', function() {\n    it ('out-of-range datapoints are not shown in tooltip', function() {\n      let data = [];\n      for (let i = 0; i < 1000; i++) {\n        data.push({x: i, y: i});\n      }\n\n      const chart = window.acquireChart({\n        type: 'scatter',\n        data: {\n          datasets: [{data}]\n        },\n        options: {\n          scales: {\n            x: {\n              min: 2\n            }\n          }\n        }\n      });\n\n      const meta0 = chart.getDatasetMeta(0);\n      const firstElement = meta0.data[0];\n\n      const evt = {\n        type: 'click',\n        chart: chart,\n        native: true, // needed otherwise things its a DOM event\n        x: firstElement.x,\n        y: firstElement.y\n      };\n\n      const elements = Chart.Interaction.modes.point(chart, evt, {intersect: true}).map(item => item.element);\n      expect(elements).not.toContain(firstElement);\n    });\n\n    it ('out-of-range datapoints are shown in tooltip if included', function() {\n      let data = [];\n      for (let i = 0; i < 1000; i++) {\n        data.push({x: i, y: i});\n      }\n\n      const chart = window.acquireChart({\n        type: 'scatter',\n        data: {\n          datasets: [{data}]\n        },\n        options: {\n          scales: {\n            x: {\n              min: 2\n            }\n          }\n        }\n      });\n\n      const meta0 = chart.getDatasetMeta(0);\n      const firstElement = meta0.data[0];\n\n      const evt = {\n        type: 'click',\n        chart: chart,\n        native: true, // needed otherwise it thinks its a DOM event\n        x: firstElement.x,\n        y: firstElement.y\n      };\n\n      const elements = Chart.Interaction.modes.point(\n        chart,\n        evt,\n        {\n          intersect: true,\n          includeInvisible: true\n        }).map(item => item.element);\n      expect(elements).toContain(firstElement);\n    });\n  });\n\n  const testCases = [\n    {\n      data: [12, 19, null, null, null, null, 5, 2],\n      clickPointIndex: 0,\n      expectedNearestPointIndex: 0\n    },\n    {\n      data: [12, 19, null, null, null, null, 5, 2],\n      clickPointIndex: 1,\n      expectedNearestPointIndex: 1},\n    {\n      data: [12, 19, null, null, null, null, 5, 2],\n      clickPointIndex: 2,\n      expectedNearestPointIndex: 1\n    },\n    {\n      data: [12, 19, null, null, null, null, 5, 2],\n      clickPointIndex: 3,\n      expectedNearestPointIndex: 1\n    },\n    {\n      data: [12, 19, null, null, null, null, 5, 2],\n      clickPointIndex: 4,\n      expectedNearestPointIndex: 6\n    },\n    {\n      data: [12, 19, null, null, null, null, 5, 2],\n      clickPointIndex: 5,\n      expectedNearestPointIndex: 6\n    },\n    {\n      data: [12, 19, null, null, null, null, 5, 2],\n      clickPointIndex: 6,\n      expectedNearestPointIndex: 6\n    },\n    {\n      data: [12, 19, null, null, null, null, 5, 2],\n      clickPointIndex: 7,\n      expectedNearestPointIndex: 7\n    },\n    {\n      data: [12, 0, null, null, null, null, 0, 2],\n      clickPointIndex: 3,\n      expectedNearestPointIndex: 1\n    },\n    {\n      data: [12, 0, null, null, null, null, 0, 2],\n      clickPointIndex: 4,\n      expectedNearestPointIndex: 6\n    },\n    {\n      data: [12, -1, null, null, null, null, -1, 2],\n      clickPointIndex: 3,\n      expectedNearestPointIndex: 1\n    },\n    {\n      data: [12, -1, null, null, null, null, -1, 2],\n      clickPointIndex: 4,\n      expectedNearestPointIndex: 6\n    },\n    {\n      data: [null, 2],\n      clickPointIndex: 0,\n      expectedNearestPointIndex: 1\n    },\n    {\n      data: [2, null],\n      clickPointIndex: 1,\n      expectedNearestPointIndex: 0\n    },\n    {\n      data: [null, null, 2],\n      clickPointIndex: 0,\n      expectedNearestPointIndex: 2\n    },\n    {\n      data: [2, null, null],\n      clickPointIndex: 2,\n      expectedNearestPointIndex: 0\n    }\n  ];\n  testCases.forEach(({data, clickPointIndex, expectedNearestPointIndex}, i) => {\n    it(`should select nearest non-null element with index ${expectedNearestPointIndex} when clicking on element with index ${clickPointIndex} in test case ${i + 1} if spanGaps=true`, function() {\n      const chart = window.acquireChart({\n        type: 'line',\n        data: {\n          labels: [1, 2, 3, 4, 5, 6, 7, 8, 9],\n          datasets: [{\n            data: data,\n            spanGaps: true,\n          }]\n        }\n      });\n      chart.update();\n      const meta = chart.getDatasetMeta(0);\n      const point = meta.data[clickPointIndex];\n\n      const evt = {\n        type: 'click',\n        chart: chart,\n        native: true, // needed otherwise things its a DOM event\n        x: point.x,\n        y: point.y,\n      };\n\n      const elements = Chart.Interaction.modes.nearest(chart, evt, {axis: 'x', intersect: false}).map(item => item.element);\n      expect(elements).toEqual([meta.data[expectedNearestPointIndex]]);\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/core.layouts.tests.js",
    "content": "function getLabels(scale) {\n  return scale.ticks.map(t => t.label);\n}\n\ndescribe('Chart.layouts', function() {\n  describe('auto', jasmine.fixture.specs('core.layouts'));\n\n  it('should be exposed through Chart.layouts', function() {\n    expect(Chart.layouts).toBeDefined();\n    expect(typeof Chart.layouts).toBe('object');\n    expect(Chart.layouts.addBox).toBeDefined();\n    expect(Chart.layouts.removeBox).toBeDefined();\n    expect(Chart.layouts.configure).toBeDefined();\n    expect(Chart.layouts.update).toBeDefined();\n  });\n\n  it('should fit a simple chart with 2 scales', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [\n          {data: [10, 5, 0, 25, 78, -10]}\n        ],\n        labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5', 'tick6']\n      }\n    }, {\n      canvas: {\n        height: 150,\n        width: 250\n      }\n    });\n\n    expect(chart.chartArea.bottom).toBeCloseToPixel(120);\n    expect(chart.chartArea.left).toBeCloseToPixel(31);\n    expect(chart.chartArea.right).toBeCloseToPixel(250);\n    expect(chart.chartArea.top).toBeCloseToPixel(32);\n\n    // Is xScale at the right spot\n    expect(chart.scales.x.bottom).toBeCloseToPixel(150);\n    expect(chart.scales.x.left).toBeCloseToPixel(31);\n    expect(chart.scales.x.right).toBeCloseToPixel(250);\n    expect(chart.scales.x.top).toBeCloseToPixel(120);\n    expect(chart.scales.x.labelRotation).toBeCloseTo(0);\n\n    // Is yScale at the right spot\n    expect(chart.scales.y.bottom).toBeCloseToPixel(120);\n    expect(chart.scales.y.left).toBeCloseToPixel(0);\n    expect(chart.scales.y.right).toBeCloseToPixel(31);\n    expect(chart.scales.y.top).toBeCloseToPixel(32);\n    expect(chart.scales.y.labelRotation).toBeCloseTo(0);\n  });\n\n  it('should fit scales that are in the top and right positions', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [\n          {data: [10, 5, 0, 25, 78, -10]}\n        ],\n        labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5', 'tick6']\n      },\n      options: {\n        scales: {\n          x: {\n            type: 'category',\n            position: 'top'\n          },\n          y: {\n            type: 'linear',\n            position: 'right'\n          }\n        }\n      }\n    }, {\n      canvas: {\n        height: 150,\n        width: 250\n      }\n    });\n\n    expect(chart.chartArea.bottom).toBeCloseToPixel(139);\n    expect(chart.chartArea.left).toBeCloseToPixel(0);\n    expect(chart.chartArea.right).toBeCloseToPixel(218);\n    expect(chart.chartArea.top).toBeCloseToPixel(62);\n\n    // Is xScale at the right spot\n    expect(chart.scales.x.bottom).toBeCloseToPixel(62);\n    expect(chart.scales.x.left).toBeCloseToPixel(0);\n    expect(chart.scales.x.right).toBeCloseToPixel(218);\n    expect(chart.scales.x.top).toBeCloseToPixel(32);\n    expect(chart.scales.x.labelRotation).toBeCloseTo(0);\n\n    // Is yScale at the right spot\n    expect(chart.scales.y.bottom).toBeCloseToPixel(139);\n    expect(chart.scales.y.left).toBeCloseToPixel(218);\n    expect(chart.scales.y.right).toBeCloseToPixel(250);\n    expect(chart.scales.y.top).toBeCloseToPixel(62);\n    expect(chart.scales.y.labelRotation).toBeCloseTo(0);\n  });\n\n  it('should fit scales that overlap the chart area', function() {\n    var chart = window.acquireChart({\n      type: 'radar',\n      data: {\n        datasets: [{\n          data: [10, 5, 0, 25, 78, -10]\n        }, {\n          data: [-19, -20, 0, -99, -50, 0]\n        }],\n        labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5', 'tick6']\n      }\n    });\n\n    expect(chart.chartArea.bottom).toBeCloseToPixel(512);\n    expect(chart.chartArea.left).toBeCloseToPixel(0);\n    expect(chart.chartArea.right).toBeCloseToPixel(512);\n    expect(chart.chartArea.top).toBeCloseToPixel(32);\n\n    var scale = chart.scales.r;\n    expect(scale.bottom).toBeCloseToPixel(512);\n    expect(scale.left).toBeCloseToPixel(0);\n    expect(scale.right).toBeCloseToPixel(512);\n    expect(scale.top).toBeCloseToPixel(32);\n    expect(scale.width).toBeCloseToPixel(496);\n    expect(scale.height).toBeCloseToPixel(464);\n  });\n\n  it('should fit multiple axes in the same position', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          yAxisID: 'y',\n          data: [10, 5, 0, 25, 78, -10]\n        }, {\n          yAxisID: 'y2',\n          data: [-19, -20, 0, -99, -50, 0]\n        }],\n        labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5', 'tick6']\n      },\n      options: {\n        scales: {\n          x: {\n            type: 'category'\n          },\n          y: {\n            type: 'linear'\n          },\n          y2: {\n            type: 'linear'\n          }\n        }\n      }\n    }, {\n      canvas: {\n        height: 150,\n        width: 250\n      }\n    });\n\n    expect(chart.chartArea.bottom).toBeCloseToPixel(110);\n    expect(chart.chartArea.left).toBeCloseToPixel(70);\n    expect(chart.chartArea.right).toBeCloseToPixel(250);\n    expect(chart.chartArea.top).toBeCloseToPixel(32);\n\n    // Is xScale at the right spot\n    expect(chart.scales.x.bottom).toBeCloseToPixel(150);\n    expect(chart.scales.x.left).toBeCloseToPixel(70);\n    expect(chart.scales.x.right).toBeCloseToPixel(250);\n    expect(chart.scales.x.top).toBeCloseToPixel(110);\n    expect(chart.scales.x.labelRotation).toBeCloseTo(40, -1);\n\n    // Are yScales at the right spot\n    expect(chart.scales.y.bottom).toBeCloseToPixel(110);\n    expect(chart.scales.y.left).toBeCloseToPixel(38);\n    expect(chart.scales.y.right).toBeCloseToPixel(70);\n    expect(chart.scales.y.top).toBeCloseToPixel(32);\n    expect(chart.scales.y.labelRotation).toBeCloseTo(0);\n\n    expect(chart.scales.y2.bottom).toBeCloseToPixel(110);\n    expect(chart.scales.y2.left).toBeCloseToPixel(0);\n    expect(chart.scales.y2.right).toBeCloseToPixel(38);\n    expect(chart.scales.y2.top).toBeCloseToPixel(32);\n    expect(chart.scales.y2.labelRotation).toBeCloseTo(0);\n  });\n\n  it ('should fit a full width box correctly', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          xAxisID: 'x',\n          data: [10, 5, 0, 25, 78, -10]\n        }, {\n          xAxisID: 'x2',\n          data: [-19, -20, 0, -99, -50, 0]\n        }],\n        labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5', 'tick6']\n      },\n      options: {\n        scales: {\n          x: {\n            type: 'category',\n            offset: false\n          },\n          x2: {\n            type: 'category',\n            position: 'top',\n            fullSize: true,\n            offset: false\n          },\n          y: {\n            type: 'linear'\n          }\n        }\n      }\n    });\n\n    expect(chart.chartArea.bottom).toBeCloseToPixel(484);\n    expect(chart.chartArea.left).toBeCloseToPixel(39);\n    expect(chart.chartArea.right).toBeCloseToPixel(496);\n    expect(chart.chartArea.top).toBeCloseToPixel(62);\n\n    // Are xScales at the right spot\n    expect(chart.scales.x.bottom).toBeCloseToPixel(512);\n    expect(chart.scales.x.left).toBeCloseToPixel(39);\n    expect(chart.scales.x.right).toBeCloseToPixel(496);\n    expect(chart.scales.x.top).toBeCloseToPixel(484);\n\n    expect(chart.scales.x2.bottom).toBeCloseToPixel(62);\n    expect(chart.scales.x2.left).toBeCloseToPixel(0);\n    expect(chart.scales.x2.right).toBeCloseToPixel(512);\n    expect(chart.scales.x2.top).toBeCloseToPixel(32);\n\n    // Is yScale at the right spot\n    expect(chart.scales.y.bottom).toBeCloseToPixel(484);\n    expect(chart.scales.y.left).toBeCloseToPixel(0);\n    expect(chart.scales.y.right).toBeCloseToPixel(39);\n    expect(chart.scales.y.top).toBeCloseToPixel(62);\n  });\n\n  describe('padding settings', function() {\n    it('should apply a single padding to all dimensions', function() {\n      var chart = window.acquireChart({\n        type: 'bar',\n        data: {\n          datasets: [\n            {\n              data: [10, 5, 0, 25, 78, -10]\n            }\n          ],\n          labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5', 'tick6']\n        },\n        options: {\n          scales: {\n            x: {\n              type: 'category',\n              display: false\n            },\n            y: {\n              type: 'linear',\n              display: false\n            }\n          },\n          plugins: {\n            legend: false,\n            title: false\n          },\n          layout: {\n            padding: 10\n          }\n        }\n      }, {\n        canvas: {\n          height: 150,\n          width: 250\n        }\n      });\n\n      expect(chart.chartArea.bottom).toBeCloseToPixel(140);\n      expect(chart.chartArea.left).toBeCloseToPixel(10);\n      expect(chart.chartArea.right).toBeCloseToPixel(240);\n      expect(chart.chartArea.top).toBeCloseToPixel(10);\n    });\n\n    it('should apply padding in all positions', function() {\n      var chart = window.acquireChart({\n        type: 'bar',\n        data: {\n          datasets: [\n            {\n              data: [10, 5, 0, 25, 78, -10]\n            }\n          ],\n          labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5', 'tick6']\n        },\n        options: {\n          scales: {\n            x: {\n              type: 'category',\n              display: false\n            },\n            y: {\n              type: 'linear',\n              display: false\n            }\n          },\n          plugins: {\n            legend: false,\n            title: false\n          },\n          layout: {\n            padding: {\n              left: 5,\n              right: 15,\n              top: 8,\n              bottom: 12\n            }\n          }\n        }\n      }, {\n        canvas: {\n          height: 150,\n          width: 250\n        }\n      });\n\n      expect(chart.chartArea.bottom).toBeCloseToPixel(138);\n      expect(chart.chartArea.left).toBeCloseToPixel(5);\n      expect(chart.chartArea.right).toBeCloseToPixel(235);\n      expect(chart.chartArea.top).toBeCloseToPixel(8);\n    });\n\n    it('should default to 0 padding if no dimensions specified', function() {\n      var chart = window.acquireChart({\n        type: 'bar',\n        data: {\n          datasets: [\n            {\n              data: [10, 5, 0, 25, 78, -10]\n            }\n          ],\n          labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5', 'tick6']\n        },\n        options: {\n          scales: {\n            x: {\n              type: 'category',\n              display: false\n            },\n            y: {\n              type: 'linear',\n              display: false\n            }\n          },\n          plugins: {\n            legend: false,\n            title: false\n          },\n          layout: {\n            padding: {}\n          }\n        }\n      }, {\n        canvas: {\n          height: 150,\n          width: 250\n        }\n      });\n\n      expect(chart.chartArea.bottom).toBeCloseToPixel(150);\n      expect(chart.chartArea.left).toBeCloseToPixel(0);\n      expect(chart.chartArea.right).toBeCloseToPixel(250);\n      expect(chart.chartArea.top).toBeCloseToPixel(0);\n    });\n  });\n\n  describe('ordering by weight', function() {\n    it('should keep higher weights outside', function() {\n      var chart = window.acquireChart({\n        type: 'bar',\n        data: {\n          datasets: [\n            {\n              data: [10, 5, 0, 25, 78, -10]\n            }\n          ],\n          labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5', 'tick6']\n        },\n        options: {\n          plugins: {\n            legend: {\n              display: true,\n              position: 'left',\n            },\n            title: {\n              display: true,\n              position: 'bottom',\n            },\n          }\n        },\n      }, {\n        canvas: {\n          height: 150,\n          width: 250\n        }\n      });\n\n      var xAxis = chart.scales.x;\n      var yAxis = chart.scales.y;\n      var legend = chart.legend;\n      var title = chart.titleBlock;\n\n      expect(yAxis.left).toBe(legend.right);\n      expect(xAxis.bottom).toBe(title.top);\n    });\n\n    it('should correctly set weights of scales and order them', function() {\n      var chart = window.acquireChart({\n        type: 'bar',\n        data: {\n          datasets: [\n            {\n              data: [10, 5, 0, 25, 78, -10]\n            }\n          ],\n          labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5', 'tick6']\n        },\n        options: {\n          scales: {\n            x: {\n              type: 'category',\n              position: 'bottom',\n              display: true,\n              weight: 1\n            },\n            x1: {\n              type: 'category',\n              position: 'bottom',\n              display: true,\n              weight: 2\n            },\n            x2: {\n              type: 'category',\n              position: 'bottom',\n              display: true\n            },\n            x3: {\n              type: 'category',\n              display: true,\n              position: 'top',\n              weight: 1\n            },\n            x4: {\n              type: 'category',\n              display: true,\n              position: 'top',\n              weight: 2\n            },\n            y: {\n              type: 'linear',\n              display: true,\n              weight: 1\n            },\n            y1: {\n              type: 'linear',\n              position: 'left',\n              display: true,\n              weight: 2\n            },\n            y2: {\n              type: 'linear',\n              position: 'left',\n              display: true\n            },\n            y3: {\n              type: 'linear',\n              display: true,\n              position: 'right',\n              weight: 1\n            },\n            y4: {\n              type: 'linear',\n              display: true,\n              position: 'right',\n              weight: 2\n            }\n          }\n        }\n      }, {\n        canvas: {\n          height: 150,\n          width: 250\n        }\n      });\n\n      var xScale0 = chart.scales.x;\n      var xScale1 = chart.scales.x1;\n      var xScale2 = chart.scales.x2;\n      var xScale3 = chart.scales.x3;\n      var xScale4 = chart.scales.x4;\n\n      var yScale0 = chart.scales.y;\n      var yScale1 = chart.scales.y1;\n      var yScale2 = chart.scales.y2;\n      var yScale3 = chart.scales.y3;\n      var yScale4 = chart.scales.y4;\n\n      expect(xScale0.weight).toBe(1);\n      expect(xScale1.weight).toBe(2);\n      expect(xScale2.weight).toBe(0);\n\n      expect(xScale3.weight).toBe(1);\n      expect(xScale4.weight).toBe(2);\n\n      expect(yScale0.weight).toBe(1);\n      expect(yScale1.weight).toBe(2);\n      expect(yScale2.weight).toBe(0);\n\n      expect(yScale3.weight).toBe(1);\n      expect(yScale4.weight).toBe(2);\n\n      var isOrderCorrect = false;\n\n      // bottom axes\n      isOrderCorrect = xScale2.top < xScale0.top && xScale0.top < xScale1.top;\n      expect(isOrderCorrect).toBe(true);\n\n      // top axes\n      isOrderCorrect = xScale4.top < xScale3.top;\n      expect(isOrderCorrect).toBe(true);\n\n      // left axes\n      isOrderCorrect = yScale1.left < yScale0.left && yScale0.left < yScale2.left;\n      expect(isOrderCorrect).toBe(true);\n\n      // right axes\n      isOrderCorrect = yScale3.left < yScale4.left;\n      expect(isOrderCorrect).toBe(true);\n    });\n  });\n\n  describe('box sizing', function() {\n    it('should correctly compute y-axis width to fit labels', function() {\n      var chart = window.acquireChart({\n        type: 'bar',\n        data: {\n          labels: ['tick 1', 'tick 2', 'tick 3', 'tick 4', 'tick 5'],\n          datasets: [{\n            data: [0, 2.25, 1.5, 1.25, 2.5]\n          }],\n        },\n        options: {\n          plugins: {\n            legend: false\n          },\n        },\n      }, {\n        canvas: {\n          height: 256,\n          width: 256\n        }\n      });\n      var yAxis = chart.scales.y;\n\n      // issue #4441: y-axis labels partially hidden.\n      // minimum horizontal space required to fit labels\n      expect(yAxis.width).toBeCloseToPixel(30);\n      expect(getLabels(yAxis)).toEqual(['0', '0.5', '1.0', '1.5', '2.0', '2.5']);\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/core.plugin.tests.js",
    "content": "describe('Chart.plugins', function() {\n  describe('Chart.notifyPlugins', function() {\n    it('should call inline plugins with arguments', function() {\n      var plugin = {hook: function() {}};\n      var chart = window.acquireChart({\n        plugins: [plugin]\n      });\n      var args = {value: 42};\n\n      spyOn(plugin, 'hook');\n\n      chart.notifyPlugins('hook', args);\n      expect(plugin.hook.calls.count()).toBe(1);\n      expect(plugin.hook.calls.first().args[0]).toBe(chart);\n      expect(plugin.hook.calls.first().args[1]).toBe(args);\n      expect(plugin.hook.calls.first().args[2]).toEqualOptions({});\n    });\n\n    it('should call global plugins with arguments', function() {\n      var plugin = {id: 'a', hook: function() {}};\n      var chart = window.acquireChart({});\n      var args = {value: 42};\n\n      spyOn(plugin, 'hook');\n\n      Chart.register(plugin);\n      chart.notifyPlugins('hook', args);\n      expect(plugin.hook.calls.count()).toBe(1);\n      expect(plugin.hook.calls.first().args[0]).toBe(chart);\n      expect(plugin.hook.calls.first().args[1]).toBe(args);\n      expect(plugin.hook.calls.first().args[2]).toEqualOptions({});\n      Chart.unregister(plugin);\n    });\n\n    it('should call plugin only once even if registered multiple times', function() {\n      var plugin = {id: 'test', hook: function() {}};\n      var chart = window.acquireChart({\n        plugins: [plugin, plugin]\n      });\n\n      spyOn(plugin, 'hook');\n\n      Chart.register([plugin, plugin]);\n      chart.notifyPlugins('hook');\n      expect(plugin.hook.calls.count()).toBe(1);\n      Chart.unregister(plugin);\n    });\n\n    it('should call plugins in the correct order (global first)', function() {\n      var results = [];\n      var chart = window.acquireChart({\n        plugins: [{\n          hook: function() {\n            results.push(1);\n          }\n        }, {\n          hook: function() {\n            results.push(2);\n          }\n        }, {\n          hook: function() {\n            results.push(3);\n          }\n        }]\n      });\n\n      var plugins = [{\n        id: 'a',\n        hook: function() {\n          results.push(4);\n        }\n      }, {\n        id: 'b',\n        hook: function() {\n          results.push(5);\n        }\n      }, {\n        id: 'c',\n        hook: function() {\n          results.push(6);\n        }\n      }];\n      Chart.register(plugins);\n\n      var ret = chart.notifyPlugins('hook');\n      expect(ret).toBeTruthy();\n      expect(results).toEqual([4, 5, 6, 1, 2, 3]);\n      Chart.unregister(plugins);\n    });\n\n    it('should return TRUE if no plugin explicitly returns FALSE', function() {\n      var chart = window.acquireChart({\n        plugins: [{\n          hook: function() {}\n        }, {\n          hook: function() {\n            return null;\n          }\n        }, {\n          hook: function() {\n            return 0;\n          }\n        }, {\n          hook: function() {\n            return true;\n          }\n        }, {\n          hook: function() {\n            return 1;\n          }\n        }]\n      });\n\n      var plugins = chart.config.plugins;\n      plugins.forEach(function(plugin) {\n        spyOn(plugin, 'hook').and.callThrough();\n      });\n\n      var ret = chart.notifyPlugins('hook');\n      expect(ret).toBeTruthy();\n      plugins.forEach(function(plugin) {\n        expect(plugin.hook).toHaveBeenCalled();\n      });\n    });\n\n    it('should return FALSE if any plugin explicitly returns FALSE', function() {\n      var chart = window.acquireChart({\n        plugins: [{\n          hook: function() {}\n        }, {\n          hook: function() {\n            return null;\n          }\n        }, {\n          hook: function() {\n            return false;\n          }\n        }, {\n          hook: function() {\n            return 42;\n          }\n        }, {\n          hook: function() {\n            return 'bar';\n          }\n        }]\n      });\n\n      var plugins = chart.config.plugins;\n      plugins.forEach(function(plugin) {\n        spyOn(plugin, 'hook').and.callThrough();\n      });\n\n      var ret = chart.notifyPlugins('hook', {cancelable: true});\n      expect(ret).toBeFalsy();\n      expect(plugins[0].hook).toHaveBeenCalled();\n      expect(plugins[1].hook).toHaveBeenCalled();\n      expect(plugins[2].hook).toHaveBeenCalled();\n      expect(plugins[3].hook).not.toHaveBeenCalled();\n      expect(plugins[4].hook).not.toHaveBeenCalled();\n    });\n  });\n\n  describe('config.options.plugins', function() {\n    it('should call plugins with options at last argument', function() {\n      var plugin = {id: 'foo', hook: function() {}};\n\n      var chart = window.acquireChart({\n        options: {\n          plugins: {\n            foo: {a: '123'},\n          }\n        }\n      });\n\n      spyOn(plugin, 'hook');\n\n      Chart.register(plugin);\n      chart.notifyPlugins('hook');\n      chart.notifyPlugins('hook', {arg1: 'bla'});\n      chart.notifyPlugins('hook', {arg1: 'bla', arg2: 42});\n\n      expect(plugin.hook.calls.count()).toBe(3);\n      expect(plugin.hook.calls.argsFor(0)[2]).toEqualOptions({a: '123'});\n      expect(plugin.hook.calls.argsFor(1)[2]).toEqualOptions({a: '123'});\n      expect(plugin.hook.calls.argsFor(2)[2]).toEqualOptions({a: '123'});\n\n      Chart.unregister(plugin);\n    });\n\n    it('should call plugins with options associated to their identifier', function() {\n      var plugins = {\n        a: {id: 'a', hook: function() {}},\n        b: {id: 'b', hook: function() {}},\n        c: {id: 'c', hook: function() {}}\n      };\n\n      Chart.register(plugins.a);\n\n      var chart = window.acquireChart({\n        plugins: [plugins.b, plugins.c],\n        options: {\n          plugins: {\n            a: {a: '123'},\n            b: {b: '456'},\n            c: {c: '789'}\n          }\n        }\n      });\n\n      spyOn(plugins.a, 'hook');\n      spyOn(plugins.b, 'hook');\n      spyOn(plugins.c, 'hook');\n\n      chart.notifyPlugins('hook');\n\n      expect(plugins.a.hook).toHaveBeenCalled();\n      expect(plugins.b.hook).toHaveBeenCalled();\n      expect(plugins.c.hook).toHaveBeenCalled();\n      expect(plugins.a.hook.calls.first().args[2]).toEqualOptions({a: '123'});\n      expect(plugins.b.hook.calls.first().args[2]).toEqualOptions({b: '456'});\n      expect(plugins.c.hook.calls.first().args[2]).toEqualOptions({c: '789'});\n\n      Chart.unregister(plugins.a);\n    });\n\n    it('should not call plugins when config.options.plugins.{id} is FALSE', function() {\n      var plugins = {\n        a: {id: 'a', hook: function() {}},\n        b: {id: 'b', hook: function() {}},\n        c: {id: 'c', hook: function() {}}\n      };\n\n      Chart.register(plugins.a);\n\n      var chart = window.acquireChart({\n        plugins: [plugins.b, plugins.c],\n        options: {\n          plugins: {\n            a: false,\n            b: false\n          }\n        }\n      });\n\n      spyOn(plugins.a, 'hook');\n      spyOn(plugins.b, 'hook');\n      spyOn(plugins.c, 'hook');\n\n      chart.notifyPlugins('hook');\n\n      expect(plugins.a.hook).not.toHaveBeenCalled();\n      expect(plugins.b.hook).not.toHaveBeenCalled();\n      expect(plugins.c.hook).toHaveBeenCalled();\n\n      Chart.unregister(plugins.a);\n    });\n\n    it('should call plugins with default options when plugin options is TRUE', function() {\n      var plugin = {id: 'a', hook: function() {}, defaults: {a: 42}};\n\n      Chart.register(plugin);\n\n      var chart = window.acquireChart({\n        options: {\n          plugins: {\n            a: true\n          }\n        }\n      });\n\n      spyOn(plugin, 'hook');\n\n      chart.notifyPlugins('hook');\n\n      expect(plugin.hook).toHaveBeenCalled();\n      expect(Object.keys(plugin.hook.calls.first().args[2])).toEqual(['a']);\n      expect(plugin.hook.calls.first().args[2]).toEqual(jasmine.objectContaining({a: 42}));\n\n      Chart.unregister(plugin);\n    });\n\n\n    it('should call plugins with default options if plugin config options is undefined', function() {\n      var plugin = {id: 'a', hook: function() {}, defaults: {a: 'foobar'}};\n\n      Chart.register(plugin);\n      spyOn(plugin, 'hook');\n\n      var chart = window.acquireChart();\n\n      chart.notifyPlugins('hook');\n\n      expect(plugin.hook).toHaveBeenCalled();\n      expect(plugin.hook.calls.first().args[2]).toEqualOptions({a: 'foobar'});\n\n      Chart.unregister(plugin);\n    });\n\n    // https://github.com/chartjs/Chart.js/issues/10482\n    it('should resolve defaults for local plugins', function() {\n      var plugin = {id: 'a', hook: function() {}, defaults: {bar: 'bar'}};\n      var chart = window.acquireChart({\n        plugins: [plugin],\n        options: {\n          plugins: {\n            a: {\n              foo: 'foo'\n            }\n          }\n        },\n      });\n\n      spyOn(plugin, 'hook');\n      chart.notifyPlugins('hook');\n\n      expect(plugin.hook).toHaveBeenCalled();\n      expect(plugin.hook.calls.first().args[2]).toEqualOptions({foo: 'foo', bar: 'bar'});\n\n      Chart.unregister(plugin);\n    });\n\n    // https://github.com/chartjs/Chart.js/issues/5111#issuecomment-355934167\n    it('should update plugin options', function() {\n      var plugin = {id: 'a', hook: function() {}};\n      var chart = window.acquireChart({\n        plugins: [plugin],\n        options: {\n          plugins: {\n            a: {\n              foo: 'foo'\n            }\n          }\n        },\n      });\n\n      spyOn(plugin, 'hook');\n\n      chart.notifyPlugins('hook');\n\n      expect(plugin.hook).toHaveBeenCalled();\n      expect(plugin.hook.calls.first().args[2]).toEqualOptions({foo: 'foo'});\n\n      chart.options.plugins.a = {bar: 'bar'};\n      chart.update();\n\n      plugin.hook.calls.reset();\n      chart.notifyPlugins('hook');\n\n      expect(plugin.hook).toHaveBeenCalled();\n      expect(plugin.hook.calls.first().args[2]).toEqualOptions({bar: 'bar'});\n    });\n\n    // https://github.com/chartjs/Chart.js/issues/10654\n    it('should resolve options even if some subnodes are set as undefined', function() {\n      var runtimeOptions;\n      var plugin = {\n        id: 'a',\n        afterUpdate: function(chart, args, options) {\n          options.l1.l2.l3.display = true;\n          runtimeOptions = options;\n        },\n        defaults: {\n          l1: {\n            l2: {\n              l3: {\n                display: false\n              }\n            }\n          }\n        }\n      };\n      window.acquireChart({\n        plugins: [plugin],\n        options: {\n          plugins: {\n            a: {\n              l1: {\n                l2: undefined\n              }\n            },\n          }\n        },\n      });\n\n      expect(runtimeOptions.l1.l2.l3.display).toBe(true);\n      Chart.unregister(plugin);\n    });\n\n    it('should disable all plugins', function() {\n      var plugin = {id: 'a', hook: function() {}};\n      var chart = window.acquireChart({\n        plugins: [plugin],\n        options: {\n          plugins: false\n        }\n      });\n\n      spyOn(plugin, 'hook');\n\n      chart.notifyPlugins('hook');\n\n      expect(plugin.hook).not.toHaveBeenCalled();\n    });\n\n    it('should not restart plugins when a double register occurs', function() {\n      var results = [];\n      var chart = window.acquireChart({\n        plugins: [{\n          start: function() {\n            results.push(1);\n          }\n        }]\n      });\n\n      Chart.register({id: 'abc', hook: function() {}});\n      Chart.register({id: 'def', hook: function() {}});\n\n      chart.update();\n\n      // The plugin on the chart should only be started once\n      expect(results).toEqual([1]);\n    });\n\n    it('should default to false for _scriptable, _indexable', function(done) {\n      const plugin = {\n        id: 'test',\n        start: function(chart, args, opts) {\n          expect(opts.fun).toEqual(jasmine.any(Function));\n          expect(opts.fun()).toEqual('test');\n          expect(opts.arr).toEqual([1, 2, 3]);\n\n          expect(opts.sub.subfun).toEqual(jasmine.any(Function));\n          expect(opts.sub.subfun()).toEqual('subtest');\n          expect(opts.sub.subarr).toEqual([3, 2, 1]);\n          done();\n        }\n      };\n      window.acquireChart({\n        options: {\n          plugins: {\n            test: {\n              fun: () => 'test',\n              arr: [1, 2, 3],\n              sub: {\n                subfun: () => 'subtest',\n                subarr: [3, 2, 1],\n              }\n            }\n          }\n        },\n        plugins: [plugin]\n      });\n    });\n\n    it('should filter event callbacks by plugin events array', async function() {\n      const results = [];\n      const chart = window.acquireChart({\n        options: {\n          events: ['mousemove', 'test', 'test2', 'pointerleave'],\n          plugins: {\n            testPlugin: {\n              events: ['test', 'pointerleave']\n            }\n          }\n        },\n        plugins: [{\n          id: 'testPlugin',\n          beforeEvent: function(_chart, args) {\n            results.push('before' + args.event.type);\n          },\n          afterEvent: function(_chart, args) {\n            results.push('after' + args.event.type);\n          }\n        }]\n      });\n      await jasmine.triggerMouseEvent(chart, 'mousemove', {x: 0, y: 0});\n      await jasmine.triggerMouseEvent(chart, 'test', {x: 0, y: 0});\n      await jasmine.triggerMouseEvent(chart, 'test2', {x: 0, y: 0});\n      await jasmine.triggerMouseEvent(chart, 'pointerleave', {x: 0, y: 0});\n      expect(results).toEqual(['beforetest', 'aftertest', 'beforemouseout', 'aftermouseout']);\n    });\n\n    it('should not call plugins after uninstall', async function() {\n      const results = [];\n      const chart = window.acquireChart({\n        options: {\n          events: ['test'],\n          plugins: {\n            testPlugin: {\n              events: ['test']\n            }\n          }\n        },\n        plugins: [{\n          id: 'testPlugin',\n          reset: () => results.push('reset'),\n          afterDestroy: () => results.push('afterDestroy'),\n          uninstall: () => results.push('uninstall'),\n        }]\n      });\n      chart.reset();\n      expect(results).toEqual(['reset']);\n      chart.destroy();\n      expect(results).toEqual(['reset', 'afterDestroy', 'uninstall']);\n      chart.reset();\n      expect(results).toEqual(['reset', 'afterDestroy', 'uninstall']);\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/core.registry.tests.js",
    "content": "describe('Chart.registry', function() {\n  it('should handle an ES6 controller extension', function() {\n    class CustomController extends Chart.DatasetController {}\n    CustomController.id = 'custom';\n    CustomController.defaults = {\n      foo: 'bar'\n    };\n    CustomController.overrides = {\n      bar: 'foo'\n    };\n    Chart.register(CustomController);\n\n    expect(Chart.registry.getController('custom')).toEqual(CustomController);\n    expect(Chart.defaults.datasets.custom).toEqual(CustomController.defaults);\n    expect(Chart.overrides.custom).toEqual(CustomController.overrides);\n\n    Chart.unregister(CustomController);\n\n    expect(function() {\n      Chart.registry.getController('custom');\n    }).toThrow(new Error('\"custom\" is not a registered controller.'));\n    expect(Chart.overrides.custom).not.toBeDefined();\n    expect(Chart.defaults.datasets.custom).not.toBeDefined();\n  });\n\n  it('should handle an ES6 scale extension', function() {\n    class CustomScale extends Chart.Scale {}\n    CustomScale.id = 'es6Scale';\n    CustomScale.defaults = {\n      foo: 'bar'\n    };\n    Chart.register(CustomScale);\n\n    expect(Chart.registry.getScale('es6Scale')).toEqual(CustomScale);\n    expect(Chart.defaults.scales.es6Scale).toEqual(CustomScale.defaults);\n\n    Chart.unregister(CustomScale);\n\n    expect(function() {\n      Chart.registry.getScale('es6Scale');\n    }).toThrow(new Error('\"es6Scale\" is not a registered scale.'));\n    expect(Chart.defaults.scales.es6Scale).not.toBeDefined();\n  });\n\n  it('should handle an ES6 element extension', function() {\n    class CustomElement extends Chart.Element {}\n    CustomElement.id = 'es6element';\n    CustomElement.defaults = {\n      foo: 'bar'\n    };\n    Chart.register(CustomElement);\n\n    expect(Chart.registry.getElement('es6element')).toEqual(CustomElement);\n    expect(Chart.defaults.elements.es6element).toEqual(CustomElement.defaults);\n\n    Chart.unregister(CustomElement);\n\n    expect(function() {\n      Chart.registry.getElement('es6element');\n    }).toThrow(new Error('\"es6element\" is not a registered element.'));\n    expect(Chart.defaults.elements.es6element).not.toBeDefined();\n  });\n\n  it('should handle an ES6 plugin', function() {\n    class CustomPlugin {}\n    CustomPlugin.id = 'es6plugin';\n    CustomPlugin.defaults = {\n      foo: 'bar'\n    };\n    Chart.register(CustomPlugin);\n\n    expect(Chart.registry.getPlugin('es6plugin')).toEqual(CustomPlugin);\n    expect(Chart.defaults.plugins.es6plugin).toEqual(CustomPlugin.defaults);\n\n    Chart.unregister(CustomPlugin);\n\n    expect(function() {\n      Chart.registry.getPlugin('es6plugin');\n    }).toThrow(new Error('\"es6plugin\" is not a registered plugin.'));\n    expect(Chart.defaults.plugins.es6plugin).not.toBeDefined();\n  });\n\n  it('should not accept an object without id', function() {\n    expect(function() {\n      Chart.register({foo: 'bar'});\n    }).toThrow(new Error('class does not have id: bar'));\n\n    class FaultyPlugin {}\n\n    expect(function() {\n      Chart.register(FaultyPlugin);\n    }).toThrow(new Error('class does not have id: class FaultyPlugin {}'));\n  });\n\n  it('should not fail when unregistering an object that is not registered', function() {\n    expect(function() {\n      Chart.unregister({id: 'foo'});\n    }).not.toThrow();\n  });\n\n  describe('Should allow registering explicitly', function() {\n    class customExtension {}\n    customExtension.id = 'custom';\n    customExtension.defaults = {\n      prop: true\n    };\n\n    it('as controller', function() {\n      Chart.registry.addControllers(customExtension);\n\n      expect(Chart.registry.getController('custom')).toEqual(customExtension);\n      expect(Chart.defaults.datasets.custom).toEqual(customExtension.defaults);\n\n      Chart.registry.removeControllers(customExtension);\n\n      expect(function() {\n        Chart.registry.getController('custom');\n      }).toThrow(new Error('\"custom\" is not a registered controller.'));\n      expect(Chart.defaults.datasets.custom).not.toBeDefined();\n    });\n\n    it('as scale', function() {\n      Chart.registry.addScales(customExtension);\n\n      expect(Chart.registry.getScale('custom')).toEqual(customExtension);\n      expect(Chart.defaults.scales.custom).toEqual(customExtension.defaults);\n\n      Chart.registry.removeScales(customExtension);\n\n      expect(function() {\n        Chart.registry.getScale('custom');\n      }).toThrow(new Error('\"custom\" is not a registered scale.'));\n      expect(Chart.defaults.scales.custom).not.toBeDefined();\n    });\n\n    it('as element', function() {\n      Chart.registry.addElements(customExtension);\n\n      expect(Chart.registry.getElement('custom')).toEqual(customExtension);\n      expect(Chart.defaults.elements.custom).toEqual(customExtension.defaults);\n\n      Chart.registry.removeElements(customExtension);\n\n      expect(function() {\n        Chart.registry.getElement('custom');\n      }).toThrow(new Error('\"custom\" is not a registered element.'));\n      expect(Chart.defaults.elements.custom).not.toBeDefined();\n    });\n\n    it('as plugin', function() {\n      Chart.registry.addPlugins(customExtension);\n\n      expect(Chart.registry.getPlugin('custom')).toEqual(customExtension);\n      expect(Chart.defaults.plugins.custom).toEqual(customExtension.defaults);\n\n      Chart.registry.removePlugins(customExtension);\n\n      expect(function() {\n        Chart.registry.getPlugin('custom');\n      }).toThrow(new Error('\"custom\" is not a registered plugin.'));\n      expect(Chart.defaults.plugins.custom).not.toBeDefined();\n    });\n  });\n\n  it('should fire before/after callbacks', function() {\n    let beforeRegisterCount = 0;\n    let afterRegisterCount = 0;\n    let beforeUnregisterCount = 0;\n    let afterUnregisterCount = 0;\n    class custom {}\n    custom.id = 'custom';\n    custom.beforeRegister = () => beforeRegisterCount++;\n    custom.afterRegister = () => afterRegisterCount++;\n    custom.beforeUnregister = () => beforeUnregisterCount++;\n    custom.afterUnregister = () => afterUnregisterCount++;\n\n    Chart.registry.addControllers(custom);\n    expect(beforeRegisterCount).withContext('beforeRegister').toBe(1);\n    expect(afterRegisterCount).withContext('afterRegister').toBe(1);\n    Chart.registry.removeControllers(custom);\n    expect(beforeUnregisterCount).withContext('beforeUnregister').toBe(1);\n    expect(afterUnregisterCount).withContext('afterUnregister').toBe(1);\n\n    Chart.registry.addScales(custom);\n    expect(beforeRegisterCount).withContext('beforeRegister').toBe(2);\n    expect(afterRegisterCount).withContext('afterRegister').toBe(2);\n    Chart.registry.removeScales(custom);\n    expect(beforeUnregisterCount).withContext('beforeUnregister').toBe(2);\n    expect(afterUnregisterCount).withContext('afterUnregister').toBe(2);\n\n    Chart.registry.addElements(custom);\n    expect(beforeRegisterCount).withContext('beforeRegister').toBe(3);\n    expect(afterRegisterCount).withContext('afterRegister').toBe(3);\n    Chart.registry.removeElements(custom);\n    expect(beforeUnregisterCount).withContext('beforeUnregister').toBe(3);\n    expect(afterUnregisterCount).withContext('afterUnregister').toBe(3);\n\n    Chart.register(custom);\n    expect(beforeRegisterCount).withContext('beforeRegister').toBe(4);\n    expect(afterRegisterCount).withContext('afterRegister').toBe(4);\n    Chart.unregister(custom);\n    expect(beforeUnregisterCount).withContext('beforeUnregister').toBe(4);\n    expect(afterUnregisterCount).withContext('afterUnregister').toBe(4);\n  });\n\n  it('should preserve existing defaults', function() {\n    Chart.defaults.datasets.test = {test1: true, test3: false};\n    Chart.overrides.test = {testA: true, testC: false};\n\n    class testController extends Chart.DatasetController {}\n    testController.id = 'test';\n    testController.defaults = {test1: false, test2: true};\n    testController.overrides = {testA: false, testB: true};\n\n    Chart.register(testController);\n    expect(Chart.defaults.datasets.test).toEqual({test1: false, test2: true, test3: false});\n    expect(Chart.overrides.test).toEqual({testA: false, testB: true, testC: false});\n\n    Chart.unregister(testController);\n    expect(Chart.defaults.datasets.test).not.toBeDefined();\n    expect(Chart.overrides.test).not.toBeDefined();\n  });\n\n  describe('should handle multiple items', function() {\n    class test1 extends Chart.DatasetController {}\n    test1.id = 'test1';\n    class test2 extends Chart.Scale {}\n    test2.id = 'test2';\n\n    it('separately', function() {\n      Chart.register(test1, test2);\n      expect(Chart.registry.getController('test1')).toEqual(test1);\n      expect(Chart.registry.getScale('test2')).toEqual(test2);\n      Chart.unregister(test1, test2);\n      expect(function() {\n        Chart.registry.getController('test1');\n      }).toThrow();\n      expect(function() {\n        Chart.registry.getScale('test2');\n      }).toThrow();\n    });\n\n    it('as array', function() {\n      Chart.register([test1, test2]);\n      expect(Chart.registry.getController('test1')).toEqual(test1);\n      expect(Chart.registry.getScale('test2')).toEqual(test2);\n      Chart.unregister([test1, test2]);\n      expect(function() {\n        Chart.registry.getController('test1');\n      }).toThrow();\n      expect(function() {\n        Chart.registry.getScale('test2');\n      }).toThrow();\n    });\n\n    it('as object', function() {\n      Chart.register({test1, test2});\n      expect(Chart.registry.getController('test1')).toEqual(test1);\n      expect(Chart.registry.getScale('test2')).toEqual(test2);\n      Chart.unregister({test1, test2});\n      expect(function() {\n        Chart.registry.getController('test1');\n      }).toThrow();\n      expect(function() {\n        Chart.registry.getScale('test2');\n      }).toThrow();\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/core.scale.tests.js",
    "content": "function getLabels(scale) {\n  return scale.ticks.map(t => t.label);\n}\n\ndescribe('Core.scale', function() {\n  describe('auto', jasmine.fixture.specs('core.scale'));\n\n  it('should provide default scale label options', function() {\n    expect(Chart.defaults.scale.title).toEqual({\n      color: Chart.defaults.color,\n      display: false,\n      text: '',\n      padding: {\n        top: 4,\n        bottom: 4\n      }\n    });\n  });\n\n  describe('displaying xAxis ticks with autoSkip=true', function() {\n    function getChart(data) {\n      return window.acquireChart({\n        type: 'line',\n        data: data,\n        options: {\n          scales: {\n            x: {\n              ticks: {\n                autoSkip: true\n              }\n            }\n          }\n        }\n      });\n    }\n\n    function getChartBigData(maxTicksLimit) {\n      return window.acquireChart({\n        type: 'line',\n        data: {\n          labels: new Array(300).fill('red'),\n          datasets: [{\n            data: new Array(300).fill(5),\n          }]\n        },\n        options: {\n          scales: {\n            x: {\n              ticks: {\n                autoSkip: true,\n                maxTicksLimit\n              }\n            }\n          }\n        }\n      });\n    }\n\n    function lastTick(chart) {\n      var xAxis = chart.scales.x;\n      var ticks = xAxis.getTicks();\n      return ticks[ticks.length - 1];\n    }\n\n    it('should use autoSkip amount of ticks when maxTicksLimit is set to a larger number as autoSkip calculation', function() {\n      var chart = getChartBigData(300);\n      expect(chart.scales.x.ticks.length).toEqual(20);\n    });\n\n    it('should use maxTicksLimit amount of ticks when maxTicksLimit is set to a smaller number as autoSkip calculation', function() {\n      var chart = getChartBigData(3);\n      expect(chart.scales.x.ticks.length).toEqual(3);\n    });\n\n    it('should display the last tick if it fits evenly with other ticks', function() {\n      var chart = getChart({\n        labels: [\n          'January 2018', 'February 2018', 'March 2018', 'April 2018',\n          'May 2018', 'June 2018', 'July 2018', 'August 2018',\n          'September 2018'\n        ],\n        datasets: [{\n          data: [12, 19, 3, 5, 2, 3, 7, 8, 9]\n        }]\n      });\n\n      expect(lastTick(chart).label).toEqual('September 2018');\n    });\n\n    it('should not display the last tick if it does not fit evenly', function() {\n      var chart = getChart({\n        labels: [\n          'January 2018', 'February 2018', 'March 2018', 'April 2018',\n          'May 2018', 'June 2018', 'July 2018', 'August 2018',\n          'September 2018', 'October 2018', 'November 2018', 'December 2018',\n          'January 2019', 'February 2019', 'March 2019', 'April 2019',\n          'May 2019', 'June 2019', 'July 2019', 'August 2019',\n          'September 2019', 'October 2019', 'November 2019', 'December 2019',\n          'January 2020', 'February 2020', 'March 2020', 'April 2020'\n        ],\n        datasets: [{\n          data: [1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7]\n        }]\n      });\n\n      expect(lastTick(chart).label).toEqual('March 2020');\n    });\n  });\n\n  var gridLineTests = [{\n    labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5'],\n    offsetGridLines: false,\n    offset: false,\n    expected: [0.5, 128.5, 256.5, 384.5, 512.5]\n  }, {\n    labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5'],\n    offsetGridLines: false,\n    offset: true,\n    expected: [51.5, 153.5, 256.5, 358.5, 460.5]\n  }, {\n    labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5'],\n    offsetGridLines: true,\n    offset: false,\n    expected: [64.5, 192.5, 320.5, 448.5]\n  }, {\n    labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5'],\n    offsetGridLines: true,\n    offset: true,\n    expected: [0.5, 102.5, 204.5, 307.5, 409.5, 512.5]\n  }, {\n    labels: ['tick1'],\n    offsetGridLines: false,\n    offset: false,\n    expected: [0.5]\n  }, {\n    labels: ['tick1'],\n    offsetGridLines: false,\n    offset: true,\n    expected: [256.5]\n  }, {\n    labels: ['tick1'],\n    offsetGridLines: true,\n    offset: false,\n    expected: [512.5]\n  }, {\n    labels: ['tick1'],\n    offsetGridLines: true,\n    offset: true,\n    expected: [0.5, 512.5]\n  }];\n\n  gridLineTests.forEach(function(test) {\n    it('should get the correct pixels for gridLine(s) for the horizontal scale when offsetGridLines is ' + test.offsetGridLines + ' and offset is ' + test.offset, function() {\n      var chart = window.acquireChart({\n        type: 'line',\n        data: {\n          datasets: [{\n            data: []\n          }],\n          labels: test.labels\n        },\n        options: {\n          scales: {\n            x: {\n              grid: {\n                offset: test.offsetGridLines,\n                drawTicks: false\n              },\n              ticks: {\n                display: false\n              },\n              offset: test.offset\n            },\n            y: {\n              display: false\n            }\n          },\n          plugins: {\n            legend: false\n          }\n        }\n      });\n\n      var xScale = chart.scales.x;\n      xScale.ctx = window.createMockContext();\n      chart.draw();\n\n      expect(xScale.ctx.getCalls().filter(function(x) {\n        return x.name === 'moveTo' && x.args[1] === 0;\n      }).map(function(x) {\n        return x.args[0];\n      })).toEqual(test.expected);\n    });\n  });\n\n  gridLineTests.forEach(function(test) {\n    it('should get the correct pixels for gridLine(s) for the vertical scale when offsetGridLines is ' + test.offsetGridLines + ' and offset is ' + test.offset, function() {\n      var chart = window.acquireChart({\n        type: 'line',\n        data: {\n          datasets: [{\n            data: []\n          }],\n          labels: test.labels\n        },\n        options: {\n          scales: {\n            x: {\n              display: false\n            },\n            y: {\n              type: 'category',\n              grid: {\n                offset: test.offsetGridLines,\n                drawTicks: false\n              },\n              ticks: {\n                display: false\n              },\n              offset: test.offset\n            }\n          },\n          plugins: {\n            legend: false\n          }\n        }\n      });\n\n      var yScale = chart.scales.y;\n      yScale.ctx = window.createMockContext();\n      chart.draw();\n\n      expect(yScale.ctx.getCalls().filter(function(x) {\n        return x.name === 'moveTo' && x.args[0] === 1;\n      }).map(function(x) {\n        return x.args[1];\n      })).toEqual(test.expected);\n    });\n  });\n\n  it('should add the correct padding for long tick labels', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        labels: [\n          'This is a very long label',\n          'This is a very long label'\n        ],\n        datasets: [{\n          data: [0, 1]\n        }]\n      },\n      options: {\n        scales: {\n          y: {\n            display: false\n          }\n        },\n        plugins: {\n          legend: false\n        }\n      }\n    }, {\n      canvas: {\n        height: 100,\n        width: 200\n      }\n    });\n\n    var scale = chart.scales.x;\n    expect(scale.left).toBeGreaterThan(100);\n    expect(scale.right).toBeGreaterThan(190);\n  });\n\n  describe('given the axes display option is set to auto', function() {\n    describe('for the x axes', function() {\n      it('should draw the axes if at least one associated dataset is visible', function() {\n        var chart = window.acquireChart({\n          type: 'line',\n          data: {\n            datasets: [{\n              data: [100, 200, 100, 50],\n              xAxisId: 'foo',\n              hidden: true,\n              labels: ['Q1', 'Q2', 'Q3', 'Q4']\n            }, {\n              data: [100, 200, 100, 50],\n              xAxisId: 'foo',\n              labels: ['Q1', 'Q2', 'Q3', 'Q4']\n            }]\n          },\n          options: {\n            scales: {\n              x: {\n                display: 'auto'\n              },\n              y: {\n                type: 'category',\n              }\n            }\n          }\n        });\n\n        var scale = chart.scales.x;\n        scale.ctx = window.createMockContext();\n        chart.draw();\n\n        expect(scale.ctx.getCalls().length).toBeGreaterThan(0);\n        expect(scale.height).toBeGreaterThan(0);\n      });\n\n      it('should not draw the axes if no associated datasets are visible', function() {\n        var chart = window.acquireChart({\n          type: 'line',\n          data: {\n            datasets: [{\n              data: [100, 200, 100, 50],\n              xAxisId: 'foo',\n              hidden: true,\n              labels: ['Q1', 'Q2', 'Q3', 'Q4']\n            }]\n          },\n          options: {\n            scales: {\n              x: {\n                display: 'auto'\n              }\n            }\n          }\n        });\n\n        var scale = chart.scales.x;\n        scale.ctx = window.createMockContext();\n        chart.draw();\n\n        expect(scale.ctx.getCalls().length).toBe(0);\n        expect(scale.height).toBe(0);\n      });\n    });\n\n    describe('for the y axes', function() {\n      it('should draw the axes if at least one associated dataset is visible', function() {\n        var chart = window.acquireChart({\n          type: 'line',\n          data: {\n            datasets: [{\n              data: [100, 200, 100, 50],\n              yAxisId: 'foo',\n              hidden: true,\n              labels: ['Q1', 'Q2', 'Q3', 'Q4']\n            }, {\n              data: [100, 200, 100, 50],\n              yAxisId: 'foo',\n              labels: ['Q1', 'Q2', 'Q3', 'Q4']\n            }]\n          },\n          options: {\n            scales: {\n              y: {\n                display: 'auto'\n              }\n            }\n          }\n        });\n\n        var scale = chart.scales.y;\n        scale.ctx = window.createMockContext();\n        chart.draw();\n\n        expect(scale.ctx.getCalls().length).toBeGreaterThan(0);\n        expect(scale.width).toBeGreaterThan(0);\n      });\n\n      it('should not draw the axes if no associated datasets are visible', function() {\n        var chart = window.acquireChart({\n          type: 'line',\n          data: {\n            datasets: [{\n              data: [100, 200, 100, 50],\n              yAxisId: 'foo',\n              hidden: true,\n              labels: ['Q1', 'Q2', 'Q3', 'Q4']\n            }]\n          },\n          options: {\n            scales: {\n              y: {\n                display: 'auto'\n              }\n            }\n          }\n        });\n\n        var scale = chart.scales.y;\n        scale.ctx = window.createMockContext();\n        chart.draw();\n\n        expect(scale.ctx.getCalls().length).toBe(0);\n        expect(scale.width).toBe(0);\n      });\n    });\n  });\n\n  describe('afterBuildTicks', function() {\n    it('should allow filtering of ticks', function() {\n      var labels = ['tick1', 'tick2', 'tick3', 'tick4', 'tick5'];\n      var chart = window.acquireChart({\n        type: 'line',\n        options: {\n          scales: {\n            x: {\n              type: 'category',\n              labels: labels,\n              afterBuildTicks: function(scale) {\n                scale.ticks = scale.ticks.slice(1);\n              }\n            }\n          }\n        }\n      });\n\n      var scale = chart.scales.x;\n      expect(getLabels(scale)).toEqual(labels.slice(1));\n    });\n\n    it('should allow no return value from callback', function() {\n      var labels = ['tick1', 'tick2', 'tick3', 'tick4', 'tick5'];\n      var chart = window.acquireChart({\n        type: 'line',\n        options: {\n          scales: {\n            x: {\n              type: 'category',\n              labels: labels,\n              afterBuildTicks: function() { }\n            }\n          }\n        }\n      });\n\n      var scale = chart.scales.x;\n      expect(getLabels(scale)).toEqual(labels);\n    });\n\n    it('should allow empty ticks', function() {\n      var labels = ['tick1', 'tick2', 'tick3', 'tick4', 'tick5'];\n      var chart = window.acquireChart({\n        type: 'line',\n        options: {\n          scales: {\n            x: {\n              type: 'category',\n              labels: labels,\n              afterBuildTicks: function(scale) {\n                scale.ticks = [];\n              }\n            }\n          }\n        }\n      });\n\n      var scale = chart.scales.x;\n      expect(scale.ticks.length).toBe(0);\n    });\n  });\n\n  describe('_layers', function() {\n    it('should default to three layers', function() {\n      var chart = window.acquireChart({\n        type: 'line',\n        options: {\n          scales: {\n            x: {\n              type: 'linear',\n            }\n          }\n        }\n      });\n\n      var scale = chart.scales.x;\n      expect(scale._layers().length).toEqual(3);\n    });\n\n    it('should create the chart with custom scale ids without axis or position options', function() {\n      function createChart() {\n        return window.acquireChart({\n          type: 'scatter',\n          data: {\n            datasets: [{\n              data: [{x: 0, y: 0}, {x: 1, y: 1}, {x: 2, y: 2}],\n              xAxisID: 'customIDx',\n              yAxisID: 'customIDy'\n            }]\n          },\n          options: {\n            scales: {\n              customIDx: {\n                type: 'linear',\n                display: false\n              },\n              customIDy: {\n                type: 'linear',\n                display: false\n              }\n            }\n          }\n        });\n      }\n\n      expect(createChart).not.toThrow();\n    });\n\n    it('should default to one layer for custom scales', function() {\n      class CustomScale extends Chart.Scale {\n        draw() {}\n        convertTicksToLabels() {\n          return ['tick'];\n        }\n      }\n      CustomScale.id = 'customScale';\n      CustomScale.defaults = {};\n      Chart.register(CustomScale);\n\n      var chart = window.acquireChart({\n        type: 'line',\n        options: {\n          scales: {\n            x: {\n              type: 'customScale',\n              grid: {\n                z: 10\n              },\n              ticks: {\n                z: 20\n              }\n            }\n          }\n        }\n      });\n\n      var scale = chart.scales.x;\n      expect(scale._layers().length).toEqual(1);\n      expect(scale._layers()[0].z).toEqual(20);\n    });\n\n    it('should default to one layer for custom scales for axis', function() {\n      class CustomScale1 extends Chart.Scale {\n        draw() {}\n        convertTicksToLabels() {\n          return ['tick'];\n        }\n      }\n      CustomScale1.id = 'customScale1';\n      CustomScale1.defaults = {axis: 'x'};\n      Chart.register(CustomScale1);\n\n      var chart = window.acquireChart({\n        type: 'line',\n        options: {\n          scales: {\n            my: {\n              type: 'customScale1',\n              grid: {\n                z: 10\n              },\n              ticks: {\n                z: 20\n              }\n            }\n          }\n        }\n      });\n\n      var scale = chart.scales.my;\n      expect(scale._layers().length).toEqual(1);\n      expect(scale._layers()[0].z).toEqual(20);\n    });\n\n    it('should fail for custom scales without any axis or position', function() {\n      class CustomScale2 extends Chart.Scale {\n        draw() {}\n      }\n      CustomScale2.id = 'customScale2';\n      CustomScale2.defaults = {};\n      Chart.register(CustomScale2);\n\n      function createChart() {\n        return window.acquireChart({\n          type: 'line',\n          options: {\n            scales: {\n              my: {\n                type: 'customScale2'\n              }\n            }\n          }\n        });\n      }\n\n      expect(createChart).toThrow(new Error('Cannot determine type of \\'my\\' axis. Please provide \\'axis\\' or \\'position\\' option.'));\n    });\n\n    it('should return 3 layers when z is not equal between ticks and grid', function() {\n      var chart = window.acquireChart({\n        type: 'line',\n        options: {\n          scales: {\n            x: {\n              type: 'linear',\n              ticks: {\n                z: 10\n              }\n            }\n          }\n        }\n      });\n\n      expect(chart.scales.x._layers().length).toEqual(3);\n\n      chart = window.acquireChart({\n        type: 'line',\n        options: {\n          scales: {\n            x: {\n              type: 'linear',\n              grid: {\n                z: 11\n              }\n            }\n          }\n        }\n      });\n\n      expect(chart.scales.x._layers().length).toEqual(3);\n\n      chart = window.acquireChart({\n        type: 'line',\n        options: {\n          scales: {\n            x: {\n              type: 'linear',\n              ticks: {\n                z: 10\n              },\n              grid: {\n                z: 11\n              }\n            }\n          }\n        }\n      });\n\n      expect(chart.scales.x._layers().length).toEqual(3);\n\n    });\n\n  });\n\n  describe('min and max', function() {\n    it('should be limited to visible data', function() {\n      var chart = window.acquireChart({\n        type: 'scatter',\n        data: {\n          datasets: [{\n            data: [{x: 100, y: 100}, {x: -100, y: -100}]\n          }, {\n            data: [{x: 10, y: 10}, {x: -10, y: -10}]\n          }]\n        },\n        options: {\n          scales: {\n            x: {\n              id: 'x',\n              type: 'linear',\n              min: -20,\n              max: 20\n            },\n            y: {\n              id: 'y',\n              type: 'linear'\n            }\n          }\n        }\n      });\n\n      expect(chart.scales.x.min).toEqual(-20);\n      expect(chart.scales.x.max).toEqual(20);\n      expect(chart.scales.y.min).toEqual(-10);\n      expect(chart.scales.y.max).toEqual(10);\n    });\n  });\n\n  describe('overrides', () => {\n    it('should create new scale', () => {\n      const chart = window.acquireChart({\n        type: 'scatter',\n        data: {\n          datasets: [{\n            data: [{x: 100, y: 100}, {x: -100, y: -100}]\n          }, {\n            data: [{x: 10, y: 10}, {x: -10, y: -10}]\n          }]\n        },\n        options: {\n          scales: {\n            x2: {\n              type: 'linear',\n              min: -20,\n              max: 20\n            }\n          }\n        }\n      });\n\n      expect(Object.keys(chart.scales).sort()).toEqual(['x', 'x2', 'y']);\n    });\n\n    it('should create new scale with custom name', () => {\n      const chart = window.acquireChart({\n        type: 'scatter',\n        data: {\n          datasets: [{\n            data: [{x: 100, y: 100}, {x: -100, y: -100}]\n          }, {\n            data: [{x: 10, y: 10}, {x: -10, y: -10}]\n          }]\n        },\n        options: {\n          scales: {\n            scaleX: {\n              axis: 'x',\n              type: 'linear',\n              min: -20,\n              max: 20\n            }\n          }\n        }\n      });\n\n      expect(Object.keys(chart.scales).sort()).toEqual(['scaleX', 'x', 'y']);\n    });\n\n    it('should throw error on scale with custom name without axis type', () => {\n      expect(() => window.acquireChart({\n        type: 'scatter',\n        data: {\n          datasets: [{\n            data: [{x: 100, y: 100}, {x: -100, y: -100}]\n          }, {\n            data: [{x: 10, y: 10}, {x: -10, y: -10}]\n          }]\n        },\n        options: {\n          scales: {\n            scaleX: {\n              type: 'linear',\n              min: -20,\n              max: 20\n            }\n          }\n        }\n      })).toThrow();\n    });\n\n    it('should read options first to determine axis', () => {\n      const chart = window.acquireChart({\n        type: 'scatter',\n        data: {\n          datasets: [{\n            data: [{x: 100, y: 100}, {x: -100, y: -100}]\n          }, {\n            data: [{x: 10, y: 10}, {x: -10, y: -10}]\n          }]\n        },\n        options: {\n          scales: {\n            xavier: {\n              axis: 'y',\n              type: 'linear',\n              min: -20,\n              max: 20\n            }\n          }\n        }\n      });\n\n      expect(chart.scales.xavier.axis).toBe('y');\n    });\n    it('should center labels when rotated in x axis', () => {\n      const chart = window.acquireChart({\n        type: 'line',\n        data: {\n          labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],\n          datasets: [{\n            label: '# of Votes',\n            data: [12, 19, 3, 5, 2, 3]\n          }]\n        },\n        options: {\n          scales: {\n            x: {\n              ticks: {\n                minRotation: 90,\n              }\n            }\n          }\n        }\n      });\n      const mapper = item => parseFloat(item.options.translation[0].toFixed(2));\n      const expected = [20.15, 113.6, 207.05, 300.5, 393.95, 487.4];\n      const actual = chart.scales.x.getLabelItems().map(mapper);\n      const len = expected.length;\n      for (let i = 0; i < len; ++i) {\n        const actualValue = actual[i];\n        const expectedValue = expected[i];\n        expect(actualValue).toBeCloseTo(expectedValue, 1);\n      }\n    });\n  });\n  describe('Scale Title stroke', ()=>{\n    function getChartWithScaleTitleStroke() {\n      return window.acquireChart({\n        type: 'line',\n        options: {\n          scales: {\n            x: {\n              type: 'linear',\n              title: {\n                display: true,\n                text: 'title-x',\n                color: '#ddd',\n                strokeWidth: 1,\n                strokeColor: '#333'\n              }\n            },\n            y: {\n              type: 'linear',\n              title: {\n                display: true,\n                text: 'title-y',\n                color: '#ddd',\n                strokeWidth: 2,\n                strokeColor: '#222'\n              }\n            }\n          }\n        }\n      });\n    }\n\n    function getChartWithoutScaleTitleStroke() {\n      return window.acquireChart({\n        type: 'line',\n        options: {\n          scales: {\n            x: {\n              type: 'linear',\n              title: {\n                display: true,\n                text: 'title-x',\n                color: '#ddd'\n              }\n            },\n            y: {\n              type: 'linear',\n              title: {\n                display: true,\n                text: 'title-y',\n                color: '#ddd'\n              }\n            }\n          }\n        }\n      });\n    }\n\n    it('should draw a scale title stroke when provided x-axis', function() {\n      var chart = getChartWithScaleTitleStroke();\n      var scale = chart.scales.x;\n      expect(scale.options.title.strokeColor).toEqual('#333');\n      expect(scale.options.title.strokeWidth).toEqual(1);\n    });\n\n    it('should draw a scale title stroke when provided y-axis', function() {\n      var chart = getChartWithScaleTitleStroke();\n      var scale = chart.scales.y;\n      expect(scale.options.title.strokeColor).toEqual('#222');\n      expect(scale.options.title.strokeWidth).toEqual(2);\n    });\n\n    it('should not draw a scale title stroke when not provided', function() {\n      var chart = getChartWithoutScaleTitleStroke();\n      var scales = chart.scales;\n      expect(scales.y.options.title.strokeColor).toBeUndefined();\n      expect(scales.y.options.title.strokeWidth).toBeUndefined();\n      expect(scales.x.options.title.strokeColor).toBeUndefined();\n      expect(scales.x.options.title.strokeWidth).toBeUndefined();\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/core.ticks.tests.js",
    "content": "function getLabels(scale) {\n  return scale.ticks.map(t => t.label);\n}\n\ndescribe('Test tick generators', function() {\n  // formatters are used as default config values so users want to be able to reference them\n  it('Should expose formatters api', function() {\n    expect(typeof Chart.Ticks).toBeDefined();\n    expect(typeof Chart.Ticks.formatters).toBeDefined();\n    expect(typeof Chart.Ticks.formatters.values).toBe('function');\n    expect(typeof Chart.Ticks.formatters.numeric).toBe('function');\n  });\n\n  it('Should generate linear spaced ticks with correct precision', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          data: []\n        }],\n      },\n      options: {\n        plugins: {\n          legend: false\n        },\n        scales: {\n          x: {\n            type: 'linear',\n            position: 'bottom',\n            ticks: {\n              callback: function(value) {\n                return value.toString();\n              }\n            }\n          },\n          y: {\n            type: 'linear',\n            ticks: {\n              callback: function(value) {\n                return value.toString();\n              }\n            }\n          }\n        }\n      }\n    });\n\n    var xLabels = getLabels(chart.scales.x);\n    var yLabels = getLabels(chart.scales.y);\n\n    expect(xLabels).toEqual(['0', '0.1', '0.2', '0.3', '0.4', '0.5', '0.6', '0.7', '0.8', '0.9', '1']);\n    expect(yLabels).toEqual(['0', '0.1', '0.2', '0.3', '0.4', '0.5', '0.6', '0.7', '0.8', '0.9', '1']);\n  });\n\n  it('Should generate logarithmic spaced ticks with correct precision', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          data: []\n        }],\n      },\n      options: {\n        plugins: {\n          legend: false\n        },\n        scales: {\n          x: {\n            type: 'logarithmic',\n            position: 'bottom',\n            min: 0.1,\n            max: 1,\n            ticks: {\n              autoSkip: false,\n              callback: function(value) {\n                return value.toString();\n              }\n            }\n          },\n          y: {\n            type: 'logarithmic',\n            min: 0.1,\n            max: 1,\n            ticks: {\n              autoSkip: false,\n              callback: function(value) {\n                return value.toString();\n              }\n            }\n          }\n        }\n      }\n    });\n\n    var xLabels = getLabels(chart.scales.x);\n    var yLabels = getLabels(chart.scales.y);\n\n    expect(xLabels).toEqual(['0.1', '0.11', '0.12', '0.13', '0.14', '0.15', '0.16', '0.17', '0.18', '0.19', '0.2', '0.25', '0.3', '0.4', '0.5', '0.6', '0.7', '0.8', '0.9', '1']);\n    expect(yLabels).toEqual(['0.1', '0.11', '0.12', '0.13', '0.14', '0.15', '0.16', '0.17', '0.18', '0.19', '0.2', '0.25', '0.3', '0.4', '0.5', '0.6', '0.7', '0.8', '0.9', '1']);\n  });\n\n  describe('formatters.numeric', function() {\n    it('should not fail on empty or 1 item array', function() {\n      const scale = {chart: {options: {locale: 'en'}}, options: {ticks: {format: {}}}};\n      expect(Chart.Ticks.formatters.numeric.apply(scale, [1, 0, []])).toEqual('1');\n      expect(Chart.Ticks.formatters.numeric.apply(scale, [1, 0, [{value: 1}]])).toEqual('1');\n      expect(Chart.Ticks.formatters.numeric.apply(scale, [1, 0, [{value: 1}, {value: 1.01}]])).toEqual('1.00');\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/element.arc.tests.js",
    "content": "// Test the rectangle element\n\ndescribe('Arc element tests', function() {\n  it ('should determine if in range', function() {\n    // Mock out the arc as if the controller put it there\n    var arc = new Chart.elements.ArcElement({\n      startAngle: 0,\n      endAngle: Math.PI / 2,\n      x: 0,\n      y: 0,\n      innerRadius: 5,\n      outerRadius: 10,\n      options: {\n        spacing: 0,\n        offset: 0,\n        borderWidth: 0\n      }\n    });\n\n    expect(arc.inRange(2, 2)).toBe(false);\n    expect(arc.inRange(7, 0)).toBe(true);\n    expect(arc.inRange(0, 11)).toBe(false);\n    expect(arc.inRange(Math.sqrt(32), Math.sqrt(32))).toBe(true);\n    expect(arc.inRange(-1.0 * Math.sqrt(7), Math.sqrt(7))).toBe(false);\n  });\n\n  it ('should determine if in range when full circle', function() {\n    // Mock out the arc as if the controller put it there\n    var arc = new Chart.elements.ArcElement({\n      startAngle: 0,\n      endAngle: Math.PI * 2,\n      x: 0,\n      y: 0,\n      innerRadius: 5,\n      outerRadius: 10,\n      options: {\n        spacing: 0,\n        offset: 0,\n        borderWidth: 0\n      }\n    });\n\n    for (const radius of [5, 7.5, 10]) {\n      for (let angle = 0; angle <= 360; angle += 22.5) {\n        const rad = angle / 180 * Math.PI;\n        const x = Math.sin(rad) * radius;\n        const y = Math.cos(rad) * radius;\n        expect(arc.inRange(x, y)).withContext(`radius: ${radius}, angle: ${angle}`).toBeTrue();\n      }\n    }\n    for (const radius of [4, 11]) {\n      for (let angle = 0; angle <= 360; angle += 22.5) {\n        const rad = angle / 180 * Math.PI;\n        const x = Math.sin(rad) * radius;\n        const y = Math.cos(rad) * radius;\n        expect(arc.inRange(x, y)).withContext(`radius: ${radius}, angle: ${angle}`).toBeFalse();\n      }\n    }\n  });\n\n  it ('should include spacing for in range check', function() {\n    // Mock out the arc as if the controller put it there\n    var arc = new Chart.elements.ArcElement({\n      startAngle: 0,\n      endAngle: Math.PI / 2,\n      x: 0,\n      y: 0,\n      innerRadius: 5,\n      outerRadius: 10,\n      options: {\n        spacing: 10,\n        offset: 0,\n        borderWidth: 0\n      }\n    });\n\n    expect(arc.inRange(7, 0)).toBe(false);\n    expect(arc.inRange(15, 0)).toBe(true);\n  });\n\n  it ('should include borderWidth for in range check', function() {\n    // Mock out the arc as if the controller put it there\n    var arc = new Chart.elements.ArcElement({\n      startAngle: 0,\n      endAngle: Math.PI / 2,\n      x: 0,\n      y: 0,\n      innerRadius: 5,\n      outerRadius: 10,\n      options: {\n        spacing: 0,\n        offset: 0,\n        borderWidth: 10\n      }\n    });\n\n    expect(arc.inRange(7, 0)).toBe(false);\n    expect(arc.inRange(15, 0)).toBe(true);\n  });\n\n  it ('should determine if in range, when full circle', function() {\n    // Mock out the arc as if the controller put it there\n    var arc = new Chart.elements.ArcElement({\n      startAngle: -Math.PI,\n      endAngle: Math.PI * 1.5,\n      x: 0,\n      y: 0,\n      innerRadius: 0,\n      outerRadius: 10,\n      circumference: Math.PI * 2,\n      options: {\n        spacing: 0,\n        offset: 0,\n        borderWidth: 0\n      }\n    });\n\n    expect(arc.inRange(7, 7)).toBe(true);\n  });\n\n  it ('should get the tooltip position', function() {\n    // Mock out the arc as if the controller put it there\n    var arc = new Chart.elements.ArcElement({\n      startAngle: 0,\n      endAngle: Math.PI / 2,\n      x: 0,\n      y: 0,\n      innerRadius: 0,\n      outerRadius: Math.sqrt(2),\n      options: {\n        spacing: 0,\n        offset: 0,\n        borderWidth: 0\n      }\n    });\n\n    var pos = arc.tooltipPosition();\n    expect(pos.x).toBeCloseTo(0.5);\n    expect(pos.y).toBeCloseTo(0.5);\n  });\n\n  it ('should get the center', function() {\n    // Mock out the arc as if the controller put it there\n    var arc = new Chart.elements.ArcElement({\n      startAngle: 0,\n      endAngle: Math.PI / 2,\n      x: 0,\n      y: 0,\n      innerRadius: 0,\n      outerRadius: Math.sqrt(2),\n      options: {\n        spacing: 0,\n        offset: 0,\n        borderWidth: 0\n      }\n    });\n\n    var center = arc.getCenterPoint();\n    expect(center.x).toBeCloseTo(0.5, 6);\n    expect(center.y).toBeCloseTo(0.5, 6);\n  });\n\n  it ('should get the center with offset and spacing', function() {\n    // Mock out the arc as if the controller put it there\n    var arc = new Chart.elements.ArcElement({\n      startAngle: 0,\n      endAngle: Math.PI / 2,\n      x: 0,\n      y: 0,\n      innerRadius: 0,\n      outerRadius: Math.sqrt(2),\n      options: {\n        spacing: 10,\n        offset: 10,\n        borderWidth: 0\n      }\n    });\n\n    var center = arc.getCenterPoint();\n    expect(center.x).toBeCloseTo(7.57, 1);\n    expect(center.y).toBeCloseTo(7.57, 1);\n  });\n\n  it ('should get the center of full circle before and after draw', function() {\n    // Mock out the arc as if the controller put it there\n    var arc = new Chart.elements.ArcElement({\n      startAngle: 0,\n      endAngle: Math.PI * 2,\n      x: 2,\n      y: 2,\n      innerRadius: 0,\n      outerRadius: 2,\n      options: {\n        spacing: 0,\n        offset: 0,\n        borderWidth: 0\n      }\n    });\n\n    var center = arc.getCenterPoint();\n    expect(center.x).toBeCloseTo(1, 6);\n    expect(center.y).toBeCloseTo(2, 6);\n\n    var ctx = window.createMockContext();\n    arc.draw(ctx);\n\n    center = arc.getCenterPoint();\n    expect(center.x).toBeCloseTo(1, 6);\n    expect(center.y).toBeCloseTo(2, 6);\n  });\n\n  it('should not draw when radius < 0', function() {\n    var ctx = window.createMockContext();\n\n    var arc = new Chart.elements.ArcElement({\n      startAngle: 0,\n      endAngle: Math.PI / 2,\n      x: 0,\n      y: 0,\n      innerRadius: -0.1,\n      outerRadius: Math.sqrt(2),\n      options: {\n        spacing: 0,\n        offset: 0,\n        borderWidth: 0\n      }\n    });\n\n    arc.draw(ctx);\n\n    expect(ctx.getCalls().length).toBe(0);\n\n    arc = new Chart.elements.ArcElement({\n      startAngle: 0,\n      endAngle: Math.PI / 2,\n      x: 0,\n      y: 0,\n      innerRadius: 0,\n      outerRadius: -1,\n      options: {\n        spacing: 0,\n        offset: 0,\n        borderWidth: 0\n      }\n    });\n\n    arc.draw(ctx);\n\n    expect(ctx.getCalls().length).toBe(0);\n  });\n\n  it('should draw when circular: false', function() {\n    var arc = new Chart.elements.ArcElement({\n      startAngle: 0,\n      endAngle: Math.PI * 2,\n      x: 2,\n      y: 2,\n      innerRadius: 0,\n      outerRadius: 2,\n      options: {\n        spacing: 0,\n        offset: 0,\n        borderWidth: 0,\n        scales: {\n          r: {\n            grid: {\n              circular: false,\n            },\n          },\n        },\n        elements: {\n          arc: {\n            circular: false\n          },\n        },\n      }\n    });\n\n    var ctx = window.createMockContext();\n    arc.draw(ctx);\n\n    expect(ctx.getCalls().length).toBeGreaterThan(0);\n  });\n\n  it ('should determine not in range when angle 0', function() {\n    // Mock out the arc as if the controller put it there\n    var arc = new Chart.elements.ArcElement({\n      startAngle: 0,\n      endAngle: 0,\n      x: 0,\n      y: 0,\n      innerRadius: 0,\n      outerRadius: 10,\n      circumference: 0,\n      options: {\n        spacing: 0,\n        offset: 0,\n        borderWidth: 0\n      }\n    });\n\n    var center = arc.getCenterPoint();\n\n    expect(arc.inRange(center.x, 1)).toBe(false);\n  });\n});\n"
  },
  {
    "path": "test/specs/element.bar.tests.js",
    "content": "// Test the bar element\n\ndescribe('Bar element tests', function() {\n  it('Should correctly identify as in range', function() {\n    var bar = new Chart.elements.BarElement({\n      base: 0,\n      width: 4,\n      x: 10,\n      y: 15\n    });\n\n    expect(bar.inRange(10, 15)).toBe(true);\n    expect(bar.inRange(10, 10)).toBe(true);\n    expect(bar.inRange(10, 16)).toBe(false);\n    expect(bar.inRange(5, 5)).toBe(false);\n\n    // Test when the y is below the base (negative bar)\n    var negativeBar = new Chart.elements.BarElement({\n      base: 0,\n      width: 4,\n      x: 10,\n      y: -15\n    });\n\n    expect(negativeBar.inRange(10, -16)).toBe(false);\n    expect(negativeBar.inRange(10, 1)).toBe(false);\n    expect(negativeBar.inRange(10, -5)).toBe(true);\n  });\n\n  it('should get the correct tooltip position', function() {\n    var bar = new Chart.elements.BarElement({\n      base: 0,\n      width: 4,\n      x: 10,\n      y: 15\n    });\n\n    expect(bar.tooltipPosition()).toEqual({\n      x: 10,\n      y: 15,\n    });\n\n    // Test when the y is below the base (negative bar)\n    var negativeBar = new Chart.elements.BarElement({\n      base: -10,\n      width: 4,\n      x: 10,\n      y: -15\n    });\n\n    expect(negativeBar.tooltipPosition()).toEqual({\n      x: 10,\n      y: -15,\n    });\n  });\n\n  it('should get the center', function() {\n    var bar = new Chart.elements.BarElement({\n      base: 0,\n      width: 4,\n      x: 10,\n      y: 15\n    });\n\n    expect(bar.getCenterPoint()).toEqual({x: 10, y: 7.5});\n  });\n});\n"
  },
  {
    "path": "test/specs/element.line.tests.js",
    "content": "// Tests for the line element\ndescribe('Chart.elements.LineElement', function() {\n  describe('auto', jasmine.fixture.specs('element.line'));\n\n  it('should be constructed', function() {\n    var line = new Chart.elements.LineElement({\n      points: [1, 2, 3, 4]\n    });\n\n    expect(line).not.toBe(undefined);\n    expect(line.points).toEqual([1, 2, 3, 4]);\n  });\n\n  it('should not cache path when animations are enabled', function(done) {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          data: [0, -1, 0],\n          label: 'dataset1',\n        }],\n        labels: ['label1', 'label2', 'label3']\n      },\n      options: {\n        animation: {\n          duration: 50,\n          onComplete: () => {\n            expect(chart.getDatasetMeta(0).dataset._path).toBeUndefined();\n            done();\n          }\n        }\n      }\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/element.point.tests.js",
    "content": "describe('Chart.elements.PointElement', function() {\n  describe('auto', jasmine.fixture.specs('element.point'));\n\n  it ('Should correctly identify as in range', function() {\n    // Mock out the point as if we were made by the controller\n    var point = new Chart.elements.PointElement({\n      options: {\n        radius: 2,\n        hitRadius: 3,\n      },\n      x: 10,\n      y: 15\n    });\n\n    expect(point.inRange(10, 15)).toBe(true);\n    expect(point.inRange(10, 10)).toBe(false);\n    expect(point.inRange(10, 5)).toBe(false);\n    expect(point.inRange(5, 5)).toBe(false);\n  });\n\n  it ('should get the correct tooltip position', function() {\n    // Mock out the point as if we were made by the controller\n    var point = new Chart.elements.PointElement({\n      options: {\n        radius: 2,\n        borderWidth: 6,\n      },\n      x: 10,\n      y: 15\n    });\n\n    expect(point.tooltipPosition()).toEqual({\n      x: 10,\n      y: 15\n    });\n  });\n\n  it('should get the correct center point', function() {\n    // Mock out the point as if we were made by the controller\n    var point = new Chart.elements.PointElement({\n      options: {\n        radius: 2,\n      },\n      x: 10,\n      y: 10\n    });\n\n    expect(point.getCenterPoint()).toEqual({x: 10, y: 10});\n  });\n\n  it ('should not draw if skipped', function() {\n    var mockContext = window.createMockContext();\n\n    // Mock out the point as if we were made by the controller\n    var point = new Chart.elements.PointElement({\n      options: {\n        radius: 2,\n        hitRadius: 3,\n      },\n      x: 10,\n      y: 15,\n      skip: true\n    });\n\n    point.draw(mockContext);\n\n    expect(mockContext.getCalls()).toEqual([]);\n  });\n});\n"
  },
  {
    "path": "test/specs/global.defaults.tests.js",
    "content": "describe('Default Configs', function() {\n  describe('Doughnut Chart', function() {\n    it('should return correct legend label objects', function() {\n      var chart = window.acquireChart({\n        type: 'doughnut',\n        data: {\n          labels: ['label1', 'label2', 'label3'],\n          datasets: [{\n            data: [10, 20, NaN],\n            backgroundColor: ['red', 'green', 'blue'],\n            borderWidth: 2,\n            borderColor: '#000'\n          }]\n        },\n      });\n\n      var expectedCommon = {\n        fontColor: '#666',\n        hidden: false,\n        strokeStyle: '#000',\n        textAlign: undefined,\n        lineWidth: 2,\n        pointStyle: undefined,\n        lineDash: [],\n        lineDashOffset: 0,\n        lineJoin: undefined,\n        borderRadius: undefined,\n      };\n\n      var expected = [{\n        text: 'label1',\n        fillStyle: 'red',\n        index: 0,\n        ...expectedCommon,\n      }, {\n        text: 'label2',\n        fillStyle: 'green',\n        index: 1,\n        ...expectedCommon,\n      }, {\n        text: 'label3',\n        fillStyle: 'blue',\n        index: 2,\n        ...expectedCommon,\n      }];\n      expect(chart.legend.legendItems).toEqual(expected);\n    });\n\n    it('should return correct legend label objects with border radius', function() {\n      var chart = window.acquireChart({\n        type: 'doughnut',\n        data: {\n          labels: ['label1'],\n          datasets: [{\n            data: [10],\n            backgroundColor: ['red'],\n            borderWidth: 2,\n            borderColor: '#000',\n            borderDash: [1, 2, 3],\n            borderDashOffset: 1,\n            borderJoinStyle: 'miter',\n            borderRadius: 3,\n          }]\n        },\n        options: {\n          plugins: {\n            legend: {\n              labels: {\n                useBorderRadius: true,\n                borderRadius: 5,\n                textAlign: 'left',\n              }\n            }\n          }\n        }\n      });\n\n      var expected = [{\n        text: 'label1',\n        fillStyle: 'red',\n        index: 0,\n        fontColor: '#666',\n        hidden: false,\n        strokeStyle: '#000',\n        textAlign: 'left',\n        lineWidth: 2,\n        pointStyle: undefined,\n        lineDash: [1, 2, 3],\n        lineDashOffset: 1,\n        lineJoin: 'miter',\n        borderRadius: 5\n      }];\n      expect(chart.legend.legendItems).toEqual(expected);\n    });\n\n    it('should hide the correct arc when a legend item is clicked', function() {\n      var config = Chart.overrides.doughnut;\n      var chart = window.acquireChart({\n        type: 'doughnut',\n        data: {\n          labels: ['label1', 'label2', 'label3'],\n          datasets: [{\n            data: [10, 20, NaN],\n            backgroundColor: ['red', 'green', 'blue'],\n            borderWidth: 2,\n            borderColor: '#000'\n          }]\n        },\n      });\n      spyOn(chart, 'update').and.callThrough();\n\n      var legendItem = chart.legend.legendItems[0];\n      config.plugins.legend.onClick(null, legendItem, chart.legend);\n\n      expect(chart.getDataVisibility(0)).toBe(false);\n      expect(chart.update).toHaveBeenCalled();\n\n      config.plugins.legend.onClick(null, legendItem, chart.legend);\n      expect(chart.getDataVisibility(0)).toBe(true);\n    });\n  });\n\n  describe('Polar Area Chart', function() {\n    it('should return correct legend label objects', function() {\n      var chart = window.acquireChart({\n        type: 'polarArea',\n        data: {\n          labels: ['label1', 'label2', 'label3'],\n          datasets: [{\n            data: [10, 20, NaN],\n            backgroundColor: ['red', 'green', 'blue'],\n            borderWidth: 2,\n            borderColor: '#000'\n          }]\n        },\n      });\n\n      var expected = [{\n        text: 'label1',\n        fillStyle: 'red',\n        fontColor: '#666',\n        hidden: false,\n        index: 0,\n        strokeStyle: '#000',\n        textAlign: undefined,\n        lineWidth: 2,\n        pointStyle: undefined\n      }, {\n        text: 'label2',\n        fillStyle: 'green',\n        fontColor: '#666',\n        hidden: false,\n        index: 1,\n        strokeStyle: '#000',\n        textAlign: undefined,\n        lineWidth: 2,\n        pointStyle: undefined\n      }, {\n        text: 'label3',\n        fillStyle: 'blue',\n        fontColor: '#666',\n        hidden: false,\n        index: 2,\n        strokeStyle: '#000',\n        textAlign: undefined,\n        lineWidth: 2,\n        pointStyle: undefined\n      }];\n      expect(chart.legend.legendItems).toEqual(expected);\n    });\n\n    it('should hide the correct arc when a legend item is clicked', function() {\n      var config = Chart.overrides.polarArea;\n      var chart = window.acquireChart({\n        type: 'polarArea',\n        data: {\n          labels: ['label1', 'label2', 'label3'],\n          datasets: [{\n            data: [10, 20, NaN],\n            backgroundColor: ['red', 'green', 'blue'],\n            borderWidth: 2,\n            borderColor: '#000'\n          }]\n        },\n      });\n      spyOn(chart, 'update').and.callThrough();\n\n      var legendItem = chart.legend.legendItems[0];\n      config.plugins.legend.onClick(null, legendItem, chart.legend);\n\n      expect(chart.getDataVisibility(0)).toBe(false);\n      expect(chart.update).toHaveBeenCalled();\n\n      config.plugins.legend.onClick(null, legendItem, chart.legend);\n      expect(chart.getDataVisibility(0)).toBe(true);\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/global.namespace.tests.js",
    "content": "describe('Chart namespace', function() {\n  describe('Chart', function() {\n    it('should a function (constructor)', function() {\n      expect(Chart instanceof Function).toBeTruthy();\n    });\n    it('should define \"core\" properties', function() {\n      expect(Chart instanceof Function).toBeTruthy();\n      expect(Chart.Animation instanceof Object).toBeTruthy();\n      expect(Chart.Animations instanceof Object).toBeTruthy();\n      expect(Chart.defaults instanceof Object).toBeTruthy();\n      expect(Chart.Element instanceof Object).toBeTruthy();\n      expect(Chart.Interaction instanceof Object).toBeTruthy();\n      expect(Chart.layouts instanceof Object).toBeTruthy();\n\n      expect(Chart.platforms.BasePlatform instanceof Function).toBeTruthy();\n      expect(Chart.platforms.BasicPlatform instanceof Function).toBeTruthy();\n      expect(Chart.platforms.DomPlatform instanceof Function).toBeTruthy();\n\n      expect(Chart.registry instanceof Object).toBeTruthy();\n      expect(Chart.Scale instanceof Object).toBeTruthy();\n      expect(Chart.Ticks instanceof Object).toBeTruthy();\n    });\n  });\n\n  describe('Chart.elements', function() {\n    it('should contains \"elements\" classes', function() {\n      expect(Chart.elements.ArcElement instanceof Function).toBeTruthy();\n      expect(Chart.elements.BarElement instanceof Function).toBeTruthy();\n      expect(Chart.elements.LineElement instanceof Function).toBeTruthy();\n      expect(Chart.elements.PointElement instanceof Function).toBeTruthy();\n    });\n  });\n\n  describe('Chart.helpers', function() {\n    it('should be an object', function() {\n      expect(Chart.helpers instanceof Object).toBeTruthy();\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/helpers.canvas.tests.js",
    "content": "'use strict';\n\ndescribe('Chart.helpers.canvas', function() {\n  describe('auto', jasmine.fixture.specs('helpers'));\n\n  var helpers = Chart.helpers;\n\n  describe('clearCanvas', function() {\n    it('should clear the chart canvas', function() {\n      var chart = acquireChart({}, {\n        canvas: {\n          style: 'width: 150px; height: 245px'\n        }\n      });\n\n      spyOn(chart.ctx, 'clearRect');\n\n      helpers.clearCanvas(chart.canvas, chart.ctx);\n\n      expect(chart.ctx.clearRect.calls.count()).toBe(1);\n      expect(chart.ctx.clearRect.calls.first().object).toBe(chart.ctx);\n      expect(chart.ctx.clearRect.calls.first().args).toEqual([0, 0, 150, 245]);\n    });\n\n    it('should not throw error when chart is null', function() {\n      function createAndClearChart() {\n        var chart = acquireChart({}, {\n          canvas: null\n        });\n        // explicitly set canvas and ctx to null since setting it in acquireChart doesn't do anything\n        chart.canvas = null;\n        chart.ctx = null;\n\n        helpers.clearCanvas(chart.canvas, chart.ctx);\n      }\n\n      expect(createAndClearChart).not.toThrow();\n    });\n  });\n\n  describe('isPointInArea', function() {\n    it('should return true when no area is provided', function() {\n      expect(helpers._isPointInArea({x: 1, y: 1})).toBe(true);\n    });\n    it('should determine if a point is in the area', function() {\n      var isPointInArea = helpers._isPointInArea;\n      var area = {left: 0, top: 0, right: 512, bottom: 256};\n\n      expect(isPointInArea({x: 0, y: 0}, area)).toBe(true);\n      expect(isPointInArea({x: -1e-12, y: -1e-12}, area)).toBe(true);\n      expect(isPointInArea({x: 512, y: 256}, area)).toBe(true);\n      expect(isPointInArea({x: 512 + 1e-12, y: 256 + 1e-12}, area)).toBe(true);\n      expect(isPointInArea({x: -0.5, y: 0}, area)).toBe(false);\n      expect(isPointInArea({x: 0, y: 256.5}, area)).toBe(false);\n    });\n  });\n\n  it('should return the width of the longest text in an Array and 2D Array', function() {\n    var context = window.createMockContext();\n    var font = \"normal 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif\";\n    var arrayOfThings1D = ['FooBar', 'Bar'];\n    var arrayOfThings2D = [['FooBar_1', 'Bar_2'], 'Foo_1'];\n\n\n    // Regardless 'FooBar' is the longest label it should return (characters * 10)\n    expect(helpers._longestText(context, font, arrayOfThings1D, {})).toEqual(60);\n    expect(helpers._longestText(context, font, arrayOfThings2D, {})).toEqual(80);\n    // We check to make sure we made the right calls to the canvas.\n    expect(context.getCalls()).toEqual([{\n      name: 'save',\n      args: []\n    }, {\n      name: 'setFont',\n      args: [\"normal 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif\"],\n    }, {\n      name: 'measureText',\n      args: ['FooBar']\n    }, {\n      name: 'measureText',\n      args: ['Bar']\n    }, {\n      name: 'restore',\n      args: []\n    }, {\n      name: 'save',\n      args: []\n    }, {\n      name: 'setFont',\n      args: [\"normal 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif\"],\n    }, {\n      name: 'measureText',\n      args: ['FooBar_1']\n    }, {\n      name: 'measureText',\n      args: ['Bar_2']\n    }, {\n      name: 'measureText',\n      args: ['Foo_1']\n    }, {\n      name: 'restore',\n      args: []\n    }]);\n  });\n\n  it('compare text with current longest and update', function() {\n    var context = window.createMockContext();\n    var data = {};\n    var gc = [];\n    var longest = 70;\n\n    expect(helpers._measureText(context, data, gc, longest, 'foobar')).toEqual(70);\n    expect(helpers._measureText(context, data, gc, longest, 'foobar_')).toEqual(70);\n    expect(helpers._measureText(context, data, gc, longest, 'foobar_1')).toEqual(80);\n    // We check to make sure we made the right calls to the canvas.\n    expect(context.getCalls()).toEqual([{\n      name: 'measureText',\n      args: ['foobar']\n    }, {\n      name: 'measureText',\n      args: ['foobar_']\n    }, {\n      name: 'measureText',\n      args: ['foobar_1']\n    }]);\n  });\n\n  describe('renderText', function() {\n    it('should render multiple lines of text', function() {\n      var context = window.createMockContext();\n      var font = {string: '12px arial', lineHeight: 20};\n      helpers.renderText(context, ['foo', 'foo2'], 0, 0, font);\n\n      expect(context.getCalls()).toEqual([{\n        name: 'save',\n        args: [],\n      }, {\n        name: 'setFont',\n        args: ['12px arial'],\n      }, {\n        name: 'fillText',\n        args: ['foo', 0, 0, undefined],\n      }, {\n        name: 'fillText',\n        args: ['foo2', 0, 20, undefined],\n      }, {\n        name: 'restore',\n        args: [],\n      }]);\n    });\n\n    it('should accept the text maxWidth', function() {\n      var context = window.createMockContext();\n      var font = {string: '12px arial', lineHeight: 20};\n      helpers.renderText(context, 'foo', 0, 0, font, {maxWidth: 30});\n      expect(context.getCalls()).toEqual([{\n        name: 'save',\n        args: [],\n      }, {\n        name: 'setFont',\n        args: ['12px arial'],\n      }, {\n        name: 'fillText',\n        args: ['foo', 0, 0, 30],\n      }, {\n        name: 'restore',\n        args: [],\n      }]);\n    });\n\n    it('should underline the text', function() {\n      var context = window.createMockContext();\n      var font = {string: '12px arial', lineHeight: 20};\n      helpers.renderText(context, 'foo', 0, 0, font, {decorationWidth: 3, underline: true});\n\n      expect(context.getCalls()).toEqual([{\n        name: 'save',\n        args: [],\n      }, {\n        name: 'setFont',\n        args: ['12px arial'],\n      }, {\n        name: 'fillText',\n        args: ['foo', 0, 0, undefined],\n      }, {\n        name: 'measureText',\n        args: ['foo'],\n      }, {\n        name: 'setStrokeStyle',\n        args: [null],\n      }, {\n        name: 'beginPath',\n        args: [],\n      }, {\n        name: 'setLineWidth',\n        args: [3],\n      }, {\n        name: 'moveTo',\n        args: [-15, 8],\n      }, {\n        name: 'lineTo',\n        args: [25, 8],\n      }, {\n        name: 'stroke',\n        args: [],\n      }, {\n        name: 'restore',\n        args: [],\n      }]);\n    });\n\n    it('should strikethrough the text', function() {\n      var context = window.createMockContext();\n      var font = {string: '12px arial', lineHeight: 20};\n      helpers.renderText(context, 'foo', 0, 0, font, {strikethrough: true});\n\n      expect(context.getCalls()).toEqual([{\n        name: 'save',\n        args: [],\n      }, {\n        name: 'setFont',\n        args: ['12px arial'],\n      }, {\n        name: 'fillText',\n        args: ['foo', 0, 0, undefined],\n      }, {\n        name: 'measureText',\n        args: ['foo'],\n      }, {\n        name: 'setStrokeStyle',\n        args: [null],\n      }, {\n        name: 'beginPath',\n        args: [],\n      }, {\n        name: 'setLineWidth',\n        args: [2],\n      }, {\n        name: 'moveTo',\n        args: [-15, 2],\n      }, {\n        name: 'lineTo',\n        args: [25, 2],\n      }, {\n        name: 'stroke',\n        args: [],\n      }, {\n        name: 'restore',\n        args: [],\n      }]);\n    });\n\n    it('should set the fill style if supplied', function() {\n      var context = window.createMockContext();\n      var font = {string: '12px arial', lineHeight: 20};\n      helpers.renderText(context, 'foo', 0, 0, font, {color: 'red'});\n\n      expect(context.getCalls()).toEqual([{\n        name: 'save',\n        args: [],\n      }, {\n        name: 'setFont',\n        args: ['12px arial'],\n      }, {\n        name: 'setFillStyle',\n        args: ['red'],\n      }, {\n        name: 'fillText',\n        args: ['foo', 0, 0, undefined],\n      }, {\n        name: 'restore',\n        args: [],\n      }]);\n    });\n\n    it('should set the stroke style if supplied', function() {\n      var context = window.createMockContext();\n      var font = {string: '12px arial', lineHeight: 20};\n      helpers.renderText(context, 'foo', 0, 0, font, {strokeColor: 'red', strokeWidth: 2});\n      expect(context.getCalls()).toEqual([{\n        name: 'save',\n        args: [],\n      }, {\n        name: 'setFont',\n        args: ['12px arial'],\n      }, {\n        name: 'setStrokeStyle',\n        args: ['red'],\n      }, {\n        name: 'setLineWidth',\n        args: [2],\n      }, {\n        name: 'strokeText',\n        args: ['foo', 0, 0, undefined],\n      }, {\n        name: 'fillText',\n        args: ['foo', 0, 0, undefined],\n      }, {\n        name: 'restore',\n        args: [],\n      }]);\n    });\n\n    it('should set the text alignment', function() {\n      var context = window.createMockContext();\n      var font = {string: '12px arial', lineHeight: 20};\n      helpers.renderText(context, 'foo', 0, 0, font, {textAlign: 'left', textBaseline: 'middle'});\n\n      expect(context.getCalls()).toEqual([{\n        name: 'save',\n        args: [],\n      }, {\n        name: 'setFont',\n        args: ['12px arial'],\n      }, {\n        name: 'setTextAlign',\n        args: ['left'],\n      }, {\n        name: 'setTextBaseline',\n        args: ['middle'],\n      }, {\n        name: 'fillText',\n        args: ['foo', 0, 0, undefined],\n      }, {\n        name: 'restore',\n        args: [],\n      }]);\n    });\n\n    it('should translate and rotate text', function() {\n      var context = window.createMockContext();\n      var font = {string: '12px arial', lineHeight: 20};\n      helpers.renderText(context, 'foo', 0, 0, font, {rotation: 90, translation: [10, 20]});\n\n      expect(context.getCalls()).toEqual([{\n        name: 'save',\n        args: [],\n      }, {\n        name: 'setFont',\n        args: ['12px arial'],\n      }, {\n        name: 'translate',\n        args: [10, 20],\n      }, {\n        name: 'rotate',\n        args: [90],\n      }, {\n        name: 'fillText',\n        args: ['foo', 0, 0, undefined],\n      }, {\n        name: 'restore',\n        args: [],\n      }]);\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/helpers.collection.tests.js",
    "content": "const {_filterBetween, _lookup, _lookupByKey, _rlookupByKey} = Chart.helpers;\n\ndescribe('helpers.collection', function() {\n  it('Should do binary search', function() {\n    const data = [0, 2, 6, 9];\n    expect(_lookup(data, 0)).toEqual({lo: 0, hi: 1});\n    expect(_lookup(data, 1)).toEqual({lo: 0, hi: 1});\n    expect(_lookup(data, 3)).toEqual({lo: 1, hi: 2});\n    expect(_lookup(data, 6)).toEqual({lo: 1, hi: 2});\n    expect(_lookup(data, 9)).toEqual({lo: 2, hi: 3});\n  });\n\n  it('Should do binary search by key', function() {\n    const data = [{x: 0}, {x: 2}, {x: 6}, {x: 9}];\n    expect(_lookupByKey(data, 'x', 0)).toEqual({lo: 0, hi: 1});\n    expect(_lookupByKey(data, 'x', 1)).toEqual({lo: 0, hi: 1});\n    expect(_lookupByKey(data, 'x', 3)).toEqual({lo: 1, hi: 2});\n    expect(_lookupByKey(data, 'x', 6)).toEqual({lo: 1, hi: 2});\n    expect(_lookupByKey(data, 'x', 9)).toEqual({lo: 2, hi: 3});\n  });\n\n  it('Should do binary search by key with last', () => {\n    expect(_lookupByKey([{x: 0}, {x: 2}, {x: 6}, {x: 9}], 'x', 25, true)).toEqual({lo: 2, hi: 3});\n    expect(_lookupByKey([{x: 0}, {x: 2}, {x: 9}, {x: 9}], 'x', 25, true)).toEqual({lo: 2, hi: 3});\n    expect(_lookupByKey([{x: 0}, {x: 2}, {x: 9}, {x: 9}, {x: 22}], 'x', 25, true)).toEqual({lo: 3, hi: 4});\n    expect(_lookupByKey([{x: 0}, {x: 2}, {x: 25}, {x: 28}], 'x', 25, true)).toEqual({lo: 1, hi: 2});\n    expect(_lookupByKey([{x: 0}, {x: 2}, {x: 25}, {x: 25}], 'x', 25, true)).toEqual({lo: 2, hi: 3});\n    expect(_lookupByKey([{x: 0}, {x: 2}, {x: 25}, {x: 25}, {x: 28}], 'x', 25, true)).toEqual({lo: 2, hi: 3});\n    expect(_lookupByKey([{x: 0}, {x: 2}, {x: 25}, {x: 25}, {x: 25}, {x: 28}, {x: 29}], 'x', 25, true)).toEqual({lo: 3, hi: 4});\n  });\n\n  it('Should do reverse binary search by key', function() {\n    const data = [{x: 10}, {x: 7}, {x: 3}, {x: 0}];\n    expect(_rlookupByKey(data, 'x', 0)).toEqual({lo: 2, hi: 3});\n    expect(_rlookupByKey(data, 'x', 3)).toEqual({lo: 2, hi: 3});\n    expect(_rlookupByKey(data, 'x', 5)).toEqual({lo: 1, hi: 2});\n    expect(_rlookupByKey(data, 'x', 8)).toEqual({lo: 0, hi: 1});\n    expect(_rlookupByKey(data, 'x', 10)).toEqual({lo: 0, hi: 1});\n  });\n\n  it('Should filter a sorted array', function() {\n    expect(_filterBetween([1, 2, 3, 4, 5, 6, 7, 8, 9], 5, 8)).toEqual([5, 6, 7, 8]);\n    expect(_filterBetween([1], 1, 1)).toEqual([1]);\n    expect(_filterBetween([1583049600000], 1584816327553, 1585680327553)).toEqual([]);\n  });\n});\n"
  },
  {
    "path": "test/specs/helpers.color.tests.js",
    "content": "const {color, getHoverColor} = Chart.helpers;\n\ndescribe('Color helper', function() {\n  function isColorInstance(obj) {\n    return typeof obj === 'object' && obj.valid;\n  }\n\n  it('should return a color when called with a color', function() {\n    expect(isColorInstance(color('rgb(1, 2, 3)'))).toBe(true);\n  });\n});\n\ndescribe('Background hover color helper', function() {\n  it('should return a modified version of color when called with a color', function() {\n    var originalColorRGB = 'rgb(70, 191, 189)';\n\n    expect(getHoverColor('#46BFBD')).not.toEqual(originalColorRGB);\n  });\n});\n\ndescribe('color and getHoverColor helpers', function() {\n  it('should return a CanvasPattern when called with a CanvasPattern', function(done) {\n    var dots = new Image();\n    dots.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAMAAAAolt3jAAAAD1BMVEUAAAD///////////////+PQt5oAAAABXRSTlMAHlFhZsfk/BEAAAAqSURBVHgBY2BgZGJmYmSAAUYWEIDzmcBcJhiXGcxlRpPFrhdmMiqgvX0AcGIBEUAo6UAAAAAASUVORK5CYII=';\n    dots.onload = function() {\n      var chartContext = document.createElement('canvas').getContext('2d');\n      var patternCanvas = document.createElement('canvas');\n      var patternContext = patternCanvas.getContext('2d');\n      var pattern = patternContext.createPattern(dots, 'repeat');\n      patternContext.fillStyle = pattern;\n      var chartPattern = chartContext.createPattern(patternCanvas, 'repeat');\n\n      expect(color(chartPattern) instanceof CanvasPattern).toBe(true);\n      expect(getHoverColor(chartPattern) instanceof CanvasPattern).toBe(true);\n\n      done();\n    };\n  });\n\n  it('should return a CanvasGradient when called with a CanvasGradient', function() {\n    var context = document.createElement('canvas').getContext('2d');\n    var gradient = context.createLinearGradient(0, 1, 2, 3);\n\n    expect(color(gradient) instanceof CanvasGradient).toBe(true);\n    expect(getHoverColor(gradient) instanceof CanvasGradient).toBe(true);\n  });\n});\n"
  },
  {
    "path": "test/specs/helpers.config.tests.js",
    "content": "describe('Chart.helpers.config', function() {\n  const {getHoverColor, _createResolver, _attachContext} = Chart.helpers;\n\n  describe('_createResolver', function() {\n    it('should resolve to raw values', function() {\n      const defaults = {\n        color: 'red',\n        backgroundColor: 'green',\n        hoverColor: (ctx, options) => getHoverColor(options.color)\n      };\n      const options = {\n        color: 'blue'\n      };\n      const resolver = _createResolver([options, defaults]);\n      expect(resolver.color).toEqual('blue');\n      expect(resolver.backgroundColor).toEqual('green');\n      expect(resolver.hoverColor).toEqual(defaults.hoverColor);\n    });\n\n    it('should resolve to parent scopes, when _fallback is true', function() {\n      const descriptors = {\n        _fallback: true\n      };\n      const defaults = {\n        root: true,\n        sub: {\n          child: true\n        }\n      };\n      const options = {\n        child: 'sub default comes before this',\n        opt: 'opt'\n      };\n      const resolver = _createResolver([options, defaults, descriptors]);\n      const sub = resolver.sub;\n      expect(sub.root).toEqual(true);\n      expect(sub.child).toEqual(true);\n      expect(sub.opt).toEqual('opt');\n    });\n\n    it('should support overriding options', function() {\n      const defaults = {\n        option1: 'defaults1',\n        option2: 'defaults2',\n        option3: 'defaults3',\n      };\n      const options = {\n        option1: 'options1',\n        option2: 'options2'\n      };\n      const overrides = {\n        option1: 'override1'\n      };\n      const resolver = _createResolver([options, defaults]);\n      expect(resolver).toEqualOptions({\n        option1: 'options1',\n        option2: 'options2',\n        option3: 'defaults3'\n      });\n      expect(resolver.override(overrides)).toEqualOptions({\n        option1: 'override1',\n        option2: 'options2',\n        option3: 'defaults3'\n      });\n    });\n\n    it('should support common object methods', function() {\n      const defaults = {\n        option1: 'defaults'\n      };\n      class Options {\n        constructor() {\n          this.option2 = 'options';\n        }\n        get getter() {\n          return 'options getter';\n        }\n      }\n      const options = new Options();\n\n      const resolver = _createResolver([options, defaults]);\n\n      expect(Object.prototype.hasOwnProperty.call(resolver, 'option2')).toBeTrue();\n\n      expect(Object.prototype.hasOwnProperty.call(resolver, 'option1')).toBeFalse();\n      expect(Object.prototype.hasOwnProperty.call(resolver, 'getter')).toBeFalse();\n      expect(Object.prototype.hasOwnProperty.call(resolver, 'nonexistent')).toBeFalse();\n\n      expect(Object.keys(resolver)).toEqual(['option2']);\n      expect(Object.getOwnPropertyNames(resolver)).toEqual(['option2', 'option1']);\n\n      expect('option2' in resolver).toBeTrue();\n      expect('option1' in resolver).toBeTrue();\n      expect('getter' in resolver).toBeFalse();\n      expect('nonexistent' in resolver).toBeFalse();\n\n      expect(resolver instanceof Options).toBeTrue();\n\n      expect(resolver.getter).toEqual('options getter');\n    });\n\n    it('should not fail on when options are frozen', function() {\n      function create() {\n        const defaults = Object.freeze({default: true});\n        const options = Object.freeze({value: true});\n        return _createResolver([options, defaults]);\n      }\n      expect(create).not.toThrow();\n    });\n\n    describe('_fallback', function() {\n      it('should follow simple _fallback', function() {\n        const defaults = {\n          interaction: {\n            mode: 'test',\n            priority: 'fall'\n          },\n          hover: {\n            _fallback: 'interaction',\n            priority: 'main'\n          }\n        };\n        const options = {\n          interaction: {\n            a: 1\n          },\n          hover: {\n            b: 2\n          }\n        };\n        const resolver = _createResolver([options, defaults]);\n        expect(resolver.hover).toEqualOptions({\n          mode: 'test',\n          priority: 'main',\n          a: 1,\n          b: 2\n        });\n      });\n\n      it('should support _fallback as function', function() {\n        const descriptors = {\n          _fallback: (prop, value) => prop === 'hover' && value.shouldFall && 'interaction',\n        };\n        const defaults = {\n          interaction: {\n            mode: 'test',\n            priority: 'fall'\n          },\n          hover: {\n            priority: 'main'\n          }\n        };\n        const options = {\n          interaction: {\n            a: 1\n          },\n          hover: {\n            shouldFall: true,\n            b: 2\n          }\n        };\n        const resolver = _createResolver([options, defaults, descriptors]);\n        expect(resolver.hover).toEqualOptions({\n          mode: 'test',\n          priority: 'main',\n          a: 1,\n          b: 2\n        });\n      });\n\n      it('should not fallback by default', function() {\n        const defaults = {\n          hover: {\n            a: 'defaults.hover'\n          },\n          controllers: {\n            y: 'defaults.controllers',\n            bar: {\n              z: 'defaults.controllers.bar',\n              hover: {\n                b: 'defaults.controllers.bar.hover'\n              }\n            }\n          },\n          x: 'defaults root'\n        };\n        const options = {\n          x: 'options',\n          hover: {\n            c: 'options.hover',\n            sub: {\n              f: 'options.hover.sub'\n            }\n          },\n          controllers: {\n            y: 'options.controllers',\n            bar: {\n              z: 'options.controllers.bar',\n              hover: {\n                d: 'options.controllers.bar.hover',\n                sub: {\n                  e: 'options.controllers.bar.hover.sub'\n                }\n              }\n            }\n          }\n        };\n        const resolver = _createResolver([options, options.controllers.bar, options.controllers, defaults.controllers.bar, defaults.controllers, defaults]);\n        expect(resolver.hover).toEqualOptions({\n          a: 'defaults.hover',\n          b: 'defaults.controllers.bar.hover',\n          c: 'options.hover',\n          d: 'options.controllers.bar.hover',\n          e: undefined,\n          f: undefined,\n          x: undefined,\n          y: undefined,\n          z: undefined\n        });\n        expect(resolver.hover.sub).toEqualOptions({\n          a: undefined,\n          b: undefined,\n          c: undefined,\n          d: undefined,\n          e: 'options.controllers.bar.hover.sub',\n          f: 'options.hover.sub',\n          x: undefined,\n          y: undefined,\n          z: undefined\n        });\n      });\n\n      it('should fallback to specific scope', function() {\n        const defaults = {\n          hover: {\n            _fallback: 'hover',\n            a: 'defaults.hover'\n          },\n          controllers: {\n            y: 'defaults.controllers',\n            bar: {\n              z: 'defaults.controllers.bar',\n              hover: {\n                b: 'defaults.controllers.bar.hover'\n              }\n            }\n          },\n          x: 'defaults root'\n        };\n        const options = {\n          x: 'options',\n          hover: {\n            c: 'options.hover',\n            sub: {\n              f: 'options.hover.sub'\n            }\n          },\n          controllers: {\n            y: 'options.controllers',\n            bar: {\n              z: 'options.controllers.bar',\n              hover: {\n                d: 'options.controllers.bar.hover',\n                sub: {\n                  e: 'options.controllers.bar.hover.sub'\n                }\n              }\n            }\n          }\n        };\n        const resolver = _createResolver([options, options.controllers.bar, options.controllers, defaults.controllers.bar, defaults.controllers, defaults]);\n        expect(resolver.hover).toEqualOptions({\n          a: 'defaults.hover',\n          b: 'defaults.controllers.bar.hover',\n          c: 'options.hover',\n          d: 'options.controllers.bar.hover',\n          e: undefined,\n          f: undefined,\n          x: undefined,\n          y: undefined,\n          z: undefined\n        });\n        expect(resolver.hover.sub).toEqualOptions({\n          a: 'defaults.hover',\n          b: 'defaults.controllers.bar.hover',\n          c: 'options.hover',\n          d: 'options.controllers.bar.hover',\n          e: 'options.controllers.bar.hover.sub',\n          f: 'options.hover.sub',\n          x: undefined,\n          y: undefined,\n          z: undefined\n        });\n      });\n\n      it('should fallback through multiple routes', function() {\n        const descriptors = {\n          _fallback: 'level1',\n          level1: {\n            _fallback: 'root'\n          },\n          level2: {\n            _fallback: 'level1'\n          }\n        };\n        const defaults = {\n          root: {\n            a: 'root'\n          },\n          level1: {\n            b: 'level1',\n          },\n          level2: {\n            level1: {\n              g: 'level2.level1'\n            },\n            c: 'level2',\n            sublevel1: {\n              d: 'sublevel1'\n            },\n            sublevel2: {\n              e: 'sublevel2',\n              level1: {\n                f: 'sublevel2.level1'\n              }\n            }\n          }\n        };\n        const resolver = _createResolver([defaults, descriptors]);\n        expect(resolver.level1).toEqualOptions({\n          a: 'root',\n          b: 'level1',\n          c: undefined\n        });\n        expect(resolver.level2).toEqualOptions({\n          a: 'root',\n          b: 'level1',\n          c: 'level2',\n          d: undefined\n        });\n        expect(resolver.level2.sublevel1).toEqualOptions({\n          a: 'root',\n          b: 'level1',\n          c: undefined,\n          d: 'sublevel1',\n          e: undefined,\n          f: undefined,\n          g: 'level2.level1'\n        });\n        expect(resolver.level2.sublevel2).toEqualOptions({\n          a: 'root',\n          b: 'level1',\n          c: undefined,\n          d: undefined,\n          e: 'sublevel2',\n          f: undefined,\n          g: 'level2.level1'\n        });\n        expect(resolver.level2.sublevel2.level1).toEqualOptions({\n          a: 'root',\n          b: 'level1',\n          c: undefined,\n          d: undefined,\n          e: undefined,\n          f: 'sublevel2.level1',\n          g: undefined // same key only included from immediate parents and root\n        });\n      });\n\n      it('should fallback through multiple routes (animations)', function() {\n        const descriptors = {\n          animations: {\n            _fallback: 'animation',\n          },\n        };\n        const defaults = {\n          animation: {\n            duration: 1000,\n            easing: 'easeInQuad'\n          },\n          animations: {\n            colors: {\n              properties: ['color', 'backgroundColor'],\n              type: 'color'\n            },\n            numbers: {\n              properties: ['x', 'y'],\n              type: 'number'\n            }\n          },\n          transitions: {\n            resize: {\n              animation: {\n                duration: 0\n              }\n            },\n            show: {\n              animation: {\n                duration: 400\n              },\n              animations: {\n                colors: {\n                  from: 'transparent'\n                }\n              }\n            }\n          }\n        };\n        const options = {\n          animation: {\n            easing: 'linear'\n          },\n          animations: {\n            colors: {\n              properties: ['color', 'borderColor', 'backgroundColor'],\n            },\n            duration: {\n              properties: ['a', 'b'],\n              type: 'boolean'\n            }\n          }\n        };\n\n        const show = _createResolver([options, defaults.transitions.show, defaults, descriptors]);\n        expect(show.animation).toEqualOptions({\n          duration: 400,\n          easing: 'linear'\n        });\n        expect(show.animations.colors._scopes).toEqual([\n          options.animations.colors,\n          defaults.transitions.show.animations.colors,\n          defaults.animations.colors,\n          options.animation,\n          defaults.transitions.show.animation,\n          defaults.animation\n        ]);\n        expect(show.animations.colors).toEqualOptions({\n          duration: 400,\n          from: 'transparent',\n          easing: 'linear',\n          type: 'color',\n          properties: ['color', 'borderColor', 'backgroundColor']\n        });\n        expect(show.animations.duration).toEqualOptions({\n          duration: 400,\n          easing: 'linear',\n          type: 'boolean',\n          properties: ['a', 'b']\n        });\n        expect(Object.getOwnPropertyNames(show.animations).filter(k => Chart.helpers.isObject(show.animations[k]))).toEqual([\n          'colors',\n          'duration',\n          'numbers',\n        ]);\n        const def = _createResolver([options, defaults, descriptors]);\n        expect(def.animation).toEqualOptions({\n          duration: 1000,\n          easing: 'linear'\n        });\n        expect(def.animations.colors._scopes).toEqual([\n          options.animations.colors,\n          defaults.animations.colors,\n          options.animation,\n          defaults.animation\n        ]);\n        expect(def.animations.colors).toEqualOptions({\n          duration: 1000,\n          easing: 'linear',\n          type: 'color',\n          properties: ['color', 'borderColor', 'backgroundColor']\n        });\n        expect(def.animations.duration).toEqualOptions({\n          duration: 1000,\n          easing: 'linear',\n          type: 'boolean',\n          properties: ['a', 'b']\n        });\n        expect(Object.getOwnPropertyNames(def.animations).filter(k => Chart.helpers.isObject(show.animations[k]))).toEqual([\n          'colors',\n          'duration',\n          'numbers',\n        ]);\n      });\n    });\n    describe('setting values', function() {\n      it('should set values to first scope', function() {\n        const defaults = {\n          value: true\n        };\n        const options = {};\n        const resolver = _createResolver([options, defaults]);\n        resolver.value = false;\n        expect(options.value).toBeFalse();\n        expect(defaults.value).toBeTrue();\n        expect(resolver.value).toBeFalse();\n      });\n\n      it('should set values of sub-objects to first scope', function() {\n        const defaults = {\n          sub: {\n            value: true\n          }\n        };\n        const options = {};\n        const resolver = _createResolver([options, defaults]);\n        resolver.sub.value = false;\n        expect(options.sub.value).toBeFalse();\n        expect(defaults.sub.value).toBeTrue();\n        expect(resolver.sub.value).toBeFalse();\n      });\n\n      it('should throw when setting a value and options is frozen', function() {\n        const defaults = Object.freeze({default: true});\n        const options = Object.freeze({value: true});\n        const resolver = _createResolver([options, defaults]);\n        function set() {\n          resolver.value = false;\n        }\n        expect(set).toThrow();\n      });\n    });\n  });\n\n  describe('_attachContext', function() {\n    it('should resolve to final values', function() {\n      const defaults = {\n        color: 'red',\n        backgroundColor: 'green',\n        hoverColor: (ctx, options) => getHoverColor(options.color)\n      };\n      const options = {\n        color: ['white', 'blue']\n      };\n      const resolver = _createResolver([options, defaults]);\n      const opts = _attachContext(resolver, {index: 1});\n      expect(opts.color).toEqual('blue');\n      expect(opts.backgroundColor).toEqual('green');\n      expect(opts.hoverColor).toEqual(getHoverColor('blue'));\n    });\n\n    it('should thrown on recursion', function() {\n      const options = {\n        foo: (ctx, opts) => opts.bar,\n        bar: (ctx, opts) => opts.xyz,\n        xyz: (ctx, opts) => opts.foo\n      };\n      const resolver = _createResolver([options]);\n      const opts = _attachContext(resolver, {test: true});\n      expect(function() {\n        return opts.foo;\n      }).toThrowError('Recursion detected: foo->bar->xyz->foo');\n    });\n\n    it('should support scriptable options in subscopes', function() {\n      const defaults = {\n        elements: {\n          point: {\n            backgroundColor: 'red'\n          }\n        }\n      };\n      const options = {\n        elements: {\n          point: {\n            borderColor: (ctx, opts) => getHoverColor(opts.backgroundColor)\n          }\n        }\n      };\n      const resolver = _createResolver([options, defaults]);\n      const opts = _attachContext(resolver, {});\n      expect(opts.elements.point.borderColor).toEqual(getHoverColor('red'));\n      expect(opts.elements.point.backgroundColor).toEqual('red');\n    });\n\n    it('same resolver should be usable with multiple contexts', function() {\n      const defaults = {\n        animation: {\n          delay: 10\n        }\n      };\n      const options = {\n        animation: (ctx) => ctx.index === 0 ? {duration: 1000} : {duration: 500}\n      };\n      const resolver = _createResolver([options, defaults]);\n      const opts1 = _attachContext(resolver, {index: 0});\n      const opts2 = _attachContext(resolver, {index: 1});\n\n      expect(opts1.animation.duration).toEqual(1000);\n      expect(opts1.animation.delay).toEqual(10);\n\n      expect(opts2.animation.duration).toEqual(500);\n      expect(opts2.animation.delay).toEqual(10);\n    });\n\n    it('should fall back from object returned from scriptable option', function() {\n      const defaults = {\n        mainScope: {\n          main: true,\n          subScope: {\n            sub: true\n          }\n        }\n      };\n      const options = {\n        mainScope: (ctx) => ({\n          mainTest: ctx.contextValue,\n          subScope: {\n            subText: 'a'\n          }\n        })\n      };\n      const opts = _attachContext(_createResolver([options, defaults]), {contextValue: 'test'});\n      expect(opts.mainScope).toEqualOptions({\n        main: true,\n        mainTest: 'test',\n        subScope: {\n          sub: true,\n          subText: 'a'\n        }\n      });\n    });\n\n    it('should resolve array of non-indexable objects properly', function() {\n      const defaults = {\n        label: {\n          value: 42,\n          text: (ctx) => ctx.text\n        },\n        labels: {\n          _fallback: 'label',\n          _indexable: false\n        }\n      };\n\n      const options = {\n        labels: [{text: 'a'}, {text: 'b'}, {value: 1}]\n      };\n      const opts = _attachContext(_createResolver([options, defaults]), {text: 'context'});\n      expect(opts).toEqualOptions({\n        labels: [\n          {\n            text: 'a',\n            value: 42\n          },\n          {\n            text: 'b',\n            value: 42\n          },\n          {\n            text: 'context',\n            value: 1\n          }\n        ]\n      });\n    });\n\n    it('should call _fallback with proper value from array when descriptor is object', function() {\n      const spy = jasmine.createSpy('fallback');\n      const descriptors = {\n        items: {\n          _fallback: spy\n        }\n      };\n      const options = {\n        items: [{test: true}]\n      };\n      const resolver = _createResolver([options, descriptors]);\n      const opts = _attachContext(resolver, {dymmy: true});\n      const item0 = opts.items[0];\n      expect(item0.test).toEqual(true);\n      expect(spy).toHaveBeenCalledWith('items', options.items[0]);\n    });\n\n    it('should call _fallback with proper value from array when descriptor and defaults are objects', function() {\n      const spy = jasmine.createSpy('fallback');\n      const descriptors = {\n        items: {\n          _fallback: spy\n        }\n      };\n      const defaults = {\n        items: {\n          type: 'defaultType'\n        }\n      };\n      const options = {\n        items: [{test: true}]\n      };\n      const resolver = _createResolver([options, defaults, descriptors]);\n      const opts = _attachContext(resolver, {dymmy: true});\n      const item0 = opts.items[0];\n      expect(item0.test).toEqual(true);\n      expect(spy).toHaveBeenCalledWith('items', options.items[0]);\n    });\n\n    it('should support overriding options', function() {\n      const options = {\n        fn1: ctx => ctx.index,\n        fn2: ctx => ctx.type\n      };\n      const override = {\n        fn1: ctx => ctx.index * 2\n      };\n      const opts = _attachContext(_createResolver([options]), {index: 2, type: 'test'});\n      expect(opts).toEqualOptions({\n        fn1: 2,\n        fn2: 'test'\n      });\n      expect(opts.override(override)).toEqualOptions({\n        fn1: 4,\n        fn2: 'test'\n      });\n    });\n\n    it('should support changing context', function() {\n      const opts = _attachContext(_createResolver([{fn: ctx => ctx.test}]), {test: 1});\n      expect(opts.fn).toEqual(1);\n      expect(opts.setContext({test: 2}).fn).toEqual(2);\n      expect(opts.fn).toEqual(1);\n    });\n\n    it('should support common object methods', function() {\n      const defaults = {\n        option1: 'defaults'\n      };\n      class Options {\n        constructor() {\n          this.option2 = () => 'options';\n        }\n        get getter() {\n          return 'options getter';\n        }\n      }\n      const options = new Options();\n      const resolver = _createResolver([options, defaults]);\n      const opts = _attachContext(resolver, {index: 1});\n\n      expect(Object.prototype.hasOwnProperty.call(opts, 'option2')).toBeTrue();\n\n      expect(Object.prototype.hasOwnProperty.call(opts, 'option1')).toBeFalse();\n      expect(Object.prototype.hasOwnProperty.call(opts, 'getter')).toBeFalse();\n      expect(Object.prototype.hasOwnProperty.call(opts, 'nonexistent')).toBeFalse();\n\n      expect(Object.keys(opts)).toEqual(['option2']);\n      expect(Object.getOwnPropertyNames(opts)).toEqual(['option2', 'option1']);\n\n      expect('option2' in opts).toBeTrue();\n      expect('option1' in opts).toBeTrue();\n      expect('getter' in opts).toBeFalse();\n      expect('nonexistent' in opts).toBeFalse();\n\n      expect(opts instanceof Options).toBeTrue();\n\n      expect(opts.getter).toEqual('options getter');\n\n      expect('test' in opts).toBeFalse();\n      expect(opts.test).toBeUndefined();\n\n      opts.test = true;\n      expect('test' in opts).toBeTrue();\n      expect(opts.test).toBeTrue();\n\n      delete opts.test;\n      expect('test' in opts).toBeFalse();\n\n      opts.test = (ctx) => ctx.index;\n      expect('test' in opts).toBeTrue();\n      expect(opts.test).toBe(1);\n\n      delete opts.test;\n      expect('test' in opts).toBeFalse();\n    });\n\n    it('should not create proxy for adapters', function() {\n      const defaults = {\n        scales: {\n          time: {\n            adapters: {\n              date: {\n                locale: {\n                  method: (arg) => arg === undefined ? 'ok' : 'fail'\n                }\n              }\n            }\n          }\n        }\n      };\n\n      const resolver = _createResolver([{}, defaults]);\n      const opts = _attachContext(resolver, {index: 1});\n      const fn = opts.scales.time.adapters.date.locale.method;\n      expect(typeof fn).toBe('function');\n      expect(fn()).toEqual('ok');\n    });\n\n    it('should not create proxy for objects with custom constructor', function() {\n      class MyClass {\n        constructor() {\n          this.string = 'test string';\n        }\n        method(arg) {\n          return arg === undefined ? 'ok' : 'fail';\n        }\n      }\n\n      const defaults = {\n        test: new MyClass()\n      };\n\n      const resolver = _createResolver([{}, defaults]);\n      const opts = _attachContext(resolver, {index: 1});\n      const fn = opts.test.method;\n      expect(typeof fn).toBe('function');\n      expect(fn()).toEqual('ok');\n      expect(opts.test.string).toEqual('test string');\n      expect(opts.test.constructor).toEqual(MyClass);\n    });\n\n    it('should properly set value to object in array of objects', function() {\n      const defaults = {};\n      const options = {\n        annotations: [{\n          value: 10\n        }, {\n          value: 20\n        }]\n      };\n      const resolver = _attachContext(_createResolver([options, defaults]), {test: true});\n      expect(resolver.annotations[0].value).toEqual(10);\n\n      resolver.annotations[0].value = 15;\n      expect(options.annotations[0].value).toEqual(15);\n      expect(options.annotations[1].value).toEqual(20);\n    });\n\n    describe('_indexable and _scriptable', function() {\n      it('should default to true', function() {\n        const options = {\n          array: [1, 2, 3],\n          func: (ctx) => ctx.index * 10\n        };\n        const opts = _attachContext(_createResolver([options]), {index: 1});\n        expect(opts.array).toEqual(2);\n        expect(opts.func).toEqual(10);\n      });\n\n      it('should allow false', function() {\n        const fn = () => 'test';\n        const options = {\n          _indexable: false,\n          _scriptable: false,\n          array: [1, 2, 3],\n          func: fn\n        };\n        const opts = _attachContext(_createResolver([options]), {index: 1});\n        expect(opts.array).toEqual([1, 2, 3]);\n        expect(opts.func).toEqual(fn);\n        expect(opts.func()).toEqual('test');\n      });\n\n      it('should allow function', function() {\n        const fn = () => 'test';\n        const options = {\n          _indexable: (prop) => prop !== 'array',\n          _scriptable: (prop) => prop === 'func',\n          array: [1, 2, 3],\n          array2: ['a', 'b', 'c'],\n          func: fn\n        };\n        const opts = _attachContext(_createResolver([options]), {index: 1});\n        expect(opts.array).toEqual([1, 2, 3]);\n        expect(opts.func).toEqual('test');\n        expect(opts.array2).toEqual('b');\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/helpers.core.tests.js",
    "content": "'use strict';\n\ndescribe('Chart.helpers.core', function() {\n  var helpers = Chart.helpers;\n\n  describe('noop', function() {\n    it('should be callable', function() {\n      expect(helpers.noop).toBeDefined();\n      expect(typeof helpers.noop).toBe('function');\n      expect(typeof helpers.noop.call).toBe('function');\n    });\n    it('should returns \"undefined\"', function() {\n      expect(helpers.noop(42)).not.toBeDefined();\n      expect(helpers.noop.call(this, 42)).not.toBeDefined();\n    });\n  });\n\n  describe('isArray', function() {\n    it('should return true if value is an array', function() {\n      expect(helpers.isArray([])).toBeTruthy();\n      expect(helpers.isArray([42])).toBeTruthy();\n      expect(helpers.isArray(new Array())).toBeTruthy();\n      expect(helpers.isArray(Array.prototype)).toBeTruthy();\n      expect(helpers.isArray(new Int8Array(2))).toBeTruthy();\n      expect(helpers.isArray(new Uint8Array())).toBeTruthy();\n      expect(helpers.isArray(new Uint8ClampedArray([128, 244]))).toBeTruthy();\n      expect(helpers.isArray(new Int16Array())).toBeTruthy();\n      expect(helpers.isArray(new Uint16Array())).toBeTruthy();\n      expect(helpers.isArray(new Int32Array())).toBeTruthy();\n      expect(helpers.isArray(new Uint32Array())).toBeTruthy();\n      expect(helpers.isArray(new Float32Array([1.2]))).toBeTruthy();\n      expect(helpers.isArray(new Float64Array([]))).toBeTruthy();\n    });\n    it('should return false if value is not an array', function() {\n      expect(helpers.isArray()).toBeFalsy();\n      expect(helpers.isArray({})).toBeFalsy();\n      expect(helpers.isArray(undefined)).toBeFalsy();\n      expect(helpers.isArray(null)).toBeFalsy();\n      expect(helpers.isArray(true)).toBeFalsy();\n      expect(helpers.isArray(false)).toBeFalsy();\n      expect(helpers.isArray(42)).toBeFalsy();\n      expect(helpers.isArray('Array')).toBeFalsy();\n      expect(helpers.isArray({__proto__: Array.prototype})).toBeFalsy();\n    });\n  });\n\n  describe('isObject', function() {\n    it('should return true if value is an object', function() {\n      expect(helpers.isObject({})).toBeTruthy();\n      expect(helpers.isObject({a: 42})).toBeTruthy();\n      expect(helpers.isObject(new Object())).toBeTruthy();\n    });\n    it('should return false if value is not an object', function() {\n      expect(helpers.isObject()).toBeFalsy();\n      expect(helpers.isObject(undefined)).toBeFalsy();\n      expect(helpers.isObject(null)).toBeFalsy();\n      expect(helpers.isObject(true)).toBeFalsy();\n      expect(helpers.isObject(false)).toBeFalsy();\n      expect(helpers.isObject(42)).toBeFalsy();\n      expect(helpers.isObject('Object')).toBeFalsy();\n      expect(helpers.isObject([])).toBeFalsy();\n      expect(helpers.isObject([42])).toBeFalsy();\n      expect(helpers.isObject(new Array())).toBeFalsy();\n      expect(helpers.isObject(new Date())).toBeFalsy();\n    });\n  });\n\n  describe('isFinite', function() {\n    it('should return true if value is a finite number', function() {\n      expect(helpers.isFinite(0)).toBeTruthy();\n      // eslint-disable-next-line no-new-wrappers\n      expect(helpers.isFinite(new Number(10))).toBeTruthy();\n    });\n\n    it('should return false if the value is infinite', function() {\n      expect(helpers.isFinite(Number.POSITIVE_INFINITY)).toBeFalsy();\n      expect(helpers.isFinite(Number.NEGATIVE_INFINITY)).toBeFalsy();\n    });\n\n    it('should return false if the value is not a number', function() {\n      expect(helpers.isFinite('a')).toBeFalsy();\n      expect(helpers.isFinite({})).toBeFalsy();\n    });\n  });\n\n  describe('isNullOrUndef', function() {\n    it('should return true if value is null/undefined', function() {\n      expect(helpers.isNullOrUndef(null)).toBeTruthy();\n      expect(helpers.isNullOrUndef(undefined)).toBeTruthy();\n    });\n    it('should return false if value is not null/undefined', function() {\n      expect(helpers.isNullOrUndef(true)).toBeFalsy();\n      expect(helpers.isNullOrUndef(false)).toBeFalsy();\n      expect(helpers.isNullOrUndef('')).toBeFalsy();\n      expect(helpers.isNullOrUndef('String')).toBeFalsy();\n      expect(helpers.isNullOrUndef(0)).toBeFalsy();\n      expect(helpers.isNullOrUndef([])).toBeFalsy();\n      expect(helpers.isNullOrUndef({})).toBeFalsy();\n      expect(helpers.isNullOrUndef([42])).toBeFalsy();\n      expect(helpers.isNullOrUndef(new Date())).toBeFalsy();\n    });\n  });\n\n  describe('valueOrDefault', function() {\n    it('should return value if defined', function() {\n      var object = {};\n      var array = [];\n\n      expect(helpers.valueOrDefault(null, 42)).toBe(null);\n      expect(helpers.valueOrDefault(false, 42)).toBe(false);\n      expect(helpers.valueOrDefault(object, 42)).toBe(object);\n      expect(helpers.valueOrDefault(array, 42)).toBe(array);\n      expect(helpers.valueOrDefault('', 42)).toBe('');\n      expect(helpers.valueOrDefault(0, 42)).toBe(0);\n    });\n    it('should return default if undefined', function() {\n      expect(helpers.valueOrDefault(undefined, 42)).toBe(42);\n      expect(helpers.valueOrDefault({}.foo, 42)).toBe(42);\n    });\n  });\n\n  describe('callback', function() {\n    it('should return undefined if fn is not a function', function() {\n      expect(helpers.callback()).not.toBeDefined();\n      expect(helpers.callback(null)).not.toBeDefined();\n      expect(helpers.callback(42)).not.toBeDefined();\n      expect(helpers.callback([])).not.toBeDefined();\n      expect(helpers.callback({})).not.toBeDefined();\n    });\n    it('should call fn with the given args', function() {\n      var spy = jasmine.createSpy('spy');\n      helpers.callback(spy);\n      helpers.callback(spy, []);\n      helpers.callback(spy, ['foo']);\n      helpers.callback(spy, [42, 'bar']);\n\n      expect(spy.calls.argsFor(0)).toEqual([]);\n      expect(spy.calls.argsFor(1)).toEqual([]);\n      expect(spy.calls.argsFor(2)).toEqual(['foo']);\n      expect(spy.calls.argsFor(3)).toEqual([42, 'bar']);\n    });\n    it('should call fn with the given scope', function() {\n      var spy = jasmine.createSpy('spy');\n      var scope = {};\n\n      helpers.callback(spy);\n      helpers.callback(spy, [], null);\n      helpers.callback(spy, [], undefined);\n      helpers.callback(spy, [], scope);\n\n      expect(spy.calls.all()[0].object).toBe(window);\n      expect(spy.calls.all()[1].object).toBe(window);\n      expect(spy.calls.all()[2].object).toBe(window);\n      expect(spy.calls.all()[3].object).toBe(scope);\n    });\n    it('should return the value returned by fn', function() {\n      expect(helpers.callback(helpers.noop, [41])).toBe(undefined);\n      expect(helpers.callback(function(i) {\n        return i + 1;\n      }, [41])).toBe(42);\n    });\n  });\n\n  describe('each', function() {\n    it('should iterate over an array forward if reverse === false', function() {\n      var scope = {};\n      var scopes = [];\n      var items = [];\n      var keys = [];\n\n      helpers.each(['foo', 'bar', 42], function(item, key) {\n        scopes.push(this);\n        items.push(item);\n        keys.push(key);\n      }, scope);\n\n      expect(scopes).toEqual([scope, scope, scope]);\n      expect(items).toEqual(['foo', 'bar', 42]);\n      expect(keys).toEqual([0, 1, 2]);\n    });\n    it('should iterate over an array backward if reverse === true', function() {\n      var scope = {};\n      var scopes = [];\n      var items = [];\n      var keys = [];\n\n      helpers.each(['foo', 'bar', 42], function(item, key) {\n        scopes.push(this);\n        items.push(item);\n        keys.push(key);\n      }, scope, true);\n\n      expect(scopes).toEqual([scope, scope, scope]);\n      expect(items).toEqual([42, 'bar', 'foo']);\n      expect(keys).toEqual([2, 1, 0]);\n    });\n    it('should iterate over object properties', function() {\n      var scope = {};\n      var scopes = [];\n      var items = [];\n\n      helpers.each({a: 'foo', b: 'bar', c: 42}, function(item, key) {\n        scopes.push(this);\n        items[key] = item;\n      }, scope);\n\n      expect(scopes).toEqual([scope, scope, scope]);\n      expect(items).toEqual(jasmine.objectContaining({a: 'foo', b: 'bar', c: 42}));\n    });\n    it('should not throw when called with a non iterable object', function() {\n      expect(function() {\n        helpers.each(undefined);\n      }).not.toThrow();\n      expect(function() {\n        helpers.each(null);\n      }).not.toThrow();\n      expect(function() {\n        helpers.each(42);\n      }).not.toThrow();\n    });\n  });\n\n  describe('_elementsEqual', function() {\n    it('should return true if arrays are the same', function() {\n      expect(helpers._elementsEqual(\n        [{datasetIndex: 0, index: 1}, {datasetIndex: 0, index: 2}],\n        [{datasetIndex: 0, index: 1}, {datasetIndex: 0, index: 2}])).toBeTruthy();\n    });\n    it('should return false if arrays are not the same', function() {\n      expect(helpers._elementsEqual([], [{datasetIndex: 0, index: 1}])).toBeFalsy();\n      expect(helpers._elementsEqual([{datasetIndex: 0, index: 2}], [{datasetIndex: 0, index: 1}])).toBeFalsy();\n    });\n  });\n\n  describe('clone', function() {\n    it('should clone primitive values', function() {\n      expect(helpers.clone()).toBe(undefined);\n      expect(helpers.clone(null)).toBe(null);\n      expect(helpers.clone(true)).toBe(true);\n      expect(helpers.clone(42)).toBe(42);\n      expect(helpers.clone('foo')).toBe('foo');\n    });\n    it('should perform a deep copy of arrays', function() {\n      var o0 = {a: 42};\n      var o1 = {s: 's'};\n      var a0 = ['bar'];\n      var a1 = [a0, o0, 2];\n      var f0 = function() {};\n      var input = [a1, o1, f0, 42, 'foo'];\n      var output = helpers.clone(input);\n\n      expect(output).toEqual(input);\n      expect(output).not.toBe(input);\n      expect(output[0]).not.toBe(a1);\n      expect(output[0][0]).not.toBe(a0);\n      expect(output[1]).not.toBe(o1);\n    });\n    it('should perform a deep copy of objects', function() {\n      var a0 = ['bar'];\n      var a1 = [1, 2, 3];\n      var o0 = {a: a1, i: 42};\n      var f0 = function() {};\n      var input = {o: o0, a: a0, f: f0, s: 'foo', i: 42};\n      var output = helpers.clone(input);\n\n      expect(output).toEqual(input);\n      expect(output).not.toBe(input);\n      expect(output.o).not.toBe(o0);\n      expect(output.o.a).not.toBe(a1);\n      expect(output.a).not.toBe(a0);\n    });\n  });\n\n  describe('merge', function() {\n    it('should not allow prototype pollution', function() {\n      var test = helpers.merge({}, JSON.parse('{\"__proto__\":{\"polluted\": true}}'));\n      expect(test.prototype).toBeUndefined();\n      expect(Object.prototype.polluted).toBeUndefined();\n    });\n    it('should update target and return it', function() {\n      var target = {a: 1};\n      var result = helpers.merge(target, {a: 2, b: 'foo'});\n      expect(target).toEqual({a: 2, b: 'foo'});\n      expect(target).toBe(result);\n    });\n    it('should return target if not an object', function() {\n      expect(helpers.merge(undefined, {a: 42})).toEqual(undefined);\n      expect(helpers.merge(null, {a: 42})).toEqual(null);\n      expect(helpers.merge('foo', {a: 42})).toEqual('foo');\n      expect(helpers.merge(['foo', 'bar'], {a: 42})).toEqual(['foo', 'bar']);\n    });\n    it('should ignore sources which are not objects', function() {\n      expect(helpers.merge({a: 42})).toEqual({a: 42});\n      expect(helpers.merge({a: 42}, null)).toEqual({a: 42});\n      expect(helpers.merge({a: 42}, 42)).toEqual({a: 42});\n    });\n    it('should recursively overwrite target with source properties', function() {\n      expect(helpers.merge({a: {b: 1}}, {a: {c: 2}})).toEqual({a: {b: 1, c: 2}});\n      expect(helpers.merge({a: {b: 1}}, {a: {b: 2}})).toEqual({a: {b: 2}});\n      expect(helpers.merge({a: [1, 2]}, {a: [3, 4]})).toEqual({a: [3, 4]});\n      expect(helpers.merge({a: 42}, {a: {b: 0}})).toEqual({a: {b: 0}});\n      expect(helpers.merge({a: 42}, {a: null})).toEqual({a: null});\n      expect(helpers.merge({a: 42}, {a: undefined})).toEqual({a: undefined});\n    });\n    it('should merge multiple sources in the correct order', function() {\n      var t0 = {a: {b: 1, c: [1, 2]}};\n      var s0 = {a: {d: 3}, e: {f: 4}};\n      var s1 = {a: {b: 5}};\n      var s2 = {a: {c: [6, 7]}, e: 'foo'};\n\n      expect(helpers.merge(t0, [s0, s1, s2])).toEqual({a: {b: 5, c: [6, 7], d: 3}, e: 'foo'});\n    });\n    it('should deep copy merged values from sources', function() {\n      var a0 = ['foo'];\n      var a1 = [1, 2, 3];\n      var o0 = {a: a1, i: 42};\n      var output = helpers.merge({}, {a: a0, o: o0});\n\n      expect(output).toEqual({a: a0, o: o0});\n      expect(output.a).not.toBe(a0);\n      expect(output.o).not.toBe(o0);\n      expect(output.o.a).not.toBe(a1);\n    });\n  });\n\n  describe('mergeIf', function() {\n    it('should not allow prototype pollution', function() {\n      var test = helpers.mergeIf({}, JSON.parse('{\"__proto__\":{\"polluted\": true}}'));\n      expect(test.prototype).toBeUndefined();\n      expect(Object.prototype.polluted).toBeUndefined();\n    });\n    it('should update target and return it', function() {\n      var target = {a: 1};\n      var result = helpers.mergeIf(target, {a: 2, b: 'foo'});\n      expect(target).toEqual({a: 1, b: 'foo'});\n      expect(target).toBe(result);\n    });\n    it('should return target if not an object', function() {\n      expect(helpers.mergeIf(undefined, {a: 42})).toEqual(undefined);\n      expect(helpers.mergeIf(null, {a: 42})).toEqual(null);\n      expect(helpers.mergeIf('foo', {a: 42})).toEqual('foo');\n      expect(helpers.mergeIf(['foo', 'bar'], {a: 42})).toEqual(['foo', 'bar']);\n    });\n    it('should ignore sources which are not objects', function() {\n      expect(helpers.mergeIf({a: 42})).toEqual({a: 42});\n      expect(helpers.mergeIf({a: 42}, null)).toEqual({a: 42});\n      expect(helpers.mergeIf({a: 42}, 42)).toEqual({a: 42});\n    });\n    it('should recursively copy source properties in target only if they do not exist in target', function() {\n      expect(helpers.mergeIf({a: {b: 1}}, {a: {c: 2}})).toEqual({a: {b: 1, c: 2}});\n      expect(helpers.mergeIf({a: {b: 1}}, {a: {b: 2}})).toEqual({a: {b: 1}});\n      expect(helpers.mergeIf({a: [1, 2]}, {a: [3, 4]})).toEqual({a: [1, 2]});\n      expect(helpers.mergeIf({a: 0}, {a: {b: 2}})).toEqual({a: 0});\n      expect(helpers.mergeIf({a: null}, {a: 42})).toEqual({a: null});\n      expect(helpers.mergeIf({a: undefined}, {a: 42})).toEqual({a: undefined});\n    });\n    it('should merge multiple sources in the correct order', function() {\n      var t0 = {a: {b: 1, c: [1, 2]}};\n      var s0 = {a: {d: 3}, e: {f: 4}};\n      var s1 = {a: {b: 5}};\n      var s2 = {a: {c: [6, 7]}, e: 'foo'};\n\n      expect(helpers.mergeIf(t0, [s0, s1, s2])).toEqual({a: {b: 1, c: [1, 2], d: 3}, e: {f: 4}});\n    });\n    it('should deep copy merged values from sources', function() {\n      var a0 = ['foo'];\n      var a1 = [1, 2, 3];\n      var o0 = {a: a1, i: 42};\n      var output = helpers.mergeIf({}, {a: a0, o: o0});\n\n      expect(output).toEqual({a: a0, o: o0});\n      expect(output.a).not.toBe(a0);\n      expect(output.o).not.toBe(o0);\n      expect(output.o.a).not.toBe(a1);\n    });\n  });\n\n  describe('resolveObjectKey', function() {\n    it('should resolve empty key to root object', function() {\n      const obj = {test: true};\n      expect(helpers.resolveObjectKey(obj, '')).toEqual(obj);\n    });\n    it('should resolve one level', function() {\n      const obj = {\n        bool: true,\n        str: 'test',\n        int: 42,\n        obj: {name: 'object'}\n      };\n      expect(helpers.resolveObjectKey(obj, 'bool')).toEqual(true);\n      expect(helpers.resolveObjectKey(obj, 'str')).toEqual('test');\n      expect(helpers.resolveObjectKey(obj, 'int')).toEqual(42);\n      expect(helpers.resolveObjectKey(obj, 'obj')).toEqual(obj.obj);\n    });\n    it('should resolve multiple levels', function() {\n      const obj = {\n        child: {\n          level: 1,\n          child: {\n            level: 2,\n            child: {\n              level: 3\n            }\n          }\n        }\n      };\n      expect(helpers.resolveObjectKey(obj, 'child.level')).toEqual(1);\n      expect(helpers.resolveObjectKey(obj, 'child.child.level')).toEqual(2);\n      expect(helpers.resolveObjectKey(obj, 'child.child.child.level')).toEqual(3);\n    });\n    it('should resolve circular reference', function() {\n      const root = {};\n      const child = {root};\n      child.child = child;\n      root.child = child;\n      expect(helpers.resolveObjectKey(root, 'child')).toEqual(child);\n      expect(helpers.resolveObjectKey(root, 'child.child.child.child.child.child')).toEqual(child);\n      expect(helpers.resolveObjectKey(root, 'child.child.root')).toEqual(root);\n    });\n    it('should break at empty key', function() {\n      const obj = {\n        child: {\n          level: 1,\n          child: {\n            level: 2,\n            child: {\n              level: 3\n            }\n          }\n        }\n      };\n      expect(helpers.resolveObjectKey(obj, 'child..level')).toEqual(obj.child);\n      expect(helpers.resolveObjectKey(obj, 'child.child.level...')).toEqual(2);\n      expect(helpers.resolveObjectKey(obj, '.')).toEqual(obj);\n      expect(helpers.resolveObjectKey(obj, '..')).toEqual(obj);\n    });\n    it('should resolve undefined', function() {\n      const obj = {\n        child: {\n          level: 1,\n          child: {\n            level: 2,\n            child: {\n              level: 3\n            }\n          }\n        }\n      };\n      expect(helpers.resolveObjectKey(obj, 'level')).toEqual(undefined);\n      expect(helpers.resolveObjectKey(obj, 'child.level.a')).toEqual(undefined);\n    });\n    it('should throw on invalid input', function() {\n      expect(() => helpers.resolveObjectKey(undefined, undefined)).toThrow();\n      expect(() => helpers.resolveObjectKey({}, null)).toThrow();\n      expect(() => helpers.resolveObjectKey({}, false)).toThrow();\n      expect(() => helpers.resolveObjectKey({}, true)).toThrow();\n      expect(() => helpers.resolveObjectKey({}, 1)).toThrow();\n    });\n    it('should allow escaping dot symbol', function() {\n      expect(helpers.resolveObjectKey({'test.dot': 10}, 'test\\\\.dot')).toEqual(10);\n      expect(helpers.resolveObjectKey({test: {dot: 10}}, 'test\\\\.dot')).toEqual(undefined);\n    });\n    it('should allow nested keys with a dot', function() {\n      expect(helpers.resolveObjectKey({\n        a: {\n          'bb.ccc': 'works',\n          bb: {\n            ccc: 'fails'\n          }\n        }\n      }, 'a.bb\\\\.ccc')).toEqual('works');\n    });\n\n  });\n\n  describe('_splitKey', function() {\n    it('should return array with one entry for string without a dot', function() {\n      expect(helpers._splitKey('')).toEqual(['']);\n      expect(helpers._splitKey('test')).toEqual(['test']);\n      const asciiWithoutDot = ' !\"#$%&\\'()*+,-/0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]^_`abcdefghijklmnopqrstuvwxyz{|}~';\n      expect(helpers._splitKey(asciiWithoutDot)).toEqual([asciiWithoutDot]);\n    });\n\n    it('should split on dot', function() {\n      expect(helpers._splitKey('test1.test2')).toEqual(['test1', 'test2']);\n      expect(helpers._splitKey('a.b.c')).toEqual(['a', 'b', 'c']);\n      expect(helpers._splitKey('a.b.')).toEqual(['a', 'b', '']);\n      expect(helpers._splitKey('a..c')).toEqual(['a', '', 'c']);\n    });\n\n    it('should preserve escaped dot', function() {\n      expect(helpers._splitKey('test1\\\\.test2')).toEqual(['test1.test2']);\n      expect(helpers._splitKey('a\\\\.b.c')).toEqual(['a.b', 'c']);\n      expect(helpers._splitKey('a.b\\\\.c')).toEqual(['a', 'b.c']);\n      expect(helpers._splitKey('a.\\\\.c')).toEqual(['a', '.c']);\n    });\n  });\n\n  describe('setsEqual', function() {\n    it('should handle set comparison', function() {\n      var a = new Set([1]);\n      var b = new Set(['1']);\n      var c = new Set([1]);\n\n      expect(helpers.setsEqual(a, b)).toBeFalse();\n      expect(helpers.setsEqual(a, c)).toBeTrue();\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/helpers.curve.tests.js",
    "content": "describe('Curve helper tests', function() {\n  let helpers;\n\n  beforeAll(function() {\n    helpers = window.Chart.helpers;\n  });\n\n  it('should spline curves', function() {\n    expect(helpers.splineCurve({\n      x: 0,\n      y: 0\n    }, {\n      x: 1,\n      y: 1\n    }, {\n      x: 2,\n      y: 0\n    }, 0)).toEqual({\n      previous: {\n        x: 1,\n        y: 1,\n      },\n      next: {\n        x: 1,\n        y: 1,\n      }\n    });\n\n    expect(helpers.splineCurve({\n      x: 0,\n      y: 0\n    }, {\n      x: 1,\n      y: 1\n    }, {\n      x: 2,\n      y: 0\n    }, 1)).toEqual({\n      previous: {\n        x: 0,\n        y: 1,\n      },\n      next: {\n        x: 2,\n        y: 1,\n      }\n    });\n  });\n\n  it('should spline curves with monotone cubic interpolation', function() {\n    var dataPoints = [\n      {x: 0, y: 0, skip: false},\n      {x: 3, y: 6, skip: false},\n      {x: 9, y: 6, skip: false},\n      {x: 12, y: 60, skip: false},\n      {x: 15, y: 60, skip: false},\n      {x: 18, y: 120, skip: false},\n      {x: null, y: null, skip: true},\n      {x: 21, y: 180, skip: false},\n      {x: 24, y: 120, skip: false},\n      {x: 27, y: 125, skip: false},\n      {x: 30, y: 105, skip: false},\n      {x: 33, y: 110, skip: false},\n      {x: 33, y: 110, skip: false},\n      {x: 36, y: 170, skip: false}\n    ];\n    helpers.splineCurveMonotone(dataPoints);\n    expect(dataPoints).toEqual([{\n      x: 0,\n      y: 0,\n      skip: false,\n      cp2x: 1,\n      cp2y: 2\n    },\n    {\n      x: 3,\n      y: 6,\n      skip: false,\n      cp1x: 2,\n      cp1y: 6,\n      cp2x: 5,\n      cp2y: 6\n    },\n    {\n      x: 9,\n      y: 6,\n      skip: false,\n      cp1x: 7,\n      cp1y: 6,\n      cp2x: 10,\n      cp2y: 6\n    },\n    {\n      x: 12,\n      y: 60,\n      skip: false,\n      cp1x: 11,\n      cp1y: 60,\n      cp2x: 13,\n      cp2y: 60\n    },\n    {\n      x: 15,\n      y: 60,\n      skip: false,\n      cp1x: 14,\n      cp1y: 60,\n      cp2x: 16,\n      cp2y: 60\n    },\n    {\n      x: 18,\n      y: 120,\n      skip: false,\n      cp1x: 17,\n      cp1y: 100\n    },\n    {\n      x: null,\n      y: null,\n      skip: true\n    },\n    {\n      x: 21,\n      y: 180,\n      skip: false,\n      cp2x: 22,\n      cp2y: 160\n    },\n    {\n      x: 24,\n      y: 120,\n      skip: false,\n      cp1x: 23,\n      cp1y: 120,\n      cp2x: 25,\n      cp2y: 120\n    },\n    {\n      x: 27,\n      y: 125,\n      skip: false,\n      cp1x: 26,\n      cp1y: 125,\n      cp2x: 28,\n      cp2y: 125\n    },\n    {\n      x: 30,\n      y: 105,\n      skip: false,\n      cp1x: 29,\n      cp1y: 105,\n      cp2x: 31,\n      cp2y: 105\n    },\n    {\n      x: 33,\n      y: 110,\n      skip: false,\n      cp1x: 32,\n      cp1y: 110,\n      cp2x: 33,\n      cp2y: 110\n    },\n    {\n      x: 33,\n      y: 110,\n      skip: false,\n      cp1x: 33,\n      cp1y: 110,\n      cp2x: 34,\n      cp2y: 110\n    },\n    {\n      x: 36,\n      y: 170,\n      skip: false,\n      cp1x: 35,\n      cp1y: 150\n    }]);\n  });\n});\n"
  },
  {
    "path": "test/specs/helpers.dom.tests.js",
    "content": "describe('DOM helpers tests', function() {\n  let helpers;\n\n  beforeAll(function() {\n    helpers = window.Chart.helpers;\n  });\n\n  it ('should get the maximum size for a node', function() {\n    // Create div with fixed size as a test bed\n    var div = document.createElement('div');\n    div.style.width = '200px';\n    div.style.height = '300px';\n\n    document.body.appendChild(div);\n\n    // Create the div we want to get the max size for\n    var innerDiv = document.createElement('div');\n    div.appendChild(innerDiv);\n\n    expect(helpers.getMaximumSize(innerDiv)).toEqual(jasmine.objectContaining({width: 200, height: 300}));\n\n    document.body.removeChild(div);\n  });\n\n  it ('should get the maximum width and height for a node in a ShadowRoot', function() {\n    // Create div with fixed size as a test bed\n    var div = document.createElement('div');\n    div.style.width = '200px';\n    div.style.height = '300px';\n\n    document.body.appendChild(div);\n\n    if (!div.attachShadow) {\n      // Shadow DOM is not natively supported\n      return;\n    }\n\n    var shadow = div.attachShadow({mode: 'closed'});\n\n    // Create the div we want to get the max size for\n    var innerDiv = document.createElement('div');\n    shadow.appendChild(innerDiv);\n\n    expect(helpers.getMaximumSize(innerDiv)).toEqual(jasmine.objectContaining({width: 200, height: 300}));\n\n    document.body.removeChild(div);\n  });\n\n  it ('should get the maximum width of a node that has a max-width style', function() {\n    // Create div with fixed size as a test bed\n    var div = document.createElement('div');\n    div.style.width = '200px';\n    div.style.height = '300px';\n\n    document.body.appendChild(div);\n\n    // Create the div we want to get the max size for and set a max-width style\n    var innerDiv = document.createElement('div');\n    innerDiv.style.maxWidth = '150px';\n    div.appendChild(innerDiv);\n\n    expect(helpers.getMaximumSize(innerDiv)).toEqual(jasmine.objectContaining({width: 150}));\n\n    document.body.removeChild(div);\n  });\n\n  it ('should get the maximum height of a node that has a max-height style', function() {\n    // Create div with fixed size as a test bed\n    var div = document.createElement('div');\n    div.style.width = '200px';\n    div.style.height = '300px';\n\n    document.body.appendChild(div);\n\n    // Create the div we want to get the max size for and set a max-height style\n    var innerDiv = document.createElement('div');\n    innerDiv.style.maxHeight = '150px';\n    div.appendChild(innerDiv);\n\n    expect(helpers.getMaximumSize(innerDiv)).toEqual(jasmine.objectContaining({height: 150}));\n\n    document.body.removeChild(div);\n  });\n\n  it ('should get the maximum width of a node when the parent has a max-width style', function() {\n    // Create div with fixed size as a test bed\n    var div = document.createElement('div');\n    div.style.width = '200px';\n    div.style.height = '300px';\n\n    document.body.appendChild(div);\n\n    // Create an inner wrapper around our div we want to size and give that a max-width style\n    var parentDiv = document.createElement('div');\n    parentDiv.style.maxWidth = '150px';\n    div.appendChild(parentDiv);\n\n    // Create the div we want to get the max size for\n    var innerDiv = document.createElement('div');\n    parentDiv.appendChild(innerDiv);\n\n    expect(helpers.getMaximumSize(innerDiv)).toEqual(jasmine.objectContaining({width: 150}));\n\n    document.body.removeChild(div);\n  });\n\n  it ('should get the maximum height of a node when the parent has a max-height style', function() {\n    // Create div with fixed size as a test bed\n    var div = document.createElement('div');\n    div.style.width = '200px';\n    div.style.height = '300px';\n\n    document.body.appendChild(div);\n\n    // Create an inner wrapper around our div we want to size and give that a max-height style\n    var parentDiv = document.createElement('div');\n    parentDiv.style.maxHeight = '150px';\n    div.appendChild(parentDiv);\n\n    // Create the div we want to get the max size for\n    var innerDiv = document.createElement('div');\n    innerDiv.style.height = '300px'; // make it large\n    parentDiv.appendChild(innerDiv);\n\n    expect(helpers.getMaximumSize(innerDiv)).toEqual(jasmine.objectContaining({height: 150}));\n\n    document.body.removeChild(div);\n  });\n\n  it ('should get the maximum width of a node that has a percentage max-width style', function() {\n    // Create div with fixed size as a test bed\n    var div = document.createElement('div');\n    div.style.width = '200px';\n    div.style.height = '300px';\n\n    document.body.appendChild(div);\n\n    // Create the div we want to get the max size for and set a max-width style\n    var innerDiv = document.createElement('div');\n    innerDiv.style.maxWidth = '50%';\n    div.appendChild(innerDiv);\n\n    expect(helpers.getMaximumSize(innerDiv)).toEqual(jasmine.objectContaining({width: 100}));\n\n    document.body.removeChild(div);\n  });\n\n  it('should get the maximum height of a node that has a percentage max-height style', function() {\n    // Create div with fixed size as a test bed\n    var div = document.createElement('div');\n    div.style.width = '200px';\n    div.style.height = '300px';\n\n    document.body.appendChild(div);\n\n    // Create the div we want to get the max size for and set a max-height style\n    var innerDiv = document.createElement('div');\n    innerDiv.style.maxHeight = '50%';\n    div.appendChild(innerDiv);\n\n    expect(helpers.getMaximumSize(innerDiv)).toEqual(jasmine.objectContaining({height: 150}));\n\n    document.body.removeChild(div);\n  });\n\n  it ('should get the maximum width of a node when the parent has a percentage max-width style', function() {\n    // Create div with fixed size as a test bed\n    var div = document.createElement('div');\n    div.style.width = '200px';\n    div.style.height = '300px';\n\n    document.body.appendChild(div);\n\n    // Create an inner wrapper around our div we want to size and give that a max-width style\n    var parentDiv = document.createElement('div');\n    parentDiv.style.maxWidth = '50%';\n    div.appendChild(parentDiv);\n\n    // Create the div we want to get the max size for\n    var innerDiv = document.createElement('div');\n    parentDiv.appendChild(innerDiv);\n\n    expect(helpers.getMaximumSize(innerDiv)).toEqual(jasmine.objectContaining({width: 100}));\n\n    document.body.removeChild(div);\n  });\n\n  it ('should get the maximum height of a node when the parent has a percentage max-height style', function() {\n    // Create div with fixed size as a test bed\n    var div = document.createElement('div');\n    div.style.width = '200px';\n    div.style.height = '300px';\n\n    document.body.appendChild(div);\n\n    // Create an inner wrapper around our div we want to size and give that a max-height style\n    var parentDiv = document.createElement('div');\n    parentDiv.style.maxHeight = '50%';\n    div.appendChild(parentDiv);\n\n    var innerDiv = document.createElement('div');\n    innerDiv.style.height = '300px'; // make it large\n    parentDiv.appendChild(innerDiv);\n\n    expect(helpers.getMaximumSize(innerDiv)).toEqual(jasmine.objectContaining({height: 150}));\n\n    document.body.removeChild(div);\n  });\n\n  it ('Should get padding of parent as number (pixels) when defined as percent (returns incorrectly in IE11)', function() {\n\n    // Create div with fixed size as a test bed\n    var div = document.createElement('div');\n    div.style.width = '300px';\n    div.style.height = '300px';\n    document.body.appendChild(div);\n\n    // Inner DIV to have 5% padding of parent\n    var innerDiv = document.createElement('div');\n\n    div.appendChild(innerDiv);\n\n    var canvas = document.createElement('canvas');\n    innerDiv.appendChild(canvas);\n\n    // No padding\n    expect(helpers.getMaximumSize(canvas)).toEqual(jasmine.objectContaining({width: 300}));\n\n    // test with percentage\n    innerDiv.style.padding = '5%';\n    expect(helpers.getMaximumSize(canvas)).toEqual(jasmine.objectContaining({width: 270}));\n\n    // test with pixels\n    innerDiv.style.padding = '10px';\n    expect(helpers.getMaximumSize(canvas)).toEqual(jasmine.objectContaining({width: 280}));\n\n    document.body.removeChild(div);\n  });\n\n  it ('should leave styled height and width on canvas if explicitly set', function() {\n    var chart = window.acquireChart({}, {\n      canvas: {\n        height: 200,\n        width: 200,\n        style: 'height: 400px; width: 400px;'\n      }\n    });\n\n    helpers.retinaScale(chart, true);\n\n    var canvas = chart.canvas;\n\n    expect(canvas.style.height).toBe('400px');\n    expect(canvas.style.width).toBe('400px');\n  });\n\n  it ('should handle devicePixelRatio correctly', function() {\n    const chartWidth = 800;\n    const chartHeight = 400;\n    let devicePixelRatio = 0.8999999761581421; // 1.7999999523162842;\n    var chart = window.acquireChart({}, {\n      canvas: {\n        width: chartWidth,\n        height: chartHeight,\n      }\n    });\n\n    helpers.retinaScale(chart, devicePixelRatio, true);\n\n    var canvas = chart.canvas;\n    expect(canvas.width).toBe(Math.round(chartWidth * devicePixelRatio));\n    expect(canvas.height).toBe(Math.round(chartHeight * devicePixelRatio));\n\n    expect(chart.width).toBe(chartWidth);\n    expect(chart.height).toBe(chartHeight);\n\n    expect(canvas.style.width).toBe(`${chartWidth}px`);\n    expect(canvas.style.height).toBe(`${chartHeight}px`);\n  });\n\n  describe('getRelativePosition', function() {\n    it('should use offsetX/Y when available', function() {\n      const event = {offsetX: 50, offsetY: 100};\n      const chart = window.acquireChart({}, {\n        canvas: {\n          height: 200,\n          width: 200,\n        }\n      });\n      expect(helpers.getRelativePosition(event, chart)).toEqual({x: 50, y: 100});\n\n      const chart2 = window.acquireChart({}, {\n        canvas: {\n          height: 200,\n          width: 200,\n          style: 'padding: 10px'\n        }\n      });\n      expect(helpers.getRelativePosition(event, chart2)).toEqual({\n        x: Math.round((event.offsetX - 10) / 180 * 200),\n        y: Math.round((event.offsetY - 10) / 180 * 200)\n      });\n\n      const chart3 = window.acquireChart({}, {\n        canvas: {\n          height: 200,\n          width: 200,\n          style: 'width: 400px, height: 400px; padding: 10px'\n        }\n      });\n      expect(helpers.getRelativePosition(event, chart3)).toEqual({\n        x: Math.round((event.offsetX - 10) / 360 * 400),\n        y: Math.round((event.offsetY - 10) / 360 * 400)\n      });\n\n      const chart4 = window.acquireChart({}, {\n        canvas: {\n          height: 200,\n          width: 200,\n          style: 'width: 400px, height: 400px; padding: 10px; position: absolute; left: 20, top: 20'\n        }\n      });\n      expect(helpers.getRelativePosition(event, chart4)).toEqual({\n        x: Math.round((event.offsetX - 10) / 360 * 400),\n        y: Math.round((event.offsetY - 10) / 360 * 400)\n      });\n\n    });\n\n    it('should calculate from clientX/Y as fallback', function() {\n      const chart = window.acquireChart({}, {\n        canvas: {\n          height: 200,\n          width: 200,\n        }\n      });\n\n      const event = {\n        clientX: 50,\n        clientY: 100\n      };\n\n      const rect = chart.canvas.getBoundingClientRect();\n      const pos = helpers.getRelativePosition(event, chart);\n      expect(Math.abs(pos.x - Math.round(event.clientX - rect.x))).toBeLessThanOrEqual(1);\n      expect(Math.abs(pos.y - Math.round(event.clientY - rect.y))).toBeLessThanOrEqual(1);\n\n      const chart2 = window.acquireChart({}, {\n        canvas: {\n          height: 200,\n          width: 200,\n          style: 'padding: 10px'\n        }\n      });\n      const rect2 = chart2.canvas.getBoundingClientRect();\n      const pos2 = helpers.getRelativePosition(event, chart2);\n      expect(Math.abs(pos2.x - Math.round((event.clientX - rect2.x - 10) / 180 * 200))).toBeLessThanOrEqual(1);\n      expect(Math.abs(pos2.y - Math.round((event.clientY - rect2.y - 10) / 180 * 200))).toBeLessThanOrEqual(1);\n\n      const chart3 = window.acquireChart({}, {\n        canvas: {\n          height: 200,\n          width: 200,\n          style: 'width: 400px, height: 400px; padding: 10px'\n        }\n      });\n      const rect3 = chart3.canvas.getBoundingClientRect();\n      const pos3 = helpers.getRelativePosition(event, chart3);\n      expect(Math.abs(pos3.x - Math.round((event.clientX - rect3.x - 10) / 360 * 400))).toBeLessThanOrEqual(1);\n      expect(Math.abs(pos3.y - Math.round((event.clientY - rect3.y - 10) / 360 * 400))).toBeLessThanOrEqual(1);\n    });\n\n    it ('should get the correct relative position for a node in a ShadowRoot', function() {\n      const event = {\n        offsetX: 50,\n        offsetY: 100,\n        clientX: 50,\n        clientY: 100\n      };\n\n      const chart = window.acquireChart({}, {\n        canvas: {\n          height: 200,\n          width: 200,\n        },\n        useShadowDOM: true\n      });\n\n      event.target = chart.canvas.parentNode.host;\n      expect(event.target.shadowRoot).not.toEqual(null);\n      const rect = chart.canvas.getBoundingClientRect();\n      const pos = helpers.getRelativePosition(event, chart);\n      expect(Math.abs(pos.x - Math.round(event.clientX - rect.x))).toBeLessThanOrEqual(1);\n      expect(Math.abs(pos.y - Math.round(event.clientY - rect.y))).toBeLessThanOrEqual(1);\n\n      const chart2 = window.acquireChart({}, {\n        canvas: {\n          height: 200,\n          width: 200,\n          style: 'padding: 10px'\n        },\n        useShadowDOM: true\n      });\n\n      event.target = chart2.canvas.parentNode.host;\n      const rect2 = chart2.canvas.getBoundingClientRect();\n      const pos2 = helpers.getRelativePosition(event, chart2);\n      expect(Math.abs(pos2.x - Math.round((event.clientX - rect2.x - 10) / 180 * 200))).toBeLessThanOrEqual(1);\n      expect(Math.abs(pos2.y - Math.round((event.clientY - rect2.y - 10) / 180 * 200))).toBeLessThanOrEqual(1);\n\n      const chart3 = window.acquireChart({}, {\n        canvas: {\n          height: 200,\n          width: 200,\n          style: 'width: 400px, height: 400px; padding: 10px'\n        },\n        useShadowDOM: true\n      });\n\n      event.target = chart3.canvas.parentNode.host;\n      const rect3 = chart3.canvas.getBoundingClientRect();\n      const pos3 = helpers.getRelativePosition(event, chart3);\n      expect(Math.abs(pos3.x - Math.round((event.clientX - rect3.x - 10) / 360 * 400))).toBeLessThanOrEqual(1);\n      expect(Math.abs(pos3.y - Math.round((event.clientY - rect3.y - 10) / 360 * 400))).toBeLessThanOrEqual(1);\n    });\n\n    it('Should not return NaN with a custom event', async function() {\n      let dataX = null;\n      let dataY = null;\n      const chart = window.acquireChart(\n        {\n          type: 'bar',\n          data: {\n            datasets: [{\n              data: [{x: 'first', y: 10}, {x: 'second', y: 5}, {x: 'third', y: 15}]\n            }]\n          },\n          options: {\n            onHover: (e) => {\n              const canvasPosition = Chart.helpers.getRelativePosition(e, chart);\n\n              dataX = canvasPosition.x;\n              dataY = canvasPosition.y;\n            }\n          }\n        });\n\n      const point = chart.getDatasetMeta(0).data[1];\n      await jasmine.triggerMouseEvent(chart, 'mousemove', point);\n\n      expect(dataX).not.toEqual(NaN);\n      expect(dataY).not.toEqual(NaN);\n    });\n\n    it('Should give consistent results for native and chart events', async function() {\n      let chartPosition = null;\n      const chart = window.acquireChart(\n        {\n          type: 'bar',\n          data: {\n            datasets: [{\n              data: [{x: 'first', y: 10}, {x: 'second', y: 5}, {x: 'third', y: 15}]\n            }]\n          },\n          options: {\n            onHover: (chartEvent) => {\n              chartPosition = Chart.helpers.getRelativePosition(chartEvent, chart);\n            }\n          }\n        });\n\n      const point = chart.getDatasetMeta(0).data[1];\n      const nativeEvent = await jasmine.triggerMouseEvent(chart, 'mousemove', point);\n      const nativePosition = Chart.helpers.getRelativePosition(nativeEvent, chart);\n\n      expect(chartPosition).not.toBeNull();\n      expect(nativePosition).toEqual({x: chartPosition.x, y: chartPosition.y});\n    });\n  });\n\n  it('should respect aspect ratio and container width', () => {\n    const container = document.createElement('div');\n    container.style.width = '200px';\n    container.style.height = '500px';\n\n    document.body.appendChild(container);\n\n    const target = document.createElement('div');\n    target.style.width = '500px';\n    target.style.height = '500px';\n    container.appendChild(target);\n\n    expect(helpers.getMaximumSize(target, 200, 500, 1)).toEqual(jasmine.objectContaining({width: 200, height: 200}));\n\n    document.body.removeChild(container);\n  });\n\n  it('should respect aspect ratio and container height', () => {\n    const container = document.createElement('div');\n    container.style.width = '500px';\n    container.style.height = '200px';\n\n    document.body.appendChild(container);\n\n    const target = document.createElement('div');\n    target.style.width = '500px';\n    target.style.height = '500px';\n    container.appendChild(target);\n\n    expect(helpers.getMaximumSize(target, 500, 200, 1)).toEqual(jasmine.objectContaining({width: 200, height: 200}));\n\n    document.body.removeChild(container);\n  });\n\n  it('should respect aspect ratio and skip container height', () => {\n    const container = document.createElement('div');\n    container.style.width = '500px';\n    container.style.height = '200px';\n\n    document.body.appendChild(container);\n\n    const target = document.createElement('div');\n    target.style.width = '500px';\n    target.style.height = '500px';\n    container.appendChild(target);\n\n    expect(helpers.getMaximumSize(target, undefined, undefined, 1)).toEqual(jasmine.objectContaining({width: 500, height: 500}));\n\n    document.body.removeChild(container);\n  });\n\n  it('should round non-integer container dimensions', () => {\n    const container = document.createElement('div');\n    container.style.width = '799.999px';\n    container.style.height = '299.999px';\n\n    document.body.appendChild(container);\n\n    const target = document.createElement('div');\n    target.style.width = '200px';\n    target.style.height = '100px';\n    container.appendChild(target);\n\n    expect(helpers.getMaximumSize(target, undefined, undefined, 2)).toEqual(jasmine.objectContaining({width: 800, height: 400}));\n\n    document.body.removeChild(container);\n  });\n});\n"
  },
  {
    "path": "test/specs/helpers.easing.tests.js",
    "content": "'use strict';\n\ndescribe('Chart.helpers.easingEffects', function() {\n  var helpers = Chart.helpers;\n\n  describe('effects', function() {\n    var expected = {\n      easeInOutBack: [-0, -0.03751855, -0.09255566, -0.07883348, 0.08992579, 0.5, 0.91007421, 1.07883348, 1.09255566, 1.03751855, 1],\n      easeInOutBounce: [0, 0.03, 0.11375, 0.045, 0.34875, 0.5, 0.65125, 0.955, 0.88625, 0.97, 1],\n      easeInOutCirc: [-0, 0.01010205, 0.04174243, 0.1, 0.2, 0.5, 0.8, 0.9, 0.95825757, 0.98989795, 1],\n      easeInOutCubic: [0, 0.004, 0.032, 0.108, 0.256, 0.5, 0.744, 0.892, 0.968, 0.996, 1],\n      easeInOutElastic: [0, 0.00033916, -0.00390625, 0.02393889, -0.11746158, 0.5, 1.11746158, 0.97606111, 1.00390625, 0.99966084, 1],\n      easeInOutExpo: [0, 0.00195313, 0.0078125, 0.03125, 0.125, 0.5, 0.875, 0.96875, 0.9921875, 0.99804688, 1],\n      easeInOutQuad: [0, 0.02, 0.08, 0.18, 0.32, 0.5, 0.68, 0.82, 0.92, 0.98, 1],\n      easeInOutQuart: [0, 0.0008, 0.0128, 0.0648, 0.2048, 0.5, 0.7952, 0.9352, 0.9872, 0.9992, 1],\n      easeInOutQuint: [0, 0.00016, 0.00512, 0.03888, 0.16384, 0.5, 0.83616, 0.96112, 0.99488, 0.99984, 1],\n      easeInOutSine: [-0, 0.02447174, 0.0954915, 0.20610737, 0.3454915, 0.5, 0.6545085, 0.79389263, 0.9045085, 0.97552826, 1],\n      easeInBack: [-0, -0.01431422, -0.04645056, -0.08019954, -0.09935168, -0.0876975, -0.02902752, 0.09286774, 0.29419776, 0.59117202, 1],\n      easeInBounce: [0, 0.011875, 0.06, 0.069375, 0.2275, 0.234375, 0.09, 0.319375, 0.6975, 0.924375, 1],\n      easeInCirc: [-0, 0.00501256, 0.0202041, 0.0460608, 0.08348486, 0.1339746, 0.2, 0.28585716, 0.4, 0.56411011, 1],\n      easeInCubic: [0, 0.001, 0.008, 0.027, 0.064, 0.125, 0.216, 0.343, 0.512, 0.729, 1],\n      easeInExpo: [0, 0.00195313, 0.00390625, 0.0078125, 0.015625, 0.03125, 0.0625, 0.125, 0.25, 0.5, 1],\n      easeInElastic: [0, 0.00195313, -0.00195313, -0.00390625, 0.015625, -0.015625, -0.03125, 0.125, -0.125, -0.25, 1],\n      easeInQuad: [0, 0.01, 0.04, 0.09, 0.16, 0.25, 0.36, 0.49, 0.64, 0.81, 1],\n      easeInQuart: [0, 0.0001, 0.0016, 0.0081, 0.0256, 0.0625, 0.1296, 0.2401, 0.4096, 0.6561, 1],\n      easeInQuint: [0, 0.00001, 0.00032, 0.00243, 0.01024, 0.03125, 0.07776, 0.16807, 0.32768, 0.59049, 1],\n      easeInSine: [0, 0.01231166, 0.04894348, 0.10899348, 0.19098301, 0.29289322, 0.41221475, 0.5460095, 0.69098301, 0.84356553, 1],\n      easeOutBack: [0, 0.40882798, 0.70580224, 0.90713226, 1.02902752, 1.0876975, 1.09935168, 1.08019954, 1.04645056, 1.01431422, 1],\n      easeOutBounce: [0, 0.075625, 0.3025, 0.680625, 0.91, 0.765625, 0.7725, 0.930625, 0.94, 0.988125, 1],\n      easeOutCirc: [0, 0.43588989, 0.6, 0.71414284, 0.8, 0.8660254, 0.91651514, 0.9539392, 0.9797959, 0.99498744, 1],\n      easeOutElastic: [0, 1.25, 1.125, 0.875, 1.03125, 1.015625, 0.984375, 1.00390625, 1.00195313, 0.99804688, 1],\n      easeOutExpo: [0, 0.5, 0.75, 0.875, 0.9375, 0.96875, 0.984375, 0.9921875, 0.99609375, 0.99804688, 1],\n      easeOutCubic: [0, 0.271, 0.488, 0.657, 0.784, 0.875, 0.936, 0.973, 0.992, 0.999, 1],\n      easeOutQuad: [0, 0.19, 0.36, 0.51, 0.64, 0.75, 0.84, 0.91, 0.96, 0.99, 1],\n      easeOutQuart: [-0, 0.3439, 0.5904, 0.7599, 0.8704, 0.9375, 0.9744, 0.9919, 0.9984, 0.9999, 1],\n      easeOutQuint: [0, 0.40951, 0.67232, 0.83193, 0.92224, 0.96875, 0.98976, 0.99757, 0.99968, 0.99999, 1],\n      easeOutSine: [0, 0.15643447, 0.30901699, 0.4539905, 0.58778525, 0.70710678, 0.80901699, 0.89100652, 0.95105652, 0.98768834, 1],\n      linear: [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1]\n    };\n\n    function generate(method) {\n      var fn = helpers.easingEffects[method];\n      var accuracy = Math.pow(10, 8);\n      var count = 10;\n      var values = [];\n      var i;\n\n      for (i = 0; i <= count; ++i) {\n        values.push(Math.round(accuracy * fn(i / count)) / accuracy);\n      }\n\n      return values;\n    }\n\n    Object.keys(helpers.easingEffects).forEach(function(method) {\n      it ('\"' + method + '\" should return expected values', function() {\n        expect(generate(method)).toEqual(expected[method]);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/helpers.interpolation.tests.js",
    "content": "const {_pointInLine, _steppedInterpolation, _bezierInterpolation} = Chart.helpers;\n\ndescribe('helpers.interpolation', function() {\n  it('Should interpolate a point in line', function() {\n    expect(_pointInLine({x: 10, y: 10}, {x: 20, y: 20}, 0)).toEqual({x: 10, y: 10});\n    expect(_pointInLine({x: 10, y: 10}, {x: 20, y: 20}, 0.5)).toEqual({x: 15, y: 15});\n    expect(_pointInLine({x: 10, y: 10}, {x: 20, y: 20}, 1)).toEqual({x: 20, y: 20});\n  });\n\n  it('Should interpolate a point in stepped line', function() {\n    expect(_steppedInterpolation({x: 10, y: 10}, {x: 20, y: 20}, 0, 'before')).toEqual({x: 10, y: 10});\n    expect(_steppedInterpolation({x: 10, y: 10}, {x: 20, y: 20}, 0.4, 'before')).toEqual({x: 14, y: 20});\n    expect(_steppedInterpolation({x: 10, y: 10}, {x: 20, y: 20}, 0.5, 'before')).toEqual({x: 15, y: 20});\n    expect(_steppedInterpolation({x: 10, y: 10}, {x: 20, y: 20}, 1, 'before')).toEqual({x: 20, y: 20});\n\n    expect(_steppedInterpolation({x: 10, y: 10}, {x: 20, y: 20}, 0, 'middle')).toEqual({x: 10, y: 10});\n    expect(_steppedInterpolation({x: 10, y: 10}, {x: 20, y: 20}, 0.4, 'middle')).toEqual({x: 14, y: 10});\n    expect(_steppedInterpolation({x: 10, y: 10}, {x: 20, y: 20}, 0.5, 'middle')).toEqual({x: 15, y: 20});\n    expect(_steppedInterpolation({x: 10, y: 10}, {x: 20, y: 20}, 1, 'middle')).toEqual({x: 20, y: 20});\n\n    expect(_steppedInterpolation({x: 10, y: 10}, {x: 20, y: 20}, 0, 'after')).toEqual({x: 10, y: 10});\n    expect(_steppedInterpolation({x: 10, y: 10}, {x: 20, y: 20}, 0.4, 'after')).toEqual({x: 14, y: 10});\n    expect(_steppedInterpolation({x: 10, y: 10}, {x: 20, y: 20}, 0.5, 'after')).toEqual({x: 15, y: 10});\n    expect(_steppedInterpolation({x: 10, y: 10}, {x: 20, y: 20}, 1, 'after')).toEqual({x: 20, y: 20});\n  });\n\n  it('Should interpolate a point in curve', function() {\n    const pt1 = {x: 10, y: 10, cp2x: 12, cp2y: 12};\n    const pt2 = {x: 20, y: 30, cp1x: 18, cp1y: 28};\n\n    expect(_bezierInterpolation(pt1, pt2, 0)).toEqual({x: 10, y: 10});\n    expect(_bezierInterpolation(pt1, pt2, 0.2)).toBeCloseToPoint({x: 11.616, y: 12.656});\n    expect(_bezierInterpolation(pt1, pt2, 1)).toEqual({x: 20, y: 30});\n  });\n});\n"
  },
  {
    "path": "test/specs/helpers.math.tests.js",
    "content": "const math = Chart.helpers;\n\ndescribe('Chart.helpers.math', function() {\n  var factorize = math._factorize;\n  var decimalPlaces = math._decimalPlaces;\n\n  it('should factorize', function() {\n    expect(factorize(1000)).toEqual([1, 2, 4, 5, 8, 10, 20, 25, 40, 50, 100, 125, 200, 250, 500]);\n    expect(factorize(60)).toEqual([1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30]);\n    expect(factorize(30)).toEqual([1, 2, 3, 5, 6, 10, 15]);\n    expect(factorize(24)).toEqual([1, 2, 3, 4, 6, 8, 12]);\n    expect(factorize(12)).toEqual([1, 2, 3, 4, 6]);\n    expect(factorize(4)).toEqual([1, 2]);\n    expect(factorize(-1)).toEqual([]);\n    expect(factorize(2.76)).toEqual([]);\n  });\n\n  it('should do a log10 operation', function() {\n    expect(math.log10(0)).toBe(-Infinity);\n\n    // Check all allowed powers of 10, which should return integer values\n    var maxPowerOf10 = Math.floor(math.log10(Number.MAX_VALUE));\n    for (var i = 0; i < maxPowerOf10; i += 1) {\n      expect(math.log10(Math.pow(10, i))).toBe(i);\n    }\n  });\n\n  it('should get the correct number of decimal places', function() {\n    expect(decimalPlaces(100)).toBe(0);\n    expect(decimalPlaces(1)).toBe(0);\n    expect(decimalPlaces(0)).toBe(0);\n    expect(decimalPlaces(0.01)).toBe(2);\n    expect(decimalPlaces(-0.01)).toBe(2);\n    expect(decimalPlaces('1')).toBe(undefined);\n    expect(decimalPlaces('')).toBe(undefined);\n    expect(decimalPlaces(undefined)).toBe(undefined);\n    expect(decimalPlaces(12345678.1234)).toBe(4);\n    expect(decimalPlaces(1234567890.1234567)).toBe(7);\n  });\n\n  it('should get an angle from a point', function() {\n    var center = {\n      x: 0,\n      y: 0\n    };\n\n    expect(math.getAngleFromPoint(center, {\n      x: 0,\n      y: 10\n    })).toEqual({\n      angle: Math.PI / 2,\n      distance: 10,\n    });\n\n    expect(math.getAngleFromPoint(center, {\n      x: Math.sqrt(2),\n      y: Math.sqrt(2)\n    })).toEqual({\n      angle: Math.PI / 4,\n      distance: 2\n    });\n\n    expect(math.getAngleFromPoint(center, {\n      x: -1.0 * Math.sqrt(2),\n      y: -1.0 * Math.sqrt(2)\n    })).toEqual({\n      angle: Math.PI * 1.25,\n      distance: 2\n    });\n  });\n\n  it('should convert between radians and degrees', function() {\n    expect(math.toRadians(180)).toBe(Math.PI);\n    expect(math.toRadians(90)).toBe(0.5 * Math.PI);\n    expect(math.toDegrees(Math.PI)).toBe(180);\n    expect(math.toDegrees(Math.PI * 3 / 2)).toBe(270);\n  });\n\n  it('should correctly determine if two numbers are essentially equal', function() {\n    expect(math.almostEquals(0, Number.EPSILON, 2 * Number.EPSILON)).toBe(true);\n    expect(math.almostEquals(1, 1.1, 0.0001)).toBe(false);\n    expect(math.almostEquals(1e30, 1e30 + Number.EPSILON, 0)).toBe(false);\n    expect(math.almostEquals(1e30, 1e30 + Number.EPSILON, 2 * Number.EPSILON)).toBe(true);\n  });\n\n  it('should get the correct sign', function() {\n    expect(math.sign(0)).toBe(0);\n    expect(math.sign(10)).toBe(1);\n    expect(math.sign(-5)).toBe(-1);\n  });\n\n  it('should correctly determine if a numbers are essentially whole', function() {\n    expect(math.almostWhole(0.99999, 0.0001)).toBe(true);\n    expect(math.almostWhole(0.9, 0.0001)).toBe(false);\n    expect(math.almostWhole(1234567890123, 0.0001)).toBe(true);\n    expect(math.almostWhole(1234567890123.001, 0.0001)).toBe(false);\n  });\n\n  it('should detect a number', function() {\n    expect(math.isNumber(123)).toBe(true);\n    expect(math.isNumber('123')).toBe(true);\n    expect(math.isNumber(null)).toBe(false);\n    expect(math.isNumber(NaN)).toBe(false);\n    expect(math.isNumber(undefined)).toBe(false);\n    expect(math.isNumber('cbc')).toBe(false);\n    expect(math.isNumber(Symbol())).toBe(false);\n    expect(math.isNumber(Object.create(null))).toBe(false);\n  });\n\n  it('should compute shortest distance between angles', function() {\n    expect(math._angleDiff(1, 2)).toEqual(-1);\n    expect(math._angleDiff(2, 1)).toEqual(1);\n    expect(math._angleDiff(0, 3.15)).toBeCloseTo(3.13, 2);\n    expect(math._angleDiff(0, 3.13)).toEqual(-3.13);\n    expect(math._angleDiff(6.2, 0)).toBeCloseTo(-0.08, 2);\n    expect(math._angleDiff(6.3, 0)).toBeCloseTo(0.02, 2);\n    expect(math._angleDiff(4 * Math.PI, -4 * Math.PI)).toBeCloseTo(0, 4);\n    expect(math._angleDiff(4 * Math.PI, -3 * Math.PI)).toBeCloseTo(-3.14, 2);\n    expect(math._angleDiff(6.28, 3.1)).toBeCloseTo(-3.1, 2);\n    expect(math._angleDiff(6.28, 3.2)).toBeCloseTo(3.08, 2);\n  });\n\n  it('should normalize angles correctly', function() {\n    expect(math._normalizeAngle(-Math.PI)).toEqual(Math.PI);\n    expect(math._normalizeAngle(Math.PI)).toEqual(Math.PI);\n    expect(math._normalizeAngle(2)).toEqual(2);\n    expect(math._normalizeAngle(5 * Math.PI)).toEqual(Math.PI);\n    expect(math._normalizeAngle(-50 * Math.PI)).toBeCloseTo(6.28, 2);\n  });\n\n  it('should determine if angle is between boundaries', function() {\n    expect(math._angleBetween(2, 1, 3)).toBeTrue();\n    expect(math._angleBetween(2, 3, 1)).toBeFalse();\n    expect(math._angleBetween(-3.14, 2, 4)).toBeTrue();\n    expect(math._angleBetween(-3.14, 4, 2)).toBeFalse();\n    expect(math._angleBetween(0, -1, 1)).toBeTrue();\n    expect(math._angleBetween(-1, 0, 1)).toBeFalse();\n    expect(math._angleBetween(-15 * Math.PI, 3.1, 3.2)).toBeTrue();\n    expect(math._angleBetween(15 * Math.PI, -3.2, -3.1)).toBeTrue();\n  });\n});\n"
  },
  {
    "path": "test/specs/helpers.options.tests.js",
    "content": "const {toLineHeight, toPadding, toFont, resolve, toTRBLCorners} = Chart.helpers;\n\ndescribe('Chart.helpers.options', function() {\n  describe('toLineHeight', function() {\n    it ('should support keyword values', function() {\n      expect(toLineHeight('normal', 16)).toBe(16 * 1.2);\n    });\n    it ('should support unitless values', function() {\n      expect(toLineHeight(1.4, 16)).toBe(16 * 1.4);\n      expect(toLineHeight('1.4', 16)).toBe(16 * 1.4);\n    });\n    it ('should support length values', function() {\n      expect(toLineHeight('42px', 16)).toBe(42);\n      expect(toLineHeight('1.4em', 16)).toBe(16 * 1.4);\n    });\n    it ('should support percentage values', function() {\n      expect(toLineHeight('140%', 16)).toBe(16 * 1.4);\n    });\n    it ('should fallback to default (1.2) for invalid values', function() {\n      expect(toLineHeight(null, 16)).toBe(16 * 1.2);\n      expect(toLineHeight(undefined, 16)).toBe(16 * 1.2);\n      expect(toLineHeight('foobar', 16)).toBe(16 * 1.2);\n    });\n  });\n\n  describe('toTRBLCorners', function() {\n    it('should support number values', function() {\n      expect(toTRBLCorners(4)).toEqual(\n        {topLeft: 4, topRight: 4, bottomLeft: 4, bottomRight: 4});\n      expect(toTRBLCorners(4.5)).toEqual(\n        {topLeft: 4.5, topRight: 4.5, bottomLeft: 4.5, bottomRight: 4.5});\n    });\n    it('should support string values', function() {\n      expect(toTRBLCorners('4')).toEqual(\n        {topLeft: 4, topRight: 4, bottomLeft: 4, bottomRight: 4});\n      expect(toTRBLCorners('4.5')).toEqual(\n        {topLeft: 4.5, topRight: 4.5, bottomLeft: 4.5, bottomRight: 4.5});\n    });\n    it('should support object values', function() {\n      expect(toTRBLCorners({topLeft: 1, topRight: 2, bottomLeft: 3, bottomRight: 4})).toEqual(\n        {topLeft: 1, topRight: 2, bottomLeft: 3, bottomRight: 4});\n      expect(toTRBLCorners({topLeft: 1.5, topRight: 2.5, bottomLeft: 3.5, bottomRight: 4.5})).toEqual(\n        {topLeft: 1.5, topRight: 2.5, bottomLeft: 3.5, bottomRight: 4.5});\n      expect(toTRBLCorners({topLeft: '1', topRight: '2', bottomLeft: '3', bottomRight: '4'})).toEqual(\n        {topLeft: 1, topRight: 2, bottomLeft: 3, bottomRight: 4});\n    });\n    it('should fallback to 0 for invalid values', function() {\n      expect(toTRBLCorners({topLeft: 'foo', topRight: 'foo', bottomLeft: 'foo', bottomRight: 'foo'})).toEqual(\n        {topLeft: 0, topRight: 0, bottomLeft: 0, bottomRight: 0});\n      expect(toTRBLCorners({topLeft: null, topRight: null, bottomLeft: null, bottomRight: null})).toEqual(\n        {topLeft: 0, topRight: 0, bottomLeft: 0, bottomRight: 0});\n      expect(toTRBLCorners({})).toEqual(\n        {topLeft: 0, topRight: 0, bottomLeft: 0, bottomRight: 0});\n      expect(toTRBLCorners('foo')).toEqual(\n        {topLeft: 0, topRight: 0, bottomLeft: 0, bottomRight: 0});\n      expect(toTRBLCorners(null)).toEqual(\n        {topLeft: 0, topRight: 0, bottomLeft: 0, bottomRight: 0});\n      expect(toTRBLCorners(undefined)).toEqual(\n        {topLeft: 0, topRight: 0, bottomLeft: 0, bottomRight: 0});\n    });\n  });\n\n  describe('toPadding', function() {\n    it ('should support number values', function() {\n      expect(toPadding(4)).toEqual(\n        {top: 4, right: 4, bottom: 4, left: 4, height: 8, width: 8});\n      expect(toPadding(4.5)).toEqual(\n        {top: 4.5, right: 4.5, bottom: 4.5, left: 4.5, height: 9, width: 9});\n    });\n    it ('should support string values', function() {\n      expect(toPadding('4')).toEqual(\n        {top: 4, right: 4, bottom: 4, left: 4, height: 8, width: 8});\n      expect(toPadding('4.5')).toEqual(\n        {top: 4.5, right: 4.5, bottom: 4.5, left: 4.5, height: 9, width: 9});\n    });\n    it ('should support object values', function() {\n      expect(toPadding({top: 1, right: 2, bottom: 3, left: 4})).toEqual(\n        {top: 1, right: 2, bottom: 3, left: 4, height: 4, width: 6});\n      expect(toPadding({top: 1.5, right: 2.5, bottom: 3.5, left: 4.5})).toEqual(\n        {top: 1.5, right: 2.5, bottom: 3.5, left: 4.5, height: 5, width: 7});\n      expect(toPadding({top: '1', right: '2', bottom: '3', left: '4'})).toEqual(\n        {top: 1, right: 2, bottom: 3, left: 4, height: 4, width: 6});\n    });\n    it ('should fallback to 0 for invalid values', function() {\n      expect(toPadding({top: 'foo', right: 'foo', bottom: 'foo', left: 'foo'})).toEqual(\n        {top: 0, right: 0, bottom: 0, left: 0, height: 0, width: 0});\n      expect(toPadding({top: null, right: null, bottom: null, left: null})).toEqual(\n        {top: 0, right: 0, bottom: 0, left: 0, height: 0, width: 0});\n      expect(toPadding({})).toEqual(\n        {top: 0, right: 0, bottom: 0, left: 0, height: 0, width: 0});\n      expect(toPadding('foo')).toEqual(\n        {top: 0, right: 0, bottom: 0, left: 0, height: 0, width: 0});\n      expect(toPadding(null)).toEqual(\n        {top: 0, right: 0, bottom: 0, left: 0, height: 0, width: 0});\n      expect(toPadding(undefined)).toEqual(\n        {top: 0, right: 0, bottom: 0, left: 0, height: 0, width: 0});\n    });\n    it('should support x / y shorthands', function() {\n      expect(toPadding({x: 1, y: 2})).toEqual(\n        {top: 2, right: 1, bottom: 2, left: 1, height: 4, width: 2});\n      expect(toPadding({x: 1, left: 0})).toEqual(\n        {top: 0, right: 1, bottom: 0, left: 0, height: 0, width: 1});\n      expect(toPadding({y: 5, bottom: 0})).toEqual(\n        {top: 5, right: 0, bottom: 0, left: 0, height: 5, width: 0});\n    });\n  });\n\n  describe('toFont', function() {\n    it('should return a font with default values', function() {\n      const defaultFont = Object.assign({}, Chart.defaults.font);\n\n      Object.assign(Chart.defaults.font, {\n        family: 'foobar',\n        size: 42,\n        style: 'oblique 9deg',\n        lineHeight: 1.5\n      });\n\n      expect(toFont({})).toEqual({\n        family: 'foobar',\n        lineHeight: 63,\n        size: 42,\n        string: 'oblique 9deg 42px foobar',\n        style: 'oblique 9deg',\n        weight: null\n      });\n\n      Object.assign(Chart.defaults.font, defaultFont);\n    });\n    it ('should return a font with given values', function() {\n      expect(toFont({\n        family: 'bla',\n        lineHeight: 8,\n        size: 21,\n        style: 'oblique -90deg'\n      })).toEqual({\n        family: 'bla',\n        lineHeight: 8 * 21,\n        size: 21,\n        string: 'oblique -90deg 21px bla',\n        style: 'oblique -90deg',\n        weight: null\n      });\n    });\n    it ('should handle a string font size', function() {\n      expect(toFont({\n        family: 'bla',\n        lineHeight: 8,\n        size: '21',\n        style: 'italic'\n      })).toEqual({\n        family: 'bla',\n        lineHeight: 8 * 21,\n        size: 21,\n        string: 'italic 21px bla',\n        style: 'italic',\n        weight: null\n      });\n    });\n    it('should return null as a font string if size or family are missing', function() {\n      const fontFamily = Chart.defaults.font.family;\n      const fontSize = Chart.defaults.font.size;\n      delete Chart.defaults.font.family;\n      delete Chart.defaults.font.size;\n\n      expect(toFont({\n        style: 'italic',\n        size: 12\n      }).string).toBeNull();\n      expect(toFont({\n        style: 'italic',\n        family: 'serif'\n      }).string).toBeNull();\n\n      Chart.defaults.font.family = fontFamily;\n      Chart.defaults.font.size = fontSize;\n    });\n    it('font.style should be optional for font strings', function() {\n      const fontStyle = Chart.defaults.font.style;\n      delete Chart.defaults.font.style;\n\n      expect(toFont({\n        size: 12,\n        family: 'serif'\n      }).string).toBe('12px serif');\n\n      Chart.defaults.font.style = fontStyle;\n    });\n  });\n\n  describe('resolve', function() {\n    it ('should fallback to the first defined input', function() {\n      expect(resolve([42])).toBe(42);\n      expect(resolve([42, 'foo'])).toBe(42);\n      expect(resolve([undefined, 42, 'foo'])).toBe(42);\n      expect(resolve([42, 'foo', undefined])).toBe(42);\n      expect(resolve([undefined])).toBe(undefined);\n    });\n    it ('should correctly handle empty values (null, 0, \"\")', function() {\n      expect(resolve([0, 'foo'])).toBe(0);\n      expect(resolve(['', 'foo'])).toBe('');\n      expect(resolve([null, 'foo'])).toBe(null);\n    });\n    it ('should support indexable options if index is provided', function() {\n      var input = [42, 'foo', 'bar'];\n      expect(resolve([input], undefined, 0)).toBe(42);\n      expect(resolve([input], undefined, 1)).toBe('foo');\n      expect(resolve([input], undefined, 2)).toBe('bar');\n    });\n    it ('should fallback if an indexable option value is undefined', function() {\n      var input = [42, undefined, 'bar'];\n      expect(resolve([input], undefined, 1)).toBe(undefined);\n      expect(resolve([input, 'foo'], undefined, 1)).toBe('foo');\n    });\n    it ('should loop if an indexable option index is out of bounds', function() {\n      var input = [42, undefined, 'bar'];\n      expect(resolve([input], undefined, 3)).toBe(42);\n      expect(resolve([input, 'foo'], undefined, 4)).toBe('foo');\n      expect(resolve([input, 'foo'], undefined, 5)).toBe('bar');\n    });\n    it ('should not handle indexable options if index is undefined', function() {\n      var array = [42, 'foo', 'bar'];\n      expect(resolve([array])).toBe(array);\n      expect(resolve([array], undefined, undefined)).toBe(array);\n    });\n    it ('should support scriptable options if context is provided', function() {\n      var input = function(context) {\n        return context.v * 2;\n      };\n      expect(resolve([42], {v: 42})).toBe(42);\n      expect(resolve([input], {v: 42})).toBe(84);\n    });\n    it ('should fallback if a scriptable option returns undefined', function() {\n      var input = function() {};\n      expect(resolve([input], {v: 42})).toBe(undefined);\n      expect(resolve([input, 'foo'], {v: 42})).toBe('foo');\n      expect(resolve([input, undefined, 'foo'], {v: 42})).toBe('foo');\n    });\n    it ('should not handle scriptable options if context is undefined', function() {\n      var input = function(context) {\n        return context.v * 2;\n      };\n      expect(resolve([input])).toBe(input);\n      expect(resolve([input], undefined)).toBe(input);\n    });\n    it ('should handle scriptable and indexable option', function() {\n      var input = function(context) {\n        return [context.v, undefined, 'bar'];\n      };\n      expect(resolve([input, 'foo'], {v: 42}, 0)).toBe(42);\n      expect(resolve([input, 'foo'], {v: 42}, 1)).toBe('foo');\n      expect(resolve([input, 'foo'], {v: 42}, 5)).toBe('bar');\n      expect(resolve([input, ['foo', 'bar']], {v: 42}, 1)).toBe('bar');\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/helpers.segment.tests.js",
    "content": "const {_boundSegment} = Chart.helpers;\n\ndescribe('helpers.segments', function() {\n  describe('_boundSegment', function() {\n    const points = [{x: 10, y: 1}, {x: 20, y: 2}, {x: 30, y: 3}];\n    const segment = {start: 0, end: 2, loop: false};\n\n    it('should not find segment from before the line', function() {\n      expect(_boundSegment(segment, points, {property: 'x', start: 5, end: 9.99999})).toEqual([]);\n    });\n\n    it('should not find segment from after the line', function() {\n      expect(_boundSegment(segment, points, {property: 'x', start: 30.00001, end: 800})).toEqual([]);\n    });\n\n    it('should find segment when starting before line', function() {\n      expect(_boundSegment(segment, points, {property: 'x', start: 5, end: 15})).toEqual([{start: 0, end: 1, loop: false, style: undefined}]);\n    });\n\n    it('should find segment directly on point', function() {\n      expect(_boundSegment(segment, points, {property: 'x', start: 10, end: 10})).toEqual([{start: 0, end: 0, loop: false, style: undefined}]);\n    });\n\n    it('should find segment from range between points', function() {\n      expect(_boundSegment(segment, points, {property: 'x', start: 11, end: 14})).toEqual([{start: 0, end: 1, loop: false, style: undefined}]);\n    });\n\n    it('should find segment from point between points', function() {\n      expect(_boundSegment(segment, points, {property: 'x', start: 22, end: 22})).toEqual([{start: 1, end: 2, loop: false, style: undefined}]);\n    });\n\n    it('should find whole segment', function() {\n      expect(_boundSegment(segment, points, {property: 'x', start: 0, end: 50})).toEqual([{start: 0, end: 2, loop: false, style: undefined}]);\n    });\n\n    it('should find correct segment from near points', function() {\n      expect(_boundSegment(segment, points, {property: 'x', start: 10.001, end: 29.999})).toEqual([{start: 0, end: 2, loop: false, style: undefined}]);\n    });\n\n    it('should find segment from after the line', function() {\n      expect(_boundSegment(segment, points, {property: 'x', start: 25, end: 35})).toEqual([{start: 1, end: 2, loop: false, style: undefined}]);\n    });\n\n    it('should find multiple segments', function() {\n      const points2 = [{x: 0, y: 100}, {x: 1, y: 50}, {x: 2, y: 70}, {x: 4, y: 80}, {x: 5, y: -100}];\n      expect(_boundSegment({start: 0, end: 4, loop: false}, points2, {property: 'y', start: 60, end: 60})).toEqual([\n        {start: 0, end: 1, loop: false, style: undefined},\n        {start: 1, end: 2, loop: false, style: undefined},\n        {start: 3, end: 4, loop: false, style: undefined},\n      ]);\n    });\n\n    it('should find correct segments when there are multiple points with same property value', function() {\n      const repeatedPoints = [{x: 1, y: 5}, {x: 1, y: 6}, {x: 2, y: 5}, {x: 2, y: 6}, {x: 3, y: 5}, {x: 3, y: 6}, {x: 3, y: 7}];\n      expect(_boundSegment({start: 0, end: 6, loop: false}, repeatedPoints, {property: 'x', start: 1, end: 1.1})).toEqual([{start: 0, end: 2, loop: false, style: undefined}]);\n      expect(_boundSegment({start: 0, end: 6, loop: false}, repeatedPoints, {property: 'x', start: 2, end: 2.1})).toEqual([{start: 2, end: 4, loop: false, style: undefined}]);\n      expect(_boundSegment({start: 0, end: 6, loop: false}, repeatedPoints, {property: 'x', start: 2, end: 3.1})).toEqual([{start: 2, end: 6, loop: false, style: undefined}]);\n      expect(_boundSegment({start: 0, end: 6, loop: false}, repeatedPoints, {property: 'x', start: 0, end: 8})).toEqual([{start: 0, end: 6, loop: false, style: undefined}]);\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/mixed.tests.js",
    "content": "describe('Mixed charts', function() {\n  describe('auto', jasmine.fixture.specs('mixed'));\n\n  it('shoud be constructed with doughnuts chart', function() {\n    const chart = window.acquireChart({\n      data: {\n        datasets: [{\n          type: 'line',\n          data: [10, 20, 30, 40],\n        }, {\n          type: 'doughnut',\n          data: [10, 20, 30, 50],\n        }\n        ],\n        labels: []\n      }\n    });\n\n    const meta0 = chart.getDatasetMeta(0);\n    expect(meta0.type).toEqual('line');\n    const meta1 = chart.getDatasetMeta(1);\n    expect(meta1.type).toEqual('doughnut');\n  });\n\n  it('shoud be constructed with pie chart', function() {\n    const chart = window.acquireChart({\n      data: {\n        datasets: [{\n          type: 'bar',\n          data: [10, 20, 30, 40],\n        }, {\n          type: 'pie',\n          data: [10, 20, 30, 50],\n        }\n        ],\n        labels: []\n      }\n    });\n\n    const meta0 = chart.getDatasetMeta(0);\n    expect(meta0.type).toEqual('bar');\n    const meta1 = chart.getDatasetMeta(1);\n    expect(meta1.type).toEqual('pie');\n  });\n\n});\n"
  },
  {
    "path": "test/specs/platform.basic.tests.js",
    "content": "describe('Platform.basic', function() {\n\n  it('should automatically choose the BasicPlatform for offscreen canvas', function() {\n    const chart = acquireChart({type: 'line'}, {useOffscreenCanvas: true});\n\n    expect(chart.platform).toBeInstanceOf(Chart.platforms.BasicPlatform);\n\n    chart.destroy();\n  });\n\n  it('should disable animations', function() {\n    const chart = acquireChart({type: 'line', options: {animation: {}}}, {useOffscreenCanvas: true});\n\n    expect(chart.options.animation).toEqual(false);\n\n    chart.destroy();\n  });\n\n\n  it('supports choosing the BasicPlatform in a web worker', function(done) {\n    const canvas = document.createElement('canvas');\n    if (!canvas.transferControlToOffscreen) {\n      pending();\n    }\n    const offscreenCanvas = canvas.transferControlToOffscreen();\n\n    const worker = new Worker('base/test/BasicChartWebWorker.js');\n    worker.onmessage = (event) => {\n      worker.terminate();\n      const {type, errorMessage} = event.data;\n      if (type === 'error') {\n        done.fail(errorMessage);\n      } else if (type === 'success') {\n        expect(type).toEqual('success');\n        done();\n      } else {\n        done.fail('invalid message type sent by worker: ' + type);\n      }\n    };\n\n    worker.postMessage({type: 'initialize', canvas: offscreenCanvas}, [offscreenCanvas]);\n  });\n\n  describe('with offscreenCanvas', function() {\n    it('supports laying out a simple chart', function() {\n      const chart = acquireChart({\n        type: 'bar',\n        data: {\n          datasets: [\n            {data: [10, 5, 0, 25, 78, -10]}\n          ],\n          labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5', 'tick6']\n        }\n      }, {\n        canvas: {\n          height: 150,\n          width: 250\n        },\n        useOffscreenCanvas: true,\n      });\n\n      expect(chart.platform).toBeInstanceOf(Chart.platforms.BasicPlatform);\n\n      expect(chart.chartArea.bottom).toBeCloseToPixel(120);\n      expect(chart.chartArea.left).toBeCloseToPixel(31);\n      expect(chart.chartArea.right).toBeCloseToPixel(250);\n      expect(chart.chartArea.top).toBeCloseToPixel(32);\n    });\n\n    it('supports resizing a chart', function() {\n      const chart = acquireChart({\n        type: 'bar',\n        data: {\n          datasets: [\n            {data: [10, 5, 0, 25, 78, -10]}\n          ],\n          labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5', 'tick6']\n        }\n      }, {\n        canvas: {\n          height: 150,\n          width: 250\n        },\n        useOffscreenCanvas: true,\n      });\n\n      expect(chart.platform).toBeInstanceOf(Chart.platforms.BasicPlatform);\n\n      const canvasElement = chart.canvas;\n      canvasElement.height = 200;\n      canvasElement.width = 300;\n      chart.resize();\n\n      expect(chart.chartArea.bottom).toBeCloseToPixel(150);\n      expect(chart.chartArea.left).toBeCloseToPixel(31);\n      expect(chart.chartArea.right).toBeCloseToPixel(300);\n      expect(chart.chartArea.top).toBeCloseToPixel(32);\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/platform.dom.tests.js",
    "content": "const DomPlatform = Chart.platforms.DomPlatform;\n\ndescribe('Platform.dom', function() {\n\n  describe('context acquisition', function() {\n    var canvasId = 'chartjs-canvas';\n\n    beforeEach(function() {\n      var canvas = document.createElement('canvas');\n      canvas.setAttribute('id', canvasId);\n      window.document.body.appendChild(canvas);\n    });\n\n    afterEach(function() {\n      document.getElementById(canvasId).remove();\n    });\n\n    it('should use the DomPlatform by default', function() {\n      var chart = acquireChart({type: 'line'});\n\n      expect(chart.platform).toBeInstanceOf(Chart.platforms.DomPlatform);\n\n      chart.destroy();\n    });\n\n    // see https://github.com/chartjs/Chart.js/issues/2807\n    it('should gracefully handle invalid item', function() {\n      var chart = new Chart('foobar');\n\n      expect(chart).not.toBeValidChart();\n\n      chart.destroy();\n    });\n\n    it('should accept a DOM element id', function() {\n      var canvas = document.getElementById(canvasId);\n      var chart = new Chart(canvasId);\n\n      expect(chart).toBeValidChart();\n      expect(chart.canvas).toBe(canvas);\n      expect(chart.ctx).toBe(canvas.getContext('2d'));\n\n      chart.destroy();\n    });\n\n    it('should accept a canvas element', function() {\n      var canvas = document.getElementById(canvasId);\n      var chart = new Chart(canvas);\n\n      expect(chart).toBeValidChart();\n      expect(chart.canvas).toBe(canvas);\n      expect(chart.ctx).toBe(canvas.getContext('2d'));\n\n      chart.destroy();\n    });\n\n    it('should accept a canvas context2D', function() {\n      var canvas = document.getElementById(canvasId);\n      var context = canvas.getContext('2d');\n      var chart = new Chart(context);\n\n      expect(chart).toBeValidChart();\n      expect(chart.canvas).toBe(canvas);\n      expect(chart.ctx).toBe(context);\n\n      chart.destroy();\n    });\n\n    it('should accept an array containing canvas', function() {\n      var canvas = document.getElementById(canvasId);\n      var chart = new Chart([canvas]);\n\n      expect(chart).toBeValidChart();\n      expect(chart.canvas).toBe(canvas);\n      expect(chart.ctx).toBe(canvas.getContext('2d'));\n\n      chart.destroy();\n    });\n\n    it('should accept a canvas from an iframe', function(done) {\n      var iframe = document.createElement('iframe');\n      iframe.onload = function() {\n        var doc = iframe.contentDocument;\n        doc.body.innerHTML += '<canvas id=\"chart\"></canvas>';\n        var canvas = doc.getElementById('chart');\n        var chart = new Chart(canvas);\n\n        expect(chart).toBeValidChart();\n        expect(chart.canvas).toBe(canvas);\n        expect(chart.ctx).toBe(canvas.getContext('2d'));\n\n        chart.destroy();\n        canvas.remove();\n        iframe.remove();\n\n        done();\n      };\n\n      document.body.appendChild(iframe);\n    });\n  });\n\n  describe('config.options.aspectRatio', function() {\n    it('should use default \"global\" aspect ratio for render and display sizes', function() {\n      var chart = acquireChart({\n        options: {\n          responsive: false\n        }\n      }, {\n        canvas: {\n          style: 'width: 620px'\n        }\n      });\n\n      expect(chart).toBeChartOfSize({\n        dw: 620, dh: 310,\n        rw: 620, rh: 310,\n      });\n    });\n\n    it('should use default \"chart\" aspect ratio for render and display sizes', function() {\n      var ratio = Chart.overrides.doughnut.aspectRatio;\n      Chart.overrides.doughnut.aspectRatio = 1;\n\n      var chart = acquireChart({\n        type: 'doughnut',\n        options: {\n          responsive: false\n        }\n      }, {\n        canvas: {\n          style: 'width: 425px'\n        }\n      });\n\n      Chart.overrides.doughnut.aspectRatio = ratio;\n\n      expect(chart).toBeChartOfSize({\n        dw: 425, dh: 425,\n        rw: 425, rh: 425,\n      });\n    });\n\n    it('should use \"user\" aspect ratio for render and display sizes', function() {\n      var chart = acquireChart({\n        options: {\n          responsive: false,\n          aspectRatio: 3\n        }\n      }, {\n        canvas: {\n          style: 'width: 405px'\n        }\n      });\n\n      expect(chart).toBeChartOfSize({\n        dw: 405, dh: 135,\n        rw: 405, rh: 135,\n      });\n    });\n\n    it('should not apply aspect ratio when height specified', function() {\n      var chart = acquireChart({\n        options: {\n          responsive: false,\n          aspectRatio: 3\n        }\n      }, {\n        canvas: {\n          style: 'width: 400px; height: 410px'\n        }\n      });\n\n      expect(chart).toBeChartOfSize({\n        dw: 400, dh: 410,\n        rw: 400, rh: 410,\n      });\n    });\n  });\n\n  describe('config.options.responsive: false', function() {\n    it('should use default canvas size for render and display sizes', function() {\n      var chart = acquireChart({\n        options: {\n          responsive: false\n        }\n      }, {\n        canvas: {\n          style: ''\n        }\n      });\n\n      expect(chart).toBeChartOfSize({\n        dw: 300, dh: 150,\n        rw: 300, rh: 150,\n      });\n    });\n\n    it('should use canvas attributes for render and display sizes', function() {\n      var chart = acquireChart({\n        options: {\n          responsive: false\n        }\n      }, {\n        canvas: {\n          style: '',\n          width: 305,\n          height: 245,\n        }\n      });\n\n      expect(chart).toBeChartOfSize({\n        dw: 305, dh: 245,\n        rw: 305, rh: 245,\n      });\n    });\n\n    it('should use canvas style for render and display sizes (if no attributes)', function() {\n      var chart = acquireChart({\n        options: {\n          responsive: false\n        }\n      }, {\n        canvas: {\n          style: 'width: 345px; height: 125px'\n        }\n      });\n\n      expect(chart).toBeChartOfSize({\n        dw: 345, dh: 125,\n        rw: 345, rh: 125,\n      });\n    });\n\n    it('should use attributes for the render size and style for the display size', function() {\n      var chart = acquireChart({\n        options: {\n          responsive: false\n        }\n      }, {\n        canvas: {\n          style: 'width: 345px; height: 125px;',\n          width: 165,\n          height: 85,\n        }\n      });\n\n      expect(chart).toBeChartOfSize({\n        dw: 345, dh: 125,\n        rw: 165, rh: 85,\n      });\n    });\n\n    // https://github.com/chartjs/Chart.js/issues/3860\n    it('should support decimal display width and/or height', function() {\n      var chart = acquireChart({\n        options: {\n          responsive: false\n        }\n      }, {\n        canvas: {\n          style: 'width: 345.42px; height: 125.42px;'\n        }\n      });\n\n      expect(chart).toBeChartOfSize({\n        dw: 345, dh: 125,\n        rw: 345, rh: 125,\n      });\n    });\n  });\n\n  describe('config.options.responsive: true (maintainAspectRatio: true)', function() {\n    it('should fit parent using aspect ratio to calculate size', function() {\n      var chart = acquireChart({\n        options: {\n          responsive: true,\n          maintainAspectRatio: true\n        }\n      }, {\n        canvas: {\n          style: 'width: 150px; height: 245px'\n        },\n        wrapper: {\n          style: 'width: 300px; height: 350px'\n        }\n      });\n\n      waitForResize(chart, () => {\n        expect(chart).toBeChartOfSize({\n          dw: 214, dh: 350,\n          rw: 214, rh: 350,\n        });\n      });\n    });\n  });\n\n  describe('controller.destroy', function() {\n    it('should reset context to default values', function() {\n      var wrapper = document.createElement('div');\n      var canvas = document.createElement('canvas');\n      wrapper.appendChild(canvas);\n      window.document.body.appendChild(wrapper);\n      var chart = new Chart(canvas, {});\n      var context = chart.ctx;\n\n      chart.destroy();\n\n      // https://www.w3.org/TR/2dcontext/#conformance-requirements\n      Chart.helpers.each({\n        fillStyle: '#000000',\n        font: '10px sans-serif',\n        lineJoin: 'miter',\n        lineCap: 'butt',\n        lineWidth: 1,\n        miterLimit: 10,\n        shadowBlur: 0,\n        shadowColor: 'rgba(0, 0, 0, 0)',\n        shadowOffsetX: 0,\n        shadowOffsetY: 0,\n        strokeStyle: '#000000',\n        textAlign: 'start',\n        textBaseline: 'alphabetic'\n      }, function(value, key) {\n        expect(context[key]).toBe(value);\n      });\n\n      wrapper.parentNode.removeChild(wrapper);\n    });\n\n    it('should restore canvas initial values', function(done) {\n      var wrapper = document.createElement('div');\n      var canvas = document.createElement('canvas');\n\n      canvas.setAttribute('width', 180);\n      canvas.setAttribute('style', 'width: 512px; height: 480px');\n      wrapper.setAttribute('style', 'width: 450px; height: 450px; position: relative');\n\n      wrapper.appendChild(canvas);\n      window.document.body.appendChild(wrapper);\n\n      var chart = new Chart(canvas.getContext('2d'), {\n        options: {\n          responsive: true,\n          maintainAspectRatio: false\n        }\n      });\n\n      waitForResize(chart, function() {\n        expect(chart).toBeChartOfSize({\n          dw: 475, dh: 450,\n          rw: 475, rh: 450,\n        });\n\n        chart.destroy();\n\n        expect(canvas.getAttribute('width')).toBe('180');\n        expect(canvas.getAttribute('height')).toBe(null);\n        expect(canvas.style.width).toBe('512px');\n        expect(canvas.style.height).toBe('480px');\n        expect(canvas.style.display).toBe('');\n\n        wrapper.parentNode.removeChild(wrapper);\n        done();\n      });\n      wrapper.style.width = '475px';\n    });\n  });\n\n  describe('event handling', function() {\n    it('should notify plugins about events', async function() {\n      var notifiedEvent;\n      var plugin = {\n        afterEvent: function(chart, args) {\n          notifiedEvent = args.event;\n        }\n      };\n      var chart = acquireChart({\n        type: 'line',\n        data: {\n          labels: ['A', 'B', 'C', 'D'],\n          datasets: [{\n            data: [10, 20, 30, 100]\n          }]\n        },\n        options: {\n          responsive: true\n        },\n        plugins: [plugin]\n      });\n\n      await jasmine.triggerMouseEvent(chart, 'click', {\n        x: chart.width / 2,\n        y: chart.height / 2\n      });\n      // Check that notifiedEvent is correct\n      expect(notifiedEvent).not.toBe(undefined);\n\n      // Is type correctly translated\n      expect(notifiedEvent.type).toBe('click');\n\n      // Relative Position\n      expect(notifiedEvent.x).toBeCloseToPixel(chart.width / 2);\n      expect(notifiedEvent.y).toBeCloseToPixel(chart.height / 2);\n    });\n  });\n\n  describe('isAttached', function() {\n    it('should detect detached when canvas is attached to DOM', function() {\n      var platform = new DomPlatform();\n      var canvas = document.createElement('canvas');\n      var div = document.createElement('div');\n      var anotherDiv = document.createElement('div');\n\n      expect(platform.isAttached(canvas)).toEqual(false);\n      div.appendChild(canvas);\n      expect(platform.isAttached(canvas)).toEqual(false);\n      anotherDiv.appendChild(div);\n      expect(platform.isAttached(canvas)).toEqual(false);\n      document.body.appendChild(anotherDiv);\n\n      expect(platform.isAttached(canvas)).toEqual(true);\n\n      anotherDiv.removeChild(div);\n      expect(platform.isAttached(canvas)).toEqual(false);\n      div.removeChild(canvas);\n      expect(platform.isAttached(canvas)).toEqual(false);\n      document.body.removeChild(anotherDiv);\n      expect(platform.isAttached(canvas)).toEqual(false);\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/plugin.colors.tests.js",
    "content": "describe('Plugin.colors', () => {\n  describe('auto', jasmine.fixture.specs('plugin.colors'));\n\n  describe('Plugin.colors.chartDefaults', () => {\n    beforeAll(() => {\n      Chart.defaults.backgroundColor = ['green', 'yellow'];\n    });\n\n    afterAll(() => {\n      Chart.defaults.backgroundColor = 'rgba(0,0,0,0.1)';\n    });\n\n    it('should not use colors plugin when chart defaults are given', () => {\n      const chart = window.acquireChart({\n        type: 'bar',\n        data: {\n          datasets: [{\n            data: [1, 10],\n            label: 'dataset1'\n          }],\n          labels: ['label1', 'label2']\n        },\n        options: {\n          plugins: {\n            colors: {\n              enabled: true\n            }\n          }\n        }\n      });\n\n      const meta = chart.getDatasetMeta(0);\n      expect(meta.data[0].options.backgroundColor).toBe('green');\n      expect(meta.data[1].options.backgroundColor).toBe('yellow');\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/plugin.decimation.tests.js",
    "content": "describe('Plugin.decimation', function() {\n\n  describe('auto', jasmine.fixture.specs('plugin.decimation'));\n\n  describe('lttb', function() {\n    const originalData = [\n      {x: 0, y: 0},\n      {x: 1, y: 1},\n      {x: 2, y: 2},\n      {x: 3, y: 3},\n      {x: 4, y: 4},\n      {x: 5, y: 5},\n      {x: 6, y: 6},\n      {x: 7, y: 7},\n      {x: 8, y: 8},\n      {x: 9, y: 9}];\n\n    it('should draw all element if sample is greater than data based on canvas width', function() {\n      var chart = window.acquireChart({\n        type: 'line',\n        data: {\n          datasets: [{\n            data: originalData,\n            label: 'dataset1'\n          }]\n        },\n        scales: {\n          x: {\n            type: 'linear',\n            min: 0,\n            max: 9\n          }\n        },\n        options: {\n          plugins: {\n            decimation: {\n              enabled: true,\n              algorithm: 'lttb',\n              samples: 100\n            }\n          }\n        }\n      }, {\n        canvas: {\n          height: 1,\n          width: 1\n        },\n        wrapper: {\n          height: 1,\n          width: 1\n        }\n      });\n\n      expect(chart.data.datasets[0].data.length).toBe(10);\n    });\n\n    it('should draw the specified number of elements based on canvas width', function() {\n      var chart = window.acquireChart({\n        type: 'line',\n        data: {\n          datasets: [{\n            data: originalData,\n            label: 'dataset1'\n          }]\n        },\n        options: {\n          parsing: false,\n          scales: {\n            x: {\n              type: 'linear',\n              min: 0,\n              max: 9\n            }\n          },\n          plugins: {\n            decimation: {\n              enabled: true,\n              algorithm: 'lttb',\n              samples: 7\n            }\n          }\n        }\n      }, {\n        canvas: {\n          height: 1,\n          width: 1\n        },\n        wrapper: {\n          height: 1,\n          width: 1\n        }\n      });\n\n      expect(chart.data.datasets[0].data.length).toBe(7);\n    });\n\n    it('should draw the specified number of elements based on threshold', function() {\n      var chart = window.acquireChart({\n        type: 'line',\n        data: {\n          datasets: [{\n            data: originalData,\n            label: 'dataset1'\n          }]\n        },\n        options: {\n          parsing: false,\n          scales: {\n            x: {\n              type: 'linear'\n            }\n          },\n          plugins: {\n            decimation: {\n              enabled: true,\n              algorithm: 'lttb',\n              samples: 5,\n              threshold: 7\n            }\n          }\n        }\n      }, {\n        canvas: {\n          height: 100,\n          width: 100\n        },\n        wrapper: {\n          height: 100,\n          width: 100\n        }\n      });\n\n      expect(chart.data.datasets[0].data.length).toBe(5);\n    });\n\n    it('should draw all element only in range', function() {\n      var chart = window.acquireChart({\n        type: 'line',\n        data: {\n          datasets: [{\n            data: originalData,\n            label: 'dataset1'\n          }]\n        },\n        options: {\n          parsing: false,\n          scales: {\n            x: {\n              type: 'linear',\n              min: 3,\n              max: 6\n            }\n          },\n          plugins: {\n            decimation: {\n              enabled: true,\n              algorithm: 'lttb',\n              samples: 7\n            }\n          }\n        }\n      }, {\n        canvas: {\n          height: 1,\n          width: 1\n        },\n        wrapper: {\n          height: 1,\n          width: 1\n        }\n      });\n\n      // Data range is 4 (3->6) and the first point is added\n      const expectedPoints = 5;\n      expect(chart.data.datasets[0].data.length).toBe(expectedPoints);\n      expect(chart.data.datasets[0].data[0].x).toBe(originalData[2].x);\n      expect(chart.data.datasets[0].data[1].x).toBe(originalData[3].x);\n      expect(chart.data.datasets[0].data[2].x).toBe(originalData[4].x);\n      expect(chart.data.datasets[0].data[3].x).toBe(originalData[5].x);\n      expect(chart.data.datasets[0].data[4].x).toBe(originalData[6].x);\n    });\n\n    it('should not crash with uneven points', function() {\n      const data = [];\n      for (let i = 0; i < 15552; i++) {\n        data.push({x: i, y: i});\n      }\n\n      function createChart() {\n        return window.acquireChart({\n          type: 'line',\n          data: {\n            datasets: [{\n              data\n            }]\n          },\n          options: {\n            devicePixelRatio: 1.25,\n            parsing: false,\n            scales: {\n              x: {\n                type: 'linear'\n              }\n            },\n            plugins: {\n              decimation: {\n                enabled: true,\n                algorithm: 'lttb'\n              }\n            }\n          }\n        }, {\n          canvas: {width: 511, height: 511},\n        });\n      }\n      expect(createChart).not.toThrow();\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/plugin.filler.tests.js",
    "content": "describe('Plugin.filler', function() {\n  const fillerPluginRegisterWarning = 'Tried to use the \\'fill\\' option without the \\'Filler\\' plugin enabled. Please import and register the \\'Filler\\' plugin and make sure it is not disabled in the options';\n  function decodedFillValues(chart) {\n    return chart.data.datasets.map(function(dataset, index) {\n      var meta = chart.getDatasetMeta(index) || {};\n      expect(meta.$filler).toBeDefined();\n      return meta.$filler.fill;\n    });\n  }\n\n  describe('auto', jasmine.fixture.specs('plugin.filler'));\n\n  describe('dataset.fill', function() {\n    it('Should show a warning when trying to use the filler plugin in the dataset when it\\'s not registered', function() {\n      spyOn(console, 'warn');\n      Chart.unregister(Chart.Filler);\n      window.acquireChart({\n        type: 'line',\n        data: {\n          datasets: [{\n            fill: true\n          }]\n        }\n      });\n\n      expect(console.warn).toHaveBeenCalledWith(fillerPluginRegisterWarning);\n\n      Chart.register(Chart.Filler);\n    });\n\n    it('Should show a warning when trying to use the filler plugin in the root options when it\\'s not registered', function() {\n      // jasmine.createSpy('warn');\n      spyOn(console, 'warn');\n      Chart.unregister(Chart.Filler);\n      window.acquireChart({\n        type: 'line',\n        data: {\n          datasets: [{\n          }]\n        },\n        options: {\n          fill: true\n        }\n      });\n\n      expect(console.warn).toHaveBeenCalledWith(fillerPluginRegisterWarning);\n\n      Chart.register(Chart.Filler);\n    });\n\n    it('should support boundaries', function() {\n      var chart = window.acquireChart({\n        type: 'line',\n        data: {\n          datasets: [\n            {fill: 'origin'},\n            {fill: 'start'},\n            {fill: 'end'},\n          ]\n        }\n      });\n\n      expect(decodedFillValues(chart)).toEqual(['origin', 'start', 'end']);\n    });\n\n    it('should support absolute dataset index', function() {\n      var chart = window.acquireChart({\n        type: 'line',\n        data: {\n          datasets: [\n            {fill: 1},\n            {fill: 3},\n            {fill: 0},\n            {fill: 2},\n          ]\n        }\n      });\n\n      expect(decodedFillValues(chart)).toEqual([1, 3, 0, 2]);\n    });\n\n    it('should support relative dataset index', function() {\n      var chart = window.acquireChart({\n        type: 'line',\n        data: {\n          datasets: [\n            {fill: '+3'},\n            {fill: '-1'},\n            {fill: '+1'},\n            {fill: '-2'},\n          ]\n        }\n      });\n\n      expect(decodedFillValues(chart)).toEqual([\n        3, // 0 + 3\n        0, // 1 - 1\n        3, // 2 + 1\n        1, // 3 - 2\n      ]);\n    });\n\n    it('should handle default fill when true (origin)', function() {\n      var chart = window.acquireChart({\n        type: 'line',\n        data: {\n          datasets: [\n            {fill: true},\n            {fill: false},\n          ]\n        }\n      });\n\n      expect(decodedFillValues(chart)).toEqual(['origin', false]);\n    });\n\n    it('should ignore self dataset index', function() {\n      var chart = window.acquireChart({\n        type: 'line',\n        data: {\n          datasets: [\n            {fill: 0},\n            {fill: '-0'},\n            {fill: '+0'},\n            {fill: 3},\n          ]\n        }\n      });\n\n      expect(decodedFillValues(chart)).toEqual([\n        false, // 0 === 0\n        false, // 1 === 1 - 0\n        false, // 2 === 2 + 0\n        false, // 3 === 3\n      ]);\n    });\n\n    it('should ignore out of bounds dataset index', function() {\n      var chart = window.acquireChart({\n        type: 'line',\n        data: {\n          datasets: [\n            {fill: -2},\n            {fill: 4},\n            {fill: '-3'},\n            {fill: '+1'},\n          ]\n        }\n      });\n\n      expect(decodedFillValues(chart)).toEqual([\n        false, // 0 - 2 < 0\n        false, // 1 + 4 > 3\n        false, // 2 - 3 < 0\n        false, // 3 + 1 > 3\n      ]);\n    });\n\n    it('should ignore invalid values', function() {\n      var chart = window.acquireChart({\n        type: 'line',\n        data: {\n          datasets: [\n            {fill: 'foo'},\n            {fill: '+foo'},\n            {fill: '-foo'},\n            {fill: '+1.1'},\n            {fill: '-2.2'},\n            {fill: 3.3},\n            {fill: -4.4},\n            {fill: NaN},\n            {fill: Infinity},\n            {fill: ''},\n            {fill: null},\n            {fill: []},\n          ]\n        }\n      });\n\n      expect(decodedFillValues(chart)).toEqual([\n        false, // NaN (string)\n        false, // NaN (string)\n        false, // NaN (string)\n        false, // float (string)\n        false, // float (string)\n        false, // float (number)\n        false, // float (number)\n        false, // NaN\n        false, // !isFinite\n        false, // empty string\n        false, // null\n        false, // array\n      ]);\n    });\n  });\n\n  describe('options.plugins.filler.propagate', function() {\n    it('should compute propagated fill targets if true', function() {\n      var chart = window.acquireChart({\n        type: 'line',\n        data: {\n          datasets: [\n            {fill: 'start', hidden: true},\n            {fill: '-1', hidden: true},\n            {fill: 1, hidden: true},\n            {fill: '-2', hidden: true},\n            {fill: '+1'},\n            {fill: '+2'},\n            {fill: '-1'},\n            {fill: 'end', hidden: true},\n          ]\n        },\n        options: {\n          plugins: {\n            filler: {\n              propagate: true\n            }\n          }\n        }\n      });\n\n\n      expect(decodedFillValues(chart)).toEqual([\n        'start', // 'start'\n        'start', // 1 - 1 -> 0 (hidden) -> 'start'\n        'start', // 1 (hidden) -> 0 (hidden) -> 'start'\n        'start', // 3 - 2 -> 1 (hidden) -> 0 (hidden) -> 'start'\n        5,       // 4 + 1\n        'end',   // 5 + 2 -> 7 (hidden) -> 'end'\n        5,       // 6 - 1 -> 5\n        'end',   // 'end'\n      ]);\n    });\n\n    it('should preserve initial fill targets if false', function() {\n      var chart = window.acquireChart({\n        type: 'line',\n        data: {\n          datasets: [\n            {fill: 'start', hidden: true},\n            {fill: '-1', hidden: true},\n            {fill: 1, hidden: true},\n            {fill: '-2', hidden: true},\n            {fill: '+1'},\n            {fill: '+2'},\n            {fill: '-1'},\n            {fill: 'end', hidden: true},\n          ]\n        },\n        options: {\n          plugins: {\n            filler: {\n              propagate: false\n            }\n          }\n        }\n      });\n\n      expect(decodedFillValues(chart)).toEqual([\n        'start', // 'origin'\n        0,       // 1 - 1\n        1,       // 1\n        1,       // 3 - 2\n        5,       // 4 + 1\n        7,       // 5 + 2\n        5,       // 6 - 1\n        'end',   // 'end'\n      ]);\n    });\n\n    it('should prevent recursive propagation', function() {\n      var chart = window.acquireChart({\n        type: 'line',\n        data: {\n          datasets: [\n            {fill: '+2', hidden: true},\n            {fill: '-1', hidden: true},\n            {fill: '-1', hidden: true},\n            {fill: '-2'}\n          ]\n        },\n        options: {\n          plugins: {\n            filler: {\n              propagate: true\n            }\n          }\n        }\n      });\n\n      expect(decodedFillValues(chart)).toEqual([\n        false, // 0 + 2 -> 2 (hidden) -> 1 (hidden) -> 0 (loop)\n        false, // 1 - 1 -> 0 (hidden) -> 2 (hidden) -> 1 (loop)\n        false, // 2 - 1 -> 1 (hidden) -> 0 (hidden) -> 2 (loop)\n        false, // 3 - 2 -> 1 (hidden) -> 0 (hidden) -> 2 (hidden) -> 1 (loop)\n      ]);\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/plugin.legend.tests.js",
    "content": "// Test the rectangle element\ndescribe('Legend block tests', function() {\n  describe('auto', jasmine.fixture.specs('plugin.legend'));\n\n  it('should have the correct default config', function() {\n    expect(Chart.defaults.plugins.legend).toEqual({\n      display: true,\n      position: 'top',\n      align: 'center',\n      fullSize: true,\n      reverse: false,\n      weight: 1000,\n\n      // a callback that will handle\n      onClick: jasmine.any(Function),\n      onHover: null,\n      onLeave: null,\n\n      labels: {\n        color: jasmine.any(Function),\n        boxWidth: 40,\n        padding: 10,\n        generateLabels: jasmine.any(Function)\n      },\n\n      title: {\n        color: jasmine.any(Function),\n        display: false,\n        position: 'center',\n        text: '',\n      }\n    });\n  });\n\n  it('should update bar chart correctly', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          label: 'dataset1',\n          backgroundColor: '#f31',\n          borderCapStyle: 'butt',\n          borderDash: [2, 2],\n          borderDashOffset: 5.5,\n          data: []\n        }, {\n          label: 'dataset2',\n          hidden: true,\n          borderJoinStyle: 'miter',\n          data: []\n        }, {\n          label: 'dataset3',\n          borderWidth: 10,\n          borderColor: 'green',\n          pointStyle: 'crossRot',\n          data: []\n        }],\n        labels: []\n      }\n    });\n\n    expect(chart.legend.legendItems).toEqual([{\n      text: 'dataset1',\n      borderRadius: undefined,\n      fillStyle: '#f31',\n      fontColor: '#666',\n      hidden: false,\n      lineCap: undefined,\n      lineDash: undefined,\n      lineDashOffset: undefined,\n      lineJoin: undefined,\n      lineWidth: 0,\n      strokeStyle: 'rgba(0,0,0,0.1)',\n      pointStyle: undefined,\n      rotation: undefined,\n      textAlign: undefined,\n      datasetIndex: 0\n    }, {\n      text: 'dataset2',\n      borderRadius: undefined,\n      fillStyle: 'rgba(0,0,0,0.1)',\n      fontColor: '#666',\n      hidden: true,\n      lineCap: undefined,\n      lineDash: undefined,\n      lineDashOffset: undefined,\n      lineJoin: undefined,\n      lineWidth: 0,\n      strokeStyle: 'rgba(0,0,0,0.1)',\n      pointStyle: undefined,\n      rotation: undefined,\n      textAlign: undefined,\n      datasetIndex: 1\n    }, {\n      text: 'dataset3',\n      borderRadius: undefined,\n      fillStyle: 'rgba(0,0,0,0.1)',\n      fontColor: '#666',\n      hidden: false,\n      lineCap: undefined,\n      lineDash: undefined,\n      lineDashOffset: undefined,\n      lineJoin: undefined,\n      lineWidth: 10,\n      strokeStyle: 'green',\n      pointStyle: 'crossRot',\n      rotation: undefined,\n      textAlign: undefined,\n      datasetIndex: 2\n    }]);\n  });\n\n  it('should update line chart correctly', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          label: 'dataset1',\n          backgroundColor: '#f31',\n          borderCapStyle: 'round',\n          borderDash: [2, 2],\n          borderDashOffset: 5.5,\n          data: []\n        }, {\n          label: 'dataset2',\n          hidden: true,\n          borderJoinStyle: 'round',\n          data: []\n        }, {\n          label: 'dataset3',\n          borderWidth: 10,\n          borderColor: 'green',\n          pointStyle: 'crossRot',\n          fill: false,\n          data: []\n        }],\n        labels: []\n      }\n    });\n\n    expect(chart.legend.legendItems).toEqual([{\n      text: 'dataset1',\n      borderRadius: undefined,\n      fillStyle: '#f31',\n      fontColor: '#666',\n      hidden: false,\n      lineCap: 'round',\n      lineDash: [2, 2],\n      lineDashOffset: 5.5,\n      lineJoin: 'miter',\n      lineWidth: 3,\n      strokeStyle: 'rgba(0,0,0,0.1)',\n      pointStyle: undefined,\n      rotation: undefined,\n      textAlign: undefined,\n      datasetIndex: 0\n    }, {\n      text: 'dataset2',\n      borderRadius: undefined,\n      fillStyle: 'rgba(0,0,0,0.1)',\n      fontColor: '#666',\n      hidden: true,\n      lineCap: 'butt',\n      lineDash: [],\n      lineDashOffset: 0,\n      lineJoin: 'round',\n      lineWidth: 3,\n      strokeStyle: 'rgba(0,0,0,0.1)',\n      pointStyle: undefined,\n      rotation: undefined,\n      textAlign: undefined,\n      datasetIndex: 1\n    }, {\n      text: 'dataset3',\n      borderRadius: undefined,\n      fillStyle: 'rgba(0,0,0,0.1)',\n      fontColor: '#666',\n      hidden: false,\n      lineCap: 'butt',\n      lineDash: [],\n      lineDashOffset: 0,\n      lineJoin: 'miter',\n      lineWidth: 10,\n      strokeStyle: 'green',\n      pointStyle: undefined,\n      rotation: undefined,\n      textAlign: undefined,\n      datasetIndex: 2\n    }]);\n  });\n\n  it('should reverse correctly', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          label: 'dataset1',\n          backgroundColor: '#f31',\n          borderCapStyle: 'round',\n          borderDash: [2, 2],\n          borderDashOffset: 5.5,\n          data: []\n        }, {\n          label: 'dataset2',\n          hidden: true,\n          borderJoinStyle: 'round',\n          data: []\n        }, {\n          label: 'dataset3',\n          borderWidth: 10,\n          borderColor: 'green',\n          pointStyle: 'crossRot',\n          fill: false,\n          data: []\n        }],\n        labels: []\n      },\n      options: {\n        plugins: {\n          legend: {\n            reverse: true\n          }\n        }\n      }\n    });\n\n    expect(chart.legend.legendItems).toEqual([{\n      text: 'dataset3',\n      borderRadius: undefined,\n      fillStyle: 'rgba(0,0,0,0.1)',\n      fontColor: '#666',\n      hidden: false,\n      lineCap: 'butt',\n      lineDash: [],\n      lineDashOffset: 0,\n      lineJoin: 'miter',\n      lineWidth: 10,\n      strokeStyle: 'green',\n      pointStyle: undefined,\n      rotation: undefined,\n      textAlign: undefined,\n      datasetIndex: 2\n    }, {\n      text: 'dataset2',\n      borderRadius: undefined,\n      fillStyle: 'rgba(0,0,0,0.1)',\n      fontColor: '#666',\n      hidden: true,\n      lineCap: 'butt',\n      lineDash: [],\n      lineDashOffset: 0,\n      lineJoin: 'round',\n      lineWidth: 3,\n      strokeStyle: 'rgba(0,0,0,0.1)',\n      pointStyle: undefined,\n      rotation: undefined,\n      textAlign: undefined,\n      datasetIndex: 1\n    }, {\n      text: 'dataset1',\n      borderRadius: undefined,\n      fillStyle: '#f31',\n      fontColor: '#666',\n      hidden: false,\n      lineCap: 'round',\n      lineDash: [2, 2],\n      lineDashOffset: 5.5,\n      lineJoin: 'miter',\n      lineWidth: 3,\n      strokeStyle: 'rgba(0,0,0,0.1)',\n      pointStyle: undefined,\n      rotation: undefined,\n      textAlign: undefined,\n      datasetIndex: 0\n    }]);\n  });\n\n  it('should filter items', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          label: 'dataset1',\n          backgroundColor: '#f31',\n          borderCapStyle: 'butt',\n          borderDash: [2, 2],\n          borderDashOffset: 5.5,\n          data: []\n        }, {\n          label: 'dataset2',\n          hidden: true,\n          borderJoinStyle: 'miter',\n          data: [],\n          legendHidden: true,\n        }, {\n          label: 'dataset3',\n          borderWidth: 10,\n          borderRadius: 10,\n          borderColor: 'green',\n          pointStyle: 'crossRot',\n          data: []\n        }],\n        labels: []\n      },\n      options: {\n        plugins: {\n          legend: {\n            labels: {\n              filter: function(legendItem, data) {\n                var dataset = data.datasets[legendItem.datasetIndex];\n                return !dataset.legendHidden;\n              }\n            }\n          }\n        }\n      }\n    });\n\n    expect(chart.legend.legendItems).toEqual([{\n      text: 'dataset1',\n      borderRadius: undefined,\n      fillStyle: '#f31',\n      fontColor: '#666',\n      hidden: false,\n      lineCap: undefined,\n      lineDash: undefined,\n      lineDashOffset: undefined,\n      lineJoin: undefined,\n      lineWidth: 0,\n      strokeStyle: 'rgba(0,0,0,0.1)',\n      pointStyle: undefined,\n      rotation: undefined,\n      textAlign: undefined,\n      datasetIndex: 0\n    }, {\n      text: 'dataset3',\n      borderRadius: undefined,\n      fillStyle: 'rgba(0,0,0,0.1)',\n      fontColor: '#666',\n      hidden: false,\n      lineCap: undefined,\n      lineDash: undefined,\n      lineDashOffset: undefined,\n      lineJoin: undefined,\n      lineWidth: 10,\n      strokeStyle: 'green',\n      pointStyle: 'crossRot',\n      rotation: undefined,\n      textAlign: undefined,\n      datasetIndex: 2\n    }]);\n  });\n\n  it('should sort items', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          label: 'dataset1',\n          backgroundColor: '#f31',\n          borderCapStyle: 'round',\n          borderDash: [2, 2],\n          borderDashOffset: 5.5,\n          data: []\n        }, {\n          label: 'dataset2',\n          hidden: true,\n          borderJoinStyle: 'round',\n          data: []\n        }, {\n          label: 'dataset3',\n          borderWidth: 10,\n          borderColor: 'green',\n          pointStyle: 'crossRot',\n          fill: false,\n          data: []\n        }],\n        labels: []\n      },\n      options: {\n        plugins: {\n          legend: {\n            labels: {\n              sort: function(a, b) {\n                return b.datasetIndex > a.datasetIndex ? 1 : -1;\n              }\n            }\n          }\n        }\n      }\n    });\n\n    expect(chart.legend.legendItems).toEqual([{\n      text: 'dataset3',\n      borderRadius: undefined,\n      fillStyle: 'rgba(0,0,0,0.1)',\n      fontColor: '#666',\n      hidden: false,\n      lineCap: 'butt',\n      lineDash: [],\n      lineDashOffset: 0,\n      lineJoin: 'miter',\n      lineWidth: 10,\n      strokeStyle: 'green',\n      pointStyle: undefined,\n      rotation: undefined,\n      textAlign: undefined,\n      datasetIndex: 2\n    }, {\n      text: 'dataset2',\n      borderRadius: undefined,\n      fillStyle: 'rgba(0,0,0,0.1)',\n      fontColor: '#666',\n      hidden: true,\n      lineCap: 'butt',\n      lineDash: [],\n      lineDashOffset: 0,\n      lineJoin: 'round',\n      lineWidth: 3,\n      strokeStyle: 'rgba(0,0,0,0.1)',\n      pointStyle: undefined,\n      rotation: undefined,\n      textAlign: undefined,\n      datasetIndex: 1\n    }, {\n      text: 'dataset1',\n      borderRadius: undefined,\n      fillStyle: '#f31',\n      fontColor: '#666',\n      hidden: false,\n      lineCap: 'round',\n      lineDash: [2, 2],\n      lineDashOffset: 5.5,\n      lineJoin: 'miter',\n      lineWidth: 3,\n      strokeStyle: 'rgba(0,0,0,0.1)',\n      pointStyle: undefined,\n      rotation: undefined,\n      textAlign: undefined,\n      datasetIndex: 0\n    }]);\n  });\n\n  it('should not throw when the label options are missing', function() {\n    var makeChart = function() {\n      window.acquireChart({\n        type: 'bar',\n        data: {\n          datasets: [{\n            label: 'dataset1',\n            backgroundColor: '#f31',\n            borderCapStyle: 'butt',\n            borderDash: [2, 2],\n            borderDashOffset: 5.5,\n            data: []\n          }],\n          labels: []\n        },\n        options: {\n          plugins: {\n            legend: {\n              labels: false,\n            }\n          }\n        }\n      });\n    };\n    expect(makeChart).not.toThrow();\n  });\n\n  it('should not draw legend items outside of the chart bounds', function() {\n    var chart = window.acquireChart(\n      {\n        type: 'line',\n        data: {\n          datasets: [1, 2, 3].map(function(n) {\n            return {\n              label: 'dataset' + n,\n              data: []\n            };\n          }),\n          labels: []\n        },\n        options: {\n          plugins: {\n            legend: {\n              position: 'right'\n            }\n          }\n        }\n      },\n      {\n        canvas: {\n          width: 512,\n          height: 105\n        }\n      }\n    );\n\n    // Check some basic assertions about the test setup\n    expect(chart.width).toBe(512);\n    expect(chart.legend.legendHitBoxes.length).toBe(3);\n\n    // Check whether any legend items reach outside the established bounds\n    chart.legend.legendHitBoxes.forEach(function(item) {\n      expect(item.left + item.width).toBeLessThanOrEqual(chart.width);\n    });\n  });\n\n  it('should draw legend with multiline labels', function() {\n    const chart = window.acquireChart({\n      type: 'doughnut',\n      data: {\n        labels: [\n          'ABCDE',\n          [\n            'ABCDE',\n            'ABCDE',\n          ],\n          [\n            'Some Text',\n            'Some Text',\n            'Some Text',\n          ],\n          'ABCDE',\n        ],\n        datasets: [\n          {\n            label: 'test',\n            data: [\n              73.42,\n              18.13,\n              7.54,\n              0.9,\n              0.0025,\n              1.8e-5,\n            ],\n            backgroundColor: [\n              '#0078C2',\n              '#56CAF5',\n              '#B1E3F9',\n              '#FBBC8D',\n              '#F6A3BE',\n              '#4EC2C1',\n            ],\n          },\n        ],\n      },\n      options: {\n        plugins: {\n          legend: {\n            labels: {\n              usePointStyle: true,\n              pointStyle: 'rect',\n            },\n            position: 'right',\n            align: 'center',\n            maxWidth: 860,\n          },\n        },\n        aspectRatio: 3,\n      },\n    });\n\n    // Check some basic assertions about the test setup\n    expect(chart.legend.legendHitBoxes.length).toBe(4);\n\n    // Check whether any legend items reach outside the established bounds\n    chart.legend.legendHitBoxes.forEach(function(item) {\n      expect(item.left + item.width).toBeLessThanOrEqual(chart.width);\n    });\n  });\n\n  it('should draw items with a custom boxHeight', function() {\n    var chart = window.acquireChart(\n      {\n        type: 'line',\n        data: {\n          datasets: [{\n            label: 'dataset1',\n            data: []\n          }],\n          labels: []\n        },\n        options: {\n          plugins: {\n            legend: {\n              position: 'right',\n              labels: {\n                boxHeight: 40\n              }\n            }\n          }\n        }\n      },\n      {\n        canvas: {\n          width: 512,\n          height: 105\n        }\n      }\n    );\n    const hitBox = chart.legend.legendHitBoxes[0];\n    expect(hitBox.height).toBe(40);\n  });\n\n  it('should pick up the first item when the property is an array', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          label: 'dataset1',\n          backgroundColor: ['#f31', '#666', '#14e'],\n          borderWidth: [5, 10, 15],\n          borderColor: ['red', 'green', 'blue'],\n          data: []\n        }],\n        labels: []\n      }\n    });\n\n    expect(chart.legend.legendItems).toEqual([{\n      text: 'dataset1',\n      borderRadius: undefined,\n      fillStyle: '#f31',\n      fontColor: '#666',\n      hidden: false,\n      lineCap: undefined,\n      lineDash: undefined,\n      lineDashOffset: undefined,\n      lineJoin: undefined,\n      lineWidth: 5,\n      strokeStyle: 'red',\n      pointStyle: undefined,\n      rotation: undefined,\n      textAlign: undefined,\n      datasetIndex: 0\n    }]);\n  });\n\n  it('should use the borderRadius in the legend', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          label: 'dataset1',\n          backgroundColor: ['#f31', '#666', '#14e'],\n          borderWidth: [5, 10, 15],\n          borderColor: ['red', 'green', 'blue'],\n          borderRadius: 10,\n          data: []\n        }],\n        labels: []\n      },\n      options: {\n        plugins: {\n          legend: {\n            labels: {\n              useBorderRadius: true,\n            }\n          }\n        }\n      }\n    });\n\n    expect(chart.legend.legendItems).toEqual([{\n      text: 'dataset1',\n      borderRadius: 10,\n      fillStyle: '#f31',\n      fontColor: '#666',\n      hidden: false,\n      lineCap: undefined,\n      lineDash: undefined,\n      lineDashOffset: undefined,\n      lineJoin: undefined,\n      lineWidth: 5,\n      strokeStyle: 'red',\n      pointStyle: undefined,\n      rotation: undefined,\n      textAlign: undefined,\n      datasetIndex: 0\n    }]);\n  });\n\n  it('should use the value for the first item when the property is a function', function() {\n    var helpers = window.Chart.helpers;\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          label: 'dataset1',\n          backgroundColor: function(ctx) {\n            var value = ctx.dataset.data[ctx.dataIndex] || 0;\n            return helpers.color({r: value * 10, g: 0, b: 0}).rgbString();\n          },\n          borderWidth: function(ctx) {\n            var value = ctx.dataset.data[ctx.dataIndex] || 0;\n            return value;\n          },\n          borderColor: function(ctx) {\n            var value = ctx.dataset.data[ctx.dataIndex] || 0;\n            return helpers.color({r: 255 - value * 10, g: 0, b: 0}).rgbString();\n          },\n          data: [5, 10, 15, 20]\n        }],\n        labels: ['A', 'B', 'C', 'D']\n      }\n    });\n\n    expect(chart.legend.legendItems).toEqual([{\n      text: 'dataset1',\n      borderRadius: undefined,\n      fillStyle: 'rgb(50, 0, 0)',\n      fontColor: '#666',\n      hidden: false,\n      lineCap: undefined,\n      lineDash: undefined,\n      lineDashOffset: undefined,\n      lineJoin: undefined,\n      lineWidth: 5,\n      strokeStyle: 'rgb(205, 0, 0)',\n      pointStyle: undefined,\n      rotation: undefined,\n      textAlign: undefined,\n      datasetIndex: 0\n    }]);\n  });\n\n  it('should draw correctly when usePointStyle is true', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          label: 'dataset1',\n          backgroundColor: '#f31',\n          borderCapStyle: 'butt',\n          borderDash: [2, 2],\n          borderDashOffset: 5.5,\n          borderWidth: 0,\n          borderColor: '#f31',\n          pointStyle: 'crossRot',\n          pointBackgroundColor: 'rgba(0,0,0,0.1)',\n          pointBorderWidth: 5,\n          pointBorderColor: 'green',\n          data: []\n        }, {\n          label: 'dataset2',\n          backgroundColor: '#f31',\n          borderJoinStyle: 'miter',\n          borderWidth: 2,\n          borderColor: '#f31',\n          pointStyle: 'crossRot',\n          pointRotation: 15,\n          data: []\n        }],\n        labels: []\n      },\n      options: {\n        plugins: {\n          legend: {\n            labels: {\n              usePointStyle: true\n            }\n          }\n        }\n      }\n    });\n\n    expect(chart.legend.legendItems).toEqual([{\n      text: 'dataset1',\n      borderRadius: undefined,\n      fillStyle: 'rgba(0,0,0,0.1)',\n      fontColor: '#666',\n      hidden: false,\n      lineCap: undefined,\n      lineDash: undefined,\n      lineDashOffset: undefined,\n      lineJoin: undefined,\n      lineWidth: 5,\n      strokeStyle: 'green',\n      pointStyle: 'crossRot',\n      rotation: 0,\n      textAlign: undefined,\n      datasetIndex: 0\n    }, {\n      text: 'dataset2',\n      borderRadius: undefined,\n      fillStyle: '#f31',\n      fontColor: '#666',\n      hidden: false,\n      lineCap: undefined,\n      lineDash: undefined,\n      lineDashOffset: undefined,\n      lineJoin: undefined,\n      lineWidth: 2,\n      strokeStyle: '#f31',\n      pointStyle: 'crossRot',\n      rotation: 15,\n      textAlign: undefined,\n      datasetIndex: 1\n    }]);\n  });\n\n  it('should draw correctly when usePointStyle is true and pointStyle override is set', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          label: 'dataset1',\n          backgroundColor: '#f31',\n          borderCapStyle: 'butt',\n          borderDash: [2, 2],\n          borderDashOffset: 5.5,\n          borderWidth: 0,\n          borderColor: '#f31',\n          pointStyle: 'crossRot',\n          pointBackgroundColor: 'rgba(0,0,0,0.1)',\n          pointBorderWidth: 5,\n          pointBorderColor: 'green',\n          data: []\n        }, {\n          label: 'dataset2',\n          backgroundColor: '#f31',\n          borderJoinStyle: 'miter',\n          borderWidth: 2,\n          borderColor: '#f31',\n          pointStyle: 'crossRot',\n          pointRotation: 15,\n          data: []\n        }],\n        labels: []\n      },\n      options: {\n        plugins: {\n          legend: {\n            labels: {\n              usePointStyle: true,\n              pointStyle: 'star'\n            }\n          }\n        }\n      }\n    });\n\n    expect(chart.legend.legendItems).toEqual([{\n      text: 'dataset1',\n      borderRadius: undefined,\n      fillStyle: 'rgba(0,0,0,0.1)',\n      fontColor: '#666',\n      hidden: false,\n      lineCap: undefined,\n      lineDash: undefined,\n      lineDashOffset: undefined,\n      lineJoin: undefined,\n      lineWidth: 5,\n      strokeStyle: 'green',\n      pointStyle: 'star',\n      rotation: 0,\n      textAlign: undefined,\n      datasetIndex: 0\n    }, {\n      text: 'dataset2',\n      borderRadius: undefined,\n      fillStyle: '#f31',\n      fontColor: '#666',\n      hidden: false,\n      lineCap: undefined,\n      lineDash: undefined,\n      lineDashOffset: undefined,\n      lineJoin: undefined,\n      lineWidth: 2,\n      strokeStyle: '#f31',\n      pointStyle: 'star',\n      rotation: 15,\n      textAlign: undefined,\n      datasetIndex: 1\n    }]);\n  });\n\n  it('should not crash when the legend defaults are false', function() {\n    const oldDefaults = Chart.defaults.plugins.legend;\n\n    Chart.defaults.set({\n      plugins: {\n        legend: false,\n      },\n    });\n\n    var chart = window.acquireChart({\n      type: 'doughnut',\n      data: {\n        datasets: [{\n          label: 'dataset1',\n          data: [1, 2, 3, 4]\n        }],\n        labels: ['', '', '', '']\n      },\n    });\n    expect(chart).toBeDefined();\n\n    Chart.defaults.set({\n      plugins: {\n        legend: oldDefaults,\n      },\n    });\n  });\n\n  it('should not read onClick from chart options', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May'],\n        datasets: [{\n          label: 'dataset',\n          backgroundColor: 'red',\n          borderColor: 'red',\n          data: [120, 23, 24, 45, 51]\n        }]\n      },\n      options: {\n        responsive: true,\n        onClick() { },\n        plugins: {\n          legend: {\n            display: true\n          }\n        }\n      }\n    });\n    expect(chart.legend.options.onClick).toBe(Chart.defaults.plugins.legend.onClick);\n  });\n\n  it('should read labels.color from chart options', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May'],\n        datasets: [{\n          label: 'dataset',\n          backgroundColor: 'red',\n          borderColor: 'red',\n          data: [120, 23, 24, 45, 51]\n        }]\n      },\n      options: {\n        responsive: true,\n        color: 'green',\n        plugins: {\n          legend: {\n            display: true\n          }\n        }\n      }\n    });\n    expect(chart.legend.options.labels.color).toBe('green');\n    expect(chart.legend.options.title.color).toBe('green');\n  });\n\n\n  describe('config update', function() {\n    it('should update the options', function() {\n      var chart = acquireChart({\n        type: 'line',\n        data: {\n          labels: ['A', 'B', 'C', 'D'],\n          datasets: [{\n            data: [10, 20, 30, 100]\n          }]\n        },\n        options: {\n          plugins: {\n            legend: {\n              display: true\n            }\n          }\n        }\n      });\n      expect(chart.legend.options.display).toBe(true);\n\n      chart.options.plugins.legend.display = false;\n      chart.update();\n      expect(chart.legend.options.display).toBe(false);\n    });\n\n    it('should update the associated layout item', function() {\n      var chart = acquireChart({\n        type: 'line',\n        data: {},\n        options: {\n          plugins: {\n            legend: {\n              fullSize: true,\n              position: 'top',\n              weight: 150\n            }\n          }\n        }\n      });\n\n      expect(chart.legend.fullSize).toBe(true);\n      expect(chart.legend.position).toBe('top');\n      expect(chart.legend.weight).toBe(150);\n\n      chart.options.plugins.legend.fullSize = false;\n      chart.options.plugins.legend.position = 'left';\n      chart.options.plugins.legend.weight = 42;\n      chart.update();\n\n      expect(chart.legend.fullSize).toBe(false);\n      expect(chart.legend.position).toBe('left');\n      expect(chart.legend.weight).toBe(42);\n    });\n\n    it('should remove the legend if the new options are false', function() {\n      var chart = acquireChart({\n        type: 'line',\n        data: {\n          labels: ['A', 'B', 'C', 'D'],\n          datasets: [{\n            data: [10, 20, 30, 100]\n          }]\n        }\n      });\n      expect(chart.legend).not.toBe(undefined);\n\n      chart.options.plugins.legend = false;\n      chart.update();\n      expect(chart.legend).toBe(undefined);\n    });\n\n    it('should create the legend if the legend options are changed to exist', function() {\n      var chart = acquireChart({\n        type: 'line',\n        data: {\n          labels: ['A', 'B', 'C', 'D'],\n          datasets: [{\n            data: [10, 20, 30, 100]\n          }]\n        },\n        options: {\n          plugins: {\n            legend: false\n          }\n        }\n      });\n      expect(chart.legend).toBe(undefined);\n\n      chart.options.plugins.legend = {};\n      chart.update();\n      expect(chart.legend).not.toBe(undefined);\n      expect(chart.legend.options).toEqualOptions(Object.assign({},\n        // replace scriptable options with resolved values\n        Chart.defaults.plugins.legend,\n        {\n          labels: {color: Chart.defaults.color},\n          title: {color: Chart.defaults.color}\n        }\n      ));\n    });\n  });\n\n  describe('callbacks', function() {\n    it('should call onClick, onHover and onLeave at the correct times', async function() {\n      var clickItem = null;\n      var hoverItem = null;\n      var leaveItem = null;\n\n      var chart = acquireChart({\n        type: 'line',\n        data: {\n          labels: ['A', 'B', 'C', 'D'],\n          datasets: [{\n            data: [10, 20, 30, 100]\n          }]\n        },\n        options: {\n          plugins: {\n            legend: {\n              onClick: function(_, item) {\n                clickItem = item;\n              },\n              onHover: function(_, item) {\n                hoverItem = item;\n              },\n              onLeave: function(_, item) {\n                leaveItem = item;\n              }\n            }\n          }\n        }\n      });\n\n      var hb = chart.legend.legendHitBoxes[0];\n      var el = {\n        x: hb.left + (hb.width / 2),\n        y: hb.top + (hb.height / 2)\n      };\n\n      await jasmine.triggerMouseEvent(chart, 'click', el);\n      expect(clickItem).toBe(chart.legend.legendItems[0]);\n\n      await jasmine.triggerMouseEvent(chart, 'mousemove', el);\n      expect(hoverItem).toBe(chart.legend.legendItems[0]);\n\n      await jasmine.triggerMouseEvent(chart, 'mousemove', chart.getDatasetMeta(0).data[0]);\n      expect(leaveItem).toBe(chart.legend.legendItems[0]);\n    });\n\n    it('should call onLeave when the mouse leaves the canvas', async function() {\n      var hoverItem = null;\n      var leaveItem = null;\n\n      var chart = acquireChart({\n        type: 'line',\n        data: {\n          labels: ['A', 'B', 'C', 'D'],\n          datasets: [{\n            data: [10, 20, 30, 100]\n          }]\n        },\n        options: {\n          plugins: {\n            legend: {\n              onHover: function(_, item) {\n                hoverItem = item;\n              },\n              onLeave: function(_, item) {\n                leaveItem = item;\n              }\n            }\n          }\n        }\n      });\n\n      var hb = chart.legend.legendHitBoxes[0];\n      var el = {\n        x: hb.left + (hb.width / 2),\n        y: hb.top + (hb.height / 2)\n      };\n\n      await jasmine.triggerMouseEvent(chart, 'mousemove', el);\n      expect(hoverItem).toBe(chart.legend.legendItems[0]);\n\n      await jasmine.triggerMouseEvent(chart, 'mouseout');\n      expect(leaveItem).toBe(chart.legend.legendItems[0]);\n    });\n\n\n    it('should call onClick for the correct item when in RTL mode', async function() {\n      var clickItem = null;\n\n      var chart = acquireChart({\n        type: 'line',\n        data: {\n          labels: ['A', 'B', 'C', 'D'],\n          datasets: [{\n            data: [10, 20, 30, 100],\n            label: 'dataset 1'\n          }, {\n            data: [10, 20, 30, 100],\n            label: 'dataset 2'\n          }]\n        },\n        options: {\n          plugins: {\n            legend: {\n              onClick: function(_, item) {\n                clickItem = item;\n              },\n            }\n          }\n        }\n      });\n\n      var hb = chart.legend.legendHitBoxes[0];\n      var el = {\n        x: hb.left + (hb.width / 2),\n        y: hb.top + (hb.height / 2)\n      };\n\n      await jasmine.triggerMouseEvent(chart, 'click', el);\n      expect(clickItem).toBe(chart.legend.legendItems[0]);\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/plugin.subtitle.tests.js",
    "content": "describe('plugin.subtitle', function() {\n  describe('auto', jasmine.fixture.specs('plugin.subtitle'));\n});\n"
  },
  {
    "path": "test/specs/plugin.title.tests.js",
    "content": "// Test the rectangle element\n\nvar Title = Chart.registry.getPlugin('title')._element;\n\ndescribe('Plugin.title', function() {\n  describe('auto', jasmine.fixture.specs('plugin.title'));\n\n  it('Should have the correct default config', function() {\n    expect(Chart.defaults.plugins.title).toEqual({\n      align: 'center',\n      color: Chart.defaults.color,\n      display: false,\n      position: 'top',\n      fullSize: true,\n      weight: 2000,\n      font: {\n        weight: 'bold'\n      },\n      padding: 10,\n      text: ''\n    });\n  });\n\n  it('should update correctly', function() {\n    var chart = {\n      options: Chart.helpers.clone(Chart.defaults)\n    };\n\n    var options = Chart.helpers.clone(Chart.defaults.plugins.title);\n    options.text = 'My title';\n\n    var title = new Title({\n      chart: chart,\n      options: options\n    });\n\n    title.update(400, 200);\n\n    expect(title.width).toEqual(0);\n    expect(title.height).toEqual(0);\n\n    // Now we have a height since we display\n    title.options.display = true;\n\n    title.update(400, 200);\n\n    expect(title.width).toEqual(400);\n    expect(title.height).toEqual(34.4);\n  });\n\n  it('should update correctly when vertical', function() {\n    var chart = {\n      options: Chart.helpers.clone(Chart.defaults)\n    };\n\n    var options = Chart.helpers.clone(Chart.defaults.plugins.title);\n    options.text = 'My title';\n    options.position = 'left';\n\n    var title = new Title({\n      chart: chart,\n      options: options\n    });\n\n    title.update(200, 400);\n\n    expect(title.width).toEqual(0);\n    expect(title.height).toEqual(0);\n\n    // Now we have a height since we display\n    title.options.display = true;\n\n    title.update(200, 400);\n\n    expect(title.width).toEqual(34.4);\n    expect(title.height).toEqual(400);\n  });\n\n  it('should have the correct size when there are multiple lines of text', function() {\n    var chart = {\n      options: Chart.helpers.clone(Chart.defaults)\n    };\n\n    var options = Chart.helpers.clone(Chart.defaults.plugins.title);\n    options.text = ['line1', 'line2'];\n    options.position = 'left';\n    options.display = true;\n    options.font.lineHeight = 1.5;\n\n    var title = new Title({\n      chart: chart,\n      options: options\n    });\n\n    title.update(200, 400);\n\n    expect(title.width).toEqual(56);\n    expect(title.height).toEqual(400);\n  });\n\n  it('should draw correctly horizontally', function() {\n    var chart = {\n      options: Chart.helpers.clone(Chart.defaults)\n    };\n    var context = window.createMockContext();\n\n    var options = Chart.helpers.clone(Chart.defaults.plugins.title);\n    options.text = 'My title';\n\n    var title = new Title({\n      chart: chart,\n      options: options,\n      ctx: context\n    });\n\n    title.update(400, 200);\n    title.draw();\n\n    expect(context.getCalls()).toEqual([]);\n\n    // Now we have a height since we display\n    title.options.display = true;\n\n    title.update(400, 200);\n    title.top = 50;\n    title.left = 100;\n    title.bottom = title.top + title.height;\n    title.right = title.left + title.width;\n    title.draw();\n\n    expect(context.getCalls()).toEqual([{\n      name: 'save',\n      args: []\n    }, {\n      name: 'setFont',\n      args: [\"normal bold 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif\"],\n    }, {\n      name: 'translate',\n      args: [300, 67.2]\n    }, {\n      name: 'rotate',\n      args: [0]\n    }, {\n      name: 'setFillStyle',\n      args: ['#666']\n    }, {\n      name: 'setTextAlign',\n      args: ['center'],\n    }, {\n      name: 'setTextBaseline',\n      args: ['middle'],\n    }, {\n      name: 'fillText',\n      args: ['My title', 0, 0, 400]\n    }, {\n      name: 'restore',\n      args: []\n    }]);\n  });\n\n  it ('should draw correctly vertically', function() {\n    var chart = {\n      options: Chart.helpers.clone(Chart.defaults)\n    };\n    var context = window.createMockContext();\n\n    var options = Chart.helpers.clone(Chart.defaults.plugins.title);\n    options.text = 'My title';\n    options.position = 'left';\n\n    var title = new Title({\n      chart: chart,\n      options: options,\n      ctx: context\n    });\n\n    title.update(200, 400);\n    title.draw();\n\n    expect(context.getCalls()).toEqual([]);\n\n    // Now we have a height since we display\n    title.options.display = true;\n\n    title.update(200, 400);\n    title.top = 50;\n    title.left = 100;\n    title.bottom = title.top + title.height;\n    title.right = title.left + title.width;\n    title.draw();\n\n    expect(context.getCalls()).toEqual([{\n      name: 'save',\n      args: []\n    }, {\n      name: 'setFont',\n      args: [\"normal bold 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif\"],\n    }, {\n      name: 'translate',\n      args: [117.2, 250]\n    }, {\n      name: 'rotate',\n      args: [-0.5 * Math.PI]\n    }, {\n      name: 'setFillStyle',\n      args: ['#666']\n    }, {\n      name: 'setTextAlign',\n      args: ['center'],\n    }, {\n      name: 'setTextBaseline',\n      args: ['middle'],\n    }, {\n      name: 'fillText',\n      args: ['My title', 0, 0, 400]\n    }, {\n      name: 'restore',\n      args: []\n    }]);\n\n    // Rotation is other way on right side\n    title.options.position = 'right';\n\n    // Reset call tracker\n    context.resetCalls();\n\n    title.update(200, 400);\n    title.top = 50;\n    title.left = 100;\n    title.bottom = title.top + title.height;\n    title.right = title.left + title.width;\n    title.draw();\n\n    expect(context.getCalls()).toEqual([{\n      name: 'save',\n      args: []\n    }, {\n      name: 'setFont',\n      args: [\"normal bold 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif\"],\n    }, {\n      name: 'translate',\n      args: [117.2, 250]\n    }, {\n      name: 'rotate',\n      args: [0.5 * Math.PI]\n    }, {\n      name: 'setFillStyle',\n      args: ['#666']\n    }, {\n      name: 'setTextAlign',\n      args: ['center'],\n    }, {\n      name: 'setTextBaseline',\n      args: ['middle'],\n    }, {\n      name: 'fillText',\n      args: ['My title', 0, 0, 400]\n    }, {\n      name: 'restore',\n      args: []\n    }]);\n  });\n\n  describe('config update', function() {\n    it ('should update the options', function() {\n      var chart = acquireChart({\n        type: 'line',\n        data: {\n          labels: ['A', 'B', 'C', 'D'],\n          datasets: [{\n            data: [10, 20, 30, 100]\n          }]\n        },\n        options: {\n          plugins: {\n            title: {\n              display: true\n            }\n          }\n        }\n      });\n      expect(chart.titleBlock.options.display).toBe(true);\n\n      chart.options.plugins.title.display = false;\n      chart.update();\n      expect(chart.titleBlock.options.display).toBe(false);\n    });\n\n    it ('should update the associated layout item', function() {\n      var chart = acquireChart({\n        type: 'line',\n        data: {},\n        options: {\n          plugins: {\n            title: {\n              fullSize: true,\n              position: 'top',\n              weight: 150\n            }\n          }\n        }\n      });\n\n      expect(chart.titleBlock.fullSize).toBe(true);\n      expect(chart.titleBlock.position).toBe('top');\n      expect(chart.titleBlock.weight).toBe(150);\n\n      chart.options.plugins.title.fullSize = false;\n      chart.options.plugins.title.position = 'left';\n      chart.options.plugins.title.weight = 42;\n      chart.update();\n\n      expect(chart.titleBlock.fullSize).toBe(false);\n      expect(chart.titleBlock.position).toBe('left');\n      expect(chart.titleBlock.weight).toBe(42);\n    });\n\n    it ('should remove the title if the new options are false', function() {\n      var chart = acquireChart({\n        type: 'line',\n        data: {\n          labels: ['A', 'B', 'C', 'D'],\n          datasets: [{\n            data: [10, 20, 30, 100]\n          }]\n        }\n      });\n      expect(chart.titleBlock).not.toBe(undefined);\n\n      chart.options.plugins.title = false;\n      chart.update();\n      expect(chart.titleBlock).toBe(undefined);\n    });\n\n    it ('should create the title if the title options are changed to exist', function() {\n      var chart = acquireChart({\n        type: 'line',\n        data: {\n          labels: ['A', 'B', 'C', 'D'],\n          datasets: [{\n            data: [10, 20, 30, 100]\n          }]\n        },\n        options: {\n          plugins: {\n            title: false\n          }\n        }\n      });\n      expect(chart.titleBlock).toBe(undefined);\n\n      chart.options.plugins.title = {};\n      chart.update();\n      expect(chart.titleBlock).not.toBe(undefined);\n      expect(chart.titleBlock.options).toEqualOptions(Chart.defaults.plugins.title);\n    });\n  });\n});\n"
  },
  {
    "path": "test/specs/plugin.tooltip.tests.js",
    "content": "// Test the rectangle element\nconst tooltipPlugin = Chart.registry.getPlugin('tooltip');\nconst Tooltip = tooltipPlugin._element;\n\ndescribe('Plugin.Tooltip', function() {\n  describe('auto', jasmine.fixture.specs('plugin.tooltip'));\n\n  describe('config', function() {\n    it('should not include the dataset label in the body string if not defined', function() {\n      var data = {\n        datasets: [{\n          data: [10, 20, 30],\n          pointHoverBorderColor: 'rgb(255, 0, 0)',\n          pointHoverBackgroundColor: 'rgb(0, 255, 0)'\n        }],\n        labels: ['Point 1', 'Point 2', 'Point 3']\n      };\n      var tooltipItem = {\n        index: 1,\n        datasetIndex: 0,\n        dataset: data.datasets[0],\n        label: 'Point 2',\n        formattedValue: '20'\n      };\n\n      var label = Chart.defaults.plugins.tooltip.callbacks.label(tooltipItem);\n      expect(label).toBe('20');\n\n      data.datasets[0].label = 'My dataset';\n      label = Chart.defaults.plugins.tooltip.callbacks.label(tooltipItem);\n      expect(label).toBe('My dataset: 20');\n    });\n  });\n\n  describe('index mode', function() {\n    it('Should only use x distance when intersect is false', async function() {\n      var chart = window.acquireChart({\n        type: 'line',\n        data: {\n          datasets: [{\n            label: 'Dataset 1',\n            data: [10, 20, 30],\n            pointHoverBorderColor: 'rgb(255, 0, 0)',\n            pointHoverBackgroundColor: 'rgb(0, 255, 0)'\n          }, {\n            label: 'Dataset 2',\n            data: [40, 40, 40],\n            pointHoverBorderColor: 'rgb(0, 0, 255)',\n            pointHoverBackgroundColor: 'rgb(0, 255, 255)'\n          }],\n          labels: ['Point 1', 'Point 2', 'Point 3']\n        },\n        options: {\n          plugins: {\n            tooltip: {\n              mode: 'index',\n              intersect: false,\n              padding: {\n                left: 6,\n                top: 6,\n                right: 6,\n                bottom: 6\n              }\n            }\n          },\n          hover: {\n            mode: 'index',\n            intersect: false\n          }\n        }\n      });\n\n      // Trigger an event over top of the\n      var meta = chart.getDatasetMeta(0);\n      var point = meta.data[1];\n\n      // Check and see if tooltip was displayed\n      var tooltip = chart.tooltip;\n      var defaults = Chart.defaults;\n\n      await jasmine.triggerMouseEvent(chart, 'mousemove', {x: point.x, y: chart.chartArea.top + 10});\n\n      expect(tooltip.options.padding).toEqualOptions({\n        left: 6,\n        top: 6,\n        right: 6,\n        bottom: 6,\n      });\n      expect(tooltip.xAlign).toEqual('left');\n      expect(tooltip.yAlign).toEqual('center');\n      expect(tooltip.options.bodyColor).toEqual('#fff');\n\n      expect(tooltip.options.bodyFont).toEqualOptions({\n        family: defaults.font.family,\n        style: defaults.font.style,\n        size: defaults.font.size,\n      });\n\n      expect(tooltip.options).toEqualOptions({\n        bodyAlign: 'left',\n        bodySpacing: 2,\n      });\n\n      expect(tooltip.options.titleColor).toEqual('#fff');\n      expect(tooltip.options.titleFont).toEqualOptions({\n        family: defaults.font.family,\n        weight: 'bold',\n        size: defaults.font.size,\n      });\n\n      expect(tooltip.options).toEqualOptions({\n        titleAlign: 'left',\n        titleSpacing: 2,\n        titleMarginBottom: 6,\n      });\n\n      expect(tooltip.options.footerColor).toEqual('#fff');\n      expect(tooltip.options.footerFont).toEqualOptions({\n        family: defaults.font.family,\n        weight: 'bold',\n        size: defaults.font.size,\n      });\n\n      expect(tooltip.options).toEqualOptions({\n        footerAlign: 'left',\n        footerSpacing: 2,\n        footerMarginTop: 6,\n      });\n\n      expect(tooltip.options).toEqualOptions({\n        // Appearance\n        caretSize: 5,\n        caretPadding: 2,\n        cornerRadius: 6,\n        backgroundColor: 'rgba(0,0,0,0.8)',\n        multiKeyBackground: '#fff',\n        displayColors: true\n      });\n\n      expect(tooltip).toEqual(jasmine.objectContaining({\n        opacity: 1,\n\n        // Text\n        title: ['Point 2'],\n        beforeBody: [],\n        body: [{\n          before: [],\n          lines: ['Dataset 1: 20'],\n          after: []\n        }, {\n          before: [],\n          lines: ['Dataset 2: 40'],\n          after: []\n        }],\n        afterBody: [],\n        footer: [],\n        labelColors: [{\n          borderColor: defaults.borderColor,\n          backgroundColor: defaults.backgroundColor,\n          borderWidth: 1,\n          borderDash: undefined,\n          borderDashOffset: undefined,\n          borderRadius: 0,\n        }, {\n          borderColor: defaults.borderColor,\n          backgroundColor: defaults.backgroundColor,\n          borderWidth: 1,\n          borderDash: undefined,\n          borderDashOffset: undefined,\n          borderRadius: 0,\n        }]\n      }));\n\n      expect(tooltip.x).toBeCloseToPixel(266);\n      expect(tooltip.y).toBeCloseToPixel(150);\n    });\n\n    it('Should only display if intersecting if intersect is set', async function() {\n      var chart = window.acquireChart({\n        type: 'line',\n        data: {\n          datasets: [{\n            label: 'Dataset 1',\n            data: [10, 20, 30],\n            pointHoverBorderColor: 'rgb(255, 0, 0)',\n            pointHoverBackgroundColor: 'rgb(0, 255, 0)'\n          }, {\n            label: 'Dataset 2',\n            data: [40, 40, 40],\n            pointHoverBorderColor: 'rgb(0, 0, 255)',\n            pointHoverBackgroundColor: 'rgb(0, 255, 255)'\n          }],\n          labels: ['Point 1', 'Point 2', 'Point 3']\n        },\n        options: {\n          plugins: {\n            tooltip: {\n              mode: 'index',\n              intersect: true\n            }\n          }\n        }\n      });\n\n      // Trigger an event over top of the\n      var meta = chart.getDatasetMeta(0);\n      var point = meta.data[1];\n\n      await jasmine.triggerMouseEvent(chart, 'mousemove', {x: point.x, y: 0});\n      // Check and see if tooltip was displayed\n      var tooltip = chart.tooltip;\n\n      expect(tooltip).toEqual(jasmine.objectContaining({\n        opacity: 0,\n      }));\n    });\n  });\n\n  it('Should display in single mode', async function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          label: 'Dataset 1',\n          data: [10, 20, 30],\n          pointHoverBorderColor: 'rgb(255, 0, 0)',\n          pointHoverBackgroundColor: 'rgb(0, 255, 0)'\n        }, {\n          label: 'Dataset 2',\n          data: [40, 40, 40],\n          pointHoverBorderColor: 'rgb(0, 0, 255)',\n          pointHoverBackgroundColor: 'rgb(0, 255, 255)'\n        }],\n        labels: ['Point 1', 'Point 2', 'Point 3']\n      },\n      options: {\n        plugins: {\n          tooltip: {\n            mode: 'nearest',\n            intersect: true\n          }\n        }\n      }\n    });\n\n    // Trigger an event over top of the\n    var meta = chart.getDatasetMeta(0);\n    var point = meta.data[1];\n\n    await jasmine.triggerMouseEvent(chart, 'mousemove', point);\n    // Check and see if tooltip was displayed\n    var tooltip = chart.tooltip;\n    var defaults = Chart.defaults;\n\n    expect(tooltip.options.padding).toEqual(6);\n    expect(tooltip.xAlign).toEqual('left');\n    expect(tooltip.yAlign).toEqual('center');\n\n    expect(tooltip.options.bodyFont).toEqual(jasmine.objectContaining({\n      family: defaults.font.family,\n      style: defaults.font.style,\n      size: defaults.font.size,\n    }));\n\n    expect(tooltip.options).toEqualOptions({\n      bodyAlign: 'left',\n      bodySpacing: 2,\n    });\n\n    expect(tooltip.options.titleFont).toEqual(jasmine.objectContaining({\n      family: defaults.font.family,\n      weight: 'bold',\n      size: defaults.font.size,\n    }));\n\n    expect(tooltip.options).toEqualOptions({\n      titleAlign: 'left',\n      titleSpacing: 2,\n      titleMarginBottom: 6,\n    });\n\n    expect(tooltip.options.footerFont).toEqualOptions({\n      family: defaults.font.family,\n      weight: 'bold',\n      size: defaults.font.size,\n    });\n\n    expect(tooltip.options).toEqualOptions({\n      footerAlign: 'left',\n      footerSpacing: 2,\n      footerMarginTop: 6,\n    });\n\n    expect(tooltip.options).toEqualOptions({\n      // Appearance\n      caretSize: 5,\n      caretPadding: 2,\n      cornerRadius: 6,\n      backgroundColor: 'rgba(0,0,0,0.8)',\n      multiKeyBackground: '#fff',\n      displayColors: true\n    });\n\n    expect(tooltip.opacity).toEqual(1);\n    expect(tooltip.title).toEqual(['Point 2']);\n    expect(tooltip.beforeBody).toEqual([]);\n    expect(tooltip.body).toEqual([{\n      before: [],\n      lines: ['Dataset 1: 20'],\n      after: []\n    }]);\n    expect(tooltip.afterBody).toEqual([]);\n    expect(tooltip.footer).toEqual([]);\n    expect(tooltip.labelTextColors).toEqual(['#fff']);\n\n    expect(tooltip.labelColors).toEqual([{\n      borderColor: defaults.borderColor,\n      backgroundColor: defaults.backgroundColor,\n      borderWidth: 1,\n      borderDash: undefined,\n      borderDashOffset: undefined,\n      borderRadius: 0,\n    }]);\n\n    expect(tooltip.x).toBeCloseToPixel(267);\n    expect(tooltip.y).toBeCloseToPixel(308);\n  });\n\n  it('Should display information from user callbacks', async function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          label: 'Dataset 1',\n          data: [10, 20, 30],\n          pointHoverBorderColor: 'rgb(255, 0, 0)',\n          pointHoverBackgroundColor: 'rgb(0, 255, 0)'\n        }, {\n          label: 'Dataset 2',\n          data: [40, 40, 40],\n          pointHoverBorderColor: 'rgb(0, 0, 255)',\n          pointHoverBackgroundColor: 'rgb(0, 255, 255)'\n        }],\n        labels: ['Point 1', 'Point 2', 'Point 3']\n      },\n      options: {\n        plugins: {\n          tooltip: {\n            mode: 'index',\n            callbacks: {\n              beforeTitle: function() {\n                return 'beforeTitle';\n              },\n              title: function() {\n                return 'title';\n              },\n              afterTitle: function() {\n                return 'afterTitle';\n              },\n              beforeBody: function() {\n                return 'beforeBody';\n              },\n              beforeLabel: function() {\n                return 'beforeLabel';\n              },\n              label: function() {\n                return 'label';\n              },\n              afterLabel: function() {\n                return 'afterLabel';\n              },\n              afterBody: function() {\n                return 'afterBody';\n              },\n              beforeFooter: function() {\n                return 'beforeFooter';\n              },\n              footer: function() {\n                return 'footer';\n              },\n              afterFooter: function() {\n                return 'afterFooter';\n              },\n              labelTextColor: function() {\n                return 'labelTextColor';\n              },\n              labelPointStyle: function() {\n                return {\n                  pointStyle: 'labelPointStyle',\n                  rotation: 42\n                };\n              }\n            }\n          }\n        }\n      }\n    });\n\n    // Trigger an event over top of the\n    var meta = chart.getDatasetMeta(0);\n    var point = meta.data[1];\n\n    await jasmine.triggerMouseEvent(chart, 'mousemove', point);\n    // Check and see if tooltip was displayed\n    var tooltip = chart.tooltip;\n    var defaults = Chart.defaults;\n\n    expect(tooltip.options.padding).toEqual(6);\n    expect(tooltip.xAlign).toEqual('left');\n    expect(tooltip.yAlign).toEqual('center');\n\n    expect(tooltip.options.bodyFont).toEqual(jasmine.objectContaining({\n      family: defaults.font.family,\n      style: defaults.font.style,\n      size: defaults.font.size,\n    }));\n\n    expect(tooltip.options).toEqualOptions({\n      bodyAlign: 'left',\n      bodySpacing: 2,\n    });\n\n    expect(tooltip.options.titleFont).toEqual(jasmine.objectContaining({\n      family: defaults.font.family,\n      weight: 'bold',\n      size: defaults.font.size,\n    }));\n\n    expect(tooltip.options).toEqualOptions({\n      titleSpacing: 2,\n      titleMarginBottom: 6,\n    });\n\n    expect(tooltip.options.footerFont).toEqual(jasmine.objectContaining({\n      family: defaults.font.family,\n      weight: 'bold',\n      size: defaults.font.size,\n    }));\n\n    expect(tooltip.options).toEqualOptions({\n      footerAlign: 'left',\n      footerSpacing: 2,\n      footerMarginTop: 6,\n    });\n\n    expect(tooltip.options).toEqualOptions({\n      // Appearance\n      caretSize: 5,\n      caretPadding: 2,\n      cornerRadius: 6,\n      backgroundColor: 'rgba(0,0,0,0.8)',\n      multiKeyBackground: '#fff',\n    });\n\n    expect(tooltip).toEqual(jasmine.objectContaining({\n      opacity: 1,\n\n      // Text\n      title: ['beforeTitle', 'title', 'afterTitle'],\n      beforeBody: ['beforeBody'],\n      body: [{\n        before: ['beforeLabel'],\n        lines: ['label'],\n        after: ['afterLabel']\n      }, {\n        before: ['beforeLabel'],\n        lines: ['label'],\n        after: ['afterLabel']\n      }],\n      afterBody: ['afterBody'],\n      footer: ['beforeFooter', 'footer', 'afterFooter'],\n      labelTextColors: ['labelTextColor', 'labelTextColor'],\n      labelColors: [{\n        borderColor: defaults.borderColor,\n        backgroundColor: defaults.backgroundColor,\n        borderWidth: 1,\n        borderDash: undefined,\n        borderDashOffset: undefined,\n        borderRadius: 0,\n      }, {\n        borderColor: defaults.borderColor,\n        backgroundColor: defaults.backgroundColor,\n        borderWidth: 1,\n        borderDash: undefined,\n        borderDashOffset: undefined,\n        borderRadius: 0,\n      }],\n      labelPointStyles: [{\n        pointStyle: 'labelPointStyle',\n        rotation: 42\n      }, {\n        pointStyle: 'labelPointStyle',\n        rotation: 42\n      }]\n    }));\n\n    expect(tooltip.x).toBeCloseToPixel(267);\n    expect(tooltip.y).toBeCloseToPixel(58);\n  });\n\n  it('Should provide context object to user callbacks', async function() {\n    const chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          label: 'Dataset 1',\n          data: [{x: 1, y: 10}, {x: 2, y: 20}, {x: 3, y: 30}]\n        }]\n      },\n      options: {\n        scales: {\n          x: {\n            type: 'linear'\n          }\n        },\n        plugins: {\n          tooltip: {\n            mode: 'index',\n            callbacks: {\n              beforeLabel: function(ctx) {\n                return ctx.parsed.x + ',' + ctx.parsed.y;\n              }\n            }\n          }\n        }\n      }\n    });\n\n    // Trigger an event over top of the\n    const meta = chart.getDatasetMeta(0);\n    const point = meta.data[1];\n\n    await jasmine.triggerMouseEvent(chart, 'mousemove', point);\n    expect(chart.tooltip.body[0].before).toEqual(['2,20']);\n  });\n\n  it('Should allow sorting items', async function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          label: 'Dataset 1',\n          data: [10, 20, 30],\n          pointHoverBorderColor: 'rgb(255, 0, 0)',\n          pointHoverBackgroundColor: 'rgb(0, 255, 0)'\n        }, {\n          label: 'Dataset 2',\n          data: [40, 40, 40],\n          pointHoverBorderColor: 'rgb(0, 0, 255)',\n          pointHoverBackgroundColor: 'rgb(0, 255, 255)'\n        }],\n        labels: ['Point 1', 'Point 2', 'Point 3']\n      },\n      options: {\n        plugins: {\n          tooltip: {\n            mode: 'index',\n            itemSort: function(a, b) {\n              return a.datasetIndex > b.datasetIndex ? -1 : 1;\n            }\n          }\n        }\n      }\n    });\n\n    // Trigger an event over top of the\n    var meta0 = chart.getDatasetMeta(0);\n    var point0 = meta0.data[1];\n\n    await jasmine.triggerMouseEvent(chart, 'mousemove', point0);\n    // Check and see if tooltip was displayed\n    var tooltip = chart.tooltip;\n    var defaults = Chart.defaults;\n\n    expect(tooltip).toEqual(jasmine.objectContaining({\n      // Positioning\n      xAlign: 'left',\n      yAlign: 'center',\n\n      // Text\n      title: ['Point 2'],\n      beforeBody: [],\n      body: [{\n        before: [],\n        lines: ['Dataset 2: 40'],\n        after: []\n      }, {\n        before: [],\n        lines: ['Dataset 1: 20'],\n        after: []\n      }],\n      afterBody: [],\n      footer: [],\n      labelColors: [{\n        borderColor: defaults.borderColor,\n        backgroundColor: defaults.backgroundColor,\n        borderWidth: 1,\n        borderDash: undefined,\n        borderDashOffset: undefined,\n        borderRadius: 0,\n      }, {\n        borderColor: defaults.borderColor,\n        backgroundColor: defaults.backgroundColor,\n        borderWidth: 1,\n        borderDash: undefined,\n        borderDashOffset: undefined,\n        borderRadius: 0,\n      }]\n    }));\n\n    expect(tooltip.x).toBeCloseToPixel(267);\n    expect(tooltip.y).toBeCloseToPixel(150);\n  });\n\n  it('Should allow reversing items', async function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          label: 'Dataset 1',\n          data: [10, 20, 30],\n          pointHoverBorderColor: 'rgb(255, 0, 0)',\n          pointHoverBackgroundColor: 'rgb(0, 255, 0)'\n        }, {\n          label: 'Dataset 2',\n          data: [40, 40, 40],\n          pointHoverBorderColor: 'rgb(0, 0, 255)',\n          pointHoverBackgroundColor: 'rgb(0, 255, 255)'\n        }],\n        labels: ['Point 1', 'Point 2', 'Point 3']\n      },\n      options: {\n        plugins: {\n          tooltip: {\n            mode: 'index',\n            reverse: true\n          }\n        }\n      }\n    });\n\n    // Trigger an event over top of the\n    var meta0 = chart.getDatasetMeta(0);\n    var point0 = meta0.data[1];\n\n    await jasmine.triggerMouseEvent(chart, 'mousemove', point0);\n    // Check and see if tooltip was displayed\n    var tooltip = chart.tooltip;\n    var defaults = Chart.defaults;\n\n    expect(tooltip).toEqual(jasmine.objectContaining({\n      // Positioning\n      xAlign: 'left',\n      yAlign: 'center',\n\n      // Text\n      title: ['Point 2'],\n      beforeBody: [],\n      body: [{\n        before: [],\n        lines: ['Dataset 2: 40'],\n        after: []\n      }, {\n        before: [],\n        lines: ['Dataset 1: 20'],\n        after: []\n      }],\n      afterBody: [],\n      footer: [],\n      labelColors: [{\n        borderColor: defaults.borderColor,\n        backgroundColor: defaults.backgroundColor,\n        borderWidth: 1,\n        borderDash: undefined,\n        borderDashOffset: undefined,\n        borderRadius: 0,\n      }, {\n        borderColor: defaults.borderColor,\n        backgroundColor: defaults.backgroundColor,\n        borderWidth: 1,\n        borderDash: undefined,\n        borderDashOffset: undefined,\n        borderRadius: 0,\n      }]\n    }));\n\n    expect(tooltip.x).toBeCloseToPixel(267);\n    expect(tooltip.y).toBeCloseToPixel(150);\n  });\n\n  it('Should follow dataset order', async function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          label: 'Dataset 1',\n          data: [10, 20, 30],\n          pointHoverBorderColor: 'rgb(255, 0, 0)',\n          pointHoverBackgroundColor: 'rgb(0, 255, 0)',\n          order: 10\n        }, {\n          label: 'Dataset 2',\n          data: [40, 40, 40],\n          pointHoverBorderColor: 'rgb(0, 0, 255)',\n          pointHoverBackgroundColor: 'rgb(0, 255, 255)',\n          order: 5\n        }],\n        labels: ['Point 1', 'Point 2', 'Point 3']\n      },\n      options: {\n        plugins: {\n          tooltip: {\n            mode: 'index'\n          }\n        }\n      }\n    });\n\n    // Trigger an event over top of the\n    var meta0 = chart.getDatasetMeta(0);\n    var point0 = meta0.data[1];\n\n    await jasmine.triggerMouseEvent(chart, 'mousemove', point0);\n    // Check and see if tooltip was displayed\n    var tooltip = chart.tooltip;\n    var defaults = Chart.defaults;\n\n    expect(tooltip).toEqual(jasmine.objectContaining({\n      // Positioning\n      xAlign: 'left',\n      yAlign: 'center',\n\n      // Text\n      title: ['Point 2'],\n      beforeBody: [],\n      body: [{\n        before: [],\n        lines: ['Dataset 2: 40'],\n        after: []\n      }, {\n        before: [],\n        lines: ['Dataset 1: 20'],\n        after: []\n      }],\n      afterBody: [],\n      footer: [],\n      labelColors: [{\n        borderColor: defaults.borderColor,\n        backgroundColor: defaults.backgroundColor,\n        borderWidth: 1,\n        borderDash: undefined,\n        borderDashOffset: undefined,\n        borderRadius: 0,\n      }, {\n        borderColor: defaults.borderColor,\n        backgroundColor: defaults.backgroundColor,\n        borderWidth: 1,\n        borderDash: undefined,\n        borderDashOffset: undefined,\n        borderRadius: 0,\n      }]\n    }));\n\n    expect(tooltip.x).toBeCloseToPixel(267);\n    expect(tooltip.y).toBeCloseToPixel(150);\n  });\n\n  it('should filter items from the tooltip using the callback', async function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          label: 'Dataset 1',\n          data: [10, 20, 30],\n          pointHoverBorderColor: 'rgb(255, 0, 0)',\n          pointHoverBackgroundColor: 'rgb(0, 255, 0)',\n          tooltipHidden: true\n        }, {\n          label: 'Dataset 2',\n          data: [40, 40, 40],\n          pointHoverBorderColor: 'rgb(0, 0, 255)',\n          pointHoverBackgroundColor: 'rgb(0, 255, 255)'\n        }],\n        labels: ['Point 1', 'Point 2', 'Point 3']\n      },\n      options: {\n        plugins: {\n          tooltip: {\n            mode: 'index',\n            filter: function(tooltipItem, index, tooltipItems, data) {\n              // For testing purposes remove the first dataset that has a tooltipHidden property\n              return !data.datasets[tooltipItem.datasetIndex].tooltipHidden;\n            }\n          }\n        }\n      }\n    });\n\n    // Trigger an event over top of the\n    var meta0 = chart.getDatasetMeta(0);\n    var point0 = meta0.data[1];\n\n    await jasmine.triggerMouseEvent(chart, 'mousemove', point0);\n    // Check and see if tooltip was displayed\n    var tooltip = chart.tooltip;\n    var defaults = Chart.defaults;\n\n    expect(tooltip).toEqual(jasmine.objectContaining({\n      // Positioning\n      xAlign: 'left',\n      yAlign: 'center',\n\n      // Text\n      title: ['Point 2'],\n      beforeBody: [],\n      body: [{\n        before: [],\n        lines: ['Dataset 2: 40'],\n        after: []\n      }],\n      afterBody: [],\n      footer: [],\n      labelColors: [{\n        borderColor: defaults.borderColor,\n        backgroundColor: defaults.backgroundColor,\n        borderWidth: 1,\n        borderDash: undefined,\n        borderDashOffset: undefined,\n        borderRadius: 0,\n      }]\n    }));\n  });\n\n  it('should set the caretPadding based on a config setting', async function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          label: 'Dataset 1',\n          data: [10, 20, 30],\n          pointHoverBorderColor: 'rgb(255, 0, 0)',\n          pointHoverBackgroundColor: 'rgb(0, 255, 0)',\n          tooltipHidden: true\n        }, {\n          label: 'Dataset 2',\n          data: [40, 40, 40],\n          pointHoverBorderColor: 'rgb(0, 0, 255)',\n          pointHoverBackgroundColor: 'rgb(0, 255, 255)'\n        }],\n        labels: ['Point 1', 'Point 2', 'Point 3']\n      },\n      options: {\n        plugins: {\n          tooltip: {\n            caretPadding: 10\n          }\n        }\n      }\n    });\n\n    // Trigger an event over top of the\n    var meta0 = chart.getDatasetMeta(0);\n    var point0 = meta0.data[1];\n\n    await jasmine.triggerMouseEvent(chart, 'mousemove', point0);\n    // Check and see if tooltip was displayed\n    var tooltip = chart.tooltip;\n\n    expect(tooltip.options).toEqualOptions({\n      // Positioning\n      caretPadding: 10,\n    });\n  });\n\n  ['line', 'bar'].forEach(function(type) {\n    it('Should have dataPoints in a ' + type + ' chart', async function() {\n      var chart = window.acquireChart({\n        type: type,\n        data: {\n          datasets: [{\n            label: 'Dataset 1',\n            data: [10, 20, 30],\n            pointHoverBorderColor: 'rgb(255, 0, 0)',\n            pointHoverBackgroundColor: 'rgb(0, 255, 0)'\n          }, {\n            label: 'Dataset 2',\n            data: [40, 40, 40],\n            pointHoverBorderColor: 'rgb(0, 0, 255)',\n            pointHoverBackgroundColor: 'rgb(0, 255, 255)'\n          }],\n          labels: ['Point 1', 'Point 2', 'Point 3']\n        },\n        options: {\n          plugins: {\n            tooltip: {\n              mode: 'nearest',\n              intersect: true\n            }\n          }\n        }\n      });\n\n      // Trigger an event over top of the element\n      var pointIndex = 1;\n      var datasetIndex = 0;\n      var point = chart.getDatasetMeta(datasetIndex).data[pointIndex];\n\n      await jasmine.triggerMouseEvent(chart, 'mousemove', point);\n      // Check and see if tooltip was displayed\n      var tooltip = chart.tooltip;\n\n      expect(tooltip instanceof Object).toBe(true);\n      expect(tooltip.dataPoints instanceof Array).toBe(true);\n      expect(tooltip.dataPoints.length).toBe(1);\n\n      var tooltipItem = tooltip.dataPoints[0];\n\n      expect(tooltipItem.dataIndex).toBe(pointIndex);\n      expect(tooltipItem.datasetIndex).toBe(datasetIndex);\n      expect(typeof tooltipItem.label).toBe('string');\n      expect(tooltipItem.label).toBe(chart.data.labels[pointIndex]);\n      expect(typeof tooltipItem.formattedValue).toBe('string');\n      expect(tooltipItem.formattedValue).toBe('' + chart.data.datasets[datasetIndex].data[pointIndex]);\n    });\n  });\n\n  it('Should not update if active element has not changed', async function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          label: 'Dataset 1',\n          data: [10, 20, 30],\n          pointHoverBorderColor: 'rgb(255, 0, 0)',\n          pointHoverBackgroundColor: 'rgb(0, 255, 0)'\n        }, {\n          label: 'Dataset 2',\n          data: [40, 40, 40],\n          pointHoverBorderColor: 'rgb(0, 0, 255)',\n          pointHoverBackgroundColor: 'rgb(0, 255, 255)'\n        }],\n        labels: ['Point 1', 'Point 2', 'Point 3']\n      },\n      options: {\n        plugins: {\n          tooltip: {\n            mode: 'nearest',\n            intersect: true,\n            callbacks: {\n              title: function() {\n                return 'registering callback...';\n              }\n            }\n          }\n        }\n      }\n    });\n\n    // Trigger an event over top of the\n    var meta = chart.getDatasetMeta(0);\n    var firstPoint = meta.data[1];\n\n    var tooltip = chart.tooltip;\n    spyOn(tooltip, 'update').and.callThrough();\n\n    // First dispatch change event, should update tooltip\n    await jasmine.triggerMouseEvent(chart, 'mousemove', firstPoint);\n    expect(tooltip.update).toHaveBeenCalledWith(true, undefined);\n\n    // Reset calls\n    tooltip.update.calls.reset();\n\n    // Second dispatch change event (same event), should not update tooltip\n    await jasmine.triggerMouseEvent(chart, 'mousemove', firstPoint);\n    expect(tooltip.update).not.toHaveBeenCalled();\n  });\n\n  it('Should update if active elements are the same, but the position has changed', async function() {\n    const chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          label: 'Dataset 1',\n          data: [10, 20, 30],\n          pointHoverBorderColor: 'rgb(255, 0, 0)',\n          pointHoverBackgroundColor: 'rgb(0, 255, 0)'\n        }, {\n          label: 'Dataset 2',\n          data: [40, 40, 40],\n          pointHoverBorderColor: 'rgb(0, 0, 255)',\n          pointHoverBackgroundColor: 'rgb(0, 255, 255)'\n        }],\n        labels: ['Point 1', 'Point 2', 'Point 3']\n      },\n      options: {\n        scales: {\n          x: {\n            stacked: true,\n          },\n          y: {\n            stacked: true\n          }\n        },\n        plugins: {\n          tooltip: {\n            mode: 'nearest',\n            position: 'nearest',\n            intersect: true,\n            callbacks: {\n              title: function() {\n                return 'registering callback...';\n              }\n            }\n          }\n        }\n      }\n    });\n\n    // Trigger an event over top of the\n    const meta = chart.getDatasetMeta(0);\n    const firstPoint = meta.data[1];\n\n    const meta2 = chart.getDatasetMeta(1);\n    const secondPoint = meta2.data[1];\n\n    const tooltip = chart.tooltip;\n    spyOn(tooltip, 'update');\n\n    // First dispatch change event, should update tooltip\n    await jasmine.triggerMouseEvent(chart, 'mousemove', firstPoint);\n    expect(tooltip.update).toHaveBeenCalledWith(true, undefined);\n\n    // Reset calls\n    tooltip.update.calls.reset();\n\n    // Second dispatch change event (same event), should update tooltip\n    // because position mode is 'nearest'\n    await jasmine.triggerMouseEvent(chart, 'mousemove', secondPoint);\n    expect(tooltip.update).toHaveBeenCalledWith(true, undefined);\n  });\n\n  describe('positioners', function() {\n    it('Should call custom positioner with correct parameters and scope', async function() {\n\n      tooltipPlugin.positioners.test = function() {\n        return {x: 0, y: 0};\n      };\n\n      spyOn(tooltipPlugin.positioners, 'test').and.callThrough();\n\n      var chart = window.acquireChart({\n        type: 'line',\n        data: {\n          datasets: [{\n            label: 'Dataset 1',\n            data: [10, 20, 30],\n            pointHoverBorderColor: 'rgb(255, 0, 0)',\n            pointHoverBackgroundColor: 'rgb(0, 255, 0)'\n          }, {\n            label: 'Dataset 2',\n            data: [40, 40, 40],\n            pointHoverBorderColor: 'rgb(0, 0, 255)',\n            pointHoverBackgroundColor: 'rgb(0, 255, 255)'\n          }],\n          labels: ['Point 1', 'Point 2', 'Point 3']\n        },\n        options: {\n          plugins: {\n            tooltip: {\n              mode: 'nearest',\n              position: 'test'\n            }\n          }\n        }\n      });\n\n      // Trigger an event over top of the\n      var pointIndex = 1;\n      var datasetIndex = 0;\n      var meta = chart.getDatasetMeta(datasetIndex);\n      var point = meta.data[pointIndex];\n      var fn = tooltipPlugin.positioners.test;\n\n      await jasmine.triggerMouseEvent(chart, 'mousemove', point);\n      expect(fn.calls.count()).toBe(2);\n      expect(fn.calls.first().args[0] instanceof Array).toBe(true);\n      expect(Object.prototype.hasOwnProperty.call(fn.calls.first().args[1], 'x')).toBe(true);\n      expect(Object.prototype.hasOwnProperty.call(fn.calls.first().args[1], 'y')).toBe(true);\n      expect(fn.calls.first().object instanceof Tooltip).toBe(true);\n    });\n\n    it('Should ignore same x position when calculating average position with index interaction on stacked bar', async function() {\n      var chart = window.acquireChart({\n        type: 'bar',\n        data: {\n          datasets: [{\n            label: 'Dataset 1',\n            data: [10, 20, 30],\n            pointHoverBorderColor: 'rgb(255, 0, 0)',\n            pointHoverBackgroundColor: 'rgb(0, 255, 0)',\n            stack: 'stack1',\n          }, {\n            label: 'Dataset 2',\n            data: [40, 40, 40],\n            pointHoverBorderColor: 'rgb(0, 0, 255)',\n            pointHoverBackgroundColor: 'rgb(0, 255, 255)',\n            stack: 'stack1',\n          }, {\n            label: 'Dataset 3',\n            data: [90, 100, 110],\n            pointHoverBorderColor: 'rgb(0, 0, 255)',\n            pointHoverBackgroundColor: 'rgb(0, 255, 255)'\n          }],\n          labels: ['Point 1', 'Point 2', 'Point 3']\n        },\n        options: {\n          interaction: {\n            mode: 'index'\n          },\n          plugins: {\n            position: 'average',\n          },\n        }\n      });\n\n      // Trigger an event over top of the\n      var pointIndex = 1;\n      var datasetIndex = 0;\n      var meta = chart.getDatasetMeta(datasetIndex);\n      var point = meta.data[pointIndex];\n      await jasmine.triggerMouseEvent(chart, 'mousemove', point);\n\n      var tooltipModel = chart.tooltip;\n      const activeElements = tooltipModel.getActiveElements();\n\n      const xPositionArray = activeElements.map((element) => element.element.x);\n      const xPositionArrayAverage = xPositionArray.reduce((a, b) => a + b) / xPositionArray.length;\n\n      const xPositionSet = new Set(xPositionArray);\n      const xPositionSetAverage = [...xPositionSet].reduce((a, b) => a + b) / xPositionSet.size;\n\n      expect(xPositionArray.length).toBe(3);\n      expect(xPositionSet.size).toBe(2);\n      expect(tooltipModel.caretX).not.toBe(xPositionArrayAverage);\n      expect(tooltipModel.caretX).toBe(xPositionSetAverage);\n    });\n\n    it('Should not fail with all hiden data elements on the average positioner', function() {\n      const averagePositioner = tooltipPlugin.positioners.average;\n\n      // Simulate `hasValue` returns false\n      expect(() => averagePositioner([{x: 'invalidNumber', y: 'invalidNumber'}])).not.toThrow();\n      const result = averagePositioner([{x: 'invalidNumber', y: 'invalidNumber'}]);\n      expect(result).toBe(false);\n    });\n  });\n\n  it('Should avoid tooltip truncation in x axis if there is enough space to show tooltip without truncation', async function() {\n    var chart = window.acquireChart({\n      type: 'pie',\n      data: {\n        datasets: [{\n          data: [\n            50,\n            50\n          ],\n          backgroundColor: [\n            'rgb(255, 0, 0)',\n            'rgb(0, 255, 0)'\n          ],\n          label: 'Dataset 1'\n        }],\n        labels: [\n          'Red long tooltip text to avoid unnecessary loop steps',\n          'Green long tooltip text to avoid unnecessary loop steps'\n        ]\n      },\n      options: {\n        responsive: true,\n        animation: {\n          // without this slice center point is calculated wrong\n          animateRotate: false\n        },\n        plugins: {\n          tooltip: {\n            animation: false\n          }\n        }\n      }\n    });\n\n    async function testSlice(slice, count) {\n      var meta = chart.getDatasetMeta(0);\n      var point = meta.data[slice].getCenterPoint();\n      var tooltipPosition = meta.data[slice].tooltipPosition();\n\n      async function recursive(left) {\n        chart.config.data.labels[slice] = chart.config.data.labels[slice] + 'XX';\n        chart.update();\n\n        await jasmine.triggerMouseEvent(chart, 'mouseout', point);\n        await jasmine.triggerMouseEvent(chart, 'mousemove', point);\n        var tooltip = chart.tooltip;\n        expect(tooltip.dataPoints.length).toBe(1);\n        expect(tooltip.x).toBeGreaterThanOrEqual(0);\n        if (tooltip.width <= chart.width) {\n          expect(tooltip.x + tooltip.width).toBeLessThanOrEqual(chart.width);\n        }\n        expect(tooltip.caretX).toBeCloseToPixel(tooltipPosition.x);\n        // if tooltip is longer than chart area then all tests done\n        if (left === 0) {\n          throw new Error('max iterations reached');\n        }\n        if (tooltip.width < chart.width) {\n          await recursive(left - 1);\n        }\n      }\n\n      await recursive(count);\n    }\n\n    // Trigger an event over top of the slice\n    for (var slice = 0; slice < 2; slice++) {\n      await testSlice(slice, 20);\n    }\n  });\n\n  it('Should split newlines into separate lines in user callbacks', async function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          label: 'Dataset 1',\n          data: [10, 20, 30],\n          pointHoverBorderColor: 'rgb(255, 0, 0)',\n          pointHoverBackgroundColor: 'rgb(0, 255, 0)'\n        }, {\n          label: 'Dataset 2',\n          data: [40, 40, 40],\n          pointHoverBorderColor: 'rgb(0, 0, 255)',\n          pointHoverBackgroundColor: 'rgb(0, 255, 255)'\n        }],\n        labels: ['Point 1', 'Point 2', 'Point 3']\n      },\n      options: {\n        plugins: {\n          tooltip: {\n            mode: 'index',\n            callbacks: {\n              beforeTitle: function() {\n                return 'beforeTitle\\nnewline';\n              },\n              title: function() {\n                return 'title\\nnewline';\n              },\n              afterTitle: function() {\n                return 'afterTitle\\nnewline';\n              },\n              beforeBody: function() {\n                return 'beforeBody\\nnewline';\n              },\n              beforeLabel: function() {\n                return 'beforeLabel\\nnewline';\n              },\n              label: function() {\n                return 'label';\n              },\n              afterLabel: function() {\n                return 'afterLabel\\nnewline';\n              },\n              afterBody: function() {\n                return 'afterBody\\nnewline';\n              },\n              beforeFooter: function() {\n                return 'beforeFooter\\nnewline';\n              },\n              footer: function() {\n                return 'footer\\nnewline';\n              },\n              afterFooter: function() {\n                return 'afterFooter\\nnewline';\n              },\n              labelTextColor: function() {\n                return 'labelTextColor';\n              }\n            }\n          }\n        }\n      }\n    });\n\n    // Trigger an event over top of the\n    var meta = chart.getDatasetMeta(0);\n    var point = meta.data[1];\n\n    await jasmine.triggerMouseEvent(chart, 'mousemove', point);\n\n    // Check and see if tooltip was displayed\n    var tooltip = chart.tooltip;\n    var defaults = Chart.defaults;\n\n    expect(tooltip.options.padding).toEqual(6);\n    expect(tooltip.xAlign).toEqual('center');\n    expect(tooltip.yAlign).toEqual('top');\n\n    expect(tooltip.options.bodyFont).toEqualOptions({\n      family: defaults.font.family,\n      style: defaults.font.style,\n      size: defaults.font.size,\n    });\n\n    expect(tooltip.options).toEqualOptions({\n      bodyAlign: 'left',\n      bodySpacing: 2,\n    });\n\n    expect(tooltip.options.titleFont).toEqualOptions({\n      family: defaults.font.family,\n      weight: 'bold',\n      size: defaults.font.size,\n    });\n\n    expect(tooltip.options).toEqualOptions({\n      titleAlign: 'left',\n      titleSpacing: 2,\n      titleMarginBottom: 6,\n    });\n\n    expect(tooltip.options.footerFont).toEqualOptions({\n      family: defaults.font.family,\n      weight: 'bold',\n      size: defaults.font.size,\n    });\n\n    expect(tooltip.options).toEqualOptions({\n      footerAlign: 'left',\n      footerSpacing: 2,\n      footerMarginTop: 6,\n    });\n\n    expect(tooltip.options).toEqualOptions({\n      // Appearance\n      caretSize: 5,\n      caretPadding: 2,\n      cornerRadius: 6,\n      backgroundColor: 'rgba(0,0,0,0.8)',\n      multiKeyBackground: '#fff',\n    });\n\n    expect(tooltip).toEqualOptions({\n      opacity: 1,\n\n      // Text\n      title: ['beforeTitle', 'newline', 'title', 'newline', 'afterTitle', 'newline'],\n      beforeBody: ['beforeBody', 'newline'],\n      body: [{\n        before: ['beforeLabel', 'newline'],\n        lines: ['label'],\n        after: ['afterLabel', 'newline']\n      }, {\n        before: ['beforeLabel', 'newline'],\n        lines: ['label'],\n        after: ['afterLabel', 'newline']\n      }],\n      afterBody: ['afterBody', 'newline'],\n      footer: ['beforeFooter', 'newline', 'footer', 'newline', 'afterFooter', 'newline'],\n      labelTextColors: ['labelTextColor', 'labelTextColor'],\n      labelColors: [{\n        borderColor: defaults.borderColor,\n        backgroundColor: defaults.backgroundColor\n      }, {\n        borderColor: defaults.borderColor,\n        backgroundColor: defaults.backgroundColor\n      }]\n    });\n  });\n\n  describe('text align', function() {\n    var defaults = Chart.defaults;\n    var makeView = function(title, body, footer) {\n      const model = {\n        // Positioning\n        x: 100,\n        y: 100,\n        width: 100,\n        height: 100,\n        xAlign: 'left',\n        yAlign: 'top',\n\n        options: {\n          setContext: () => model.options,\n          enabled: true,\n\n          padding: 5,\n\n          // Body\n          bodyFont: {\n            family: defaults.font.family,\n            style: defaults.font.style,\n            size: defaults.font.size,\n          },\n          bodyColor: '#fff',\n          bodyAlign: body,\n          bodySpacing: 2,\n\n          // Title\n          titleFont: {\n            family: defaults.font.family,\n            weight: 'bold',\n            size: defaults.font.size,\n          },\n          titleColor: '#fff',\n          titleAlign: title,\n          titleSpacing: 2,\n          titleMarginBottom: 6,\n\n          // Footer\n          footerFont: {\n            family: defaults.font.family,\n            weight: 'bold',\n            size: defaults.font.size,\n          },\n          footerColor: '#fff',\n          footerAlign: footer,\n          footerSpacing: 2,\n          footerMarginTop: 6,\n\n          // Appearance\n          caretSize: 5,\n          cornerRadius: 6,\n          caretPadding: 2,\n          borderColor: '#aaa',\n          borderWidth: 1,\n          backgroundColor: 'rgba(0,0,0,0.8)',\n          multiKeyBackground: '#fff',\n          displayColors: false\n\n        },\n        opacity: 1,\n\n        // Text\n        title: ['title'],\n        beforeBody: [],\n        body: [{\n          before: [],\n          lines: ['label'],\n          after: []\n        }],\n        afterBody: [],\n        footer: ['footer'],\n        labelTextColors: ['#fff'],\n        labelColors: [{\n          borderColor: 'rgb(255, 0, 0)',\n          backgroundColor: 'rgb(0, 255, 0)'\n        }, {\n          borderColor: 'rgb(0, 0, 255)',\n          backgroundColor: 'rgb(0, 255, 255)'\n        }]\n      };\n      return model;\n    };\n    var drawBody = [\n      {name: 'save', args: []},\n      {name: 'setFillStyle', args: ['rgba(0,0,0,0.8)']},\n      {name: 'setStrokeStyle', args: ['#aaa']},\n      {name: 'setLineWidth', args: [1]},\n      {name: 'beginPath', args: []},\n      {name: 'moveTo', args: [106, 100]},\n      {name: 'lineTo', args: [106, 100]},\n      {name: 'lineTo', args: [111, 95]},\n      {name: 'lineTo', args: [116, 100]},\n      {name: 'lineTo', args: [194, 100]},\n      {name: 'quadraticCurveTo', args: [200, 100, 200, 106]},\n      {name: 'lineTo', args: [200, 194]},\n      {name: 'quadraticCurveTo', args: [200, 200, 194, 200]},\n      {name: 'lineTo', args: [106, 200]},\n      {name: 'quadraticCurveTo', args: [100, 200, 100, 194]},\n      {name: 'lineTo', args: [100, 106]},\n      {name: 'quadraticCurveTo', args: [100, 100, 106, 100]},\n      {name: 'closePath', args: []},\n      {name: 'fill', args: []},\n      {name: 'stroke', args: []}\n    ];\n\n    var mockContext = window.createMockContext();\n    var tooltip = new Tooltip({\n      chart: {\n        getContext: () => ({}),\n        options: {\n          plugins: {\n            tooltip: {\n              animation: false,\n            }\n          }\n        }\n      }\n    });\n\n    it('Should go left', function() {\n      mockContext.resetCalls();\n      Chart.helpers.merge(tooltip, makeView('left', 'left', 'left'));\n      tooltip.draw(mockContext);\n\n      expect(mockContext.getCalls()).toEqual(Array.prototype.concat(drawBody, [\n        {name: 'setTextAlign', args: ['left']},\n        {name: 'setTextBaseline', args: ['middle']},\n        {name: 'setFillStyle', args: ['#fff']},\n        {name: 'setFont', args: [\"normal bold 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif\"]},\n        {name: 'fillText', args: ['title', 105, 112.2]},\n        {name: 'setTextAlign', args: ['left']},\n        {name: 'setTextBaseline', args: ['middle']},\n        {name: 'setFont', args: [\"normal 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif\"]},\n        {name: 'setFillStyle', args: ['#fff']},\n        {name: 'setFillStyle', args: ['#fff']},\n        {name: 'fillText', args: ['label', 105, 132.6]},\n        {name: 'setTextAlign', args: ['left']},\n        {name: 'setTextBaseline', args: ['middle']},\n        {name: 'setFillStyle', args: ['#fff']},\n        {name: 'setFont', args: [\"normal bold 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif\"]},\n        {name: 'fillText', args: ['footer', 105, 153]},\n        {name: 'restore', args: []}\n      ]));\n    });\n\n    it('Should go right', function() {\n      mockContext.resetCalls();\n      Chart.helpers.merge(tooltip, makeView('right', 'right', 'right'));\n      tooltip.draw(mockContext);\n\n      expect(mockContext.getCalls()).toEqual(Array.prototype.concat(drawBody, [\n        {name: 'setTextAlign', args: ['right']},\n        {name: 'setTextBaseline', args: ['middle']},\n        {name: 'setFillStyle', args: ['#fff']},\n        {name: 'setFont', args: [\"normal bold 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif\"]},\n        {name: 'fillText', args: ['title', 195, 112.2]},\n        {name: 'setTextAlign', args: ['right']},\n        {name: 'setTextBaseline', args: ['middle']},\n        {name: 'setFont', args: [\"normal 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif\"]},\n        {name: 'setFillStyle', args: ['#fff']},\n        {name: 'setFillStyle', args: ['#fff']},\n        {name: 'fillText', args: ['label', 195, 132.6]},\n        {name: 'setTextAlign', args: ['right']},\n        {name: 'setTextBaseline', args: ['middle']},\n        {name: 'setFillStyle', args: ['#fff']},\n        {name: 'setFont', args: [\"normal bold 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif\"]},\n        {name: 'fillText', args: ['footer', 195, 153]},\n        {name: 'restore', args: []}\n      ]));\n    });\n\n    it('Should center', function() {\n      mockContext.resetCalls();\n      Chart.helpers.merge(tooltip, makeView('center', 'center', 'center'));\n      tooltip.draw(mockContext);\n\n      expect(mockContext.getCalls()).toEqual(Array.prototype.concat(drawBody, [\n        {name: 'setTextAlign', args: ['center']},\n        {name: 'setTextBaseline', args: ['middle']},\n        {name: 'setFillStyle', args: ['#fff']},\n        {name: 'setFont', args: [\"normal bold 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif\"]},\n        {name: 'fillText', args: ['title', 150, 112.2]},\n        {name: 'setTextAlign', args: ['center']},\n        {name: 'setTextBaseline', args: ['middle']},\n        {name: 'setFont', args: [\"normal 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif\"]},\n        {name: 'setFillStyle', args: ['#fff']},\n        {name: 'setFillStyle', args: ['#fff']},\n        {name: 'fillText', args: ['label', 150, 132.6]},\n        {name: 'setTextAlign', args: ['center']},\n        {name: 'setTextBaseline', args: ['middle']},\n        {name: 'setFillStyle', args: ['#fff']},\n        {name: 'setFont', args: [\"normal bold 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif\"]},\n        {name: 'fillText', args: ['footer', 150, 153]},\n        {name: 'restore', args: []}\n      ]));\n    });\n\n    it('Should allow mixed', function() {\n      mockContext.resetCalls();\n      Chart.helpers.merge(tooltip, makeView('right', 'center', 'left'));\n      tooltip.draw(mockContext);\n\n      expect(mockContext.getCalls()).toEqual(Array.prototype.concat(drawBody, [\n        {name: 'setTextAlign', args: ['right']},\n        {name: 'setTextBaseline', args: ['middle']},\n        {name: 'setFillStyle', args: ['#fff']},\n        {name: 'setFont', args: [\"normal bold 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif\"]},\n        {name: 'fillText', args: ['title', 195, 112.2]},\n        {name: 'setTextAlign', args: ['center']},\n        {name: 'setTextBaseline', args: ['middle']},\n        {name: 'setFont', args: [\"normal 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif\"]},\n        {name: 'setFillStyle', args: ['#fff']},\n        {name: 'setFillStyle', args: ['#fff']},\n        {name: 'fillText', args: ['label', 150, 132.6]},\n        {name: 'setTextAlign', args: ['left']},\n        {name: 'setTextBaseline', args: ['middle']},\n        {name: 'setFillStyle', args: ['#fff']},\n        {name: 'setFont', args: [\"normal bold 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif\"]},\n        {name: 'fillText', args: ['footer', 105, 153]},\n        {name: 'restore', args: []}\n      ]));\n    });\n  });\n\n  describe('active elements', function() {\n    it('should set the active elements', function() {\n      var chart = window.acquireChart({\n        type: 'line',\n        data: {\n          datasets: [{\n            label: 'Dataset 1',\n            data: [10, 20, 30],\n            pointHoverBorderColor: 'rgb(255, 0, 0)',\n            pointHoverBackgroundColor: 'rgb(0, 255, 0)'\n          }],\n          labels: ['Point 1', 'Point 2', 'Point 3']\n        },\n      });\n\n      const meta = chart.getDatasetMeta(0);\n      chart.tooltip.setActiveElements([{datasetIndex: 0, index: 0}], {x: 0, y: 0});\n      expect(chart.tooltip.getActiveElements()[0].element).toBe(meta.data[0]);\n    });\n\n    it('should not replace the user set active elements by event replay', async function() {\n      var chart = window.acquireChart({\n        type: 'line',\n        data: {\n          datasets: [{\n            label: 'Dataset 1',\n            data: [10, 20, 30],\n            pointHoverBorderColor: 'rgb(255, 0, 0)',\n            pointHoverBackgroundColor: 'rgb(0, 255, 0)'\n          }],\n          labels: ['Point 1', 'Point 2', 'Point 3']\n        },\n        options: {\n          events: ['pointerdown', 'pointerup']\n        }\n      });\n\n      const meta = chart.getDatasetMeta(0);\n      const point0 = meta.data[0];\n      const point1 = meta.data[1];\n\n      await jasmine.triggerMouseEvent(chart, 'pointerdown', {x: point0.x, y: point0.y});\n      expect(chart.tooltip.opacity).toBe(1);\n      expect(chart.tooltip.getActiveElements()).toEqual([{datasetIndex: 0, index: 0, element: point0}]);\n\n      chart.tooltip.setActiveElements([{datasetIndex: 0, index: 1}]);\n      chart.update();\n      expect(chart.tooltip.opacity).toBe(1);\n      expect(chart.tooltip.getActiveElements()).toEqual([{datasetIndex: 0, index: 1, element: point1}]);\n\n      chart.tooltip.setActiveElements([]);\n      chart.update();\n      expect(chart.tooltip.opacity).toBe(0);\n      expect(chart.tooltip.getActiveElements().length).toBe(0);\n    });\n\n    it('should not change the active elements on events outside chartArea, except for mouseout', async function() {\n      var chart = acquireChart({\n        type: 'line',\n        data: {\n          labels: ['A', 'B', 'C', 'D'],\n          datasets: [{\n            data: [10, 20, 30, 100]\n          }],\n        },\n        options: {\n          scales: {\n            x: {display: false},\n            y: {display: false}\n          },\n          layout: {\n            padding: 5\n          }\n        }\n      });\n\n      var point = chart.getDatasetMeta(0).data[0];\n\n      await jasmine.triggerMouseEvent(chart, 'mousemove', {x: point.x, y: point.y});\n      expect(chart.tooltip.getActiveElements()).toEqual([{datasetIndex: 0, index: 0, element: point}]);\n\n      await jasmine.triggerMouseEvent(chart, 'mousemove', {x: 1, y: 1});\n      expect(chart.tooltip.getActiveElements()).toEqual([{datasetIndex: 0, index: 0, element: point}]);\n\n      await jasmine.triggerMouseEvent(chart, 'mouseout', {x: 1, y: 1});\n      expect(chart.tooltip.getActiveElements()).toEqual([]);\n    });\n\n    it('should update active elements when datasets are removed and added', async function() {\n      var dataset = {\n        label: 'Dataset 1',\n        data: [10, 20, 30],\n        pointHoverBorderColor: 'rgb(255, 0, 0)',\n        pointHoverBackgroundColor: 'rgb(0, 255, 0)'\n      };\n      var chart = window.acquireChart({\n        type: 'line',\n        data: {\n          datasets: [dataset],\n          labels: ['Point 1', 'Point 2', 'Point 3']\n        },\n        options: {\n          plugins: {\n            tooltip: {\n              mode: 'nearest',\n              intersect: true\n            }\n          }\n        }\n      });\n\n      var meta = chart.getDatasetMeta(0);\n      var point = meta.data[1];\n      var expectedPoint = jasmine.objectContaining({datasetIndex: 0, index: 1});\n\n      await jasmine.triggerMouseEvent(chart, 'mousemove', point);\n\n      expect(chart.tooltip.getActiveElements()).toEqual([expectedPoint]);\n\n      chart.data.datasets = [];\n      chart.update();\n\n      expect(chart.tooltip.getActiveElements()).toEqual([]);\n\n      chart.data.datasets = [dataset];\n      chart.update();\n\n      expect(chart.tooltip.getActiveElements()).toEqual([expectedPoint]);\n    });\n  });\n\n  it('should tolerate datasets removed on events outside chartArea', async function() {\n    const dataset1 = {\n      label: 'Dataset 1',\n      data: [10, 20, 30],\n    };\n    const dataset2 = {\n      label: 'Dataset 2',\n      data: [10, 25, 35],\n    };\n    const chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [dataset1, dataset2],\n        labels: ['Point 1', 'Point 2', 'Point 3']\n      },\n      options: {\n        plugins: {\n          tooltip: {\n            mode: 'index',\n            intersect: false\n          }\n        }\n      }\n    });\n\n    const meta = chart.getDatasetMeta(0);\n    const point = meta.data[1];\n    const expectedPoints = [jasmine.objectContaining({datasetIndex: 0, index: 1}), jasmine.objectContaining({datasetIndex: 1, index: 1})];\n\n    await jasmine.triggerMouseEvent(chart, 'mousemove', point);\n    await jasmine.triggerMouseEvent(chart, 'mousemove', {x: chart.chartArea.left - 5, y: point.y});\n\n    expect(chart.tooltip.getActiveElements()).toEqual(expectedPoints);\n\n    chart.data.datasets = [dataset1];\n    chart.update();\n\n    await jasmine.triggerMouseEvent(chart, 'mousemove', {x: 2, y: 1});\n\n    expect(chart.tooltip.getActiveElements()).toEqual([expectedPoints[0]]);\n  });\n\n  it('should tolerate elements removed on events outside chartArea', async function() {\n    const dataset1 = {\n      label: 'Dataset 1',\n      data: [10, 20, 30],\n    };\n    const dataset2 = {\n      label: 'Dataset 2',\n      data: [10, 25, 35],\n    };\n    const chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [dataset1, dataset2],\n        labels: ['Point 1', 'Point 2', 'Point 3']\n      },\n      options: {\n        plugins: {\n          tooltip: {\n            mode: 'index',\n            intersect: false\n          }\n        }\n      }\n    });\n\n    const meta = chart.getDatasetMeta(0);\n    const point = meta.data[1];\n    const expectedPoints = [jasmine.objectContaining({datasetIndex: 0, index: 1}), jasmine.objectContaining({datasetIndex: 1, index: 1})];\n\n    await jasmine.triggerMouseEvent(chart, 'mousemove', point);\n    await jasmine.triggerMouseEvent(chart, 'mousemove', {x: chart.chartArea.left - 5, y: point.y});\n\n    expect(chart.tooltip.getActiveElements()).toEqual(expectedPoints);\n\n    dataset1.data = dataset1.data.slice(0, 1);\n    chart.data.datasets = [dataset1];\n    chart.update();\n\n    await jasmine.triggerMouseEvent(chart, 'mousemove', {x: 2, y: 1});\n\n    expect(chart.tooltip.getActiveElements()).toEqual([]);\n  });\n\n  describe('events', function() {\n    it('should not be called on events not in plugin events array', async function() {\n      var chart = window.acquireChart({\n        type: 'line',\n        data: {\n          datasets: [{\n            label: 'Dataset 1',\n            data: [10, 20, 30],\n            pointHoverBorderColor: 'rgb(255, 0, 0)',\n            pointHoverBackgroundColor: 'rgb(0, 255, 0)'\n          }],\n          labels: ['Point 1', 'Point 2', 'Point 3']\n        },\n        options: {\n          plugins: {\n            tooltip: {\n              events: ['click']\n            }\n          }\n        }\n      });\n\n      const meta = chart.getDatasetMeta(0);\n      const point = meta.data[1];\n\n      await jasmine.triggerMouseEvent(chart, 'mousemove', point);\n      expect(chart.tooltip.opacity).toEqual(0);\n      await jasmine.triggerMouseEvent(chart, 'click', point);\n      expect(chart.tooltip.opacity).toEqual(1);\n    });\n  });\n\n  it('should use default callback if user callback returns undefined', async() => {\n    const chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          label: 'Dataset 1',\n          data: [10, 20, 30],\n          pointHoverBorderColor: 'rgb(255, 0, 0)',\n          pointHoverBackgroundColor: 'rgb(0, 255, 0)'\n        }, {\n          label: 'Dataset 2',\n          data: [40, 40, 40],\n          pointHoverBorderColor: 'rgb(0, 0, 255)',\n          pointHoverBackgroundColor: 'rgb(0, 255, 255)'\n        }],\n        labels: ['Point 1', 'Point 2', 'Point 3']\n      },\n      options: {\n        plugins: {\n          tooltip: {\n            callbacks: {\n              beforeTitle() {\n                return undefined;\n              },\n              title() {\n                return undefined;\n              },\n              afterTitle() {\n                return undefined;\n              },\n              beforeBody() {\n                return undefined;\n              },\n              beforeLabel() {\n                return undefined;\n              },\n              label() {\n                return undefined;\n              },\n              afterLabel() {\n                return undefined;\n              },\n              afterBody() {\n                return undefined;\n              },\n              beforeFooter() {\n                return undefined;\n              },\n              footer() {\n                return undefined;\n              },\n              afterFooter() {\n                return undefined;\n              },\n              labelTextColor() {\n                return undefined;\n              },\n              labelPointStyle() {\n                return undefined;\n              }\n            }\n          }\n        }\n      }\n    });\n    const {defaults} = Chart;\n    const {tooltip} = chart;\n    const point = chart.getDatasetMeta(0).data[0];\n\n    await jasmine.triggerMouseEvent(chart, 'mousemove', point);\n\n    expect(tooltip).toEqual(jasmine.objectContaining({\n      opacity: 1,\n\n      // Text\n      title: ['Point 1'],\n      beforeBody: [],\n      body: [{\n        before: [],\n        lines: ['Dataset 1: 10'],\n        after: []\n      }],\n      afterBody: [],\n      footer: [],\n      labelTextColors: ['#fff'],\n      labelColors: [{\n        borderColor: defaults.borderColor,\n        backgroundColor: defaults.backgroundColor,\n        borderWidth: 1,\n        borderDash: undefined,\n        borderDashOffset: undefined,\n        borderRadius: 0,\n      }],\n      labelPointStyles: [{\n        pointStyle: 'circle',\n        rotation: 0\n      }]\n    }));\n  });\n});\n"
  },
  {
    "path": "test/specs/scale.category.tests.js",
    "content": "function getLabels(scale) {\n  return scale.ticks.map(t => t.label);\n}\n\ndescribe('Category scale tests', function() {\n  describe('auto', jasmine.fixture.specs('scale.category'));\n\n  it('Should register the constructor with the registry', function() {\n    var Constructor = Chart.registry.getScale('category');\n    expect(Constructor).not.toBe(undefined);\n    expect(typeof Constructor).toBe('function');\n  });\n\n  it('Should have the correct default config', function() {\n    var defaultConfig = Chart.defaults.scales.category;\n    expect(defaultConfig).toEqual({\n      ticks: {\n        callback: Chart.registry.getScale('category').defaults.ticks.callback\n      }\n    });\n  });\n\n  it('Should generate ticks from the data xLabels', function() {\n    var labels = ['tick1', 'tick2', 'tick3', 'tick4', 'tick5'];\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        xLabels: labels,\n        datasets: [{\n          data: [10, 5, 0, 25, 78]\n        }]\n      },\n      options: {\n        scales: {\n          x: {\n            type: 'category',\n          }\n        }\n      }\n    });\n\n    var scale = chart.scales.x;\n    expect(getLabels(scale)).toEqual(labels);\n  });\n\n  it('Should generate ticks from the data yLabels', function() {\n    var labels = ['tick1', 'tick2', 'tick3', 'tick4', 'tick5'];\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        yLabels: labels,\n        datasets: [{\n          data: [10, 5, 0, 25, 78]\n        }]\n      },\n      options: {\n        scales: {\n          y: {\n            type: 'category'\n          }\n        }\n      }\n    });\n\n    var scale = chart.scales.y;\n    expect(getLabels(scale)).toEqual(labels);\n  });\n\n  it('Should generate ticks from the axis labels', function() {\n    var labels = ['tick1', 'tick2', 'tick3', 'tick4', 'tick5'];\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          data: [10, 5, 0, 25, 78]\n        }]\n      },\n      options: {\n        scales: {\n          x: {\n            type: 'category',\n            labels: labels\n          }\n        }\n      }\n    });\n\n    var scale = chart.scales.x;\n    expect(getLabels(scale)).toEqual(labels);\n  });\n\n  it('Should generate missing labels', function() {\n    var labels = ['a', 'b', 'c', 'd'];\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          data: {a: 1, b: 3, c: -1, d: 10}\n        }]\n      },\n      options: {\n        scales: {\n          x: {\n            type: 'category',\n            labels: ['a']\n          }\n        }\n      }\n    });\n\n    var scale = chart.scales.x;\n    expect(getLabels(scale)).toEqual(labels);\n\n  });\n\n  it('should parse only to a valid index', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          xAxisID: 'x',\n          yAxisID: 'y',\n          data: [10, 5, 0, 25, 78]\n        }],\n        labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5']\n      },\n      options: {\n        scales: {\n          x: {\n            type: 'category',\n            position: 'bottom'\n          },\n          y: {\n            type: 'linear'\n          }\n        }\n      }\n    });\n\n    var scale = chart.scales.x;\n\n    expect(scale.parse(-10)).toEqual(0);\n    expect(scale.parse(-0.1)).toEqual(0);\n    expect(scale.parse(4.1)).toEqual(4);\n    expect(scale.parse(5)).toEqual(4);\n    expect(scale.parse(1)).toEqual(1);\n    expect(scale.parse(1.4)).toEqual(1);\n    expect(scale.parse(1.5)).toEqual(2);\n    expect(scale.parse('tick2')).toEqual(1);\n  });\n\n  it('should get the correct label for the index', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          xAxisID: 'x',\n          yAxisID: 'y',\n          data: [10, 5, 0, 25, 78]\n        }],\n        labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5']\n      },\n      options: {\n        scales: {\n          x: {\n            type: 'category',\n            position: 'bottom'\n          },\n          y: {\n            type: 'linear'\n          }\n        }\n      }\n    });\n\n    var scale = chart.scales.x;\n\n    expect(scale.getLabelForValue(1)).toBe('tick2');\n  });\n\n  it('Should get the correct pixel for a value when horizontal', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          xAxisID: 'x',\n          yAxisID: 'y',\n          data: [10, 5, 0, 25, 78]\n        }],\n        labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick_last']\n      },\n      options: {\n        scales: {\n          x: {\n            type: 'category',\n            position: 'bottom'\n          },\n          y: {\n            type: 'linear'\n          }\n        }\n      }\n    });\n\n    var xScale = chart.scales.x;\n    expect(xScale.getPixelForValue(0)).toBeCloseToPixel(23 + 6); // plus lineHeight\n    expect(xScale.getValueForPixel(23)).toBe(0);\n\n    expect(xScale.getPixelForValue(4)).toBeCloseToPixel(487);\n    expect(xScale.getValueForPixel(487)).toBe(4);\n\n    xScale.options.offset = true;\n    chart.update();\n\n    expect(xScale.getPixelForValue(0)).toBeCloseToPixel(71 + 6); // plus lineHeight\n    expect(xScale.getValueForPixel(69)).toBe(0);\n\n    expect(xScale.getPixelForValue(4)).toBeCloseToPixel(461);\n    expect(xScale.getValueForPixel(417)).toBe(4);\n  });\n\n  it('Should get the correct pixel for a value when there are repeated labels', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          xAxisID: 'x',\n          yAxisID: 'y',\n          data: [10, 5, 0, 25, 78]\n        }],\n        labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick_last']\n      },\n      options: {\n        scales: {\n          x: {\n            type: 'category',\n            position: 'bottom'\n          },\n          y: {\n            type: 'linear'\n          }\n        }\n      }\n    });\n\n    var xScale = chart.scales.x;\n    expect(xScale.getPixelForValue('tick1')).toBeCloseToPixel(23 + 6); // plus lineHeight\n  });\n\n  it('Should get the correct pixel for a value when horizontal and zoomed', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          xAxisID: 'x',\n          yAxisID: 'y',\n          data: [10, 5, 0, 25, 78]\n        }],\n        labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick_last']\n      },\n      options: {\n        scales: {\n          x: {\n            type: 'category',\n            position: 'bottom',\n            min: 'tick2',\n            max: 'tick4'\n          },\n          y: {\n            type: 'linear'\n          }\n        }\n      }\n    });\n\n    var xScale = chart.scales.x;\n    expect(xScale.getPixelForValue(1)).toBeCloseToPixel(23 + 6); // plus lineHeight\n    expect(xScale.getPixelForValue(3)).toBeCloseToPixel(496);\n\n    xScale.options.offset = true;\n    chart.update();\n\n    expect(xScale.getPixelForValue(1)).toBeCloseToPixel(103 + 6); // plus lineHeight\n    expect(xScale.getPixelForValue(3)).toBeCloseToPixel(429);\n  });\n\n  it('should get the correct pixel for a value when vertical', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          xAxisID: 'x',\n          yAxisID: 'y',\n          data: ['3', '5', '1', '4', '2']\n        }],\n        labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5'],\n        yLabels: ['1', '2', '3', '4', '5']\n      },\n      options: {\n        scales: {\n          x: {\n            type: 'category',\n            position: 'bottom',\n          },\n          y: {\n            type: 'category',\n            position: 'left'\n          }\n        }\n      }\n    });\n\n    var yScale = chart.scales.y;\n    expect(yScale.getPixelForValue(0)).toBeCloseToPixel(32);\n    expect(yScale.getValueForPixel(257)).toBe(2);\n\n    expect(yScale.getPixelForValue(4)).toBeCloseToPixel(484);\n    expect(yScale.getValueForPixel(144)).toBe(1);\n\n    yScale.options.offset = true;\n    chart.update();\n\n    expect(yScale.getPixelForValue(0)).toBeCloseToPixel(77);\n    expect(yScale.getValueForPixel(256)).toBe(2);\n\n    expect(yScale.getPixelForValue(4)).toBeCloseToPixel(438);\n    expect(yScale.getValueForPixel(167)).toBe(1);\n  });\n\n  it('should get the correct pixel for a value when vertical and zoomed', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          xAxisID: 'x',\n          yAxisID: 'y',\n          data: ['3', '5', '1', '4', '2']\n        }],\n        labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5'],\n        yLabels: ['1', '2', '3', '4', '5']\n      },\n      options: {\n        scales: {\n          x: {\n            type: 'category',\n            position: 'bottom',\n          },\n          y: {\n            type: 'category',\n            position: 'left',\n            min: '2',\n            max: '4'\n          }\n        }\n      }\n    });\n\n    var yScale = chart.scales.y;\n\n    expect(yScale.getPixelForValue(1)).toBeCloseToPixel(32);\n    expect(yScale.getPixelForValue(3)).toBeCloseToPixel(482);\n\n    yScale.options.offset = true;\n    chart.update();\n\n    expect(yScale.getPixelForValue(1)).toBeCloseToPixel(107);\n    expect(yScale.getPixelForValue(3)).toBeCloseToPixel(407);\n  });\n\n  it('Should get the correct pixel for an object value when horizontal', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          xAxisID: 'x',\n          yAxisID: 'y',\n          data: [\n            {x: 0, y: 10},\n            {x: 1, y: 5},\n            {x: 2, y: 0},\n            {x: 3, y: 25},\n            {x: 0, y: 78}\n          ]\n        }],\n        labels: [0, 1, 2, 3]\n      },\n      options: {\n        scales: {\n          x: {\n            type: 'category',\n            position: 'bottom'\n          },\n          y: {\n            type: 'linear'\n          }\n        }\n      }\n    });\n\n    var xScale = chart.scales.x;\n    expect(xScale.getPixelForValue(0)).toBeCloseToPixel(29);\n    expect(xScale.getPixelForValue(3)).toBeCloseToPixel(506);\n    expect(xScale.getPixelForValue(4)).toBeCloseToPixel(664);\n  });\n\n  it('Should get the correct pixel for an object value when vertical', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          xAxisID: 'x',\n          yAxisID: 'y',\n          data: [\n            {x: 0, y: 2},\n            {x: 1, y: 4},\n            {x: 2, y: 0},\n            {x: 3, y: 3},\n            {x: 0, y: 1}\n          ]\n        }],\n        labels: [0, 1, 2, 3],\n        yLabels: [0, 1, 2, 3, 4]\n      },\n      options: {\n        scales: {\n          x: {\n            type: 'category',\n            position: 'bottom'\n          },\n          y: {\n            type: 'category',\n            position: 'left'\n          }\n        }\n      }\n    });\n\n    var yScale = chart.scales.y;\n    expect(yScale.getPixelForValue(0)).toBeCloseToPixel(32);\n    expect(yScale.getPixelForValue(4)).toBeCloseToPixel(483);\n  });\n\n  it('Should get the correct pixel for an object value in a bar chart', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          xAxisID: 'x',\n          yAxisID: 'y',\n          data: [\n            {x: 0, y: 10},\n            {x: 1, y: 5},\n            {x: 2, y: 0},\n            {x: 3, y: 25},\n            {x: 0, y: 78}\n          ]\n        }],\n        labels: [0, 1, 2, 3]\n      },\n      options: {\n        scales: {\n          x: {\n            type: 'category',\n            position: 'bottom'\n          },\n          y: {\n            type: 'linear'\n          }\n        }\n      }\n    });\n\n    var xScale = chart.scales.x;\n    expect(xScale.getPixelForValue(0)).toBeCloseToPixel(89);\n    expect(xScale.getPixelForValue(3)).toBeCloseToPixel(451);\n    expect(xScale.getPixelForValue(4)).toBeCloseToPixel(572);\n  });\n\n  it('Should get the correct pixel for an object value in a horizontal bar chart', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          data: [\n            {x: 10, y: 0},\n            {x: 5, y: 1},\n            {x: 0, y: 2},\n            {x: 25, y: 3},\n            {x: 78, y: 0}\n          ]\n        }],\n        labels: [0, 1, 2, 3]\n      },\n      options: {\n        indexAxis: 'y',\n        scales: {\n          x: {\n            type: 'linear',\n            position: 'bottom'\n          },\n          y: {\n            type: 'category'\n          }\n        }\n      }\n    });\n\n    var yScale = chart.scales.y;\n    expect(yScale.getPixelForValue(0)).toBeCloseToPixel(88);\n    expect(yScale.getPixelForValue(3)).toBeCloseToPixel(426);\n    expect(yScale.getPixelForValue(4)).toBeCloseToPixel(538);\n  });\n\n  it('Should be consistent on pixels and values with autoSkipped ticks', function() {\n    var labels = [];\n    for (let i = 0; i < 50; i++) {\n      labels.push('very long label ' + i);\n    }\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        labels,\n        datasets: [{\n          data: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]\n        }]\n      }\n    });\n\n    var scale = chart.scales.x;\n    expect(scale.ticks.length).toBeLessThan(50);\n\n    let x = 0;\n    for (let i = 0; i < 50; i++) {\n      var x2 = scale.getPixelForValue(labels[i]);\n      var x3 = scale.getPixelForValue(i);\n      expect(x2).toEqual(x3);\n      expect(x2).toBeGreaterThan(x);\n      expect(scale.getValueForPixel(x2)).toBe(i);\n      x = x2;\n    }\n  });\n\n  it('Should bound to ticks/data', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        labels: ['a', 'b', 'c', 'd'],\n        datasets: [{\n          data: {b: 1, c: 99}\n        }]\n      },\n      options: {\n        scales: {\n          x: {\n            type: 'category',\n            bounds: 'data'\n          }\n        }\n      }\n    });\n\n    expect(chart.scales.x.min).toEqual(1);\n    expect(chart.scales.x.max).toEqual(2);\n\n    chart.options.scales.x.bounds = 'ticks';\n    chart.update();\n\n    expect(chart.scales.x.min).toEqual(0);\n    expect(chart.scales.x.max).toEqual(3);\n  });\n});\n"
  },
  {
    "path": "test/specs/scale.linear.tests.js",
    "content": "function getLabels(scale) {\n  return scale.ticks.map(t => t.label);\n}\n\ndescribe('Linear Scale', function() {\n  describe('auto', jasmine.fixture.specs('scale.linear'));\n\n  it('Should register the constructor with the registry', function() {\n    var Constructor = Chart.registry.getScale('linear');\n    expect(Constructor).not.toBe(undefined);\n    expect(typeof Constructor).toBe('function');\n  });\n\n  it('Should have the correct default config', function() {\n    var defaultConfig = Chart.defaults.scales.linear;\n\n    expect(defaultConfig.ticks.callback).toEqual(jasmine.any(Function));\n  });\n\n  it('Should correctly determine the max & min data values', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          yAxisID: 'y',\n          data: [10, 5, 0, -5, 78, -100]\n        }, {\n          yAxisID: 'y2',\n          data: [-1000, 1000],\n        }, {\n          yAxisID: 'y',\n          data: [150]\n        }],\n        labels: ['a', 'b', 'c', 'd', 'e', 'f']\n      },\n      options: {\n        scales: {\n          y: {\n            type: 'linear'\n          },\n          y2: {\n            type: 'linear',\n            position: 'right',\n          }\n        }\n      }\n    });\n\n    expect(chart.scales.y).not.toEqual(undefined); // must construct\n    expect(chart.scales.y.min).toBe(-100);\n    expect(chart.scales.y.max).toBe(150);\n  });\n\n  it('Should handle when only a min value is provided', () => {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          yAxisID: 'y',\n          data: [200]\n        }],\n      },\n      options: {\n        scales: {\n          y: {\n            type: 'linear',\n            min: 250\n          }\n        }\n      }\n    });\n\n    expect(chart.scales.y.min).toBe(250);\n  });\n\n  it('Should handle when only a max value is provided', () => {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          yAxisID: 'y',\n          data: [200]\n        }],\n      },\n      options: {\n        scales: {\n          y: {\n            type: 'linear',\n            max: 150\n          }\n        }\n      }\n    });\n\n    expect(chart.scales.y).not.toEqual(undefined); // must construct\n    expect(chart.scales.y.max).toBe(150);\n  });\n\n  it('Should correctly determine the max & min of string data values', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          yAxisID: 'y',\n          data: ['10', '5', '0', '-5', '78', '-100']\n        }, {\n          yAxisID: 'y2',\n          data: ['-1000', '1000'],\n        }, {\n          yAxisID: 'y',\n          data: ['150']\n        }],\n        labels: ['a', 'b', 'c', 'd', 'e', 'f']\n      },\n      options: {\n        scales: {\n          y: {\n            type: 'linear'\n          },\n          y2: {\n            type: 'linear',\n            position: 'right'\n          }\n        }\n      }\n    });\n\n    expect(chart.scales.y).not.toEqual(undefined); // must construct\n    expect(chart.scales.y.min).toBe(-100);\n    expect(chart.scales.y.max).toBe(150);\n  });\n\n  it('Should correctly determine the max & min when no values provided and suggested minimum and maximum are set', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          yAxisID: 'y',\n          data: []\n        }],\n        labels: ['a', 'b', 'c', 'd', 'e', 'f']\n      },\n      options: {\n        scales: {\n          y: {\n            type: 'linear',\n            suggestedMin: -10,\n            suggestedMax: 15\n          }\n        }\n      }\n    });\n\n    expect(chart.scales.y).not.toEqual(undefined); // must construct\n    expect(chart.scales.y.min).toBe(-10);\n    expect(chart.scales.y.max).toBe(15);\n  });\n\n  it('Should correctly determine the max & min when no datasets are associated and suggested minimum and maximum are set', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: []\n      },\n      options: {\n        scales: {\n          y: {\n            type: 'linear',\n            suggestedMin: -10,\n            suggestedMax: 0\n          }\n        }\n      }\n    });\n\n    expect(chart.scales.y.min).toBe(-10);\n    expect(chart.scales.y.max).toBe(0);\n  });\n\n  it('Should correctly determine the max & min data values ignoring hidden datasets', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          yAxisID: 'y',\n          data: ['10', '5', '0', '-5', '78', '-100']\n        }, {\n          yAxisID: 'y2',\n          data: ['-1000', '1000'],\n        }, {\n          yAxisID: 'y',\n          data: ['150'],\n          hidden: true\n        }],\n        labels: ['a', 'b', 'c', 'd', 'e', 'f']\n      },\n      options: {\n        scales: {\n          y: {\n            type: 'linear'\n          },\n          y2: {\n            position: 'right',\n            type: 'linear'\n          }\n        }\n      }\n    });\n\n    expect(chart.scales.y).not.toEqual(undefined); // must construct\n    expect(chart.scales.y.min).toBe(-100);\n    expect(chart.scales.y.max).toBe(80);\n  });\n\n  it('Should correctly determine the max & min data values ignoring data that is NaN', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          yAxisID: 'y',\n          data: [null, 90, NaN, undefined, 45, 30, Infinity, -Infinity]\n        }],\n        labels: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']\n      },\n      options: {\n        scales: {\n          y: {\n            type: 'linear',\n            beginAtZero: false\n          }\n        }\n      }\n    });\n\n    expect(chart.scales.y.min).toBe(30);\n    expect(chart.scales.y.max).toBe(90);\n\n    // Scale is now stacked\n    chart.scales.y.options.stacked = true;\n    chart.update();\n\n    expect(chart.scales.y.min).toBe(30);\n    expect(chart.scales.y.max).toBe(90);\n\n    chart.scales.y.options.beginAtZero = true;\n    chart.update();\n\n    expect(chart.scales.y.min).toBe(0);\n    expect(chart.scales.y.max).toBe(90);\n  });\n\n  it('Should correctly determine the max & min data values for small numbers', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          yAxisID: 'y',\n          data: [-1e-8, 3e-8, -4e-8, 6e-8]\n        }],\n        labels: ['a', 'b', 'c', 'd']\n      },\n      options: {\n        scales: {\n          y: {\n            type: 'linear'\n          }\n        }\n      }\n    });\n\n    expect(chart.scales.y).not.toEqual(undefined); // must construct\n    expect(chart.scales.y.min * 1e8).toBeCloseTo(-4);\n    expect(chart.scales.y.max * 1e8).toBeCloseTo(6);\n  });\n\n  it('Should correctly determine the max & min for scatter data', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          xAxisID: 'x',\n          yAxisID: 'y',\n          data: [{\n            x: 10,\n            y: 100\n          }, {\n            x: -10,\n            y: 0\n          }, {\n            x: 0,\n            y: 0\n          }, {\n            x: 99,\n            y: 7\n          }]\n        }],\n      },\n      options: {\n        scales: {\n          x: {\n            type: 'linear',\n            position: 'bottom'\n          },\n          y: {\n            type: 'linear'\n          }\n        }\n      }\n    });\n    chart.update();\n\n    expect(chart.scales.x.min).toBe(-20);\n    expect(chart.scales.x.max).toBe(100);\n    expect(chart.scales.y.min).toBe(0);\n    expect(chart.scales.y.max).toBe(100);\n  });\n\n  it('Should correctly get the label for the given index', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          xAxisID: 'x',\n          yAxisID: 'y',\n          data: [{\n            x: 10,\n            y: 100\n          }, {\n            x: -10,\n            y: 0\n          }, {\n            x: 0,\n            y: 0\n          }, {\n            x: 99,\n            y: 7\n          }]\n        }],\n      },\n      options: {\n        scales: {\n          x: {\n            type: 'linear',\n            position: 'bottom'\n          },\n          y: {\n            type: 'linear'\n          }\n        }\n      }\n    });\n    chart.update();\n\n    expect(chart.scales.y.getLabelForValue(7)).toBe('7');\n  });\n\n  it('Should correctly use the locale setting when getting a label', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          xAxisID: 'x',\n          yAxisID: 'y',\n          data: [{\n            x: 10,\n            y: 100\n          }, {\n            x: -10,\n            y: 0\n          }, {\n            x: 0,\n            y: 0\n          }, {\n            x: 99,\n            y: 7\n          }]\n        }],\n      },\n      options: {\n        locale: 'de-DE',\n        scales: {\n          x: {\n            type: 'linear',\n            position: 'bottom'\n          },\n          y: {\n            type: 'linear'\n          }\n        }\n      }\n    });\n    chart.update();\n\n    expect(chart.scales.y.getLabelForValue(7.07)).toBe('7,07');\n  });\n\n  it('Should correctly determine the min and max data values when stacked mode is turned on', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          yAxisID: 'y',\n          data: [10, 5, 0, -5, 78, -100],\n          type: 'bar'\n        }, {\n          yAxisID: 'y2',\n          data: [-1000, 1000],\n        }, {\n          yAxisID: 'y',\n          data: [150, 0, 0, -100, -10, 9],\n          type: 'bar'\n        }, {\n          yAxisID: 'y',\n          data: [10, 10, 10, 10, 10, 10],\n          type: 'line'\n        }],\n        labels: ['a', 'b', 'c', 'd', 'e', 'f']\n      },\n      options: {\n        scales: {\n          y: {\n            type: 'linear',\n            stacked: true\n          },\n          y2: {\n            position: 'right',\n            type: 'linear'\n          }\n        }\n      }\n    });\n    chart.update();\n\n    expect(chart.scales.y.min).toBe(-150);\n    expect(chart.scales.y.max).toBe(200);\n  });\n\n  it('Should correctly determine the min and max data values when stacked mode is turned on and there are hidden datasets', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          yAxisID: 'y',\n          data: [10, 5, 0, -5, 78, -100],\n        }, {\n          yAxisID: 'y2',\n          data: [-1000, 1000],\n        }, {\n          yAxisID: 'y',\n          data: [150, 0, 0, -100, -10, 9],\n        }, {\n          yAxisID: 'y',\n          data: [10, 20, 30, 40, 50, 60],\n          hidden: true\n        }],\n        labels: ['a', 'b', 'c', 'd', 'e', 'f']\n      },\n      options: {\n        scales: {\n          y: {\n            type: 'linear',\n            stacked: true\n          },\n          y2: {\n            position: 'right',\n            type: 'linear'\n          }\n        }\n      }\n    });\n    chart.update();\n\n    expect(chart.scales.y.min).toBe(-150);\n    expect(chart.scales.y.max).toBe(200);\n  });\n\n  it('Should correctly determine the min and max data values when stacked mode is turned on there are multiple types of datasets', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          yAxisID: 'y',\n          type: 'bar',\n          data: [10, 5, 0, -5, 78, -100]\n        }, {\n          type: 'line',\n          data: [10, 10, 10, 10, 10, 10],\n        }, {\n          type: 'bar',\n          data: [150, 0, 0, -100, -10, 9]\n        }],\n        labels: ['a', 'b', 'c', 'd', 'e', 'f']\n      },\n      options: {\n        scales: {\n          y: {\n            type: 'linear',\n            stacked: true\n          }\n        }\n      }\n    });\n\n    chart.scales.y.determineDataLimits();\n    expect(chart.scales.y.min).toBe(-105);\n    expect(chart.scales.y.max).toBe(160);\n  });\n\n  it('Should ensure that the scale has a max and min that are not equal', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [],\n        labels: ['a', 'b', 'c', 'd', 'e', 'f']\n      },\n      options: {\n        scales: {\n          y: {\n            type: 'linear'\n          }\n        }\n      }\n    });\n\n    expect(chart.scales.y).not.toEqual(undefined); // must construct\n    expect(chart.scales.y.min).toBe(0);\n    expect(chart.scales.y.max).toBe(1);\n  });\n\n  it('Should ensure that the scale has a max and min that are not equal - large positive numbers', function() {\n    // https://github.com/chartjs/Chart.js/issues/9377\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          // Value larger than Number.MAX_SAFE_INTEGER\n          data: [10000000000000000]\n        }],\n        labels: ['a']\n      },\n      options: {\n        scales: {\n          y: {\n            type: 'linear'\n          }\n        }\n      }\n    });\n\n    expect(chart.scales.y).not.toEqual(undefined); // must construct\n    expect(chart.scales.y.min).toBe(10000000000000000 * 0.95);\n    expect(chart.scales.y.max).toBe(10000000000000000 * 1.05);\n  });\n\n  it('Should ensure that the scale has a max and min that are not equal - large negative numbers', function() {\n    // https://github.com/chartjs/Chart.js/issues/9377\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          // Value larger than Number.MAX_SAFE_INTEGER\n          data: [-10000000000000000]\n        }],\n        labels: ['a']\n      },\n      options: {\n        scales: {\n          y: {\n            type: 'linear'\n          }\n        }\n      }\n    });\n\n    expect(chart.scales.y).not.toEqual(undefined); // must construct\n    expect(chart.scales.y.max).toBe(-10000000000000000 * 0.95);\n    expect(chart.scales.y.min).toBe(-10000000000000000 * 1.05);\n  });\n\n  it('Should ensure that the scale has a max and min that are not equal when beginAtZero is set', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [],\n        labels: ['a', 'b', 'c', 'd', 'e', 'f']\n      },\n      options: {\n        scales: {\n          y: {\n            type: 'linear',\n            beginAtZero: true\n          }\n        }\n      }\n    });\n\n    expect(chart.scales.y).not.toEqual(undefined); // must construct\n    expect(chart.scales.y.min).toBe(0);\n    expect(chart.scales.y.max).toBe(1);\n  });\n\n  it('Should use the suggestedMin and suggestedMax options', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          yAxisID: 'y',\n          data: [1, 1, 1, 2, 1, 0]\n        }],\n        labels: ['a', 'b', 'c', 'd', 'e', 'f']\n      },\n      options: {\n        scales: {\n          y: {\n            type: 'linear',\n            suggestedMax: 10,\n            suggestedMin: -10\n          }\n        }\n      }\n    });\n\n    expect(chart.scales.y).not.toEqual(undefined); // must construct\n    expect(chart.scales.y.min).toBe(-10);\n    expect(chart.scales.y.max).toBe(10);\n  });\n\n  it('Should use the min and max options', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          yAxisID: 'y',\n          data: [1, 1, 1, 2, 1, 0]\n        }],\n        labels: ['a', 'b', 'c', 'd', 'e', 'f']\n      },\n      options: {\n        scales: {\n          y: {\n            type: 'linear',\n            max: 1010,\n            min: -1010\n          }\n        }\n      }\n    });\n\n    expect(chart.scales.y).not.toEqual(undefined); // must construct\n    expect(chart.scales.y.min).toBe(-1010);\n    expect(chart.scales.y.max).toBe(1010);\n    var labels = getLabels(chart.scales.y);\n    expect(labels[0]).toBe('-1,010');\n    expect(labels[labels.length - 1]).toBe('1,010');\n  });\n\n  it('Should use min, max and stepSize to create fixed spaced ticks', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          yAxisID: 'y',\n          data: [10, 3, 6, 8, 3, 1]\n        }],\n        labels: ['a', 'b', 'c', 'd', 'e', 'f']\n      },\n      options: {\n        scales: {\n          y: {\n            type: 'linear',\n            min: 1,\n            max: 11,\n            ticks: {\n              stepSize: 2\n            }\n          }\n        }\n      }\n    });\n\n    expect(chart.scales.y).not.toEqual(undefined); // must construct\n    expect(chart.scales.y.min).toBe(1);\n    expect(chart.scales.y.max).toBe(11);\n    expect(getLabels(chart.scales.y)).toEqual(['1', '3', '5', '7', '9', '11']);\n  });\n\n  it('Should not generate any ticks > max if max is specified', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      options: {\n        scales: {\n          x: {\n            type: 'linear',\n            min: 2.404e-8,\n            max: 2.4143e-8,\n            ticks: {\n              includeBounds: false,\n            },\n          },\n        },\n      },\n    });\n\n    expect(chart.scales.x.min).toBe(2.404e-8);\n    expect(chart.scales.x.max).toBe(2.4143e-8);\n    expect(chart.scales.x.ticks[chart.scales.x.ticks.length - 1].value).toBeLessThanOrEqual(2.4143e-8);\n  });\n\n  it('Should not generate insane amounts of ticks with small stepSize and large range', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      options: {\n        scales: {\n          y: {\n            type: 'linear',\n            min: 1,\n            max: 1E10,\n            ticks: {\n              stepSize: 2,\n              autoSkip: false\n            }\n          }\n        }\n      }\n    });\n\n    expect(chart.scales.y.min).toBe(1);\n    expect(chart.scales.y.max).toBe(1E10);\n    expect(chart.scales.y.ticks.length).toBeLessThanOrEqual(1000);\n  });\n\n  it('Should create decimal steps if stepSize is a decimal number', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          yAxisID: 'y',\n          data: [10, 3, 6, 8, 3, 1]\n        }],\n        labels: ['a', 'b', 'c', 'd', 'e', 'f']\n      },\n      options: {\n        scales: {\n          y: {\n            type: 'linear',\n            ticks: {\n              stepSize: 2.5\n            }\n          }\n        }\n      }\n    });\n\n    expect(chart.scales.y).not.toEqual(undefined); // must construct\n    expect(chart.scales.y.min).toBe(0);\n    expect(chart.scales.y.max).toBe(10);\n    expect(getLabels(chart.scales.y)).toEqual(['0', '2.5', '5', '7.5', '10']);\n  });\n\n  describe('precision', function() {\n    it('Should create integer steps if precision is 0', function() {\n      var chart = window.acquireChart({\n        type: 'bar',\n        data: {\n          datasets: [{\n            yAxisID: 'y',\n            data: [0, 1, 2, 1, 0, 1]\n          }],\n          labels: ['a', 'b', 'c', 'd', 'e', 'f']\n        },\n        options: {\n          scales: {\n            y: {\n              type: 'linear',\n              ticks: {\n                precision: 0\n              }\n            }\n          }\n        }\n      });\n\n      expect(chart.scales.y).not.toEqual(undefined); // must construct\n      expect(chart.scales.y.min).toBe(0);\n      expect(chart.scales.y.max).toBe(2);\n      expect(getLabels(chart.scales.y)).toEqual(['0', '1', '2']);\n    });\n\n    it('Should round the step size to the given number of decimal places', function() {\n      var chart = window.acquireChart({\n        type: 'bar',\n        data: {\n          datasets: [{\n            yAxisID: 'y',\n            data: [0, 0.001, 0.002, 0.003, 0, 0.001]\n          }],\n          labels: ['a', 'b', 'c', 'd', 'e', 'f']\n        },\n        options: {\n          scales: {\n            y: {\n              type: 'linear',\n              ticks: {\n                precision: 2\n              }\n            }\n          }\n        }\n      });\n\n      expect(chart.scales.y).not.toEqual(undefined); // must construct\n      expect(chart.scales.y.min).toBe(0);\n      expect(chart.scales.y.max).toBe(0.01);\n      expect(getLabels(chart.scales.y)).toEqual(['0', '0.01']);\n    });\n  });\n\n\n  it('should forcibly include 0 in the range if the beginAtZero option is used', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          yAxisID: 'y',\n          data: [20, 30, 40, 50]\n        }],\n        labels: ['a', 'b', 'c', 'd']\n      },\n      options: {\n        scales: {\n          y: {\n            type: 'linear',\n            beginAtZero: false\n          }\n        }\n      }\n    });\n\n    expect(chart.scales.y).not.toEqual(undefined); // must construct\n    expect(getLabels(chart.scales.y)).toEqual(['20', '25', '30', '35', '40', '45', '50']);\n\n    chart.scales.y.options.beginAtZero = true;\n    chart.update();\n    expect(getLabels(chart.scales.y)).toEqual(['0', '5', '10', '15', '20', '25', '30', '35', '40', '45', '50']);\n\n    chart.data.datasets[0].data = [-20, -30, -40, -50];\n    chart.update();\n    expect(getLabels(chart.scales.y)).toEqual(['-50', '-45', '-40', '-35', '-30', '-25', '-20', '-15', '-10', '-5', '0']);\n\n    chart.scales.y.options.beginAtZero = false;\n    chart.update();\n    expect(getLabels(chart.scales.y)).toEqual(['-50', '-45', '-40', '-35', '-30', '-25', '-20']);\n  });\n\n  it('Should generate tick marks in the correct order in reversed mode', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          yAxisID: 'y',\n          data: [10, 5, 0, 25, 78]\n        }],\n        labels: ['a', 'b', 'c', 'd']\n      },\n      options: {\n        scales: {\n          y: {\n            type: 'linear',\n            reverse: true\n          }\n        }\n      }\n    });\n\n    expect(getLabels(chart.scales.y)).toEqual(['80', '70', '60', '50', '40', '30', '20', '10', '0']);\n    expect(chart.scales.y.start).toBe(80);\n    expect(chart.scales.y.end).toBe(0);\n  });\n\n  it('should use the correct number of decimal places in the default format function', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          yAxisID: 'y',\n          data: [0.06, 0.005, 0, 0.025, 0.0078]\n        }],\n        labels: ['a', 'b', 'c', 'd']\n      },\n      options: {\n        scales: {\n          y: {\n            type: 'linear',\n          }\n        }\n      }\n    });\n    expect(getLabels(chart.scales.y)).toEqual(['0', '0.01', '0.02', '0.03', '0.04', '0.05', '0.06']);\n  });\n\n  it('Should correctly limit the maximum number of ticks', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        labels: ['a', 'b'],\n        datasets: [{\n          data: [0.5, 2.5]\n        }]\n      },\n      options: {\n        scales: {\n          y: {\n            beginAtZero: false\n          }\n        }\n      }\n    });\n\n    expect(getLabels(chart.scales.y)).toEqual(['0.5', '1.0', '1.5', '2.0', '2.5']);\n\n    chart.options.scales.y.ticks.maxTicksLimit = 11;\n    chart.update();\n\n    expect(getLabels(chart.scales.y)).toEqual(['0.5', '1.0', '1.5', '2.0', '2.5']);\n\n    chart.options.scales.y.ticks.maxTicksLimit = 21;\n    chart.update();\n\n    expect(getLabels(chart.scales.y)).toEqual([\n      '0.5',\n      '0.6', '0.7', '0.8', '0.9', '1.0', '1.1', '1.2', '1.3', '1.4', '1.5',\n      '1.6', '1.7', '1.8', '1.9', '2.0', '2.1', '2.2', '2.3', '2.4', '2.5'\n    ]);\n\n    chart.options.scales.y.ticks.maxTicksLimit = 11;\n    chart.options.scales.y.ticks.stepSize = 0.01;\n    chart.update();\n\n    expect(getLabels(chart.scales.y)).toEqual(['0.5', '1.0', '1.5', '2.0', '2.5']);\n\n    chart.options.scales.y.min = 0.3;\n    chart.options.scales.y.max = 2.8;\n    chart.update();\n\n    expect(getLabels(chart.scales.y)).toEqual(['0.3', '0.8', '1.3', '1.8', '2.3', '2.8']);\n  });\n\n  it('Should bound to data', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        labels: ['a', 'b'],\n        datasets: [{\n          data: [1, 99]\n        }]\n      },\n      options: {\n        scales: {\n          y: {\n            bounds: 'data'\n          }\n        }\n      }\n    });\n\n    expect(chart.scales.y.min).toEqual(1);\n    expect(chart.scales.y.max).toEqual(99);\n  });\n\n  it('Should build labels using the user supplied callback', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          yAxisID: 'y',\n          data: [10, 5, 0, 25, 78]\n        }],\n        labels: ['a', 'b', 'c', 'd']\n      },\n      options: {\n        scales: {\n          y: {\n            type: 'linear',\n            ticks: {\n              callback: function(value, index) {\n                return index.toString();\n              }\n            }\n          }\n        }\n      }\n    });\n\n    // Just the index\n    expect(getLabels(chart.scales.y)).toEqual(['0', '1', '2', '3', '4', '5', '6', '7', '8']);\n  });\n\n  it('Should get the correct pixel value for a point', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        labels: [-1, 1],\n        datasets: [{\n          xAxisID: 'x',\n          yAxisID: 'y',\n          data: [-1, 1]\n        }],\n      },\n      options: {\n        scales: {\n          x: {\n            type: 'linear',\n            position: 'bottom'\n          },\n          y: {\n            type: 'linear'\n          }\n        }\n      }\n    });\n\n    var xScale = chart.scales.x;\n    expect(xScale.getPixelForValue(1)).toBeCloseToPixel(501); // right - paddingRight\n    expect(xScale.getPixelForValue(-1)).toBeCloseToPixel(31 + 3); // left + paddingLeft + tick padding\n    expect(xScale.getPixelForValue(0)).toBeCloseToPixel(266 + 3 / 2); // halfway*/\n\n    expect(xScale.getValueForPixel(501)).toBeCloseTo(1, 1e-2);\n    expect(xScale.getValueForPixel(31)).toBeCloseTo(-1, 1e-2);\n    expect(xScale.getValueForPixel(266)).toBeCloseTo(0, 1e-2);\n\n    var yScale = chart.scales.y;\n    expect(yScale.getPixelForValue(1)).toBeCloseToPixel(32); // right - paddingRight\n    expect(yScale.getPixelForValue(-1)).toBeCloseToPixel(484); // left + paddingLeft\n    expect(yScale.getPixelForValue(0)).toBeCloseToPixel(258); // halfway*/\n\n    expect(yScale.getValueForPixel(32)).toBeCloseTo(1, 1e-2);\n    expect(yScale.getValueForPixel(484)).toBeCloseTo(-1, 1e-2);\n    expect(yScale.getValueForPixel(258)).toBeCloseTo(0, 1e-2);\n  });\n\n  it('should fit correctly', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          xAxisID: 'x',\n          yAxisID: 'y',\n          data: [{\n            x: 10,\n            y: 100\n          }, {\n            x: -10,\n            y: 0\n          }, {\n            x: 0,\n            y: 0\n          }, {\n            x: 99,\n            y: 7\n          }]\n        }],\n      },\n      options: {\n        scales: {\n          x: {\n            type: 'linear',\n            position: 'bottom'\n          },\n          y: {\n            type: 'linear'\n          }\n        }\n      }\n    });\n\n    var xScale = chart.scales.x;\n    var yScale = chart.scales.y;\n    expect(xScale.paddingTop).toBeCloseToPixel(0);\n    expect(xScale.paddingBottom).toBeCloseToPixel(0);\n    expect(xScale.paddingLeft).toBeCloseToPixel(12);\n    expect(xScale.paddingRight).toBeCloseToPixel(13.5);\n    expect(xScale.width).toBeCloseToPixel(468 - 3); // minus tick padding\n    expect(xScale.height).toBeCloseToPixel(30);\n\n    expect(yScale.paddingTop).toBeCloseToPixel(10);\n    expect(yScale.paddingBottom).toBeCloseToPixel(10);\n    expect(yScale.paddingLeft).toBeCloseToPixel(0);\n    expect(yScale.paddingRight).toBeCloseToPixel(0);\n    expect(yScale.width).toBeCloseToPixel(31 + 3); // plus tick padding\n    expect(yScale.height).toBeCloseToPixel(450);\n\n    // Extra size when scale label showing\n    xScale.options.title.display = true;\n    yScale.options.title.display = true;\n    chart.update();\n\n    expect(xScale.paddingTop).toBeCloseToPixel(0);\n    expect(xScale.paddingBottom).toBeCloseToPixel(0);\n    expect(xScale.paddingLeft).toBeCloseToPixel(12);\n    expect(xScale.paddingRight).toBeCloseToPixel(13.5);\n    expect(xScale.width).toBeCloseToPixel(442);\n    expect(xScale.height).toBeCloseToPixel(50);\n\n    expect(yScale.paddingTop).toBeCloseToPixel(10);\n    expect(yScale.paddingBottom).toBeCloseToPixel(10);\n    expect(yScale.paddingLeft).toBeCloseToPixel(0);\n    expect(yScale.paddingRight).toBeCloseToPixel(0);\n    expect(yScale.width).toBeCloseToPixel(58);\n    expect(yScale.height).toBeCloseToPixel(429);\n  });\n\n  it('should fit correctly when display is turned off', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          xAxisID: 'x',\n          yAxisID: 'y',\n          data: [{\n            x: 10,\n            y: 100\n          }, {\n            x: -10,\n            y: 0\n          }, {\n            x: 0,\n            y: 0\n          }, {\n            x: 99,\n            y: 7\n          }]\n        }],\n      },\n      options: {\n        scales: {\n          x: {\n            type: 'linear',\n            position: 'bottom'\n          },\n          y: {\n            type: 'linear',\n            grid: {\n              drawTicks: false,\n            },\n            border: {\n              display: false\n            },\n            title: {\n              display: false,\n              lineHeight: 1.2\n            },\n            ticks: {\n              display: false,\n              padding: 0\n            }\n          }\n        }\n      }\n    });\n\n    var yScale = chart.scales.y;\n    expect(yScale.width).toBeCloseToPixel(0);\n  });\n\n  it('max and min value should be valid and finite when charts datasets are hidden', function() {\n    var barData = {\n      labels: ['S1', 'S2', 'S3'],\n      datasets: [{\n        label: 'Closed',\n        backgroundColor: '#382765',\n        data: [2500, 2000, 1500]\n      }, {\n        label: 'In Progress',\n        backgroundColor: '#7BC225',\n        data: [1000, 2000, 1500]\n      }, {\n        label: 'Assigned',\n        backgroundColor: '#ffC225',\n        data: [1000, 2000, 1500]\n      }]\n    };\n\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: barData,\n      options: {\n        indexAxis: 'y',\n        scales: {\n          x: {\n            stacked: true\n          },\n          y: {\n            stacked: true\n          }\n        }\n      }\n    });\n\n    barData.datasets.forEach(function(data, index) {\n      var meta = chart.getDatasetMeta(index);\n      meta.hidden = true;\n      chart.update();\n    });\n\n    expect(chart.scales.x.min).toEqual(0);\n    expect(chart.scales.x.max).toEqual(1);\n  });\n\n  it('max and min value should be valid when min is set and all datasets are hidden', function() {\n    var barData = {\n      labels: ['S1', 'S2', 'S3'],\n      datasets: [{\n        label: 'dataset 1',\n        backgroundColor: '#382765',\n        data: [2500, 2000, 1500],\n        hidden: true,\n      }]\n    };\n\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: barData,\n      options: {\n        indexAxis: 'y',\n        scales: {\n          x: {\n            min: 20\n          }\n        }\n      }\n    });\n\n    expect(chart.scales.x.min).toEqual(20);\n    expect(chart.scales.x.max).toEqual(21);\n  });\n\n  it('min settings should be used if set to zero', function() {\n    var barData = {\n      labels: ['S1', 'S2', 'S3'],\n      datasets: [{\n        label: 'dataset 1',\n        backgroundColor: '#382765',\n        data: [2500, 2000, 1500]\n      }]\n    };\n\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: barData,\n      options: {\n        indexAxis: 'y',\n        scales: {\n          x: {\n            min: 0,\n            max: 3000\n          }\n        }\n      }\n    });\n\n    expect(chart.scales.x.min).toEqual(0);\n  });\n\n  it('max settings should be used if set to zero', function() {\n    var barData = {\n      labels: ['S1', 'S2', 'S3'],\n      datasets: [{\n        label: 'dataset 1',\n        backgroundColor: '#382765',\n        data: [-2500, -2000, -1500]\n      }]\n    };\n\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: barData,\n      options: {\n        indexAxis: 'y',\n        scales: {\n          x: {\n            min: -3000,\n            max: 0\n          }\n        }\n      }\n    });\n\n    expect(chart.scales.x.max).toEqual(0);\n  });\n\n  it('Should get correct pixel values when horizontal', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          data: [0.05, -25, 10, 15, 20, 25, 30, 35]\n        }]\n      },\n      options: {\n        indexAxis: 'y',\n        scales: {\n          x: {\n            type: 'linear',\n          }\n        }\n      }\n    });\n\n    var start = chart.chartArea.left;\n    var end = chart.chartArea.right;\n    var min = -30;\n    var max = 40;\n    var scale = chart.scales.x;\n\n    expect(scale.getPixelForValue(max)).toBeCloseToPixel(end);\n    expect(scale.getPixelForValue(min)).toBeCloseToPixel(start);\n    expect(scale.getValueForPixel(end)).toBeCloseTo(max, 4);\n    expect(scale.getValueForPixel(start)).toBeCloseTo(min, 4);\n\n    scale.options.reverse = true;\n    chart.update();\n\n    start = chart.chartArea.left;\n    end = chart.chartArea.right;\n\n    expect(scale.getPixelForValue(max)).toBeCloseToPixel(start);\n    expect(scale.getPixelForValue(min)).toBeCloseToPixel(end);\n    expect(scale.getValueForPixel(end)).toBeCloseTo(min, 4);\n    expect(scale.getValueForPixel(start)).toBeCloseTo(max, 4);\n  });\n\n  it('Should get correct pixel values when vertical', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          data: [0.05, -25, 10, 15, 20, 25, 30, 35]\n        }]\n      },\n      options: {\n        scales: {\n          y: {\n            type: 'linear',\n          }\n        }\n      }\n    });\n\n    var start = chart.chartArea.bottom;\n    var end = chart.chartArea.top;\n    var min = -30;\n    var max = 40;\n    var scale = chart.scales.y;\n\n    expect(scale.getPixelForValue(max)).toBeCloseToPixel(end);\n    expect(scale.getPixelForValue(min)).toBeCloseToPixel(start);\n    expect(scale.getValueForPixel(end)).toBeCloseTo(max, 4);\n    expect(scale.getValueForPixel(start)).toBeCloseTo(min, 4);\n\n    scale.options.reverse = true;\n    chart.update();\n\n    start = chart.chartArea.bottom;\n    end = chart.chartArea.top;\n\n    expect(scale.getPixelForValue(max)).toBeCloseToPixel(start);\n    expect(scale.getPixelForValue(min)).toBeCloseToPixel(end);\n    expect(scale.getValueForPixel(end)).toBeCloseTo(min, 4);\n    expect(scale.getValueForPixel(start)).toBeCloseTo(max, 4);\n  });\n\n  it('should not throw errors when chart size is negative', function() {\n    function createChart() {\n      return window.acquireChart({\n        type: 'bar',\n        data: {\n          labels: [0, 1, 2, 3, 4, 5, 6, 7, '7+'],\n          datasets: [{\n            data: [29.05, 4, 15.69, 11.69, 2.84, 4, 0, 3.84, 4],\n          }],\n        },\n        options: {\n          plugins: false,\n          layout: {\n            padding: {top: 30, left: 1, right: 1, bottom: 1}\n          }\n        }\n      }, {\n        canvas: {\n          height: 0,\n          width: 0\n        }\n      });\n    }\n\n    expect(createChart).not.toThrow();\n  });\n});\n"
  },
  {
    "path": "test/specs/scale.logarithmic.tests.js",
    "content": "function getLabels(scale) {\n  return scale.ticks.map(t => t.label);\n}\n\ndescribe('Logarithmic Scale tests', function() {\n  describe('auto', jasmine.fixture.specs('scale.logarithmic'));\n\n  it('should register', function() {\n    var Constructor = Chart.registry.getScale('logarithmic');\n    expect(Constructor).not.toBe(undefined);\n    expect(typeof Constructor).toBe('function');\n  });\n\n  it('should have the correct default config', function() {\n    var defaultConfig = Chart.defaults.scales.logarithmic;\n    expect(defaultConfig).toEqual({\n      ticks: {\n        callback: Chart.Ticks.formatters.logarithmic,\n        major: {\n          enabled: true\n        }\n      }\n    });\n\n    // Is this actually a function\n    expect(defaultConfig.ticks.callback).toEqual(jasmine.any(Function));\n  });\n\n  it('should correctly determine the max & min data values', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          yAxisID: 'y',\n          data: [42, 1000, 64, 100],\n        }, {\n          yAxisID: 'y1',\n          data: [10, 5, 5000, 78, 450]\n        }, {\n          yAxisID: 'y1',\n          data: [150]\n        }, {\n          yAxisID: 'y2',\n          data: [20, 0, 150, 1800, 3040]\n        }, {\n          yAxisID: 'y3',\n          data: [67, 0.0004, 0, 820, 0.001]\n        }],\n        labels: ['a', 'b', 'c', 'd', 'e']\n      },\n      options: {\n        scales: {\n          y: {\n            id: 'y',\n            type: 'logarithmic'\n          },\n          y1: {\n            type: 'logarithmic',\n            position: 'right'\n          },\n          y2: {\n            type: 'logarithmic',\n            position: 'right'\n          },\n          y3: {\n            position: 'right',\n            type: 'logarithmic'\n          }\n        }\n      }\n    });\n\n    expect(chart.scales.y).not.toEqual(undefined); // must construct\n    expect(chart.scales.y.min).toBe(10);\n    expect(chart.scales.y.max).toBe(1000);\n\n    expect(chart.scales.y1).not.toEqual(undefined); // must construct\n    expect(chart.scales.y1.min).toBe(1);\n    expect(chart.scales.y1.max).toBe(5000);\n\n    expect(chart.scales.y2).not.toEqual(undefined); // must construct\n    expect(chart.scales.y2.min).toBe(10);\n    expect(chart.scales.y2.max).toBe(4000);\n\n    expect(chart.scales.y3).not.toEqual(undefined); // must construct\n    expect(chart.scales.y3.min).toBeCloseTo(0.0001, 4);\n    expect(chart.scales.y3.max).toBe(900);\n  });\n\n  it('should correctly determine the max & min of string data values', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          yAxisID: 'y',\n          data: ['42', '1000', '64', '100'],\n        }, {\n          yAxisID: 'y1',\n          data: ['10', '5', '5000', '78', '450']\n        }, {\n          yAxisID: 'y1',\n          data: ['150']\n        }, {\n          yAxisID: 'y2',\n          data: ['20', '0', '150', '1800', '3040']\n        }, {\n          yAxisID: 'y3',\n          data: ['67', '0.0004', '0', '820', '0.001']\n        }],\n        labels: ['a', 'b', 'c', 'd', 'e']\n      },\n      options: {\n        scales: {\n          y: {\n            type: 'logarithmic'\n          },\n          y1: {\n            position: 'right',\n            type: 'logarithmic'\n          },\n          y2: {\n            position: 'right',\n            type: 'logarithmic'\n          },\n          y3: {\n            position: 'right',\n            type: 'logarithmic'\n          }\n        }\n      }\n    });\n\n    expect(chart.scales.y).not.toEqual(undefined); // must construct\n    expect(chart.scales.y.min).toBe(40);\n    expect(chart.scales.y.max).toBe(1000);\n\n    expect(chart.scales.y1).not.toEqual(undefined); // must construct\n    expect(chart.scales.y1.min).toBe(5);\n    expect(chart.scales.y1.max).toBe(5000);\n\n    expect(chart.scales.y2).not.toEqual(undefined); // must construct\n    expect(chart.scales.y2.min).toBe(10);\n    expect(chart.scales.y2.max).toBe(4000);\n\n    expect(chart.scales.y3).not.toEqual(undefined); // must construct\n    expect(chart.scales.y3.min).toBeCloseTo(0.0001, 4);\n    expect(chart.scales.y3.max).toBe(900);\n  });\n\n  it('should correctly determine the max & min data values when there are hidden datasets', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          yAxisID: 'y1',\n          data: [10, 5, 5000, 78, 450]\n        }, {\n          yAxisID: 'y',\n          data: [42, 1000, 64, 100],\n        }, {\n          yAxisID: 'y1',\n          data: [50000],\n          hidden: true\n        }, {\n          yAxisID: 'y2',\n          data: [20, 0, 7400, 14, 291]\n        }, {\n          yAxisID: 'y2',\n          data: [6, 0.0007, 9, 890, 60000],\n          hidden: true\n        }],\n        labels: ['a', 'b', 'c', 'd', 'e']\n      },\n      options: {\n        scales: {\n          y: {\n            type: 'logarithmic'\n          },\n          y1: {\n            position: 'right',\n            type: 'logarithmic'\n          },\n          y2: {\n            position: 'right',\n            type: 'logarithmic'\n          }\n        }\n      }\n    });\n\n    expect(chart.scales.y1).not.toEqual(undefined); // must construct\n    expect(chart.scales.y1.min).toBe(5);\n    expect(chart.scales.y1.max).toBe(5000);\n\n    expect(chart.scales.y2).not.toEqual(undefined); // must construct\n    expect(chart.scales.y2.min).toBe(10);\n    expect(chart.scales.y2.max).toBe(8000);\n  });\n\n  it('should correctly determine the max & min data values when there is NaN data', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          yAxisID: 'y',\n          data: [undefined, 10, null, 5, 5000, NaN, 78, 450]\n        }, {\n          yAxisID: 'y',\n          data: [undefined, 28, null, 1000, 500, NaN, 50, 42, Infinity, -Infinity]\n        }, {\n          yAxisID: 'y1',\n          data: [undefined, 30, null, 9400, 0, NaN, 54, 836]\n        }, {\n          yAxisID: 'y1',\n          data: [undefined, 0, null, 800, 9, NaN, 894, 21]\n        }],\n        labels: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']\n      },\n      options: {\n        scales: {\n          y: {\n            type: 'logarithmic'\n          },\n          y1: {\n            position: 'right',\n            type: 'logarithmic'\n          }\n        }\n      }\n    });\n\n    expect(chart.scales.y).not.toEqual(undefined); // must construct\n    expect(chart.scales.y.min).toBe(1);\n    expect(chart.scales.y.max).toBe(5000);\n\n    // Turn on stacked mode since it uses it's own\n    chart.options.scales.y.stacked = true;\n    chart.update();\n\n    expect(chart.scales.y.min).toBe(1);\n    expect(chart.scales.y.max).toBe(6000);\n\n    expect(chart.scales.y1).not.toEqual(undefined); // must construct\n    expect(chart.scales.y1.min).toBe(1);\n    expect(chart.scales.y1.max).toBe(10000);\n  });\n\n  it('should correctly determine the max & min for scatter data', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          data: [\n            {x: 10, y: 100},\n            {x: 2, y: 6},\n            {x: 65, y: 121},\n            {x: 99, y: 7}\n          ]\n        }]\n      },\n      options: {\n        scales: {\n          x: {\n            type: 'logarithmic',\n            position: 'bottom'\n          },\n          y: {\n            type: 'logarithmic'\n          }\n        }\n      }\n    });\n\n    expect(chart.scales.x.min).toBe(2);\n    expect(chart.scales.x.max).toBe(100);\n\n    expect(chart.scales.y.min).toBe(6);\n    expect(chart.scales.y.max).toBe(150);\n  });\n\n  it('should correctly determine the max & min for scatter data when 0 values are present', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          data: [\n            {x: 7, y: 950},\n            {x: 289, y: 0},\n            {x: 0, y: 8},\n            {x: 23, y: 0.04}\n          ]\n        }]\n      },\n      options: {\n        scales: {\n          x: {\n            type: 'logarithmic',\n            position: 'bottom'\n          },\n          y: {\n            type: 'logarithmic'\n          }\n        }\n      }\n    });\n\n    expect(chart.scales.x.min).toBe(1);\n    expect(chart.scales.x.max).toBe(30);\n\n    expect(chart.scales.y.min).toBe(0.01);\n    expect(chart.scales.y.max).toBe(1000);\n  });\n\n  it('should correctly determine the min and max data values when stacked mode is turned on', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          type: 'bar',\n          yAxisID: 'y',\n          data: [10, 5, 1, 5, 78, 100]\n        }, {\n          yAxisID: 'y1',\n          data: [0, 1000],\n        }, {\n          type: 'bar',\n          yAxisID: 'y',\n          data: [150, 10, 10, 100, 10, 9]\n        }, {\n          type: 'line',\n          yAxisID: 'y',\n          data: [100, 100, 100, 100, 100, 100]\n        }],\n        labels: ['a', 'b', 'c', 'd', 'e', 'f']\n      },\n      options: {\n        scales: {\n          y: {\n            type: 'logarithmic',\n            stacked: true\n          },\n          y1: {\n            position: 'right',\n            type: 'logarithmic'\n          }\n        }\n      }\n    });\n\n    expect(chart.scales.y.min).toBe(0.1);\n    expect(chart.scales.y.max).toBe(200);\n  });\n\n  it('should correctly determine the min and max data values when stacked mode is turned on ignoring hidden datasets', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          yAxisID: 'y',\n          data: [10, 5, 1, 5, 78, 100],\n          type: 'bar'\n        }, {\n          yAxisID: 'y1',\n          data: [0, 1000],\n          type: 'bar'\n        }, {\n          yAxisID: 'y',\n          data: [150, 10, 10, 100, 10, 9],\n          type: 'bar'\n        }, {\n          yAxisID: 'y',\n          data: [10000, 10000, 10000, 10000, 10000, 10000],\n          hidden: true,\n          type: 'bar'\n        }],\n        labels: ['a', 'b', 'c', 'd', 'e', 'f']\n      },\n      options: {\n        scales: {\n          y: {\n            type: 'logarithmic',\n            stacked: true\n          },\n          y1: {\n            position: 'right',\n            type: 'logarithmic'\n          }\n        }\n      }\n    });\n\n    expect(chart.scales.y.min).toBe(0.1);\n    expect(chart.scales.y.max).toBe(200);\n  });\n\n  it('should ensure that the scale has a max and min that are not equal', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          data: []\n        }],\n        labels: []\n      },\n      options: {\n        scales: {\n          y: {\n            type: 'logarithmic'\n          }\n        }\n      }\n    });\n\n    expect(chart.scales.y.min).toBe(1);\n    expect(chart.scales.y.max).toBe(10);\n\n    chart.data.datasets[0].data = [0.15, 0.15];\n    chart.update();\n\n    expect(chart.scales.y.min).toBe(0.1);\n    expect(chart.scales.y.max).toBe(0.15);\n  });\n\n  it('should use the min and max options', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          data: [1, 1, 1, 2, 1, 0]\n        }],\n        labels: []\n      },\n      options: {\n        scales: {\n          y: {\n            type: 'logarithmic',\n            min: 10,\n            max: 1010,\n            ticks: {\n              callback: function(value) {\n                return value;\n              }\n            }\n          }\n        }\n      }\n    });\n\n    var yScale = chart.scales.y;\n    var tickCount = yScale.ticks.length;\n    expect(yScale.min).toBe(10);\n    expect(yScale.max).toBe(1010);\n    expect(yScale.ticks[0].value).toBe(10);\n    expect(yScale.ticks[tickCount - 1].value).toBe(1010);\n  });\n\n  it('should ignore negative min and max options', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          data: [1, 1, 1, 2, 1, 0]\n        }],\n        labels: []\n      },\n      options: {\n        scales: {\n          y: {\n            type: 'logarithmic',\n            min: -10,\n            max: -1010,\n            ticks: {\n              callback: function(value) {\n                return value;\n              }\n            }\n          }\n        }\n      }\n    });\n\n    var y = chart.scales.y;\n    expect(y.min).toBe(0.1);\n    expect(y.max).toBe(2);\n  });\n\n  it('should ignore invalid min and max options', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          data: [1, 1, 1, 2, 1, 0]\n        }],\n        labels: ['a', 'b', 'c', 'd', 'e', 'f']\n      },\n      options: {\n        scales: {\n          y: {\n            type: 'logarithmic',\n            min: 'zero',\n            max: null,\n            ticks: {\n              callback: function(value) {\n                return value;\n              }\n            }\n          }\n        }\n      }\n    });\n\n    var y = chart.scales.y;\n    expect(y.min).toBe(0.1);\n    expect(y.max).toBe(2);\n  });\n\n  it('should generate tick marks', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          data: [10, 5, 2, 25, 78]\n        }],\n        labels: []\n      },\n      options: {\n        scales: {\n          y: {\n            type: 'logarithmic',\n            ticks: {\n              autoSkip: false,\n              callback: function(value) {\n                return value;\n              }\n            }\n          }\n        }\n      }\n    });\n\n    var scale = chart.scales.y;\n    expect(getLabels(scale)).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 30, 40, 50, 60, 70, 80]);\n    expect(scale.start).toEqual(1);\n    expect(scale.end).toEqual(80);\n  });\n\n  it('should generate tick marks when 0 values are present', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          data: [11, 0.8, 0, 28, 7]\n        }],\n        labels: []\n      },\n      options: {\n        scales: {\n          y: {\n            type: 'logarithmic',\n            ticks: {\n              callback: function(value) {\n                return value;\n              }\n            }\n          }\n        }\n      }\n    });\n\n    var scale = chart.scales.y;\n    // Counts down because the lines are drawn top to bottom\n    expect(getLabels(scale)).toEqual([0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1, 1.5, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 30]);\n    expect(scale.start).toEqual(0.1);\n    expect(scale.end).toEqual(30);\n  });\n\n\n  it('should generate tick marks in the correct order in reversed mode', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          data: [10, 5, 1, 25, 78]\n        }],\n        labels: []\n      },\n      options: {\n        scales: {\n          y: {\n            type: 'logarithmic',\n            reverse: true,\n            ticks: {\n              autoSkip: false,\n              callback: function(value) {\n                return value;\n              }\n            }\n          }\n        }\n      }\n    });\n\n    var scale = chart.scales.y;\n    expect(getLabels(scale)).toEqual([80, 70, 60, 50, 40, 30, 20, 15, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]);\n    expect(scale.start).toEqual(80);\n    expect(scale.end).toEqual(1);\n  });\n\n  it('should generate tick marks in the correct order in reversed mode when 0 values are present', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          data: [21, 9, 0, 10, 25]\n        }],\n        labels: []\n      },\n      options: {\n        scales: {\n          y: {\n            type: 'logarithmic',\n            reverse: true,\n            ticks: {\n              callback: function(value) {\n                return value;\n              }\n            }\n          }\n        }\n      }\n    });\n\n    var scale = chart.scales.y;\n    expect(getLabels(scale)).toEqual([30, 20, 15, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]);\n    expect(scale.start).toEqual(30);\n    expect(scale.end).toEqual(1);\n  });\n\n  it('should build labels using the default template', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          data: [10, 5, 1.1, 25, 0, 78]\n        }],\n        labels: []\n      },\n      options: {\n        scales: {\n          y: {\n            type: 'logarithmic',\n            ticks: {\n              autoSkip: false\n            }\n          }\n        }\n      }\n    });\n\n    expect(getLabels(chart.scales.y)).toEqual(['1', '2', '3', '', '5', '', '', '', '', '10', '15', '20', '30', '', '50', '60', '70', '80']);\n  });\n\n  it('should build labels using the user supplied callback', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          data: [10, 5, 2, 25, 78]\n        }],\n        labels: []\n      },\n      options: {\n        scales: {\n          y: {\n            type: 'logarithmic',\n            ticks: {\n              callback: function(value, index) {\n                return index.toString();\n              }\n            }\n          }\n        }\n      }\n    });\n\n    // Just the index\n    expect(getLabels(chart.scales.y)).toEqual(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17']);\n  });\n\n  it('should correctly get the correct label for a data item', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          yAxisID: 'y',\n          data: [10, 5, 5000, 78, 450]\n        }, {\n          yAxisID: 'y1',\n          data: [1, 1000, 10, 100],\n        }, {\n          yAxisID: 'y',\n          data: [150]\n        }],\n        labels: []\n      },\n      options: {\n        scales: {\n          y: {\n            type: 'logarithmic'\n          },\n          y1: {\n            position: 'right',\n            type: 'logarithmic'\n          }\n        }\n      }\n    });\n\n    expect(chart.scales.y.getLabelForValue(150)).toBe('150');\n  });\n\n  it('should correctly use the locale when generating the label', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          yAxisID: 'y',\n          data: [10, 5, 5000, 78, 450]\n        }, {\n          yAxisID: 'y1',\n          data: [1, 1000, 10, 100],\n        }, {\n          yAxisID: 'y',\n          data: [150]\n        }],\n        labels: []\n      },\n      options: {\n        locale: 'de-DE',\n        scales: {\n          y: {\n            type: 'logarithmic'\n          },\n          y1: {\n            position: 'right',\n            type: 'logarithmic'\n          }\n        }\n      }\n    });\n\n    expect(chart.scales.y.getLabelForValue(10.25)).toBe('10,25');\n  });\n\n  describe('when', function() {\n    var data = [\n      {\n        data: [1, 39],\n        stack: 'stack'\n      },\n      {\n        data: [1, 39],\n        stack: 'stack'\n      },\n    ];\n    var dataWithEmptyStacks = [\n      {\n        data: []\n      },\n      {\n        data: []\n      }\n    ].concat(data);\n    var config = [\n      {\n        axis: 'y',\n        firstTick: 1, // start of the axis (minimum)\n        describe: 'all stacks are defined'\n      },\n      {\n        axis: 'y',\n        data: dataWithEmptyStacks,\n        firstTick: 1,\n        describe: 'not all stacks are defined'\n      },\n      {\n        axis: 'y',\n        scale: {\n          y: {\n            min: 0\n          }\n        },\n        firstTick: 0.1,\n        describe: 'all stacks are defined and min: 0'\n      },\n      {\n        axis: 'y',\n        data: dataWithEmptyStacks,\n        scale: {\n          y: {\n            min: 0\n          }\n        },\n        firstTick: 0.1,\n        describe: 'not stacks are defined and min: 0'\n      },\n      {\n        axis: 'x',\n        firstTick: 1,\n        describe: 'all stacks are defined'\n      },\n      {\n        axis: 'x',\n        data: dataWithEmptyStacks,\n        firstTick: 1,\n        describe: 'not all stacks are defined'\n      },\n      {\n        axis: 'x',\n        scale: {\n          x: {\n            min: 0\n          }\n        },\n        firstTick: 0.1,\n        describe: 'all stacks are defined and min: 0'\n      },\n      {\n        axis: 'x',\n        data: dataWithEmptyStacks,\n        scale: {\n          x: {\n            min: 0\n          }\n        },\n        firstTick: 0.1,\n        describe: 'not all stacks are defined and min: 0'\n      },\n    ];\n    config.forEach(function(setup) {\n      var scaleConfig = {};\n      var indexAxis, chartStart, chartEnd;\n\n      if (setup.axis === 'x') {\n        indexAxis = 'y';\n        chartStart = 'left';\n        chartEnd = 'right';\n      } else {\n        indexAxis = 'x';\n        chartStart = 'bottom';\n        chartEnd = 'top';\n      }\n      scaleConfig[setup.axis] = {\n        type: 'logarithmic',\n        beginAtZero: false\n      };\n      Object.assign(scaleConfig, setup.scale);\n      scaleConfig[setup.axis].type = 'logarithmic';\n\n      var description = 'dataset has stack option and ' + setup.describe\n\t\t\t\t+ ' and axis is \"' + setup.axis + '\";';\n      describe(description, function() {\n        it('should define the correct axis limits', function() {\n          var chart = window.acquireChart({\n            type: 'bar',\n            data: {\n              labels: ['category 1', 'category 2'],\n              datasets: setup.data || data,\n            },\n            options: {\n              indexAxis,\n              scales: scaleConfig\n            }\n          });\n\n          var axisID = setup.axis;\n          var scale = chart.scales[axisID];\n          var firstTick = setup.firstTick;\n          var lastTick = 80; // last tick (should be first available tick after: 2 * 39)\n          var start = chart.chartArea[chartStart];\n          var end = chart.chartArea[chartEnd];\n\n          expect(scale.getPixelForValue(firstTick)).toBeCloseToPixel(start);\n          expect(scale.getPixelForValue(lastTick)).toBeCloseToPixel(end);\n\n          expect(scale.getValueForPixel(start)).toBeCloseTo(firstTick, 4);\n          expect(scale.getValueForPixel(end)).toBeCloseTo(lastTick, 4);\n\n          chart.scales[axisID].options.reverse = true; // Reverse mode\n          chart.update();\n\n          // chartArea might have been resized in update\n          start = chart.chartArea[chartEnd];\n          end = chart.chartArea[chartStart];\n\n          expect(scale.getPixelForValue(firstTick)).toBeCloseToPixel(start);\n          expect(scale.getPixelForValue(lastTick)).toBeCloseToPixel(end);\n\n          expect(scale.getValueForPixel(start)).toBeCloseTo(firstTick, 4);\n          expect(scale.getValueForPixel(end)).toBeCloseTo(lastTick, 4);\n        });\n      });\n    });\n  });\n\n  describe('when', function() {\n    var config = [\n      {\n        dataset: [],\n        firstTick: 1, // value of the first tick\n        lastTick: 10, // value of the last tick\n        describe: 'empty dataset, without min/max'\n      },\n      {\n        dataset: [],\n        scale: {stacked: true},\n        firstTick: 1,\n        lastTick: 10,\n        describe: 'empty dataset, without min/max, with stacked: true'\n      },\n      {\n        data: {\n          datasets: [\n            {data: [], stack: 'stack'},\n            {data: [], stack: 'stack'},\n          ],\n        },\n        type: 'bar',\n        firstTick: 1,\n        lastTick: 10,\n        describe: 'empty dataset with stack option, without min/max'\n      },\n      {\n        dataset: [],\n        scale: {min: 1},\n        firstTick: 1,\n        lastTick: 10,\n        describe: 'empty dataset, min: 1, without max'\n      },\n      {\n        dataset: [],\n        scale: {max: 80},\n        firstTick: 1,\n        lastTick: 80,\n        describe: 'empty dataset, max: 80, without min'\n      },\n      {\n        dataset: [],\n        scale: {max: 0.8},\n        firstTick: 0.01,\n        lastTick: 0.8,\n        describe: 'empty dataset, max: 0.8, without min'\n      },\n      {\n        dataset: [{x: 10, y: 10}, {x: 5, y: 5}, {x: 1, y: 1}, {x: 25, y: 25}, {x: 78, y: 78}],\n        firstTick: 1,\n        lastTick: 80,\n        describe: 'dataset min point {x: 1, y: 1}, max point {x:78, y:78}'\n      },\n    ];\n    config.forEach(function(setup) {\n      var axes = [\n        {\n          id: 'x', // horizontal scale\n          start: 'left',\n          end: 'right'\n        },\n        {\n          id: 'y', // vertical scale\n          start: 'bottom',\n          end: 'top'\n        }\n      ];\n      axes.forEach(function(axis) {\n        var expectation = 'min = ' + setup.firstTick + ', max = ' + setup.lastTick;\n        describe(setup.describe + ' and axis is \"' + axis.id + '\"; expect: ' + expectation + ';', function() {\n          beforeEach(function() {\n            var xConfig = {\n              type: 'logarithmic',\n              position: 'bottom'\n            };\n            var yConfig = {\n              type: 'logarithmic',\n              position: 'left'\n            };\n            var data = setup.data || {\n              datasets: [{\n                data: setup.dataset\n              }],\n            };\n            Object.assign(xConfig, setup.scale);\n            Object.assign(yConfig, setup.scale);\n            Object.assign(data, setup.data || {});\n            this.chart = window.acquireChart({\n              type: 'line',\n              data: data,\n              options: {\n                scales: {\n                  x: xConfig,\n                  y: yConfig\n                }\n              }\n            });\n          });\n\n          it('should get the correct pixel value for a point', function() {\n            var chart = this.chart;\n            var axisID = axis.id;\n            var scale = chart.scales[axisID];\n            var firstTick = setup.firstTick;\n            var lastTick = setup.lastTick;\n            var start = chart.chartArea[axis.start];\n            var end = chart.chartArea[axis.end];\n\n            expect(scale.getPixelForValue(firstTick)).toBeCloseToPixel(start);\n            expect(scale.getPixelForValue(lastTick)).toBeCloseToPixel(end);\n            expect(scale.getPixelForValue(0)).toBeCloseToPixel(start); // 0 is invalid, put it at the start.\n\n            expect(scale.getValueForPixel(start)).toBeCloseTo(firstTick, 4);\n            expect(scale.getValueForPixel(end)).toBeCloseTo(lastTick, 4);\n\n            chart.scales[axisID].options.reverse = true; // Reverse mode\n            chart.update();\n\n            // chartArea might have been resized in update\n            start = chart.chartArea[axis.end];\n            end = chart.chartArea[axis.start];\n\n            expect(scale.getPixelForValue(firstTick)).toBeCloseToPixel(start);\n            expect(scale.getPixelForValue(lastTick)).toBeCloseToPixel(end);\n\n            expect(scale.getValueForPixel(start)).toBeCloseTo(firstTick, 4);\n            expect(scale.getValueForPixel(end)).toBeCloseTo(lastTick, 4);\n          });\n        });\n      });\n    });\n  });\n\n  describe('when', function() {\n    var config = [\n      {\n        dataset: [],\n        scale: {min: 0},\n        lastTick: 10, // value of the last tick\n        describe: 'empty dataset, min: 0, without max'\n      },\n      {\n        dataset: [],\n        scale: {min: 0, max: 80},\n        lastTick: 80,\n        describe: 'empty dataset, min: 0, max: 80'\n      },\n      {\n        dataset: [],\n        scale: {min: 0, max: 0.8},\n        lastTick: 0.8,\n        describe: 'empty dataset, min: 0, max: 0.8'\n      },\n      {\n        dataset: [{x: 0, y: 0}, {x: 10, y: 10}, {x: 1.2, y: 1.2}, {x: 25, y: 25}, {x: 78, y: 78}],\n        lastTick: 80,\n        describe: 'dataset min point {x: 0, y: 0}, max point {x:78, y:78}'\n      },\n      {\n        dataset: [{x: 0, y: 0}, {x: 10, y: 10}, {x: 6.3, y: 6.3}, {x: 25, y: 25}, {x: 78, y: 78}],\n        lastTick: 80,\n        describe: 'dataset min point {x: 0, y: 0}, max point {x:78, y:78}'\n      },\n      {\n        dataset: [{x: 10, y: 10}, {x: 1.2, y: 1.2}, {x: 25, y: 25}, {x: 78, y: 78}],\n        scale: {min: 0},\n        lastTick: 80,\n        describe: 'dataset min point {x: 1.2, y: 1.2}, max point {x:78, y:78}, min: 0'\n      },\n      {\n        dataset: [{x: 10, y: 10}, {x: 6.3, y: 6.3}, {x: 25, y: 25}, {x: 78, y: 78}],\n        scale: {min: 0},\n        lastTick: 80,\n        describe: 'dataset min point {x: 6.3, y: 6.3}, max point {x:78, y:78}, min: 0'\n      },\n    ];\n    config.forEach(function(setup) {\n      var axes = [\n        {\n          id: 'x', // horizontal scale\n          start: 'left',\n          end: 'right'\n        },\n        {\n          id: 'y', // vertical scale\n          start: 'bottom',\n          end: 'top'\n        }\n      ];\n      axes.forEach(function(axis) {\n        var expectation = 'min = 0, max = ' + setup.lastTick;\n        describe(setup.describe + ' and axis is \"' + axis.id + '\"; expect: ' + expectation + ';', function() {\n          beforeEach(function() {\n            var xConfig = {\n              type: 'logarithmic',\n              position: 'bottom'\n            };\n            var yConfig = {\n              type: 'logarithmic',\n              position: 'left'\n            };\n            var data = setup.data || {\n              datasets: [{\n                data: setup.dataset\n              }],\n            };\n            Object.assign(xConfig, setup.scale);\n            Object.assign(yConfig, setup.scale);\n            Object.assign(data, setup.data || {});\n            this.chart = window.acquireChart({\n              type: 'line',\n              data: data,\n              options: {\n                scales: {\n                  x: xConfig,\n                  y: yConfig\n                }\n              }\n            });\n          });\n\n          it('should get the correct pixel value for a point', function() {\n            var chart = this.chart;\n            var axisID = axis.id;\n            var scale = chart.scales[axisID];\n            var lastTick = setup.lastTick;\n            var start = chart.chartArea[axis.start];\n            var end = chart.chartArea[axis.end];\n\n            expect(scale.getPixelForValue(0)).toBeCloseToPixel(start);\n            expect(scale.getPixelForValue(lastTick)).toBeCloseToPixel(end);\n\n            expect(scale.getValueForPixel(start)).toBeCloseTo(scale.min, 4);\n            expect(scale.getValueForPixel(end)).toBeCloseTo(lastTick, 4);\n\n            chart.scales[axisID].options.reverse = true; // Reverse mode\n            chart.update();\n\n            // chartArea might have been resized in update\n            start = chart.chartArea[axis.end];\n            end = chart.chartArea[axis.start];\n\n            expect(scale.getPixelForValue(0)).toBeCloseToPixel(start);\n            expect(scale.getPixelForValue(lastTick)).toBeCloseToPixel(end);\n\n            expect(scale.getValueForPixel(start)).toBeCloseTo(scale.min, 4);\n            expect(scale.getValueForPixel(end)).toBeCloseTo(lastTick, 4);\n          });\n        });\n      });\n    });\n  });\n\n  it('Should correctly determine the max & min when no values provided and suggested minimum and maximum are set', function() {\n    var chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          yAxisID: 'y',\n          data: []\n        }],\n        labels: ['a', 'b', 'c', 'd', 'e', 'f']\n      },\n      options: {\n        scales: {\n          y: {\n            type: 'logarithmic',\n            suggestedMin: 10,\n            suggestedMax: 100\n          }\n        }\n      }\n    });\n\n    expect(chart.scales.y).not.toEqual(undefined); // must construct\n    expect(chart.scales.y.min).toBe(10);\n    expect(chart.scales.y.max).toBe(100);\n  });\n\n  it('Should bound to data', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        labels: ['a', 'b'],\n        datasets: [{\n          data: [1.1, 99]\n        }]\n      },\n      options: {\n        scales: {\n          y: {\n            type: 'logarithmic',\n            bounds: 'data'\n          }\n        }\n      }\n    });\n\n    expect(chart.scales.y.min).toEqual(1.1);\n    expect(chart.scales.y.max).toEqual(99);\n  });\n});\n"
  },
  {
    "path": "test/specs/scale.radialLinear.tests.js",
    "content": "function getLabels(scale) {\n  return scale.ticks.map(t => t.label);\n}\n\n// Tests for the radial linear scale used by the polar area and radar charts\ndescribe('Test the radial linear scale', function() {\n  describe('auto', jasmine.fixture.specs('scale.radialLinear'));\n\n  it('Should register the constructor with the registry', function() {\n    var Constructor = Chart.registry.getScale('radialLinear');\n    expect(Constructor).not.toBe(undefined);\n    expect(typeof Constructor).toBe('function');\n  });\n\n  it('Should have the correct default config', function() {\n    var defaultConfig = Chart.defaults.scales.radialLinear;\n    expect(defaultConfig).toEqual({\n      display: true,\n      animate: true,\n      position: 'chartArea',\n\n      angleLines: {\n        display: true,\n        color: 'rgba(0,0,0,0.1)',\n        lineWidth: 1,\n        borderDash: [],\n        borderDashOffset: 0.0\n      },\n\n      grid: {\n        circular: false\n      },\n\n      startAngle: 0,\n\n      ticks: {\n        color: Chart.defaults.color,\n        showLabelBackdrop: true,\n        callback: defaultConfig.ticks.callback\n      },\n\n      pointLabels: {\n        backdropColor: undefined,\n        backdropPadding: 2,\n        color: Chart.defaults.color,\n        display: true,\n        font: {\n          size: 10\n        },\n        callback: defaultConfig.pointLabels.callback,\n        padding: 5,\n        centerPointLabels: false\n      }\n    });\n\n    // Is this actually a function\n    expect(defaultConfig.ticks.callback).toEqual(jasmine.any(Function));\n    expect(defaultConfig.pointLabels.callback).toEqual(jasmine.any(Function));\n  });\n\n  it('Should correctly determine the max & min data values', function() {\n    var chart = window.acquireChart({\n      type: 'radar',\n      data: {\n        datasets: [{\n          data: [10, 5, 0, -5, 78, -100]\n        }, {\n          data: [150]\n        }],\n        labels: ['label1', 'label2', 'label3', 'label4', 'label5', 'label6']\n      },\n      options: {\n        scales: {}\n      }\n    });\n\n    expect(chart.scales.r.min).toBe(-100);\n    expect(chart.scales.r.max).toBe(150);\n  });\n\n  it('Should correctly determine the max & min of string data values', function() {\n    var chart = window.acquireChart({\n      type: 'radar',\n      data: {\n        datasets: [{\n          data: ['10', '5', '0', '-5', '78', '-100']\n        }, {\n          data: ['150']\n        }],\n        labels: ['label1', 'label2', 'label3', 'label4', 'label5', 'label6']\n      },\n      options: {\n        scales: {}\n      }\n    });\n\n    expect(chart.scales.r.min).toBe(-100);\n    expect(chart.scales.r.max).toBe(150);\n  });\n\n  it('Should correctly determine the max & min data values when there are hidden datasets', function() {\n    var chart = window.acquireChart({\n      type: 'radar',\n      data: {\n        datasets: [{\n          data: ['10', '5', '0', '-5', '78', '-100']\n        }, {\n          data: ['150']\n        }, {\n          data: [1000],\n          hidden: true\n        }],\n        labels: ['label1', 'label2', 'label3', 'label4', 'label5', 'label6']\n      },\n      options: {\n        scales: {}\n      }\n    });\n\n    expect(chart.scales.r.min).toBe(-100);\n    expect(chart.scales.r.max).toBe(150);\n  });\n\n  it('Should correctly determine the max & min data values when there is NaN data', function() {\n    var chart = window.acquireChart({\n      type: 'radar',\n      data: {\n        datasets: [{\n          data: [50, 60, NaN, 70, null, undefined, Infinity, -Infinity]\n        }],\n        labels: ['label1', 'label2', 'label3', 'label4', 'label5', 'label6', 'label7', 'label8']\n      },\n      options: {\n        scales: {}\n      }\n    });\n\n    expect(chart.scales.r.min).toBe(50);\n    expect(chart.scales.r.max).toBe(70);\n  });\n\n  it('Should ensure that the scale has a max and min that are not equal', function() {\n    var chart = window.acquireChart({\n      type: 'radar',\n      data: {\n        datasets: [],\n        labels: []\n      },\n      options: {\n        scales: {\n          rScale: {}\n        }\n      }\n    });\n\n    var scale = chart.scales.rScale;\n\n    expect(scale.min).toBe(-1);\n    expect(scale.max).toBe(1);\n  });\n\n  it('Should use the suggestedMin and suggestedMax options', function() {\n    var chart = window.acquireChart({\n      type: 'radar',\n      data: {\n        datasets: [{\n          data: [1, 1, 1, 2, 1, 0]\n        }],\n        labels: ['label1', 'label2', 'label3', 'label4', 'label5', 'label6']\n      },\n      options: {\n        scales: {\n          r: {\n            suggestedMin: -10,\n            suggestedMax: 10\n          }\n        }\n      }\n    });\n\n    expect(chart.scales.r.min).toBe(-10);\n    expect(chart.scales.r.max).toBe(10);\n  });\n\n  it('Should use the min and max options', function() {\n    var chart = window.acquireChart({\n      type: 'radar',\n      data: {\n        datasets: [{\n          data: [1, 1, 1, 2, 1, 0]\n        }],\n        labels: ['label1', 'label2', 'label3', 'label4', 'label5', 'label6']\n      },\n      options: {\n        scales: {\n          r: {\n            min: -1010,\n            max: 1010\n          }\n        }\n      }\n    });\n\n    expect(chart.scales.r.min).toBe(-1010);\n    expect(chart.scales.r.max).toBe(1010);\n    expect(getLabels(chart.scales.r)).toEqual(['-1,010', '-500', '0', '500', '1,010']);\n  });\n\n  it('should forcibly include 0 in the range if the beginAtZero option is used', function() {\n    var chart = window.acquireChart({\n      type: 'radar',\n      data: {\n        datasets: [{\n          data: [20, 30, 40, 50]\n        }],\n        labels: ['label1', 'label2', 'label3', 'label4']\n      },\n      options: {\n        scales: {\n          r: {\n            beginAtZero: false\n          }\n        }\n      }\n    });\n\n    expect(getLabels(chart.scales.r)).toEqual(['20', '25', '30', '35', '40', '45', '50']);\n\n    chart.scales.r.options.beginAtZero = true;\n    chart.update();\n\n    expect(getLabels(chart.scales.r)).toEqual(['0', '5', '10', '15', '20', '25', '30', '35', '40', '45', '50']);\n\n    chart.data.datasets[0].data = [-20, -30, -40, -50];\n    chart.update();\n\n    expect(getLabels(chart.scales.r)).toEqual(['-50', '-45', '-40', '-35', '-30', '-25', '-20', '-15', '-10', '-5', '0']);\n\n    chart.scales.r.options.beginAtZero = false;\n    chart.update();\n\n    expect(getLabels(chart.scales.r)).toEqual(['-50', '-45', '-40', '-35', '-30', '-25', '-20']);\n  });\n\n  it('Should generate tick marks in the correct order in reversed mode', function() {\n    var chart = window.acquireChart({\n      type: 'radar',\n      data: {\n        datasets: [{\n          data: [10, 5, 0, 25, 78]\n        }],\n        labels: ['label1', 'label2', 'label3', 'label4', 'label5']\n      },\n      options: {\n        scales: {\n          r: {\n            reverse: true\n          }\n        }\n      }\n    });\n\n    expect(getLabels(chart.scales.r)).toEqual(['80', '70', '60', '50', '40', '30', '20', '10', '0']);\n    expect(chart.scales.r.start).toBe(80);\n    expect(chart.scales.r.end).toBe(0);\n  });\n\n  it('Should correctly limit the maximum number of ticks', function() {\n    var chart = window.acquireChart({\n      type: 'radar',\n      data: {\n        labels: ['label1', 'label2', 'label3'],\n        datasets: [{\n          data: [0.5, 1.5, 2.5]\n        }]\n      },\n      options: {\n        scales: {\n          r: {\n            pointLabels: {\n              display: false\n            }\n          }\n        }\n      }\n    });\n\n    expect(getLabels(chart.scales.r)).toEqual(['0.5', '1.0', '1.5', '2.0', '2.5']);\n\n    chart.options.scales.r.ticks.maxTicksLimit = 11;\n    chart.update();\n\n    expect(getLabels(chart.scales.r)).toEqual(['0.5', '1.0', '1.5', '2.0', '2.5']);\n\n    chart.options.scales.r.ticks.stepSize = 0.01;\n    chart.update();\n\n    expect(getLabels(chart.scales.r)).toEqual(['0.5', '1.0', '1.5', '2.0', '2.5']);\n\n    chart.options.scales.r.min = 0.3;\n    chart.options.scales.r.max = 2.8;\n    chart.update();\n\n    expect(getLabels(chart.scales.r)).toEqual(['0.3', '0.8', '1.3', '1.8', '2.3', '2.8']);\n  });\n\n  it('Should build labels using the user supplied callback', function() {\n    var chart = window.acquireChart({\n      type: 'radar',\n      data: {\n        datasets: [{\n          data: [10, 5, 0, 25, 78]\n        }],\n        labels: ['label1', 'label2', 'label3', 'label4', 'label5']\n      },\n      options: {\n        scales: {\n          r: {\n            ticks: {\n              callback: function(value, index) {\n                return index.toString();\n              }\n            }\n          }\n        }\n      }\n    });\n\n    expect(getLabels(chart.scales.r)).toEqual(['0', '1', '2', '3', '4', '5', '6', '7', '8']);\n    expect(chart.scales.r._pointLabels).toEqual(['label1', 'label2', 'label3', 'label4', 'label5']);\n  });\n\n  it('Should build point labels using the user supplied callback', function() {\n    var chart = window.acquireChart({\n      type: 'radar',\n      data: {\n        datasets: [{\n          data: [10, 5, 0, 25, 78]\n        }],\n        labels: ['label1', 'label2', 'label3', 'label4', 'label5']\n      },\n      options: {\n        scales: {\n          r: {\n            pointLabels: {\n              callback: function(value, index) {\n                return index.toString();\n              }\n            }\n          }\n        }\n      }\n    });\n\n    expect(chart.scales.r._pointLabels).toEqual(['0', '1', '2', '3', '4']);\n  });\n\n  it('Should build point labels from falsy values', function() {\n    var chart = window.acquireChart({\n      type: 'radar',\n      data: {\n        datasets: [{\n          data: [10, 5, 0, 25, 78, 20]\n        }],\n        labels: [0, '', undefined, null, NaN, false]\n      }\n    });\n\n    expect(chart.scales.r._pointLabels).toEqual([0, '', '', '', '', '']);\n  });\n\n  it('Should build point labels considering hidden data', function() {\n    const chart = window.acquireChart({\n      type: 'polarArea',\n      data: {\n        datasets: [{\n          data: [10, 5, 0, 25, 78, 20]\n        }],\n        labels: ['a', 'b', 'c', 'd', 'e', 'f']\n      }\n    });\n    chart.toggleDataVisibility(3);\n    chart.update();\n\n    expect(chart.scales.r._pointLabels).toEqual(['a', 'b', 'c', 'e', 'f']);\n  });\n\n  it('should correctly set the center point', function() {\n    var chart = window.acquireChart({\n      type: 'radar',\n      data: {\n        datasets: [{\n          data: [10, 5, 0, 25, 78]\n        }],\n        labels: ['label1', 'label2', 'label3', 'label4', 'label5']\n      },\n      options: {\n        scales: {\n          r: {\n            pointLabels: {\n              callback: function(value, index) {\n                return index.toString();\n              }\n            }\n          }\n        }\n      }\n    });\n\n    expect(chart.scales.r.drawingArea).toBe(215);\n    expect(chart.scales.r.xCenter).toBe(256);\n    expect(chart.scales.r.yCenter).toBe(280);\n  });\n\n  it('should correctly get the label for a given data index', function() {\n    var chart = window.acquireChart({\n      type: 'radar',\n      data: {\n        datasets: [{\n          data: [10, 5, 0, 25, 78]\n        }],\n        labels: ['label1', 'label2', 'label3', 'label4', 'label5']\n      },\n      options: {\n        scales: {\n          r: {\n            pointLabels: {\n              callback: function(value, index) {\n                return index.toString();\n              }\n            }\n          }\n        }\n      }\n    });\n    expect(chart.scales.r.getLabelForValue(5)).toBe('5');\n  });\n\n  it('should get the correct distance from the center point', function() {\n    var chart = window.acquireChart({\n      type: 'radar',\n      data: {\n        datasets: [{\n          data: [10, 5, 0, 25, 78]\n        }],\n        labels: ['label1', 'label2', 'label3', 'label4', 'label5']\n      },\n      options: {\n        scales: {\n          r: {\n            pointLabels: {\n              callback: function(value, index) {\n                return index.toString();\n              }\n            }\n          }\n        }\n      }\n    });\n\n    expect(chart.scales.r.getDistanceFromCenterForValue(chart.scales.r.min)).toBe(0);\n    expect(chart.scales.r.getDistanceFromCenterForValue(chart.scales.r.max)).toBe(215);\n\n    var position = chart.scales.r.getPointPositionForValue(1, 5);\n    expect(position.x).toBeCloseToPixel(269);\n    expect(position.y).toBeCloseToPixel(276);\n\n    chart.scales.r.options.reverse = true;\n    chart.update();\n\n    expect(chart.scales.r.getDistanceFromCenterForValue(chart.scales.r.min)).toBe(215);\n    expect(chart.scales.r.getDistanceFromCenterForValue(chart.scales.r.max)).toBe(0);\n  });\n\n  it('should get the correct value for a distance from the center point', function() {\n    var chart = window.acquireChart({\n      type: 'radar',\n      data: {\n        datasets: [{\n          data: [10, 5, 0, 25, 78]\n        }],\n        labels: ['label1', 'label2', 'label3', 'label4', 'label5']\n      },\n      options: {\n        scales: {\n          r: {\n            pointLabels: {\n              callback: function(value, index) {\n                return index.toString();\n              }\n            }\n          }\n        }\n      }\n    });\n\n    expect(chart.scales.r.getValueForDistanceFromCenter(0)).toBe(chart.scales.r.min);\n    expect(chart.scales.r.getValueForDistanceFromCenter(215)).toBe(chart.scales.r.max);\n\n    var dist = chart.scales.r.getDistanceFromCenterForValue(5);\n    expect(chart.scales.r.getValueForDistanceFromCenter(dist)).toBe(5);\n\n    chart.scales.r.options.reverse = true;\n    chart.update();\n\n    expect(chart.scales.r.getValueForDistanceFromCenter(0)).toBe(chart.scales.r.max);\n    expect(chart.scales.r.getValueForDistanceFromCenter(215)).toBe(chart.scales.r.min);\n  });\n\n  it('should correctly get angles for all points', function() {\n    var chart = window.acquireChart({\n      type: 'radar',\n      data: {\n        datasets: [{\n          data: [10, 5, 0, 25, 78]\n        }],\n        labels: ['label1', 'label2', 'label3', 'label4', 'label5']\n      },\n      options: {\n        scales: {\n          r: {\n            startAngle: 15,\n            pointLabels: {\n              callback: function(value, index) {\n                return index.toString();\n              }\n            }\n          }\n        },\n      }\n    });\n\n    var radToNearestDegree = function(rad) {\n      return Math.round((360 * rad) / (2 * Math.PI));\n    };\n\n    var slice = 72; // (360 / 5)\n\n    for (var i = 0; i < 5; i++) {\n      expect(radToNearestDegree(chart.scales.r.getIndexAngle(i))).toBe(15 + (slice * i));\n    }\n\n    chart.scales.r.options.startAngle = 0;\n    chart.update();\n\n    for (var x = 0; x < 5; x++) {\n      expect(radToNearestDegree(chart.scales.r.getIndexAngle(x))).toBe((slice * x));\n    }\n  });\n\n  it('should correctly get the correct label alignment for all points', function() {\n    var chart = window.acquireChart({\n      type: 'radar',\n      data: {\n        datasets: [{\n          data: [10, 5, 0, 25, 78]\n        }],\n        labels: ['label1', 'label2', 'label3', 'label4', 'label5']\n      },\n      options: {\n        scales: {\n          r: {\n            pointLabels: {\n              callback: function(value, index) {\n                return index.toString();\n              }\n            },\n            ticks: {\n              display: false\n            }\n          }\n        }\n      }\n    });\n\n    var scale = chart.scales.r;\n\n    [{\n      startAngle: 30,\n      textAlign: ['right', 'right', 'left', 'left', 'left'],\n    }, {\n      startAngle: -30,\n      textAlign: ['right', 'right', 'left', 'left', 'right'],\n    }, {\n      startAngle: 750,\n      textAlign: ['right', 'right', 'left', 'left', 'left'],\n    }].forEach(function(expected) {\n      scale.options.startAngle = expected.startAngle;\n      chart.update();\n\n      scale.ctx = window.createMockContext();\n      chart.draw();\n\n      scale.ctx.getCalls().filter(function(x) {\n        return x.name === 'setTextAlign';\n      }).forEach(function(x, i) {\n        expect(x.args[0]).withContext('startAngle: ' + expected.startAngle + ', tick: ' + i).toBe(expected.textAlign[i]);\n      });\n    });\n  });\n\n  it('should correctly get the point positions in center', function() {\n    var chart = window.acquireChart({\n      type: 'polarArea',\n      data: {\n        datasets: [{\n          data: [10, 5, 0, 25, 78]\n        }],\n        labels: ['label1', 'label2', 'label3', 'label4', 'label5']\n      },\n      options: {\n        scales: {\n          r: {\n            pointLabels: {\n              display: true,\n              padding: 5,\n              centerPointLabels: true\n            },\n            ticks: {\n              display: false\n            }\n          }\n        }\n      }\n    });\n\n    const PI = Math.PI;\n    const lavelNum = 5;\n    const padding = 5;\n    const pointLabelItems = chart.scales.r._pointLabelItems;\n    const additionalAngle = PI / lavelNum;\n    const opts = chart.scales.r.options;\n    const outerDistance = chart.scales.r.getDistanceFromCenterForValue(opts.ticks.reverse ? chart.scales.r.min : chart.scales.r.max);\n    const tickBackdropHeight = 0;\n    const yForAngle = function(y, h, angle) {\n      if (angle === 90 || angle === 270) {\n        y -= (h / 2);\n      } else if (angle > 270 || angle < 90) {\n        y -= h;\n      }\n      return y;\n    };\n    const toDegrees = function(radians) {\n      return radians * (180 / PI);\n    };\n\n    for (var i = 0; i < 5; i++) {\n      const extra = (i === 0 ? tickBackdropHeight / 2 : 0);\n      const pointLabelItem = pointLabelItems[i];\n      const pointPosition = chart.scales.r.getPointPosition(i, outerDistance + extra + padding, additionalAngle);\n      expect(pointLabelItem.x).toBe(pointPosition.x);\n      expect(pointLabelItem.y).toBe(yForAngle(pointPosition.y, 12, toDegrees(pointPosition.angle + PI / 2)));\n    }\n\n  });\n});\n"
  },
  {
    "path": "test/specs/scale.time.tests.js",
    "content": "// Time scale tests\ndescribe('Time scale tests', function() {\n  describe('auto', jasmine.fixture.specs('scale.time'));\n\n  function createScale(data, options, dimensions) {\n    var width = (dimensions && dimensions.width) || 400;\n    var height = (dimensions && dimensions.height) || 50;\n\n    options = options || {};\n    options.type = 'time';\n    options.id = 'xScale0';\n\n    var chart = window.acquireChart({\n      type: 'line',\n      data: data,\n      options: {\n        scales: {\n          x: options\n        }\n      }\n    }, {canvas: {width: width, height: height}});\n\n\n    return chart.scales.x;\n  }\n\n  function getLabels(scale) {\n    return scale.ticks.map(t => t.label);\n  }\n\n  beforeEach(function() {\n    // Need a time matcher for getValueFromPixel\n    jasmine.addMatchers({\n      toBeCloseToTime: function() {\n        return {\n          compare: function(time, expected) {\n            var result = false;\n            var actual = moment(time);\n            var diff = actual.diff(expected.value, expected.unit, true);\n            result = Math.abs(diff) < (expected.threshold !== undefined ? expected.threshold : 0.01);\n\n            return {\n              pass: result\n            };\n          }\n        };\n      }\n    });\n  });\n\n  it('should load moment.js as a dependency', function() {\n    expect(window.moment).not.toBe(undefined);\n  });\n\n  it('should register the constructor with the registry', function() {\n    var Constructor = Chart.registry.getScale('time');\n    expect(Constructor).not.toBe(undefined);\n    expect(typeof Constructor).toBe('function');\n  });\n\n  it('should have the correct default config', function() {\n    var defaultConfig = Chart.defaults.scales.time;\n    expect(defaultConfig).toEqual({\n      bounds: 'data',\n      adapters: {},\n      time: {\n        parser: false, // false == a pattern string from or a custom callback that converts its argument to a timestamp\n        unit: false, // false == automatic or override with week, month, year, etc.\n        round: false, // none, or override with week, month, year, etc.\n        isoWeekday: false, // override week start day\n        minUnit: 'millisecond',\n        displayFormats: {}\n      },\n      ticks: {\n        source: 'auto',\n        callback: false,\n        major: {\n          enabled: false\n        }\n      }\n    });\n  });\n\n  it('should correctly determine the unit', function() {\n    var date = moment('Jan 01 1990', 'MMM DD YYYY');\n    var data = [];\n    for (var i = 0; i < 60; i++) {\n      data.push({x: date.valueOf(), y: Math.random()});\n      date = date.clone().add(1, 'month');\n    }\n\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          xAxisID: 'x',\n          data: data\n        }],\n      },\n      options: {\n        scales: {\n          x: {\n            type: 'time',\n            ticks: {\n              source: 'data',\n              autoSkip: true\n            }\n          },\n        }\n      }\n    });\n\n    var scale = chart.scales.x;\n\n    expect(scale._unit).toEqual('month');\n  });\n\n  describe('when specifying limits', function() {\n    var mockData = {\n      labels: ['2015-01-01T20:00:00', '2015-01-02T20:00:00', '2015-01-03T20:00:00'],\n    };\n\n    var config;\n    beforeEach(function() {\n      config = Chart.helpers.clone(Chart.defaults.scales.time);\n      config.ticks.source = 'labels';\n      config.time.unit = 'day';\n    });\n\n    it('should use the min option when less than first label for building ticks', function() {\n      config.min = '2014-12-29T04:00:00';\n\n      var labels = getLabels(createScale(mockData, config));\n      expect(labels[0]).toEqual('Jan 1');\n    });\n\n    it('should use the min option when greater than first label for building ticks', function() {\n      config.min = '2015-01-02T04:00:00';\n\n      var labels = getLabels(createScale(mockData, config));\n      expect(labels[0]).toEqual('Jan 2');\n    });\n\n    it('should use the max option when greater than last label for building ticks', function() {\n      config.max = '2015-01-05T06:00:00';\n\n      var labels = getLabels(createScale(mockData, config));\n      expect(labels[labels.length - 1]).toEqual('Jan 3');\n    });\n\n    it('should use the max option when less than last label for building ticks', function() {\n      config.max = '2015-01-02T23:00:00';\n\n      var labels = getLabels(createScale(mockData, config));\n      expect(labels[labels.length - 1]).toEqual('Jan 2');\n    });\n  });\n\n  it('should use the isoWeekday option', function() {\n    var mockData = {\n      labels: [\n        '2015-01-01T20:00:00', // Thursday\n        '2015-01-02T20:00:00', // Friday\n        '2015-01-03T20:00:00' // Saturday\n      ]\n    };\n\n    var config = Chart.helpers.mergeIf({\n      bounds: 'ticks',\n      time: {\n        unit: 'week',\n        isoWeekday: 3 // Wednesday\n      }\n    }, Chart.defaults.scales.time);\n\n    var scale = createScale(mockData, config);\n    var ticks = getLabels(scale);\n\n    expect(ticks).toEqual(['Dec 31, 2014', 'Jan 7, 2015']);\n  });\n\n  describe('when rendering several days', function() {\n    beforeEach(function() {\n      this.chart = window.acquireChart({\n        type: 'line',\n        data: {\n          datasets: [{\n            xAxisID: 'x',\n            data: []\n          }],\n          labels: [\n            '2015-01-01T20:00:00',\n            '2015-01-02T21:00:00',\n            '2015-01-03T22:00:00',\n            '2015-01-05T23:00:00',\n            '2015-01-07T03:00',\n            '2015-01-08T10:00',\n            '2015-01-10T12:00'\n          ]\n        },\n        options: {\n          scales: {\n            x: {\n              type: 'time',\n              position: 'bottom'\n            },\n          }\n        }\n      });\n\n      this.scale = this.chart.scales.x;\n    });\n\n    it('should be bounded by the nearest week beginnings', function() {\n      var chart = this.chart;\n      var scale = this.scale;\n      expect(scale.getValueForPixel(scale.left)).toBeGreaterThan(moment(chart.data.labels[0]).startOf('week'));\n      expect(scale.getValueForPixel(scale.right)).toBeLessThan(moment(chart.data.labels[chart.data.labels.length - 1]).add(1, 'week').endOf('week'));\n    });\n\n    it('should convert between screen coordinates and times', function() {\n      var chart = this.chart;\n      var scale = this.scale;\n      var timeRange = moment(scale.max).valueOf() - moment(scale.min).valueOf();\n      var msPerPix = timeRange / scale.width;\n      var firstPointOffsetMs = moment(chart.config.data.labels[0]).valueOf() - scale.min;\n      var firstPointPixel = scale.left + firstPointOffsetMs / msPerPix;\n      var lastPointOffsetMs = moment(chart.config.data.labels[chart.config.data.labels.length - 1]).valueOf() - scale.min;\n      var lastPointPixel = scale.left + lastPointOffsetMs / msPerPix;\n\n      expect(scale.getPixelForValue(moment('2015-01-01T20:00:00').valueOf())).toBeCloseToPixel(firstPointPixel);\n      expect(scale.getPixelForValue(moment(chart.data.labels[0]).valueOf())).toBeCloseToPixel(firstPointPixel);\n      expect(scale.getValueForPixel(firstPointPixel)).toBeCloseToTime({\n        value: moment(chart.data.labels[0]),\n        unit: 'hour',\n      });\n\n      expect(scale.getPixelForValue(moment('2015-01-10T12:00').valueOf())).toBeCloseToPixel(lastPointPixel);\n      expect(scale.getValueForPixel(lastPointPixel)).toBeCloseToTime({\n        value: moment(chart.data.labels[6]),\n        unit: 'hour'\n      });\n    });\n  });\n\n  describe('when rendering several years', function() {\n    beforeEach(function() {\n      this.chart = window.acquireChart({\n        type: 'line',\n        data: {\n          labels: ['2005-07-04', '2017-01-20'],\n        },\n        options: {\n          scales: {\n            x: {\n              type: 'time',\n              bounds: 'ticks',\n              position: 'bottom'\n            },\n          }\n        }\n      }, {canvas: {width: 800, height: 200}});\n\n      this.scale = this.chart.scales.x;\n    });\n\n    it('should be bounded by nearest step\\'s year start and end', function() {\n      var scale = this.scale;\n      var ticks = scale.getTicks();\n      var step = ticks[1].value - ticks[0].value;\n      var stepsAmount = Math.floor((scale.max - scale.min) / step);\n\n      expect(scale.getValueForPixel(scale.left)).toBeCloseToTime({\n        value: moment(scale.min).startOf('year'),\n        unit: 'hour',\n      });\n      expect(scale.getValueForPixel(scale.right)).toBeCloseToTime({\n        value: moment(scale.min + step * stepsAmount).endOf('year'),\n        unit: 'hour',\n      });\n    });\n\n    it('should build the correct ticks', function() {\n      expect(getLabels(this.scale)).toEqual(['2005', '2006', '2007', '2008', '2009', '2010', '2011', '2012', '2013', '2014', '2015', '2016', '2017', '2018']);\n    });\n\n    it('should have ticks with accurate labels', function() {\n      var scale = this.scale;\n      var ticks = scale.getTicks();\n      // pixelsPerTick is an approximation which assumes same number of milliseconds per year (not true)\n      // we use a threshold of 1 day so that we still match these values\n      var pixelsPerTick = scale.width / (ticks.length - 1);\n\n      for (var i = 0; i < ticks.length - 1; i++) {\n        var offset = pixelsPerTick * i;\n        expect(scale.getValueForPixel(scale.left + offset)).toBeCloseToTime({\n          value: moment(ticks[i].label + '-01-01'),\n          unit: 'day',\n          threshold: 1,\n        });\n      }\n    });\n  });\n\n  it('should get the correct label for a data value', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          xAxisID: 'x',\n          data: [null, 10, 3]\n        }],\n        labels: ['2015-01-01T20:00:00', '2015-01-02T21:00:00', '2015-01-03T22:00:00', '2015-01-05T23:00:00', '2015-01-07T03:00', '2015-01-08T10:00', '2015-01-10T12:00'], // days\n      },\n      options: {\n        scales: {\n          x: {\n            type: 'time',\n            position: 'bottom',\n            ticks: {\n              source: 'labels',\n              autoSkip: false\n            }\n          }\n        }\n      }\n    });\n\n    var xScale = chart.scales.x;\n    var controller = chart.getDatasetMeta(0).controller;\n    expect(xScale.getLabelForValue(controller.getParsed(0)[xScale.id])).toBeTruthy();\n    expect(xScale.getLabelForValue(controller.getParsed(0)[xScale.id])).toBe('Jan 1, 2015, 8:00:00 pm');\n    expect(xScale.getLabelForValue(xScale.getValueForPixel(xScale.getPixelForTick(6)))).toBe('Jan 10, 2015, 12:00:00 pm');\n  });\n\n  describe('when ticks.callback is specified', function() {\n    beforeEach(function() {\n      this.chart = window.acquireChart({\n        type: 'line',\n        data: {\n          datasets: [{\n            xAxisID: 'x',\n            data: [0, 0]\n          }],\n          labels: ['2015-01-01T20:00:00', '2015-01-01T20:01:00']\n        },\n        options: {\n          scales: {\n            x: {\n              type: 'time',\n              time: {\n                displayFormats: {\n                  second: 'h:mm:ss'\n                }\n              },\n              ticks: {\n                callback: function(_, i) {\n                  return '<' + i + '>';\n                }\n              }\n            }\n          }\n        }\n      });\n      this.scale = this.chart.scales.x;\n    });\n\n    it('should get the correct labels for ticks', function() {\n      var labels = getLabels(this.scale);\n\n      expect(labels.length).toEqual(21);\n      expect(labels[0]).toEqual('<0>');\n      expect(labels[labels.length - 1]).toEqual('<60>');\n    });\n\n    it('should update ticks.callback correctly', function() {\n      var chart = this.chart;\n      chart.options.scales.x.ticks.callback = function(_, i) {\n        return '{' + i + '}';\n      };\n      chart.update();\n\n      var labels = getLabels(this.scale);\n      expect(labels.length).toEqual(21);\n      expect(labels[0]).toEqual('{0}');\n      expect(labels[labels.length - 1]).toEqual('{60}');\n    });\n  });\n\n  it('should get the correct label when time is specified as a string', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          xAxisID: 'x',\n          data: [{x: '2015-01-01T20:00:00', y: 10}, {x: '2015-01-02T21:00:00', y: 3}]\n        }],\n      },\n      options: {\n        scales: {\n          x: {\n            type: 'time',\n            position: 'bottom'\n          },\n        }\n      }\n    });\n\n    var xScale = chart.scales.x;\n    var controller = chart.getDatasetMeta(0).controller;\n    var value = controller.getParsed(0)[xScale.id];\n    expect(xScale.getLabelForValue(value)).toBeTruthy();\n    expect(xScale.getLabelForValue(value)).toBe('Jan 1, 2015, 8:00:00 pm');\n  });\n\n  it('should get the correct label for a data value by format', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          xAxisID: 'x',\n          data: [null, 10, 3]\n        }],\n        labels: ['2015-01-01T20:00:00', '2015-01-02T21:00:00', '2015-01-03T22:00:00', '2015-01-05T23:00:00', '2015-01-07T03:00', '2015-01-08T10:00', '2015-01-10T12:00'], // days\n      },\n      options: {\n        scales: {\n          x: {\n            type: 'time',\n            time: {\n              unit: 'day',\n              displayFormats: {\n                day: 'YYYY-MM-DD'\n              }\n            },\n            position: 'bottom',\n            ticks: {\n              source: 'labels',\n              autoSkip: false\n            }\n          }\n        }\n      }\n    });\n\n    var xScale = chart.scales.x;\n    for (const lbl of chart.data.labels) {\n      var dd = xScale._adapter.parse(lbl);\n      var parsed = lbl.split('T');\n      expect(xScale.format(dd)).toBe(parsed[0]);\n    }\n    for (const lbl of chart.data.labels) {\n      var mm = xScale._adapter.parse(lbl);\n      var yearMonth = lbl.substring(0, 7);\n      expect(xScale.format(mm, 'YYYY-MM')).toBe(yearMonth);\n    }\n  });\n\n  it('should round to isoWeekday', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          data: [{x: '2020-04-12T20:00:00', y: 1}, {x: '2020-04-13T20:00:00', y: 2}]\n        }]\n      },\n      options: {\n        scales: {\n          x: {\n            type: 'time',\n            ticks: {\n              source: 'data'\n            },\n            time: {\n              unit: 'week',\n              round: 'week',\n              isoWeekday: 1,\n              displayFormats: {\n                week: 'WW'\n              }\n            }\n          },\n        }\n      }\n    });\n\n    expect(getLabels(chart.scales.x)).toEqual(['15', '16']);\n  });\n\n  it('should get the correct label for a timestamp', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          xAxisID: 'x',\n          data: [\n            // Normally (at least with the moment.js adapter), times would be in\n            // the user's local time zone.  To allow for more stable tests, our\n            // tests/index.js sets moment.js to use UTC; use `Z` here to match.\n            {t: +new Date('2018-01-08 05:14:23.234Z'), y: 10},\n            {t: +new Date('2018-01-09 06:17:43.426Z'), y: 3}\n          ]\n        }],\n      },\n      options: {\n        parsing: {xAxisKey: 't'},\n        scales: {\n          x: {\n            type: 'time',\n            position: 'bottom'\n          },\n        }\n      }\n    });\n\n    var xScale = chart.scales.x;\n    var controller = chart.getDatasetMeta(0).controller;\n    var label = xScale.getLabelForValue(controller.getParsed(0)[xScale.id]);\n    expect(label).toEqual('Jan 8, 2018, 5:14:23 am');\n  });\n\n  it('should get the correct pixel for only one data in the dataset', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        labels: ['2016-05-27'],\n        datasets: [{\n          xAxisID: 'x',\n          data: [5]\n        }]\n      },\n      options: {\n        scales: {\n          x: {\n            display: true,\n            type: 'time'\n          }\n        }\n      }\n    });\n\n    var xScale = chart.scales.x;\n    var pixel = xScale.getPixelForValue(moment('2016-05-27').valueOf());\n\n    expect(xScale.getValueForPixel(pixel)).toEqual(moment(chart.data.labels[0]).valueOf());\n  });\n\n  it('does not create a negative width chart when hidden', function() {\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          data: []\n        }]\n      },\n      options: {\n        scales: {\n          x: {\n            type: 'time',\n            ticks: {\n              min: moment().subtract(1, 'months'),\n              max: moment(),\n            }\n          },\n        },\n        responsive: true,\n      },\n    }, {\n      wrapper: {\n        style: 'display: none',\n      },\n    });\n    expect(chart.scales.y.width).toEqual(0);\n    expect(chart.scales.y.maxWidth).toEqual(0);\n    expect(chart.width).toEqual(0);\n  });\n\n  describe('when ticks.source', function() {\n    describe('is \"labels\"', function() {\n      beforeEach(function() {\n        this.chart = window.acquireChart({\n          type: 'line',\n          data: {\n            labels: ['2017', '2019', '2020', '2025', '2042'],\n            datasets: [{data: [0, 1, 2, 3, 4, 5]}]\n          },\n          options: {\n            scales: {\n              x: {\n                type: 'time',\n                time: {\n                  parser: 'YYYY'\n                },\n                ticks: {\n                  source: 'labels'\n                }\n              }\n            }\n          }\n        });\n      });\n\n      it ('should generate ticks from \"data.labels\"', function() {\n        var scale = this.chart.scales.x;\n\n        expect(scale.min).toEqual(+moment('2017', 'YYYY'));\n        expect(scale.max).toEqual(+moment('2042', 'YYYY'));\n        expect(getLabels(scale)).toEqual([\n          '2017', '2019', '2020', '2025', '2042']);\n      });\n      it ('should not add ticks for min and max if they extend the labels range', function() {\n        var chart = this.chart;\n        var scale = chart.scales.x;\n        var options = chart.options.scales.x;\n\n        options.min = '2012';\n        options.max = '2051';\n        chart.update();\n\n        expect(scale.min).toEqual(+moment('2012', 'YYYY'));\n        expect(scale.max).toEqual(+moment('2051', 'YYYY'));\n        expect(getLabels(scale)).toEqual([\n          '2017', '2019', '2020', '2025', '2042']);\n      });\n      it ('should not duplicate ticks if min and max are the labels limits', function() {\n        var chart = this.chart;\n        var scale = chart.scales.x;\n        var options = chart.options.scales.x;\n\n        options.min = '2017';\n        options.max = '2042';\n        chart.update();\n\n        expect(scale.min).toEqual(+moment('2017', 'YYYY'));\n        expect(scale.max).toEqual(+moment('2042', 'YYYY'));\n        expect(getLabels(scale)).toEqual([\n          '2017', '2019', '2020', '2025', '2042']);\n      });\n      it ('should correctly handle empty `data.labels` using \"day\" if `time.unit` is undefined`', function() {\n        var chart = this.chart;\n        var scale = chart.scales.x;\n\n        chart.data.labels = [];\n        chart.update();\n\n        expect(scale.min).toEqual(+moment().startOf('day'));\n        expect(scale.max).toEqual(+moment().endOf('day') + 1);\n        expect(getLabels(scale)).toEqual([]);\n      });\n      it ('should correctly handle empty `data.labels` using `time.unit`', function() {\n        var chart = this.chart;\n        var scale = chart.scales.x;\n        var options = chart.options.scales.x;\n\n        options.time.unit = 'year';\n        chart.data.labels = [];\n        chart.update();\n\n        expect(scale.min).toEqual(+moment().startOf('year'));\n        expect(scale.max).toEqual(+moment().endOf('year') + 1);\n        expect(getLabels(scale)).toEqual([]);\n      });\n    });\n\n    describe('is \"data\"', function() {\n      beforeEach(function() {\n        this.chart = window.acquireChart({\n          type: 'line',\n          data: {\n            labels: ['2017', '2019', '2020', '2025', '2042'],\n            datasets: [\n              {data: [0, 1, 2, 3, 4, 5]},\n              {data: [\n                {x: '2018', y: 6},\n                {x: '2020', y: 7},\n                {x: '2043', y: 8}\n              ]}\n            ]\n          },\n          options: {\n            scales: {\n              x: {\n                type: 'time',\n                time: {\n                  parser: 'YYYY'\n                },\n                ticks: {\n                  source: 'data'\n                }\n              }\n            }\n          }\n        });\n      });\n\n      it ('should generate ticks from \"datasets.data\"', function() {\n        var scale = this.chart.scales.x;\n\n        expect(scale.min).toEqual(+moment('2017', 'YYYY'));\n        expect(scale.max).toEqual(+moment('2043', 'YYYY'));\n        expect(getLabels(scale)).toEqual([\n          '2017', '2018', '2019', '2020', '2025', '2042', '2043']);\n      });\n      it ('should not add ticks for min and max if they extend the labels range', function() {\n        var chart = this.chart;\n        var scale = chart.scales.x;\n        var options = chart.options.scales.x;\n\n        options.min = '2012';\n        options.max = '2051';\n        chart.update();\n\n        expect(scale.min).toEqual(+moment('2012', 'YYYY'));\n        expect(scale.max).toEqual(+moment('2051', 'YYYY'));\n        expect(getLabels(scale)).toEqual([\n          '2017', '2018', '2019', '2020', '2025', '2042', '2043']);\n      });\n      it ('should not duplicate ticks if min and max are the labels limits', function() {\n        var chart = this.chart;\n        var scale = chart.scales.x;\n        var options = chart.options.scales.x;\n\n        options.min = '2017';\n        options.max = '2043';\n        chart.update();\n\n        expect(scale.min).toEqual(+moment('2017', 'YYYY'));\n        expect(scale.max).toEqual(+moment('2043', 'YYYY'));\n        expect(getLabels(scale)).toEqual([\n          '2017', '2018', '2019', '2020', '2025', '2042', '2043']);\n      });\n      it ('should correctly handle empty `data.labels` using \"day\" if `time.unit` is undefined`', function() {\n        var chart = this.chart;\n        var scale = chart.scales.x;\n\n        chart.data.labels = [];\n        chart.update();\n\n        expect(scale.min).toEqual(+moment('2018', 'YYYY'));\n        expect(scale.max).toEqual(+moment('2043', 'YYYY'));\n        expect(getLabels(scale)).toEqual([\n          '2018', '2020', '2043']);\n      });\n      it ('should correctly handle empty `data.labels` and hidden datasets using `time.unit`', function() {\n        var chart = this.chart;\n        var scale = chart.scales.x;\n        var options = chart.options.scales.x;\n\n        options.time.unit = 'year';\n        chart.data.labels = [];\n        var meta = chart.getDatasetMeta(1);\n        meta.hidden = true;\n        chart.update();\n\n        expect(scale.min).toEqual(+moment().startOf('year'));\n        expect(scale.max).toEqual(+moment().endOf('year') + 1);\n        expect(getLabels(scale)).toEqual([]);\n      });\n    });\n  });\n\n  [true, false].forEach(function(normalized) {\n    describe('when normalized is ' + normalized + ' and scale type', function() {\n      describe('is \"timeseries\"', function() {\n        beforeEach(function() {\n          this.chart = window.acquireChart({\n            type: 'line',\n            data: {\n              labels: ['2017', '2019', '2020', '2025', '2042'],\n              datasets: [{data: [0, 1, 2, 3, 4]}]\n            },\n            options: {\n              normalized,\n              scales: {\n                x: {\n                  type: 'timeseries',\n                  time: {\n                    parser: 'YYYY'\n                  },\n                  ticks: {\n                    source: 'labels'\n                  }\n                },\n                y: {\n                  display: false\n                }\n              }\n            }\n          });\n        });\n\n        it ('should space data out with the same gap, whatever their time values', function() {\n          var scale = this.chart.scales.x;\n          var start = scale.left;\n          var slice = scale.width / 4;\n\n          expect(scale.getPixelForValue(moment('2017').valueOf(), 0)).toBeCloseToPixel(start);\n          expect(scale.getPixelForValue(moment('2019').valueOf(), 1)).toBeCloseToPixel(start + slice);\n          expect(scale.getPixelForValue(moment('2020').valueOf(), 2)).toBeCloseToPixel(start + slice * 2);\n          expect(scale.getPixelForValue(moment('2025').valueOf(), 3)).toBeCloseToPixel(start + slice * 3);\n          expect(scale.getPixelForValue(moment('2042').valueOf(), 4)).toBeCloseToPixel(start + slice * 4);\n        });\n        it ('should add a step before if scale.min is before the first data', function() {\n          var chart = this.chart;\n          var scale = chart.scales.x;\n          var options = chart.options.scales.x;\n\n          options.min = '2012';\n          chart.update();\n\n          var start = scale.left;\n          var slice = scale.width / 5;\n\n          expect(scale.getPixelForValue(moment('2017').valueOf(), 1)).toBeCloseToPixel(86);\n          expect(scale.getPixelForValue(moment('2042').valueOf(), 5)).toBeCloseToPixel(start + slice * 5);\n        });\n        it ('should add a step after if scale.max is after the last data', function() {\n          var chart = this.chart;\n          var scale = chart.scales.x;\n          var options = chart.options.scales.x;\n\n          options.max = '2050';\n          chart.update();\n\n          var start = scale.left;\n\n          expect(scale.getPixelForValue(moment('2017').valueOf(), 0)).toBeCloseToPixel(start);\n          expect(scale.getPixelForValue(moment('2042').valueOf(), 4)).toBeCloseToPixel(388);\n        });\n        it ('should add steps before and after if scale.min/max are outside the data range', function() {\n          var chart = this.chart;\n          var scale = chart.scales.x;\n          var options = chart.options.scales.x;\n\n          options.min = '2012';\n          options.max = '2050';\n          chart.update();\n\n          expect(scale.getPixelForValue(moment('2017').valueOf(), 1)).toBeCloseToPixel(71);\n          expect(scale.getPixelForValue(moment('2042').valueOf(), 5)).toBeCloseToPixel(401);\n        });\n      });\n      describe('is \"time\"', function() {\n        beforeEach(function() {\n          this.chart = window.acquireChart({\n            type: 'line',\n            data: {\n              labels: ['2017', '2019', '2020', '2025', '2042'],\n              datasets: [{data: [0, 1, 2, 3, 4, 5]}]\n            },\n            options: {\n              scales: {\n                x: {\n                  type: 'time',\n                  time: {\n                    parser: 'YYYY'\n                  },\n                  ticks: {\n                    source: 'labels'\n                  }\n                },\n                y: {\n                  display: false\n                }\n              }\n            }\n          });\n        });\n\n        it ('should space data out with a gap relative to their time values', function() {\n          var scale = this.chart.scales.x;\n          var start = scale.left;\n          var slice = scale.width / (2042 - 2017);\n\n          expect(scale.getPixelForValue(moment('2017').valueOf(), 0)).toBeCloseToPixel(start);\n          expect(scale.getPixelForValue(moment('2019').valueOf(), 1)).toBeCloseToPixel(start + slice * (2019 - 2017));\n          expect(scale.getPixelForValue(moment('2020').valueOf(), 2)).toBeCloseToPixel(start + slice * (2020 - 2017));\n          expect(scale.getPixelForValue(moment('2025').valueOf(), 3)).toBeCloseToPixel(start + slice * (2025 - 2017));\n          expect(scale.getPixelForValue(moment('2042').valueOf(), 4)).toBeCloseToPixel(start + slice * (2042 - 2017));\n        });\n        it ('should take in account scale min and max if outside the ticks range', function() {\n          var chart = this.chart;\n          var scale = chart.scales.x;\n          var options = chart.options.scales.x;\n\n          options.min = '2012';\n          options.max = '2050';\n          chart.update();\n\n          var start = scale.left;\n          var slice = scale.width / (2050 - 2012);\n\n          expect(scale.getPixelForValue(moment('2017').valueOf(), 0)).toBeCloseToPixel(start + slice * (2017 - 2012));\n          expect(scale.getPixelForValue(moment('2019').valueOf(), 1)).toBeCloseToPixel(start + slice * (2019 - 2012));\n          expect(scale.getPixelForValue(moment('2020').valueOf(), 2)).toBeCloseToPixel(start + slice * (2020 - 2012));\n          expect(scale.getPixelForValue(moment('2025').valueOf(), 3)).toBeCloseToPixel(start + slice * (2025 - 2012));\n          expect(scale.getPixelForValue(moment('2042').valueOf(), 4)).toBeCloseToPixel(start + slice * (2042 - 2012));\n        });\n      });\n    });\n  });\n\n  describe('when bounds', function() {\n    describe('is \"data\"', function() {\n      it ('should preserve the data range', function() {\n        var chart = window.acquireChart({\n          type: 'line',\n          data: {\n            labels: ['02/20 08:00', '02/21 09:00', '02/22 10:00', '02/23 11:00'],\n            datasets: [{data: [0, 1, 2, 3, 4, 5]}]\n          },\n          options: {\n            scales: {\n              x: {\n                type: 'time',\n                bounds: 'data',\n                time: {\n                  parser: 'MM/DD HH:mm',\n                  unit: 'day'\n                }\n              },\n              y: {\n                display: false\n              }\n            }\n          }\n        });\n\n        var scale = chart.scales.x;\n\n        expect(scale.min).toEqual(+moment('02/20 08:00', 'MM/DD HH:mm'));\n        expect(scale.max).toEqual(+moment('02/23 11:00', 'MM/DD HH:mm'));\n        expect(scale.getPixelForValue(moment('02/20 08:00', 'MM/DD HH:mm').valueOf())).toBeCloseToPixel(scale.left);\n        expect(scale.getPixelForValue(moment('02/23 11:00', 'MM/DD HH:mm').valueOf())).toBeCloseToPixel(scale.left + scale.width);\n        expect(getLabels(scale)).toEqual([\n          'Feb 21', 'Feb 22', 'Feb 23']);\n      });\n    });\n\n    describe('is \"labels\"', function() {\n      it('should preserve the label range', function() {\n        var chart = window.acquireChart({\n          type: 'line',\n          data: {\n            labels: ['02/20 08:00', '02/21 09:00', '02/22 10:00', '02/23 11:00'],\n            datasets: [{data: [0, 1, 2, 3, 4, 5]}]\n          },\n          options: {\n            scales: {\n              x: {\n                type: 'time',\n                bounds: 'ticks',\n                time: {\n                  parser: 'MM/DD HH:mm',\n                  unit: 'day'\n                }\n              },\n              y: {\n                display: false\n              }\n            }\n          }\n        });\n\n        var scale = chart.scales.x;\n        var ticks = scale.getTicks();\n\n        expect(scale.min).toEqual(ticks[0].value);\n        expect(scale.max).toEqual(ticks[ticks.length - 1].value);\n        expect(scale.getPixelForValue(moment('02/20 08:00', 'MM/DD HH:mm').valueOf())).toBeCloseToPixel(60);\n        expect(scale.getPixelForValue(moment('02/23 11:00', 'MM/DD HH:mm').valueOf())).toBeCloseToPixel(426);\n        expect(getLabels(scale)).toEqual([\n          'Feb 20', 'Feb 21', 'Feb 22', 'Feb 23', 'Feb 24']);\n      });\n    });\n  });\n\n  describe('when min and/or max are defined', function() {\n    ['auto', 'data', 'labels'].forEach(function(source) {\n      ['data', 'ticks'].forEach(function(bounds) {\n        describe('and ticks.source is \"' + source + '\" and bounds \"' + bounds + '\"', function() {\n          beforeEach(function() {\n            this.chart = window.acquireChart({\n              type: 'line',\n              data: {\n                labels: ['02/20 08:00', '02/21 09:00', '02/22 10:00', '02/23 11:00'],\n                datasets: [{data: [0, 1, 2, 3, 4, 5]}]\n              },\n              options: {\n                scales: {\n                  x: {\n                    type: 'time',\n                    bounds: bounds,\n                    time: {\n                      parser: 'MM/DD HH:mm',\n                      unit: 'day'\n                    },\n                    ticks: {\n                      source: source\n                    }\n                  },\n                  y: {\n                    display: false\n                  }\n                }\n              }\n            });\n          });\n\n          it ('should expand scale to the min/max range', function() {\n            var chart = this.chart;\n            var scale = chart.scales.x;\n            var options = chart.options.scales.x;\n            var min = '02/19 07:00';\n            var max = '02/24 08:00';\n            var minMillis = +moment(min, 'MM/DD HH:mm');\n            var maxMillis = +moment(max, 'MM/DD HH:mm');\n\n            options.min = min;\n            options.max = max;\n            chart.update();\n\n            expect(scale.min).toEqual(minMillis);\n            expect(scale.max).toEqual(maxMillis);\n            expect(scale.getPixelForValue(minMillis)).toBeCloseToPixel(scale.left);\n            expect(scale.getPixelForValue(maxMillis)).toBeCloseToPixel(scale.left + scale.width);\n            scale.getTicks().forEach(function(tick) {\n              expect(tick.value >= minMillis).toBeTruthy();\n              expect(tick.value <= maxMillis).toBeTruthy();\n            });\n          });\n          it ('should shrink scale to the min/max range', function() {\n            var chart = this.chart;\n            var scale = chart.scales.x;\n            var options = chart.options.scales.x;\n            var min = '02/21 07:00';\n            var max = '02/22 20:00';\n            var minMillis = +moment(min, 'MM/DD HH:mm');\n            var maxMillis = +moment(max, 'MM/DD HH:mm');\n\n            options.min = min;\n            options.max = max;\n            chart.update();\n\n            expect(scale.min).toEqual(minMillis);\n            expect(scale.max).toEqual(maxMillis);\n            expect(scale.getPixelForValue(minMillis)).toBeCloseToPixel(scale.left);\n            expect(scale.getPixelForValue(maxMillis)).toBeCloseToPixel(scale.left + scale.width);\n            scale.getTicks().forEach(function(tick) {\n              expect(tick.value >= minMillis).toBeTruthy();\n              expect(tick.value <= maxMillis).toBeTruthy();\n            });\n          });\n        });\n      });\n    });\n  });\n\n  ['auto', 'data', 'labels'].forEach(function(source) {\n    ['timeseries', 'time'].forEach(function(type) {\n      describe('when ticks.source is \"' + source + '\" and scale type is \"' + type + '\"', function() {\n        beforeEach(function() {\n          this.chart = window.acquireChart({\n            type: 'line',\n            data: {\n              labels: ['2017', '2018', '2019', '2020', '2021'],\n              datasets: [{data: [0, 1, 2, 3, 4]}]\n            },\n            options: {\n              scales: {\n                x: {\n                  type: type,\n                  time: {\n                    parser: 'YYYY',\n                    unit: 'year'\n                  },\n                  ticks: {\n                    source: source\n                  }\n                }\n              }\n            }\n          });\n        });\n\n        it ('should not add offset from the edges', function() {\n          var scale = this.chart.scales.x;\n\n          expect(scale.getPixelForValue(moment('2017').valueOf())).toBeCloseToPixel(scale.left);\n          expect(scale.getPixelForValue(moment('2021').valueOf())).toBeCloseToPixel(scale.left + scale.width);\n        });\n\n        it ('should add offset from the edges if offset is true', function() {\n          var chart = this.chart;\n          var scale = chart.scales.x;\n          var options = chart.options.scales.x;\n\n          options.offset = true;\n          chart.update();\n\n          var numTicks = scale.ticks.length;\n          var firstTickInterval = scale.getPixelForTick(1) - scale.getPixelForTick(0);\n          var lastTickInterval = scale.getPixelForTick(numTicks - 1) - scale.getPixelForTick(numTicks - 2);\n\n          expect(scale.getPixelForValue(moment('2017').valueOf())).toBeCloseToPixel(scale.left + firstTickInterval / 2);\n          expect(scale.getPixelForValue(moment('2021').valueOf())).toBeCloseToPixel(scale.left + scale.width - lastTickInterval / 2);\n        });\n\n        it ('should not add offset if min and max extend the labels range', function() {\n          var chart = this.chart;\n          var scale = chart.scales.x;\n          var options = chart.options.scales.x;\n\n          options.min = '2012';\n          options.max = '2051';\n          chart.update();\n\n          expect(scale.getPixelForValue(moment('2012').valueOf())).toBeCloseToPixel(scale.left);\n          expect(scale.getPixelForValue(moment('2051').valueOf())).toBeCloseToPixel(scale.left + scale.width);\n        });\n      });\n    });\n  });\n\n  it ('should handle offset when there are more data points than ticks', function() {\n    const chart = window.acquireChart({\n      type: 'bar',\n      data: {\n        datasets: [{\n          data: [{x: 631180800000, y: '31.84'}, {x: 631267200000, y: '30.89'}, {x: 631353600000, y: '33.00'}, {x: 631440000000, y: '33.52'}, {x: 631526400000, y: '32.24'}, {x: 631785600000, y: '32.74'}, {x: 631872000000, y: '31.45'}, {x: 631958400000, y: '32.60'}, {x: 632044800000, y: '31.77'}, {x: 632131200000, y: '32.45'}, {x: 632390400000, y: '31.13'}, {x: 632476800000, y: '31.82'}, {x: 632563200000, y: '30.81'}, {x: 632649600000, y: '30.07'}, {x: 632736000000, y: '29.31'}, {x: 632995200000, y: '29.82'}, {x: 633081600000, y: '30.20'}, {x: 633168000000, y: '30.78'}, {x: 633254400000, y: '30.72'}, {x: 633340800000, y: '31.62'}, {x: 633600000000, y: '30.64'}, {x: 633686400000, y: '32.36'}, {x: 633772800000, y: '34.66'}, {x: 633859200000, y: '33.96'}, {x: 633945600000, y: '34.20'}, {x: 634204800000, y: '32.20'}, {x: 634291200000, y: '32.44'}, {x: 634377600000, y: '32.72'}, {x: 634464000000, y: '32.95'}, {x: 634550400000, y: '32.95'}, {x: 634809600000, y: '30.88'}, {x: 634896000000, y: '29.44'}, {x: 634982400000, y: '29.36'}, {x: 635068800000, y: '28.84'}, {x: 635155200000, y: '30.85'}, {x: 635414400000, y: '32.00'}, {x: 635500800000, y: '32.74'}, {x: 635587200000, y: '33.16'}, {x: 635673600000, y: '34.73'}, {x: 635760000000, y: '32.89'}, {x: 636019200000, y: '32.41'}, {x: 636105600000, y: '31.15'}, {x: 636192000000, y: '30.63'}, {x: 636278400000, y: '29.60'}, {x: 636364800000, y: '29.31'}, {x: 636624000000, y: '29.83'}, {x: 636710400000, y: '27.97'}, {x: 636796800000, y: '26.18'}, {x: 636883200000, y: '26.06'}, {x: 636969600000, y: '26.34'}, {x: 637228800000, y: '27.75'}, {x: 637315200000, y: '29.05'}, {x: 637401600000, y: '28.82'}, {x: 637488000000, y: '29.43'}, {x: 637574400000, y: '29.53'}, {x: 637833600000, y: '28.50'}, {x: 637920000000, y: '28.87'}, {x: 638006400000, y: '28.11'}, {x: 638092800000, y: '27.79'}, {x: 638179200000, y: '28.18'}, {x: 638438400000, y: '28.27'}, {x: 638524800000, y: '28.29'}, {x: 638611200000, y: '29.63'}, {x: 638697600000, y: '29.13'}, {x: 638784000000, y: '26.57'}, {x: 639039600000, y: '27.19'}, {x: 639126000000, y: '27.48'}, {x: 639212400000, y: '27.79'}, {x: 639298800000, y: '28.48'}, {x: 639385200000, y: '27.88'}, {x: 639644400000, y: '25.63'}, {x: 639730800000, y: '25.02'}, {x: 639817200000, y: '25.26'}, {x: 639903600000, y: '25.00'}, {x: 639990000000, y: '26.23'}, {x: 640249200000, y: '26.22'}, {x: 640335600000, y: '26.36'}, {x: 640422000000, y: '25.45'}, {x: 640508400000, y: '24.62'}, {x: 640594800000, y: '26.65'}, {x: 640854000000, y: '26.28'}, {x: 640940400000, y: '27.25'}, {x: 641026800000, y: '25.93'}],\n          backgroundColor: '#ff6666'\n        }]\n      },\n      options: {\n        scales: {\n          x: {\n            type: 'timeseries',\n            offset: true,\n            ticks: {\n              source: 'data',\n              autoSkip: true,\n              autoSkipPadding: 0,\n              maxRotation: 0\n            }\n          },\n          y: {\n            type: 'linear',\n            border: {\n              display: false\n            }\n          }\n        }\n      },\n      plugins: {\n        legend: false\n      }\n    });\n    const scale = chart.scales.x;\n    expect(scale.getPixelForDecimal(0)).toBeCloseToPixel(29);\n    expect(scale.getPixelForDecimal(1.0)).toBeCloseToPixel(512);\n  });\n\n  ['data', 'labels'].forEach(function(source) {\n    ['timeseries', 'time'].forEach(function(type) {\n      describe('when ticks.source is \"' + source + '\" and scale type is \"' + type + '\"', function() {\n        beforeEach(function() {\n          this.chart = window.acquireChart({\n            type: 'line',\n            data: {\n              labels: ['2017', '2019', '2020', '2025', '2042'],\n              datasets: [{data: [0, 1, 2, 3, 4, 5]}]\n            },\n            options: {\n              scales: {\n                x: {\n                  id: 'x',\n                  type: type,\n                  time: {\n                    parser: 'YYYY'\n                  },\n                  ticks: {\n                    source: source\n                  }\n                }\n              }\n            }\n          });\n        });\n\n        it ('should add offset if min and max extend the labels range and offset is true', function() {\n          var chart = this.chart;\n          var scale = chart.scales.x;\n          var options = chart.options.scales.x;\n\n          options.min = '2012';\n          options.max = '2051';\n          options.offset = true;\n          chart.update();\n\n          var numTicks = scale.ticks.length;\n          var firstTickInterval = scale.getPixelForTick(1) - scale.getPixelForTick(0);\n          var lastTickInterval = scale.getPixelForTick(numTicks - 1) - scale.getPixelForTick(numTicks - 2);\n          expect(scale.getPixelForValue(moment('2012').valueOf())).toBeCloseToPixel(scale.left + firstTickInterval / 2);\n          expect(scale.getPixelForValue(moment('2051').valueOf())).toBeCloseToPixel(scale.left + scale.width - lastTickInterval / 2);\n        });\n      });\n    });\n  });\n\n  describe('Deprecations', function() {\n    describe('options.time.displayFormats', function() {\n      it('should generate defaults from adapter presets', function() {\n        var chart = window.acquireChart({\n          type: 'line',\n          data: {},\n          options: {\n            scales: {\n              x: {\n                type: 'time'\n              }\n            }\n          }\n        });\n\n        // NOTE: the test suite is configured to use moment\n        var expected = {\n          datetime: 'MMM D, YYYY, h:mm:ss a',\n          millisecond: 'h:mm:ss.SSS a',\n          second: 'h:mm:ss a',\n          minute: 'h:mm a',\n          hour: 'hA',\n          day: 'MMM D',\n          week: 'll',\n          month: 'MMM YYYY',\n          quarter: '[Q]Q - YYYY',\n          year: 'YYYY'\n        };\n\n        expect(chart.scales.x.options.time.displayFormats).toEqual(expected);\n        expect(chart.options.scales.x.time.displayFormats).toEqual(expected);\n      });\n\n      it('should merge user formats with adapter presets', function() {\n        var chart = window.acquireChart({\n          type: 'line',\n          data: {},\n          options: {\n            scales: {\n              x: {\n                type: 'time',\n                time: {\n                  displayFormats: {\n                    millisecond: 'foo',\n                    hour: 'bar',\n                    month: 'bla'\n                  }\n                }\n              }\n            }\n          }\n        });\n\n        // NOTE: the test suite is configured to use moment\n        var expected = {\n          datetime: 'MMM D, YYYY, h:mm:ss a',\n          millisecond: 'foo',\n          second: 'h:mm:ss a',\n          minute: 'h:mm a',\n          hour: 'bar',\n          day: 'MMM D',\n          week: 'll',\n          month: 'bla',\n          quarter: '[Q]Q - YYYY',\n          year: 'YYYY'\n        };\n\n        expect(chart.scales.x.options.time.displayFormats).toEqual(expected);\n        expect(chart.options.scales.x.time.displayFormats).toEqual(expected);\n      });\n    });\n  });\n\n  it('should pass chart options to date adapter', function() {\n    let chartOptions;\n\n    Chart._adapters._date.override({\n      init(options) {\n        chartOptions = options;\n      }\n    });\n\n    var chart = window.acquireChart({\n      type: 'line',\n      data: {},\n      options: {\n        locale: 'es',\n        scales: {\n          x: {\n            type: 'time'\n          },\n        }\n      }\n    });\n\n    expect(chartOptions).toEqual(chart.options);\n  });\n\n  it('should pass timestamp to ticks callback', () => {\n    let callbackValue;\n    window.acquireChart({\n      type: 'line',\n      data: {\n        datasets: [{\n          xAxisID: 'x',\n          data: [0, 0]\n        }],\n        labels: ['2015-01-01T20:00:00', '2015-01-01T20:01:00']\n      },\n      options: {\n        scales: {\n          x: {\n            type: 'time',\n            ticks: {\n              callback(value) {\n                callbackValue = value;\n                return value;\n              }\n            }\n          }\n        }\n      }\n    });\n\n    expect(typeof callbackValue).toBe('number');\n  });\n});\n"
  },
  {
    "path": "test/types/.eslintrc.yml",
    "content": "rules:\n  '@typescript-eslint/no-unused-vars': 'off'\n  object-curly-spacing: [\"warn\", \"always\"]\n  '@typescript-eslint/no-empty-interface': \"warn\"\n  '@typescript-eslint/ban-types': \"warn\"\n  '@typescript-eslint/adjacent-overload-signatures': \"warn\"\n"
  },
  {
    "path": "test/types/animation.ts",
    "content": "import { Chart } from '../../src/types.js';\n\nconst chart = new Chart('id', {\n  type: 'bar',\n  data: {\n    labels: [],\n    datasets: [{\n      data: []\n    }]\n  },\n  options: {\n    animation: false,\n    animations: {\n      colors: false,\n      numbers: {\n        properties: ['a', 'b'],\n        type: 'number',\n        from: 0,\n        to: 10,\n        delay: (ctx) => ctx.dataIndex * 100,\n        duration: (ctx) => ctx.datasetIndex * 1000,\n        loop: true,\n        easing: 'linear'\n      }\n    },\n    transitions: {\n      show: {\n        animation: {\n          duration: 10\n        },\n        animations: {\n          numbers: false\n        }\n      },\n      custom: {\n        animation: {\n          duration: 10\n        }\n      }\n    }\n  },\n});\n\n\nconst pie = new Chart('id', {\n  type: 'pie',\n  data: {\n    labels: [],\n    datasets: [{\n      data: []\n    }]\n  },\n  options: {\n    animation: false,\n  }\n});\n\n\nconst polarArea = new Chart('id', {\n  type: 'polarArea',\n  data: {\n    labels: [],\n    datasets: [{\n      data: []\n    }]\n  },\n  options: {\n    animation: false,\n  }\n});\n"
  },
  {
    "path": "test/types/autogen.js",
    "content": "import * as fs from 'fs';\nimport * as path from 'path';\nimport { fileURLToPath } from 'url';\nimport * as helpers from '../../dist/helpers.js';\n\nconst __dirname = fileURLToPath(new URL('.', import.meta.url));\n\nlet fd;\n\ntry {\n  const fn = path.resolve(__dirname, 'autogen_helpers.ts');\n  fd = fs.openSync(fn, 'w+');\n  fs.writeSync(fd, 'import * as helpers from \\'../../dist/helpers/index.js\\';\\n\\n');\n\n  fs.writeSync(fd, 'const testKeys: unknown[] = [];\\n');\n  for (const key of Object.keys(helpers)) {\n    if (key[0] !== '_' && typeof helpers[key] === 'function') {\n      fs.writeSync(fd, `testKeys.push(helpers.${key});\\n`);\n    }\n  }\n} finally {\n  if (fd !== undefined) {\n    fs.closeSync(fd);\n  }\n}\n"
  },
  {
    "path": "test/types/chart_types.ts",
    "content": "import { Chart } from '../../src/types.js';\n\nconst chart = new Chart('chart', {\n  type: 'bar',\n  data: {\n    labels: ['1', '2', '3'],\n    datasets: [{\n      data: [1, 2, 3]\n    },\n    {\n      data: [1, 2, 3],\n      categoryPercentage: 10\n    }],\n  }\n});\n\nconst chart2 = new Chart('chart', {\n  type: 'bar',\n  data: {\n    labels: ['1', '2', '3'],\n    datasets: [{\n      type: 'line',\n      data: [1, 2, 3],\n      // @ts-expect-error should not allow bar properties to be defined in a line dataset\n      categoryPercentage: 10\n    },\n    {\n      type: 'line',\n      pointBackgroundColor: 'red',\n      data: [1, 2, 3]\n    },\n    {\n      data: [1, 2, 3],\n      categoryPercentage: 10\n    }],\n  }\n});\n\nconst chart3 = new Chart('chart', {\n  data: {\n    labels: ['1', '2', '3'],\n    datasets: [{\n      type: 'bar',\n      data: [1, 2, 3],\n      categoryPercentage: 10\n    },\n    {\n      type: 'bar',\n      data: [1, 2, 3],\n      // @ts-expect-error should not allow line properties to be defined in a bar dataset\n      pointBackgroundColor: 'red',\n    }],\n  }\n});\n\n// @ts-expect-error all datasets should have a type property or a default fallback type should be set\nconst chart4 = new Chart('chart', {\n  data: {\n    labels: ['1', '2', '3'],\n    datasets: [{\n      type: 'bar',\n      data: [1, 2, 3],\n      categoryPercentage: 10\n    },\n    {\n      data: [1, 2, 3]\n    }],\n  }\n});\n"
  },
  {
    "path": "test/types/controllers/bar_floating_data.ts",
    "content": "import { Chart } from '../../../src/types.js';\n\nconst chart = new Chart('id', {\n  type: 'bar',\n  data: {\n    labels: ['1', '2', '3'],\n    datasets: [{\n      data: [[1, 2], [3, 4], [5, 6]]\n    }]\n  },\n});\n"
  },
  {
    "path": "test/types/controllers/bubble_chart_options.ts",
    "content": "import { Chart, ChartOptions } from '../../../src/types.js';\n\nconst chart = new Chart('test', {\n  type: 'bubble',\n  data: {\n    datasets: []\n  },\n  options: {\n    scales: {\n      x: {\n        min: 0,\n        max: 30,\n        ticks: {}\n      },\n      y: {\n        min: 0,\n        max: 30,\n        ticks: {},\n      },\n    }\n  }\n});\n"
  },
  {
    "path": "test/types/controllers/doughnut_meta_total.ts",
    "content": "import { Chart, ChartMeta, Element } from '../../../src/types.js';\n\nconst chart = new Chart('id', {\n  type: 'doughnut',\n  data: {\n    labels: [],\n    datasets: [{\n      data: [],\n    }]\n  },\n});\n\n// A cast is required because the exact type of ChartMeta will vary with\n// mixed charts\nconst meta = <ChartMeta<'doughnut', Element, Element>>chart.getDatasetMeta(0);\nconst total = meta.total;\n"
  },
  {
    "path": "test/types/controllers/doughnut_offset.ts",
    "content": "import { Chart, ChartMeta, Element } from '../../../src/types.js';\n\nconst chart = new Chart('id', {\n  type: 'doughnut',\n  data: {\n    labels: [],\n    datasets: [{\n      data: [],\n      offset: 40,\n    }]\n  },\n  options: {\n    offset: 20,\n  }\n});\n"
  },
  {
    "path": "test/types/controllers/doughnut_outer_radius.ts",
    "content": "import { Chart } from '../../../src/types.js';\n\nconst chart = new Chart('id', {\n  type: 'doughnut',\n  data: {\n    labels: [],\n    datasets: [{\n      data: [],\n    }]\n  },\n  options: {\n    radius: () => Math.random() > 0.5 ? 50 : '50%',\n  }\n});\n"
  },
  {
    "path": "test/types/controllers/doughnut_spacing_offset.ts",
    "content": "import { Chart, ChartMeta, Element } from '../../../src/types.js';\n\nconst chart = new Chart('id', {\n  type: 'doughnut',\n  data: {\n    datasets: [{\n      data: [10, 20, 40, 50, 5],\n      label: 'Dataset 1',\n      backgroundColor: [\n        'red',\n        'orange',\n        'yellow',\n        'green',\n        'blue'\n      ]\n    }],\n    labels: [\n      'Item 1',\n      'Item 2',\n      'Item 3',\n      'Item 4',\n      'Item 5'\n    ],\n  },\n  options: {\n    spacing: 50,\n    offset: [0, 50, 0, 0, 0],\n  }\n});\n"
  },
  {
    "path": "test/types/controllers/line_scriptable_parsed_data.ts",
    "content": "import { Chart } from '../../../src/types.js';\n\nconst chart = new Chart('id', {\n  type: 'line',\n  data: {\n    labels: [],\n    datasets: [{\n      data: [],\n      backgroundColor: (context) => {\n        return context.parsed.y > 10 ? 'green' : 'red';\n      }\n    }]\n  },\n});\n"
  },
  {
    "path": "test/types/controllers/line_segments.ts",
    "content": "import { Chart } from '../../../src/types.js';\n\nconst chart = new Chart('id', {\n  type: 'line',\n  data: {\n    labels: [],\n    datasets: [{\n      data: [],\n      segment: {\n        backgroundColor: ctx => ctx.p0.skip ? 'transparent' : undefined,\n        borderColor: ctx => ctx.p0.skip ? 'gray' : undefined,\n        borderWidth: ctx => ctx.p1.parsed.y > 10 ? 5 : undefined,\n      }\n    }]\n  },\n});\n"
  },
  {
    "path": "test/types/controllers/line_span_gaps.ts",
    "content": "import { Chart } from '../../../src/types.js';\n\nconst chart = new Chart('id', {\n  type: 'line',\n  data: {\n    datasets: [\n      {\n        label: 'Cats',\n        data: [],\n      }\n    ]\n  },\n  options: {\n    elements: {\n      line: {\n        spanGaps: true\n      }\n    },\n    scales: {\n      x: {\n        type: 'linear',\n        min: 1,\n        max: 10\n      },\n      y: {\n        type: 'linear',\n        min: 0,\n        max: 50\n      }\n    }\n  }\n});\n"
  },
  {
    "path": "test/types/controllers/line_styling_array.ts",
    "content": "import { Chart } from '../../../src/types.js';\n\nconst chart = new Chart('id', {\n  type: 'line',\n  data: {\n    labels: [],\n    datasets: [{\n      data: [],\n      backgroundColor: ['red', 'blue'],\n      hoverBackgroundColor: ['red', 'blue'],\n    }]\n  },\n});\n"
  },
  {
    "path": "test/types/controllers/radar_dataset_indexable_options.ts",
    "content": "import { Chart, ChartOptions } from '../../../src/types.js';\n\nconst chart = new Chart('test', {\n  type: 'radar',\n  data: {\n    labels: ['a', 'b', 'c'],\n    datasets: [{\n      data: [1, 2, 3],\n      backgroundColor: ['red', 'green', 'blue'],\n      borderColor: ['red', 'green', 'blue'],\n      hoverRadius: [1, 2, 3],\n      pointBackgroundColor: ['red', 'green', 'blue'],\n      pointBorderColor: ['red', 'green', 'blue'],\n      pointBorderWidth: [1, 2, 3],\n      pointHitRadius: [1, 2, 3],\n      pointHoverBackgroundColor: ['red', 'green', 'blue'],\n      pointHoverBorderColor: ['red', 'green', 'blue'],\n      pointHoverBorderWidth: [1, 2, 3],\n      pointHoverRadius: [1, 2, 3],\n      pointRadius: [1, 2, 3],\n      pointRotation: [1, 2, 3],\n      pointStyle: ['circle', 'cross', 'crossRot'],\n      radius: [1, 2, 3],\n    }]\n  },\n});\n"
  },
  {
    "path": "test/types/data_types.ts",
    "content": "import { Chart } from '../../src/types.js';\n\nconst chart = new Chart('chart', {\n  type: 'bar',\n  data: {\n    labels: ['1', '2', '3'],\n    datasets: [{ data: [[1, 2], [1, 2], [1, 2]] }],\n  }\n});\n\nconst chart2 = new Chart('chart2', {\n  type: 'bar',\n  data: {\n    datasets: [{\n      data: [{ id: 'Sales', nested: { value: 1500 } }, { id: 'Purchases', nested: { value: 500 } }],\n    }],\n  },\n  options: { parsing: { xAxisKey: 'id', yAxisKey: 'nested.value' },\n  },\n});\n"
  },
  {
    "path": "test/types/dataset_null_data.ts",
    "content": "import type { ChartDataset } from '../../src/types.js';\n\nconst dataset: ChartDataset = {\n  data: [10, null, 20],\n};\n\nconst lineDataset: ChartDataset<'line'> = {\n  data: [10, null, 20],\n};\nconst scatterDataset: ChartDataset<'scatter'> = {\n  data: [10, null, 20],\n};\nconst radarDataset: ChartDataset<'radar'> = {\n  data: [10, null, 20],\n};\n\n"
  },
  {
    "path": "test/types/date_adapter.ts",
    "content": "import { _adapters } from '../../src/types.js';\n\n_adapters._date.override<{myOption: boolean}>({\n  init() {\n    const booleanOption: boolean = this.options.myOption;\n\n    // @ts-expect-error Options is readonly.\n    this.options = {};\n  },\n  // @ts-expect-error Should return string.\n  format(timestamp) {\n    const numberArg: number = timestamp;\n  }\n});\n"
  },
  {
    "path": "test/types/defaults.ts",
    "content": "import { Chart } from '../../src/types.js';\n\nChart.defaults.scales.time.time.minUnit = 'day';\n\nChart.defaults.plugins.title.display = false;\n\nChart.defaults.datasets.bar.backgroundColor = 'red';\n\nChart.defaults.animation = { duration: 500 };\n\nChart.defaults.font.size = 8;\n\n// @ts-expect-error should be number\nChart.defaults.font.size = '8';\n\n// @ts-expect-error should be number\nChart.defaults.font.size = () => '10';\n\nChart.defaults.backgroundColor = 'red';\nChart.defaults.backgroundColor = ['red', 'blue'];\nChart.defaults.backgroundColor = (ctx) => ctx.datasetIndex % 2 === 0 ? 'red' : 'blue';\n\nChart.defaults.borderColor = 'red';\nChart.defaults.borderColor = ['red', 'blue'];\nChart.defaults.borderColor = (ctx) => ctx.datasetIndex % 2 === 0 ? 'red' : 'blue';\n\nChart.defaults.hoverBackgroundColor = 'red';\nChart.defaults.hoverBackgroundColor = ['red', 'blue'];\nChart.defaults.hoverBackgroundColor = (ctx) => ctx.datasetIndex % 2 === 0 ? 'red' : 'blue';\n\nChart.defaults.hoverBorderColor = 'red';\nChart.defaults.hoverBorderColor = ['red', 'blue'];\nChart.defaults.hoverBorderColor = (ctx) => ctx.datasetIndex % 2 === 0 ? 'red' : 'blue';\n\nChart.defaults.font = {\n  family: \"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif\",\n  size: 10\n};\n\nChart.defaults.layout = {\n  padding: {\n    bottom: 10,\n  },\n};\n\nChart.defaults.plugins.tooltip.boxPadding = 3;\n"
  },
  {
    "path": "test/types/elements/scriptable_element_options.ts",
    "content": "import { Chart } from '../../../src/types.js';\n\nconst chart = new Chart('id', {\n  type: 'line',\n  data: {\n    labels: [],\n    datasets: []\n  },\n  options: {\n    elements: {\n      line: {\n        borderWidth: () => 2,\n      },\n      point: {\n        pointStyle: (ctx) => 'star',\n      }\n    }\n  }\n});\n\nconst chart2 = new Chart('id', {\n  type: 'bar',\n  data: {\n    labels: [],\n    datasets: []\n  },\n  options: {\n    elements: {\n      bar: {\n        borderWidth: (ctx) => 2,\n      }\n    }\n  }\n});\n\nconst chart3 = new Chart('id', {\n  type: 'doughnut',\n  data: {\n    labels: [],\n    datasets: []\n  },\n  options: {\n    elements: {\n      arc: {\n        borderWidth: (ctx) => 3,\n        borderJoinStyle: (ctx) => 'miter'\n      }\n    }\n  }\n});\n"
  },
  {
    "path": "test/types/extensions/plugin.ts",
    "content": "import { Chart } from '../../../src/types.js';\n\nChart.register({\n  id: 'my-plugin',\n  afterDraw: (chart: Chart) => {\n    // noop\n  }\n});\n\nChart.register([{\n  id: 'my-plugin',\n  afterDraw: (chart: Chart) => {\n    // noop\n  },\n}]);\n\n// @ts-expect-error not assignable\nChart.register({\n  id: 'fail',\n  noComponentHasThisMethod: () => 'test'\n});\n\n// @ts-expect-error missing id\nChart.register([{\n  afterDraw: (chart: Chart) => {\n    // noop\n  },\n}]);\n"
  },
  {
    "path": "test/types/extensions/scale.ts",
    "content": "import { AnyObject } from '../../../src/types/basic.js';\nimport { CartesianScaleOptions, Chart, Scale } from '../../../src/types.js';\n\nexport type TestScaleOptions = CartesianScaleOptions & {\n  testOption?: boolean\n}\n\nexport class TestScale<O extends TestScaleOptions = TestScaleOptions> extends Scale<O> {\n  static id: 'test';\n\n  getBasePixel(): number {\n    return 0;\n  }\n\n  testMethod(): void {\n    //\n  }\n}\n\ndeclare module '../../../src/types/index.js' {\n  interface CartesianScaleTypeRegistry {\n    test: {\n      options: TestScaleOptions\n    }\n  }\n}\n\n\nChart.register(TestScale);\n\nconst chart = new Chart('id', {\n  type: 'line',\n  data: {\n    datasets: []\n  },\n  options: {\n    scales: {\n      x: {\n        type: 'test',\n        position: 'bottom',\n        testOption: true,\n        min: 0\n      }\n    }\n  }\n});\n\nChart.unregister([TestScale]);\n"
  },
  {
    "path": "test/types/helpers/dom.ts",
    "content": "import { getRelativePosition } from '../../../src/helpers/helpers.dom.js';\nimport { Chart, ChartOptions } from '../../../src/types.js';\n\nconst chart = new Chart('test', {\n  type: 'line',\n  data: {\n    datasets: []\n  }\n});\n\ngetRelativePosition(new MouseEvent('click'), chart);\n"
  },
  {
    "path": "test/types/helpers/options.ts",
    "content": "import { createContext } from '../../../src/helpers/helpers.options.js';\n\nconst context1 = createContext(null, { type: 'test1', parent: true });\nconst context2 = createContext(context1, { type: 'test2' });\n\nconst sSest: string = context1.type + context2.type;\nconst bTest: boolean = context1.parent && context2.parent;\n\n// @ts-expect-error Property 'notThere' does not exist on type '{ type: string; parent: boolean; } & { type: string; }'\ncontext2.notThere = '';\n"
  },
  {
    "path": "test/types/interaction.ts",
    "content": "import {\n  Chart, ChartData, ChartConfiguration, Element\n} from '../../src/types.js';\n\nconst data: ChartData<'line'> = { datasets: [] };\nconst chartItem = 'item';\nconst config: ChartConfiguration<'line'> = { type: 'line', data };\nconst chart: Chart = new Chart(chartItem, config);\n\ntype Item = {\n  element: Element,\n  datasetIndex: number,\n  index: number\n}\n\nconst elements: Item[] = [];\nchart.updateHoverStyle(elements, 'dataset', true);\n"
  },
  {
    "path": "test/types/layout/position.ts",
    "content": "import type { LayoutPosition } from '../../../src/types.js';\n\nconst left: LayoutPosition = 'left';\nconst right: LayoutPosition = 'right';\nconst top: LayoutPosition = 'top';\nconst bottom: LayoutPosition = 'bottom';\nconst center: LayoutPosition = 'center';\nconst axis: LayoutPosition = { x: 10 };\n\n// @ts-expect-error invalid position\nconst invalid: LayoutPosition = 'none';\n"
  },
  {
    "path": "test/types/options.ts",
    "content": "import { Chart, ChartOptions, ChartType, DoughnutControllerChartOptions } from '../../src/types.js';\n\nconst chart = new Chart('test', {\n  type: 'bar',\n  data: {\n    labels: ['a'],\n    datasets: [{\n      data: [1],\n    }, {\n      type: 'line',\n      data: [{ x: 1, y: 1 }]\n    }]\n  },\n  options: {\n    animation: {\n      duration: 500\n    },\n    backgroundColor: 'red',\n    datasets: {\n      line: {\n        animation: {\n          duration: 600\n        },\n        backgroundColor: 'blue',\n      }\n    },\n    elements: {\n      point: {\n        backgroundColor: 'red'\n      }\n    }\n  }\n});\n\nconst doughnutOptions: DoughnutControllerChartOptions = {\n  circumference: 360,\n  cutout: '50%',\n  offset: 0,\n  radius: 100,\n  rotation: 0,\n  spacing: 0,\n  animation: false,\n};\n\nconst chartOptions: ChartOptions<ChartType> = doughnutOptions;\n"
  },
  {
    "path": "test/types/overrides.ts",
    "content": "import { Chart } from '../../src/types.js';\n\nChart.overrides.bar.scales.x.type = 'time';\n\nChart.overrides.bar.plugins.title.display = false;\n\nChart.overrides.line.datasets.bar.backgroundColor = 'red';\n\nChart.overrides.line.animation = false;\nChart.overrides.line.datasets.bar.animation = { duration: 100 };\n"
  },
  {
    "path": "test/types/parsed.data.type.ts",
    "content": "import type { ParsedDataType } from '../../src/types.js';\n\ninterface test {\n  pie: ParsedDataType<'pie'>,\n  line: ParsedDataType<'line'>,\n  testA: ParsedDataType<'pie' | 'line' | 'bar'>\n  testB: ParsedDataType<'pie' | 'line' | 'bar'>\n  testC: ParsedDataType<'pie' | 'line' | 'bar'>\n}\n\nconst testImpl: test = {\n  pie: 1,\n  line: { x: 1, y: 2 },\n  testA: 1,\n  testB: { x: 1, y: 2 },\n  // @ts-expect-error testC should be limited to pie/line datatypes\n  testC: 'test'\n};\n"
  },
  {
    "path": "test/types/plugins/defaults.ts",
    "content": "import { defaults } from '../../../src/types.js';\n\n// https://github.com/chartjs/Chart.js/issues/8711\nconst original = defaults.plugins.legend.labels.generateLabels;\n\ndefaults.plugins.legend.labels.generateLabels = function(chart) {\n  return [{\n    datasetIndex: 0,\n    text: 'test'\n  }];\n};\n"
  },
  {
    "path": "test/types/plugins/plugin.colors/colors.ts",
    "content": "import { Chart } from '../../../../src/types.js';\n\nconst chart = new Chart('id', {\n  type: 'bubble',\n  data: {\n    labels: [],\n    datasets: [{\n      data: []\n    }]\n  },\n  options: {\n    plugins: {\n      colors: {\n        enabled: true,\n        forceOverride: false,\n      }\n    }\n  }\n});\n"
  },
  {
    "path": "test/types/plugins/plugin.decimation/decimation_algorithm.ts",
    "content": "import { Chart, DecimationAlgorithm } from '../../../../src/types.js';\n\nconst chart = new Chart('id', {\n  type: 'bubble',\n  data: {\n    labels: [],\n    datasets: [{\n      data: []\n    }]\n  },\n  options: {\n    plugins: {\n      decimation: {\n        algorithm: DecimationAlgorithm.lttb,\n      }\n    }\n  }\n});\n\n\nconst chart2 = new Chart('id', {\n  type: 'bubble',\n  data: {\n    labels: [],\n    datasets: [{\n      data: []\n    }]\n  },\n  options: {\n    plugins: {\n      decimation: {\n        algorithm: 'lttb',\n      }\n    }\n  }\n});\n\n\nconst chart3 = new Chart('id', {\n  type: 'bubble',\n  data: {\n    labels: [],\n    datasets: [{\n      data: []\n    }]\n  },\n  options: {\n    plugins: {\n      decimation: {\n        algorithm: DecimationAlgorithm.minmax,\n      }\n    }\n  }\n});\n\n\nconst chart4 = new Chart('id', {\n  type: 'bubble',\n  data: {\n    labels: [],\n    datasets: [{\n      data: []\n    }]\n  },\n  options: {\n    plugins: {\n      decimation: {\n        algorithm: 'min-max',\n      }\n    }\n  }\n});\n"
  },
  {
    "path": "test/types/plugins/plugin.filler/fill_target_true.ts",
    "content": "import type { ChartDataset } from '../../../../src/types.js';\n\nconst dataset: ChartDataset = {\n  data: [],\n  fill: true,\n};\n"
  },
  {
    "path": "test/types/plugins/plugin.tooltip/chart.tooltip.ts",
    "content": "import { Chart } from '../../../../src/types.js';\n\nconst chart = new Chart('id', {\n  type: 'line',\n  data: {\n    labels: [],\n    datasets: [{\n      data: []\n    }]\n  },\n});\n\nconst tooltip = chart.tooltip;\n\nconst active = tooltip && tooltip.getActiveElements();\n"
  },
  {
    "path": "test/types/plugins/plugin.tooltip/tooltip_dataset_type.ts",
    "content": "import { Chart } from '../../../../src/types.js';\n\nconst chart = new Chart('id', {\n  type: 'line',\n  data: {\n    labels: [],\n    datasets: [{\n      data: []\n    }]\n  },\n  options: {\n    plugins: {\n      tooltip: {\n        callbacks: {\n          label: (item) => {\n            return `Y Axis ${item.dataset.yAxisID}`;\n          }\n        }\n      }\n    }\n  },\n});\n"
  },
  {
    "path": "test/types/plugins/plugin.tooltip/tooltip_parsed_data.ts",
    "content": "import { Chart } from '../../../../src/types.js';\n\nconst chart = new Chart('id', {\n  type: 'bar',\n  data: {\n    labels: [],\n    datasets: [{\n      data: []\n    }]\n  },\n  options: {\n    plugins: {\n      tooltip: {\n        callbacks: {\n          label: (item) => {\n            return `Foo data ${item.parsed.y}`;\n          }\n        }\n      }\n    }\n  },\n});\n"
  },
  {
    "path": "test/types/plugins/plugin.tooltip/tooltip_parsed_data_chart_defaults.ts",
    "content": "import { Chart } from '../../../../src/types.js';\n\nChart.overrides.bubble.plugins.tooltip.callbacks.label = (item) => {\n  const { x, y, _custom: r } = item.parsed;\n  return `${item.label}: (${x}, ${y}, ${r})`;\n};\n\nconst chart = new Chart('id', {\n  type: 'bubble',\n  data: {\n    labels: [],\n    datasets: [{\n      data: []\n    }]\n  },\n});\n"
  },
  {
    "path": "test/types/plugins/plugin.tooltip/tooltip_scriptable_background_color.ts",
    "content": "import { Chart } from '../../../../src/types.js';\n\nconst chart = new Chart('id', {\n  type: 'bar',\n  data: {\n    labels: [],\n    datasets: [{\n      data: []\n    }]\n  },\n  options: {\n    plugins: {\n      tooltip: {\n        backgroundColor: (ctx) => 'black',\n      }\n    }\n  },\n});\n"
  },
  {
    "path": "test/types/register.ts",
    "content": "import {\n  Chart,\n  ArcElement,\n  LineElement,\n  BarElement,\n  PointElement,\n  BarController,\n  BubbleController,\n  DoughnutController,\n  LineController,\n  PieController,\n  PolarAreaController,\n  RadarController,\n  ScatterController,\n  CategoryScale,\n  LinearScale,\n  LogarithmicScale,\n  RadialLinearScale,\n  TimeScale,\n  TimeSeriesScale,\n  Decimation,\n  Filler,\n  Legend,\n  Title,\n  SubTitle,\n  Tooltip,\n  Colors\n} from '../../src/types.js';\n\nChart.register(\n  ArcElement,\n  LineElement,\n  BarElement,\n  PointElement,\n  BarController,\n  BubbleController,\n  DoughnutController,\n  LineController,\n  PieController,\n  PolarAreaController,\n  RadarController,\n  ScatterController,\n  CategoryScale,\n  LinearScale,\n  LogarithmicScale,\n  RadialLinearScale,\n  TimeScale,\n  TimeSeriesScale,\n  Decimation,\n  Filler,\n  Legend,\n  Title,\n  SubTitle,\n  Tooltip,\n  Colors\n);\n"
  },
  {
    "path": "test/types/scales/chart_options.ts",
    "content": "import type { ChartOptions } from '../../../src/types.js';\n\nconst chartOptions: ChartOptions<'line'> = {\n  scales: {\n    x: {\n      type: 'time',\n      time: {\n        unit: 'year'\n      }\n    },\n  }\n};\n"
  },
  {
    "path": "test/types/scales/options.ts",
    "content": "import { Chart, ScaleOptions } from '../../../src/types.js';\n\nconst chart = new Chart('test', {\n  type: 'bar',\n  data: {\n    labels: ['a'],\n    datasets: [{\n      data: [1],\n    }, {\n      type: 'line',\n      data: [{ x: 1, y: 1 }]\n    }]\n  },\n  options: {\n    scales: {\n      x: {\n        type: 'time',\n        time: {\n          unit: 'year'\n        },\n        ticks: {\n          stepSize: 1\n        }\n      },\n      x1: {\n        type: 'linear',\n        // @ts-expect-error 'time' does not exist in 'linear' options\n        time: {\n          unit: 'year'\n        }\n      },\n      y: {\n        ticks: {\n          callback(tickValue) {\n            const value = this.getLabelForValue(tickValue as number);\n            return '$' + value;\n          }\n        }\n      }\n    }\n  }\n});\n\nfunction makeChartScale(range: number): ScaleOptions<'linear'> {\n  return {\n    type: 'linear',\n    min: 0,\n    suggestedMax: range,\n  };\n}\n\nconst composedChart = new Chart('test2', {\n  type: 'bar',\n  data: {\n    labels: ['a'],\n    datasets: [{\n      data: [1],\n    }, {\n      type: 'line',\n      data: [{ x: 1, y: 1 }]\n    }]\n  },\n  options: {\n    scales: {\n      x: makeChartScale(10)\n    }\n  }\n});\n"
  },
  {
    "path": "test/types/scales/time_string_max.ts",
    "content": "import { Chart } from '../../../src/types.js';\n\nconst chart = new Chart('id', {\n  type: 'line',\n  data: {\n    datasets: [\n      {\n        label: 'Pie',\n        data: [\n        ],\n        borderColor: '#000000',\n        backgroundColor: '#00FF00'\n      }\n    ]\n  },\n  options: {\n    scales: {\n      x: {\n        type: 'time',\n        min: '2021-01-01',\n        max: '2021-12-01'\n      },\n      y: {\n        type: 'linear',\n        min: 0,\n        max: 10\n      }\n    }\n  }\n});\n"
  },
  {
    "path": "test/types/scriptable.ts",
    "content": "import type { ChartType, Scriptable, ScriptableContext } from '../../src/types.js';\n\ninterface test {\n  pie?: Scriptable<number, ScriptableContext<'pie'>>,\n  line?: Scriptable<number, ScriptableContext<'line'>>,\n  testA?: Scriptable<number, ScriptableContext<'pie'>>\n  testB?: Scriptable<number, ScriptableContext<'line' | 'bar'>>\n  testC?: Scriptable<number, ScriptableContext<'pie' | 'line' | 'bar'>>\n  testD?: Scriptable<number, ScriptableContext<ChartType>>\n}\n\nconst testImpl: test = {\n  pie: (ctx) => ctx.parsed + ctx.chart.width,\n  line: (ctx) => ctx.parsed.x + ctx.parsed.y,\n  testA: (ctx) => ctx.parsed + ctx.dataset.data[0],\n  testB: (ctx) => ctx.parsed.x + ctx.parsed.y,\n  // @ts-expect-error combined type should not be any\n  testC: (ctx) => ctx.fail,\n  // combined types are intersections and permit invalid usage\n  testD: (ctx) => ctx.parsed + ctx.parsed.x + ctx.parsed.r + ctx.parsed._custom.barEnd\n};\n\n"
  },
  {
    "path": "test/types/scriptable_core_chart_options.ts",
    "content": "import type { ChartConfiguration } from '../../src/types.js';\n\nconst getConfig = (): ChartConfiguration<'bar'> => {\n  return {\n    type: 'bar',\n    data: {\n      datasets: []\n    },\n    options: {\n      backgroundColor: (context) => context.active ? '#fff' : undefined,\n    }\n  };\n};\n\n"
  },
  {
    "path": "test/types/test_instance_assignment.ts",
    "content": "import { Chart } from '../../src/types.js';\n\nconst chart = new Chart('id', {\n  type: 'scatter',\n  data: {\n    labels: [],\n    datasets: [{\n      data: [{ x: 0, y: 1 }],\n      pointRadius: (ctx) => ctx.parsed.x,\n    }]\n  },\n});\n\ninterface Context {\n  chart: Chart;\n}\n\nconst ctx: Context = {\n  chart: chart\n};\n\n// @ts-expect-error Type '{ x: number; y: number; }[]' is not assignable to type 'number[]'.\nconst dataArray: number[] = chart.data.datasets[0].data;\n"
  },
  {
    "path": "test/types/ticks/ticks.ts",
    "content": "import { Chart, Ticks } from '../../../src/types.js';\n\n// @ts-expect-error The 'this' context... is not assignable to method's 'this' of type 'Scale<CoreScaleOptions>'.\nTicks.formatters.numeric(0, 0, [{ value: 0 }]);\n\nconst chart = new Chart('test', {\n  type: 'line',\n  data: {\n    datasets: [{\n      data: [{ x: 1, y: 1 }]\n    }]\n  },\n});\n\nTicks.formatters.numeric.call(chart.scales.x, 0, 0, [{ value: 0 }]);\n"
  },
  {
    "path": "test/types/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"noEmit\": true,\n    \"rootDir\": \"../../\"\n  },\n  \"include\": [\n    \"./\",\n    \"../../src/\",\n    \"../../dist/**/*.d.ts\"\n  ],\n  \"exclude\": [\n    \"./**/*.js\"\n  ]\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    /* Type Checking */\n    \"alwaysStrict\": true,\n    \"strictBindCallApply\": true,\n    \"strictFunctionTypes\": true,\n    /* todo: uncomment after transition to TS */\n    // \"noFallthroughCasesInSwitch\": true,\n    // \"noImplicitOverride\": true,\n    // \"noImplicitReturns\": true,\n    // \"noUnusedLocals\": true,\n    // \"noUnusedParameters\": true,\n    /* Modules */\n    \"baseUrl\": \".\",\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"NodeNext\",\n    \"resolveJsonModule\": true,\n    \"rootDir\": \"src\",\n    \"types\": [\"offscreencanvas\"],\n    /* Emit */\n    \"declaration\": true,\n    \"importsNotUsedAsValues\": \"error\",\n    \"inlineSourceMap\": true,\n    \"outDir\": \"dist\",\n    /* JavaScript Support */\n    \"allowJs\": true,\n    \"checkJs\": true,\n    /* Interop Constraints */\n    \"allowSyntheticDefaultImports\": true,\n    /* Language and Environment */\n    \"target\": \"ES6\",\n    \"lib\": [\"es2018\", \"DOM\"]\n  },\n  \"typedocOptions\": {\n    \"name\": \"Chart.js\",\n    \"entryPoints\": [\"src/types/index.d.ts\"],\n    \"readme\": \"none\",\n    \"excludeExternals\": true,\n    \"includeVersion\": true,\n    \"out\": \"./dist/docs/typedoc\"\n  },\n  \"include\": [\n    \"./src/**/*\"\n  ],\n  \"exclude\": [\n    \"./dist/**\"\n  ]\n}\n"
  }
]