Repository: chartjs/chartjs-plugin-zoom
Pull request #887 (2024-11-17 21:20:14)

chore: update some devdeps

github-actions[bot]: **Size Change:** 0 B 

**Total Size:** 19.4 kB



<details><summary>ℹ️ <strong>View Unchanged</strong></summary>

| Filename | Size |
| :--- | :---: |
| `dist/chartjs-plugin-zoom.esm.js` | 7.04 kB |
| `dist/chartjs-plugin-zoom.js` | 7.19 kB |
| `dist/chartjs-plugin-zoom.min.js` | 5.2 kB |

</details>



<a href="https://github.com/preactjs/compressed-size-action"><sub>compressed-size-action</sub></a>

Patch:
diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js
index a55fa36..bb57688 100755
--- a/docs/.vuepress/config.js
+++ b/docs/.vuepress/config.js
@@ -146,6 +146,7 @@ module.exports = {
           children: [
             'drag/category',
             'drag/linear',
+            'drag/linear-ratio',
             'drag/log',
             'drag/time',
             'drag/timeseries',
diff --git a/docs/guide/options.md b/docs/guide/options.md
index 74c6cd9..74afe03 100644
--- a/docs/guide/options.md
+++ b/docs/guide/options.md
@@ -80,7 +80,7 @@ const chart = new Chart('id', {
 | [`drawTime`](#draw-time) | `string` | `beforeDatasetsDraw` | When the dragging box is drawn on the chart
 | `threshold` | `number` | `0` | Minimal zoom distance required before actually applying zoom
 | `modifierKey` | `'ctrl'`\|`'alt'`\|`'shift'`\|`'meta'` | `null` |  Modifier key required for drag-to-zoom
-
+| `maintainAspectRatio` | `boolean` | `undefined` | Maintain aspect ratio of the chart
 
 ## Draw Time
 
diff --git a/docs/samples/drag/linear-ratio.md b/docs/samples/drag/linear-ratio.md
new file mode 100644
index 0000000..54094b3
--- /dev/null
+++ b/docs/samples/drag/linear-ratio.md
@@ -0,0 +1,112 @@
+# Linear Scales + maintainAspectRatio
+
+Zooming is performed by clicking and selecting an area over the chart with the mouse. Pan is activated by keeping `shift` pressed.
+
+```js chart-editor
+// <block:data:1>
+const NUMBER_CFG = {count: 20, min: -100, max: 100};
+const data = {
+  datasets: [{
+    label: 'My First dataset',
+    borderColor: Utils.randomColor(0.4),
+    backgroundColor: Utils.randomColor(0.1),
+    pointBorderColor: Utils.randomColor(0.7),
+    pointBackgroundColor: Utils.randomColor(0.5),
+    pointBorderWidth: 1,
+    data: Utils.points(NUMBER_CFG),
+  }, {
+    label: 'My Second dataset',
+    borderColor: Utils.randomColor(0.4),
+    backgroundColor: Utils.randomColor(0.1),
+    pointBorderColor: Utils.randomColor(0.7),
+    pointBackgroundColor: Utils.randomColor(0.5),
+    pointBorderWidth: 1,
+    data: Utils.points(NUMBER_CFG),
+  }]
+};
+// </block:data>
+
+// <block:scales:2>
+const scaleOpts = {
+  reverse: true,
+  grid: {
+    borderColor: Utils.randomColor(1),
+    color: 'rgba( 0, 0, 0, 0.1)',
+  },
+  title: {
+    display: true,
+    text: (ctx) => ctx.scale.axis + ' axis',
+  }
+};
+const scales = {
+  x: {
+    position: 'top',
+  },
+  y: {
+    position: 'right',
+  },
+};
+Object.keys(scales).forEach(scale => Object.assign(scales[scale], scaleOpts));
+// </block:scales>
+
+// <block:zoom:0>
+const dragColor = Utils.randomColor(0.4);
+const zoomOptions = {
+  pan: {
+    enabled: true,
+    mode: 'xy',
+    modifierKey: 'shift',
+  },
+  zoom: {
+    mode: 'xy',
+    drag: {
+      enabled: true,
+      borderColor: 'rgb(54, 162, 235)',
+      borderWidth: 1,
+      backgroundColor: 'rgba(54, 162, 235, 0.3)',
+      maintainAspectRatio: true,
+    }
+  }
+};
+// </block:zoom>
+
+const zoomStatus = () => zoomOptions.zoom.drag.enabled ? 'enabled' : 'disabled';
+
+// <block:config:1>
+const config = {
+  type: 'scatter',
+  data: data,
+  options: {
+    scales: scales,
+    plugins: {
+      zoom: zoomOptions,
+      title: {
+        display: true,
+        position: 'bottom',
+        text: (ctx) => 'Zoom: ' + zoomStatus()
+      }
+    },
+  }
+};
+// </block:config>
+
+const actions = [
+  {
+    name: 'Toggle zoom',
+    handler(chart) {
+      zoomOptions.zoom.drag.enabled = !zoomOptions.zoom.drag.enabled;
+      chart.update();
+    }
+  }, {
+    name: 'Reset zoom',
+    handler(chart) {
+      chart.resetZoom();
+    }
+  }
+];
+
+module.exports = {
+  actions,
+  config,
+};
+```
diff --git a/package-lock.json b/package-lock.json
index 7875992..5e0211b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -26,8 +26,8 @@
         "babel-loader": "^8.3.0",
         "chart.js": "^4.3.2",
         "chartjs-adapter-date-fns": "^3.0.0",
-        "chartjs-test-utils": "^0.3.0",
-        "concurrently": "^6.0.2",
+        "chartjs-test-utils": "^0.5.0",
+        "concurrently": "^9.1.0",
         "coveralls": "^3.1.0",
         "cross-env": "^7.0.3",
         "date-fns": "^2.21.1",
@@ -36,17 +36,17 @@
         "eslint-plugin-html": "^6.1.2",
         "eslint-plugin-markdown": "^2.0.1",
         "hammer-simulator": "^0.0.1",
-        "jasmine": "^3.7.0",
+        "jasmine": "^5.4.0",
         "karma": "^6.4.4",
         "karma-chrome-launcher": "^3.2.0",
         "karma-coverage": "^2.2.1",
         "karma-firefox-launcher": "^2.1.3",
-        "karma-jasmine": "^4.0.2",
-        "karma-jasmine-html-reporter": "^1.5.4",
+        "karma-jasmine": "^5.1.0",
+        "karma-jasmine-html-reporter": "^2.1.0",
         "karma-rollup-preprocessor": "^7.0.7",
         "karma-spec-reporter": "0.0.36",
         "ng-hammerjs": "^2.0.8",
-        "pixelmatch": "^5.2.1",
+        "pixelmatch": "^6.0.0",
         "rollup": "^4.12.1",
         "rollup-plugin-cleanup": "^3.2.1",
         "rollup-plugin-istanbul": "^5.0.0",
@@ -1757,6 +1757,102 @@
       "deprecated": "Use @eslint/object-schema instead",
       "dev": true
     },
+    "node_modules/@isaacs/cliui": {
+      "version": "8.0.2",
+      "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
+      "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
+      "dev": true,
+      "dependencies": {
+        "string-width": "^5.1.2",
+        "string-width-cjs": "npm:string-width@^4.2.0",
+        "strip-ansi": "^7.0.1",
+        "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
+        "wrap-ansi": "^8.1.0",
+        "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@isaacs/cliui/node_modules/ansi-regex": {
+      "version": "6.1.0",
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
+      "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
+      "dev": true,
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+      }
+    },
+    "node_modules/@isaacs/cliui/node_modules/ansi-styles": {
+      "version": "6.2.1",
+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
+      "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
+      "dev": true,
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+      }
+    },
+    "node_modules/@isaacs/cliui/node_modules/emoji-regex": {
+      "version": "9.2.2",
+      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+      "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
+      "dev": true
+    },
+    "node_modules/@isaacs/cliui/node_modules/string-width": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+      "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+      "dev": true,
+      "dependencies": {
+        "eastasianwidth": "^0.2.0",
+        "emoji-regex": "^9.2.2",
+        "strip-ansi": "^7.0.1"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/@isaacs/cliui/node_modules/strip-ansi": {
+      "version": "7.1.0",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+      "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+      "dev": true,
+      "dependencies": {
+        "ansi-regex": "^6.0.1"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+      }
+    },
+    "node_modules/@isaacs/cliui/node_modules/wrap-ansi": {
+      "version": "8.1.0",
+      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
+      "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
+      "dev": true,
+      "dependencies": {
+        "ansi-styles": "^6.1.0",
+        "string-width": "^5.0.1",
+        "strip-ansi": "^7.0.1"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+      }
+    },
     "node_modules/@istanbuljs/schema": {
       "version": "0.1.3",
       "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
@@ -1878,6 +1974,16 @@
         "node": ">= 8"
       }
     },
+    "node_modules/@pkgjs/parseargs": {
+      "version": "0.11.0",
+      "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
+      "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
+      "dev": true,
+      "optional": true,
+      "engines": {
+        "node": ">=14"
+      }
+    },
     "node_modules/@rollup/plugin-commonjs": {
       "version": "28.0.1",
       "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.1.tgz",
@@ -6108,15 +6214,38 @@
       }
     },
     "node_modules/chartjs-test-utils": {
-      "version": "0.3.1",
-      "resolved": "https://registry.npmjs.org/chartjs-test-utils/-/chartjs-test-utils-0.3.1.tgz",
-      "integrity": "sha512-QsRYLWOedYGsloDvJsByPNUK44TOiqnxQEO5FOrOm9SguEl5WmJDCOIdd/1ePLOX4gGRClXBDVxD7o1SJY+nWA==",
+      "version": "0.5.0",
+      "resolved": "https://registry.npmjs.org/chartjs-test-utils/-/chartjs-test-utils-0.5.0.tgz",
+      "integrity": "sha512-TQSjwace7RGr7TS3qh39O/JhALrQK0f1bItu8TfIOiqWz6DBQG5LCh+S23O54MF16YxRlJ4qDHn1hpD9QW7KCw==",
       "dev": true,
       "dependencies": {
-        "jasmine": "^3.6.4",
-        "karma": "^6.1.1",
-        "karma-jasmine": "^4.0.1",
-        "pixelmatch": "^5.2.1"
+        "pixelmatch": "^5.3.0"
+      },
+      "peerDependencies": {
+        "jasmine": ">=3.0.0",
+        "karma": ">=6.0.0",
+        "karma-jasmine": ">=4.0.0"
+      }
+    },
+    "node_modules/chartjs-test-utils/node_modules/pixelmatch": {
+      "version": "5.3.0",
+      "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-5.3.0.tgz",
+      "integrity": "sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==",
+      "dev": true,
+      "dependencies": {
+        "pngjs": "^6.0.0"
+      },
+      "bin": {
+        "pixelmatch": "bin/pixelmatch"
+      }
+    },
+    "node_modules/chartjs-test-utils/node_modules/pngjs": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz",
+      "integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==",
+      "dev": true,
+      "engines": {
+        "node": ">=12.13.0"
       }
     },
     "node_modules/chokidar": {
@@ -6550,25 +6679,95 @@
       }
     },
     "node_modules/concurrently": {
-      "version": "6.5.1",
-      "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-6.5.1.tgz",
-      "integrity": "sha512-FlSwNpGjWQfRwPLXvJ/OgysbBxPkWpiVjy1042b0U7on7S7qwwMIILRj7WTN1mTgqa582bG6NFuScOoh6Zgdag==",
+      "version": "9.1.0",
+      "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.1.0.tgz",
+      "integrity": "sha512-VxkzwMAn4LP7WyMnJNbHN5mKV9L2IbyDjpzemKr99sXNR3GqRNMMHdm7prV1ws9wg7ETj6WUkNOigZVsptwbgg==",
       "dev": true,
       "dependencies": {
-        "chalk": "^4.1.0",
-        "date-fns": "^2.16.1",
+        "chalk": "^4.1.2",
         "lodash": "^4.17.21",
-        "rxjs": "^6.6.3",
-        "spawn-command": "^0.0.2-1",
-        "supports-color": "^8.1.0",
+        "rxjs": "^7.8.1",
+        "shell-quote": "^1.8.1",
+        "supports-color": "^8.1.1",
         "tree-kill": "^1.2.2",
-        "yargs": "^16.2.0"
+        "yargs": "^17.7.2"
       },
       "bin": {
-        "concurrently": "bin/concurrently.js"
+        "conc": "dist/bin/concurrently.js",
+        "concurrently": "dist/bin/concurrently.js"
       },
       "engines": {
-        "node": ">=10.0.0"
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/open-cli-tools/concurrently?sponsor=1"
+      }
+    },
+    "node_modules/concurrently/node_modules/cliui": {
+      "version": "8.0.1",
+      "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
+      "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
+      "dev": true,
+      "dependencies": {
+        "string-width": "^4.2.0",
+        "strip-ansi": "^6.0.1",
+        "wrap-ansi": "^7.0.0"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/concurrently/node_modules/wrap-ansi": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+      "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+      "dev": true,
+      "dependencies": {
+        "ansi-styles": "^4.0.0",
+        "string-width": "^4.1.0",
+        "strip-ansi": "^6.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+      }
+    },
+    "node_modules/concurrently/node_modules/y18n": {
+      "version": "5.0.8",
+      "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+      "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+      "dev": true,
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/concurrently/node_modules/yargs": {
+      "version": "17.7.2",
+      "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+      "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+      "dev": true,
+      "dependencies": {
+        "cliui": "^8.0.1",
+        "escalade": "^3.1.1",
+        "get-caller-file": "^2.0.5",
+        "require-directory": "^2.1.1",
+        "string-width": "^4.2.3",
+        "y18n": "^5.0.5",
+        "yargs-parser": "^21.1.1"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/concurrently/node_modules/yargs-parser": {
+      "version": "21.1.1",
+      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+      "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+      "dev": true,
+      "engines": {
+        "node": ">=12"
       }
     },
     "node_modules/configstore": {
@@ -8407,6 +8606,12 @@
         "stream-shift": "^1.0.0"
       }
     },
+    "node_modules/eastasianwidth": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
+      "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
+      "dev": true
+    },
     "node_modules/ecc-jsbn": {
       "version": "0.1.2",
       "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
@@ -10012,6 +10217,34 @@
       "integrity": "sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg==",
       "dev": true
     },
+    "node_modules/foreground-child": {
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz",
+      "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==",
+      "dev": true,
+      "dependencies": {
+        "cross-spawn": "^7.0.0",
+        "signal-exit": "^4.0.1"
+      },
+      "engines": {
+        "node": ">=14"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
+    "node_modules/foreground-child/node_modules/signal-exit": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+      "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+      "dev": true,
+      "engines": {
+        "node": ">=14"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
     "node_modules/forever-agent": {
       "version": "0.6.1",
       "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
@@ -10248,6 +10481,26 @@
         "assert-plus": "^1.0.0"
       }
     },
+    "node_modules/glob": {
+      "version": "10.4.5",
+      "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
+      "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
+      "dev": true,
+      "dependencies": {
+        "foreground-child": "^3.1.0",
+        "jackspeak": "^3.1.2",
+        "minimatch": "^9.0.4",
+        "minipass": "^7.1.2",
+        "package-json-from-dist": "^1.0.0",
+        "path-scurry": "^1.11.1"
+      },
+      "bin": {
+        "glob": "dist/esm/bin.mjs"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
     "node_modules/glob-parent": {
       "version": "6.0.2",
       "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
@@ -10266,6 +10519,30 @@
       "integrity": "sha512-Iozmtbqv0noj0uDDqoL0zNq0VBEfK2YFoMAZoxJe4cwphvLR+JskfF30QhXHOR4m3KrE6NLRYw+U9MRXvifyig==",
       "dev": true
     },
+    "node_modules/glob/node_modules/brace-expansion": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+      "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+      "dev": true,
+      "dependencies": {
+        "balanced-match": "^1.0.0"
+      }
+    },
+    "node_modules/glob/node_modules/minimatch": {
+      "version": "9.0.5",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+      "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+      "dev": true,
+      "dependencies": {
+        "brace-expansion": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=16 || 14 >=14.17"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
     "node_modules/global": {
       "version": "4.4.0",
       "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz",
@@ -12027,14 +12304,29 @@
         "node": ">=8"
       }
     },
+    "node_modules/jackspeak": {
+      "version": "3.4.3",
+      "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
+      "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
+      "dev": true,
+      "dependencies": {
+        "@isaacs/cliui": "^8.0.2"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      },
+      "optionalDependencies": {
+        "@pkgjs/parseargs": "^0.11.0"
+      }
+    },
     "node_modules/jasmine": {
-      "version": "3.99.0",
-      "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-3.99.0.tgz",
-      "integrity": "sha512-YIThBuHzaIIcjxeuLmPD40SjxkEcc8i//sGMDKCgkRMVgIwRJf5qyExtlJpQeh7pkeoBSOe6lQEdg+/9uKg9mw==",
+      "version": "5.4.0",
+      "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-5.4.0.tgz",
+      "integrity": "sha512-E2u4ylX5tgGYvbynImU6EUBKKrSVB1L72FEPjGh4M55ov1VsxR26RA2JU91L9YSPFgcjo4mCLyKn/QXvEYGBkA==",
       "dev": true,
       "dependencies": {
-        "glob": "^7.1.6",
-        "jasmine-core": "~3.99.0"
+        "glob": "^10.2.2",
+        "jasmine-core": "~5.4.0"
       },
       "bin": {
         "jasmine": "bin/jasmine.js"
@@ -12044,34 +12336,6 @@
       "version": "5.4.0",
       "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.4.0.tgz",
       "integrity": "sha512-T4fio3W++llLd7LGSGsioriDHgWyhoL6YTu4k37uwJLF7DzOzspz7mNxRoM3cQdLWtL/ebazQpIf/yZGJx/gzg==",
-      "dev": true,
-      "peer": true
-    },
-    "node_modules/jasmine/node_modules/glob": {
-      "version": "7.2.3",
-      "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
-      "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
-      "deprecated": "Glob versions prior to v9 are no longer supported",
-      "dev": true,
-      "dependencies": {
-        "fs.realpath": "^1.0.0",
-        "inflight": "^1.0.4",
-        "inherits": "2",
-        "minimatch": "^3.1.1",
-        "once": "^1.3.0",
-        "path-is-absolute": "^1.0.0"
-      },
-      "engines": {
-        "node": "*"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/isaacs"
-      }
-    },
-    "node_modules/jasmine/node_modules/jasmine-core": {
-      "version": "3.99.1",
-      "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.99.1.tgz",
-      "integrity": "sha512-Hu1dmuoGcZ7AfyynN3LsfruwMbxMALMka+YtZeGoLuDEySVmVAPaonkNoBRIw/ectu8b9tVQCJNgp4a4knp+tg==",
       "dev": true
     },
     "node_modules/javascript-stringify": {
@@ -12314,35 +12578,35 @@
       }
     },
     "node_modules/karma-jasmine": {
-      "version": "4.0.2",
-      "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-4.0.2.tgz",
-      "integrity": "sha512-ggi84RMNQffSDmWSyyt4zxzh2CQGwsxvYYsprgyR1j8ikzIduEdOlcLvXjZGwXG/0j41KUXOWsUCBfbEHPWP9g==",
+      "version": "5.1.0",
+      "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-5.1.0.tgz",
+      "integrity": "sha512-i/zQLFrfEpRyQoJF9fsCdTMOF5c2dK7C7OmsuKg2D0YSsuZSfQDiLuaiktbuio6F2wiCsZSnSnieIQ0ant/uzQ==",
       "dev": true,
       "dependencies": {
-        "jasmine-core": "^3.6.0"
+        "jasmine-core": "^4.1.0"
       },
       "engines": {
-        "node": ">= 10"
+        "node": ">=12"
       },
       "peerDependencies": {
-        "karma": "*"
+        "karma": "^6.0.0"
       }
     },
     "node_modules/karma-jasmine-html-reporter": {
-      "version": "1.7.0",
-      "resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-1.7.0.tgz",
-      "integrity": "sha512-pzum1TL7j90DTE86eFt48/s12hqwQuiD+e5aXx2Dc9wDEn2LfGq6RoAxEZZjFiN0RDSCOnosEKRZWxbQ+iMpQQ==",
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-2.1.0.tgz",
+      "integrity": "sha512-sPQE1+nlsn6Hwb5t+HHwyy0A1FNCVKuL1192b+XNauMYWThz2kweiBVW1DqloRpVvZIJkIoHVB7XRpK78n1xbQ==",
       "dev": true,
       "peerDependencies": {
-        "jasmine-core": ">=3.8",
-        "karma": ">=0.9",
-        "karma-jasmine": ">=1.1"
+        "jasmine-core": "^4.0.0 || ^5.0.0",
+        "karma": "^6.0.0",
+        "karma-jasmine": "^5.0.0"
       }
     },
     "node_modules/karma-jasmine/node_modules/jasmine-core": {
-      "version": "3.99.1",
-      "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.99.1.tgz",
-      "integrity": "sha512-Hu1dmuoGcZ7AfyynN3LsfruwMbxMALMka+YtZeGoLuDEySVmVAPaonkNoBRIw/ectu8b9tVQCJNgp4a4knp+tg==",
+      "version": "4.6.1",
+      "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-4.6.1.tgz",
+      "integrity": "sha512-VYz/BjjmC3klLJlLwA4Kw8ytk0zDSmbbDLNs794VnWmkcCB7I9aAL/D48VNQtmITyPvea2C3jdUMfc3kAoy0PQ==",
       "dev": true
     },
     "node_modules/karma-rollup-preprocessor": {
@@ -13240,6 +13504,15 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/minipass": {
+      "version": "7.1.2",
+      "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
+      "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
+      "dev": true,
+      "engines": {
+        "node": ">=16 || 14 >=14.17"
+      }
+    },
     "node_modules/mississippi": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz",
@@ -14052,6 +14325,12 @@
         "node": ">=8"
       }
     },
+    "node_modules/package-json-from-dist": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
+      "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
+      "dev": true
+    },
     "node_modules/pako": {
       "version": "1.0.11",
       "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
@@ -14207,6 +14486,28 @@
       "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
       "dev": true
     },
+    "node_modules/path-scurry": {
+      "version": "1.11.1",
+      "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
+      "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
+      "dev": true,
+      "dependencies": {
+        "lru-cache": "^10.2.0",
+        "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
+      },
+      "engines": {
+        "node": ">=16 || 14 >=14.18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
+    "node_modules/path-scurry/node_modules/lru-cache": {
+      "version": "10.4.3",
+      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
+      "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
+      "dev": true
+    },
     "node_modules/path-to-regexp": {
       "version": "0.1.10",
       "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
@@ -14308,12 +14609,12 @@
       }
     },
     "node_modules/pixelmatch": {
-      "version": "5.3.0",
-      "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-5.3.0.tgz",
-      "integrity": "sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==",
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-6.0.0.tgz",
+      "integrity": "sha512-FYpL4XiIWakTnIqLqvt3uN4L9B3TsuHIvhLILzTiJZMJUsGvmKNeL4H3b6I99LRyerK9W4IuOXw+N28AtRgK2g==",
       "dev": true,
       "dependencies": {
-        "pngjs": "^6.0.0"
+        "pngjs": "^7.0.0"
       },
       "bin": {
         "pixelmatch": "bin/pixelmatch"
@@ -14384,12 +14685,12 @@
       }
     },
     "node_modules/pngjs": {
-      "version": "6.0.0",
-      "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz",
-      "integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==",
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz",
+      "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==",
       "dev": true,
       "engines": {
-        "node": ">=12.13.0"
+        "node": ">=14.19.0"
       }
     },
     "node_modules/portfinder": {
@@ -17045,17 +17346,20 @@
       }
     },
     "node_modules/rxjs": {
-      "version": "6.6.7",
-      "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
-      "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==",
+      "version": "7.8.1",
+      "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
+      "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
       "dev": true,
       "dependencies": {
-        "tslib": "^1.9.0"
-      },
-      "engines": {
-        "npm": ">=2.0.0"
+        "tslib": "^2.1.0"
       }
     },
+    "node_modules/rxjs/node_modules/tslib": {
+      "version": "2.8.1",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+      "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+      "dev": true
+    },
     "node_modules/safe-array-concat": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz",
@@ -17469,6 +17773,15 @@
         "node": ">=8"
       }
     },
+    "node_modules/shell-quote": {
+      "version": "1.8.1",
+      "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz",
+      "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==",
+      "dev": true,
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
     "node_modules/shiki": {
       "version": "1.23.0",
       "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.23.0.tgz",
@@ -17840,12 +18153,6 @@
         "url": "https://github.com/sponsors/wooorm"
       }
     },
-    "node_modules/spawn-command": {
-      "version": "0.0.2",
-      "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz",
-      "integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==",
-      "dev": true
-    },
     "node_modules/spdy": {
       "version": "4.0.2",
       "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz",
@@ -18132,6 +18439,21 @@
         "node": ">=8"
       }
     },
+    "node_modules/string-width-cjs": {
+      "name": "string-width",
+      "version": "4.2.3",
+      "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+      "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+      "dev": true,
+      "dependencies": {
+        "emoji-regex": "^8.0.0",
+        "is-fullwidth-code-point": "^3.0.0",
+        "strip-ansi": "^6.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
     "node_modules/string.prototype.trim": {
       "version": "1.2.9",
       "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz",
@@ -18231,6 +18553,19 @@
         "node": ">=8"
       }
     },
+    "node_modules/strip-ansi-cjs": {
+      "name": "strip-ansi",
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+      "dev": true,
+      "dependencies": {
+        "ansi-regex": "^5.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
     "node_modules/strip-bom-string": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz",
@@ -22449,6 +22784,24 @@
         "node": ">=6"
       }
     },
+    "node_modules/wrap-ansi-cjs": {
+      "name": "wrap-ansi",
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+      "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+      "dev": true,
+      "dependencies": {
+        "ansi-styles": "^4.0.0",
+        "string-width": "^4.1.0",
+        "strip-ansi": "^6.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+      }
+    },
     "node_modules/wrap-ansi/node_modules/ansi-regex": {
       "version": "4.1.1",
       "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
diff --git a/package.json b/package.json
index 97b952e..0786cfd 100644
--- a/package.json
+++ b/package.json
@@ -47,8 +47,8 @@
     "babel-loader": "^8.3.0",
     "chart.js": "^4.3.2",
     "chartjs-adapter-date-fns": "^3.0.0",
-    "chartjs-test-utils": "^0.3.0",
-    "concurrently": "^6.0.2",
+    "chartjs-test-utils": "^0.5.0",
+    "concurrently": "^9.1.0",
     "coveralls": "^3.1.0",
     "cross-env": "^7.0.3",
     "date-fns": "^2.21.1",
@@ -57,17 +57,17 @@
     "eslint-plugin-html": "^6.1.2",
     "eslint-plugin-markdown": "^2.0.1",
     "hammer-simulator": "^0.0.1",
-    "jasmine": "^3.7.0",
+    "jasmine": "^5.4.0",
     "karma": "^6.4.4",
     "karma-chrome-launcher": "^3.2.0",
     "karma-coverage": "^2.2.1",
     "karma-firefox-launcher": "^2.1.3",
-    "karma-jasmine": "^4.0.2",
-    "karma-jasmine-html-reporter": "^1.5.4",
+    "karma-jasmine": "^5.1.0",
+    "karma-jasmine-html-reporter": "^2.1.0",
     "karma-rollup-preprocessor": "^7.0.7",
     "karma-spec-reporter": "0.0.36",
     "ng-hammerjs": "^2.0.8",
-    "pixelmatch": "^5.2.1",
+    "pixelmatch": "^6.0.0",
     "rollup": "^4.12.1",
     "rollup-plugin-cleanup": "^3.2.1",
     "rollup-plugin-istanbul": "^5.0.0",
diff --git a/src/handlers.js b/src/handlers.js
index 2d65935..e21a9fc 100644
--- a/src/handlers.js
+++ b/src/handlers.js
@@ -96,31 +96,59 @@ export function mouseDown(chart, event) {
   addHandler(chart, window.document, 'keydown', keyDown);
 }
 
-export function computeDragRect(chart, mode, beginPointEvent, endPointEvent) {
+function applyAspectRatio(endPoint, beginPoint, aspectRatio) {
+  let width = endPoint.x - beginPoint.x;
+  let height = endPoint.y - beginPoint.y;
+  const ratio = Math.abs(width / height);
+
+  if (ratio > aspectRatio) {
+    width = Math.sign(width) * Math.abs(height * aspectRatio);
+  } else if (ratio < aspectRatio) {
+    height = Math.sign(height) * Math.abs(width / aspectRatio);
+  }
+
+  endPoint.x = beginPoint.x + width;
+  endPoint.y = beginPoint.y + height;
+}
+
+function applyMinMaxProps(rect, beginPoint, endPoint, {min, max, prop}) {
+  rect[min] = Math.max(0, Math.min(beginPoint[prop], endPoint[prop]));
+  rect[max] = Math.max(beginPoint[prop], endPoint[prop]);
+}
+
+function getReplativePoints(chart, points, maintainAspectRatio) {
+  const beginPoint = getPointPosition(points.dragStart, chart);
+  const endPoint = getPointPosition(points.dragEnd, chart);
+
+  if (maintainAspectRatio) {
+    const aspectRatio = chart.chartArea.width / chart.chartArea.height;
+    applyAspectRatio(endPoint, beginPoint, aspectRatio);
+  }
+
+  return {beginPoint, endPoint};
+}
+
+export function computeDragRect(chart, mode, points, maintainAspectRatio) {
   const xEnabled = directionEnabled(mode, 'x', chart);
   const yEnabled = directionEnabled(mode, 'y', chart);
-  let {top, left, right, bottom, width: chartWidth, height: chartHeight} = chart.chartArea;
+  const {top, left, right, bottom, width: chartWidth, height: chartHeight} = chart.chartArea;
+  const rect = {top, left, right, bottom};
 
-  const beginPoint = getPointPosition(beginPointEvent, chart);
-  const endPoint = getPointPosition(endPointEvent, chart);
+  const {beginPoint, endPoint} = getReplativePoints(chart, points, maintainAspectRatio && xEnabled && yEnabled);
 
   if (xEnabled) {
-    left = Math.max(0, Math.min(beginPoint.x, endPoint.x));
-    right = Math.min(chart.width, Math.max(beginPoint.x, endPoint.x));
+    applyMinMaxProps(rect, beginPoint, endPoint, {min: 'left', max: 'right', prop: 'x'});
   }
 
   if (yEnabled) {
-    top = Math.max(0, Math.min(beginPoint.y, endPoint.y));
-    bottom = Math.min(chart.height, Math.max(beginPoint.y, endPoint.y));
+    applyMinMaxProps(rect, beginPoint, endPoint, {min: 'top', max: 'bottom', prop: 'y'});
   }
-  const width = right - left;
-  const height = bottom - top;
+
+  const width = rect.right - rect.left;
+  const height = rect.bottom - rect.top;
 
   return {
-    left,
-    top,
-    right,
-    bottom,
+    ...rect,
     width,
     height,
     zoomX: xEnabled && width ? 1 + ((chartWidth - width) / chartWidth) : 1,
@@ -135,8 +163,8 @@ export function mouseUp(chart, event) {
   }
 
   removeHandler(chart, 'mousemove');
-  const {mode, onZoomComplete, drag: {threshold = 0}} = state.options.zoom;
-  const rect = computeDragRect(chart, mode, state.dragStart, event);
+  const {mode, onZoomComplete, drag: {threshold = 0, maintainAspectRatio}} = state.options.zoom;
+  const rect = computeDragRect(chart, mode, {dragStart: state.dragStart, dragEnd: event}, maintainAspectRatio);
   const distanceX = directionEnabled(mode, 'x', chart) ? rect.width : 0;
   const distanceY = directionEnabled(mode, 'y', chart) ? rect.height : 0;
   const distance = Math.sqrt(distanceX * distanceX + distanceY * distanceY);
diff --git a/src/plugin.js b/src/plugin.js
index e0ae2a5..012e322 100644
--- a/src/plugin.js
+++ b/src/plugin.js
@@ -13,7 +13,7 @@ function draw(chart, caller, options) {
   if (dragOptions.drawTime !== caller || !dragEnd) {
     return;
   }
-  const {left, top, width, height} = computeDragRect(chart, options.zoom.mode, dragStart, dragEnd);
+  const {left, top, width, height} = computeDragRect(chart, options.zoom.mode, {dragStart, dragEnd}, dragOptions.maintainAspectRatio);
   const ctx = chart.ctx;
 
   ctx.save();


Test patch:
diff --git a/test/specs/zoom.wheel.spec.js b/test/specs/zoom.wheel.spec.js
index d0be274..0abad4d 100644
--- a/test/specs/zoom.wheel.spec.js
+++ b/test/specs/zoom.wheel.spec.js
@@ -309,6 +309,53 @@ describe('zoom with wheel', function() {
     });
   });
 
+  it('should respect aspectRatio when mode = xy', function() {
+    const chart = window.acquireChart({
+      type: 'line',
+      data,
+      options: {
+        scales: {
+          x: {
+            type: 'linear'
+          },
+          y: {
+            type: 'linear'
+          }
+        },
+        plugins: {
+          legend: false,
+          title: false,
+          zoom: {
+            zoom: {
+              drag: {
+                enabled: true,
+                maintainAspectRatio: true,
+              },
+              mode: 'xy'
+            }
+          }
+        }
+      }
+    });
+
+    const scaleX = chart.scales.x;
+    const scaleY = chart.scales.y;
+
+    jasmine.triggerMouseEvent(chart, 'mousedown', {
+      x: scaleX.getPixelForValue(1.5),
+      y: scaleY.getPixelForValue(1.1)
+    });
+    jasmine.triggerMouseEvent(chart, 'mouseup', {
+      x: scaleX.getPixelForValue(2.8),
+      y: scaleY.getPixelForValue(1.7)
+    });
+
+    expect(scaleX.options.min).toBeCloseTo(1.5);
+    expect(scaleX.options.max).toBeCloseTo(2.1);
+    expect(scaleY.options.min).toBeCloseTo(1.1);
+    expect(scaleY.options.max).toBeCloseTo(1.7);
+  });
+
   describe('events', function() {
     it('should call onZoomStart', function() {
       const startSpy = jasmine.createSpy('started');