├── .nvmrc ├── tests ├── e2e │ ├── .gitignore │ ├── __setup__ │ │ ├── playwrightSetup.ts │ │ └── playwrightGlobalSetup.ts │ ├── __utils__ │ │ ├── types.ts │ │ ├── paths.ts │ │ ├── constants.ts │ │ ├── testHelpers.ts │ │ └── chartUtils.ts │ ├── __fixtures__ │ │ ├── index.ts │ │ ├── setup.ts │ │ └── interaction.ts │ └── interaction.spec.ts-snapshots │ │ ├── data-dragging-enabled-bar-chart-draggable-both-5625b--0-with-dragging-constrained-to-both-axes-1-Chrome.png │ │ ├── data-dragging-enabled-bar-chart-draggable-both-5625b--0-with-dragging-constrained-to-both-axes-1-Safari.png │ │ ├── data-dragging-enabled-bar-chart-draggable-both-5fd73--0-with-dragging-constrained-to-both-axes-1-Chrome.png │ │ ├── data-dragging-enabled-bar-chart-draggable-both-5fd73--0-with-dragging-constrained-to-both-axes-1-Safari.png │ │ ├── data-dragging-enabled-bar-chart-draggable-both-ae3a0--0-with-dragging-constrained-to-both-axes-1-Chrome.png │ │ ├── data-dragging-enabled-bar-chart-draggable-both-ae3a0--0-with-dragging-constrained-to-both-axes-1-Safari.png │ │ ├── data-dragging-enabled-bar-horizontal-chart-dra-a2b3c--0-with-dragging-constrained-to-both-axes-1-Chrome.png │ │ ├── data-dragging-enabled-bar-horizontal-chart-dra-a2b3c--0-with-dragging-constrained-to-both-axes-1-Safari.png │ │ ├── data-dragging-enabled-bar-horizontal-chart-dra-a2be1--0-with-dragging-constrained-to-both-axes-1-Chrome.png │ │ ├── data-dragging-enabled-bar-horizontal-chart-dra-a2be1--0-with-dragging-constrained-to-both-axes-1-Safari.png │ │ ├── data-dragging-enabled-bar-horizontal-chart-dra-e754c--0-with-dragging-constrained-to-both-axes-1-Chrome.png │ │ ├── data-dragging-enabled-bar-horizontal-chart-dra-e754c--0-with-dragging-constrained-to-both-axes-1-Safari.png │ │ ├── data-dragging-enabled-line-categorical-chart-d-09bac--0-with-dragging-constrained-to-both-axes-1-Chrome.png │ │ ├── data-dragging-enabled-line-categorical-chart-d-09bac--0-with-dragging-constrained-to-both-axes-1-Safari.png │ │ ├── data-dragging-enabled-line-categorical-chart-d-4fa57--0-with-dragging-constrained-to-both-axes-1-Chrome.png │ │ ├── data-dragging-enabled-line-categorical-chart-d-4fa57--0-with-dragging-constrained-to-both-axes-1-Safari.png │ │ ├── data-dragging-enabled-line-categorical-chart-d-6a5f7--0-with-dragging-constrained-to-both-axes-1-Chrome.png │ │ ├── data-dragging-enabled-line-categorical-chart-d-6a5f7--0-with-dragging-constrained-to-both-axes-1-Safari.png │ │ ├── data-dragging-enabled-line-linear-chart-dragga-4a7ff--0-with-dragging-constrained-to-both-axes-1-Chrome.png │ │ ├── data-dragging-enabled-line-linear-chart-dragga-4a7ff--0-with-dragging-constrained-to-both-axes-1-Safari.png │ │ ├── data-dragging-enabled-line-linear-chart-dragga-cc38a--0-with-dragging-constrained-to-both-axes-1-Chrome.png │ │ ├── data-dragging-enabled-line-linear-chart-dragga-cc38a--0-with-dragging-constrained-to-both-axes-1-Safari.png │ │ ├── data-dragging-enabled-line-linear-chart-dragga-f5abb--0-with-dragging-constrained-to-both-axes-1-Chrome.png │ │ ├── data-dragging-enabled-line-linear-chart-dragga-f5abb--0-with-dragging-constrained-to-both-axes-1-Safari.png │ │ ├── data-dragging-enabled-bar-chart-draggable-both-08ad8-150-with-dragging-constrained-to-both-axes-1-Chrome.png │ │ ├── data-dragging-enabled-bar-chart-draggable-both-08ad8-150-with-dragging-constrained-to-both-axes-1-Firefox.png │ │ ├── data-dragging-enabled-bar-chart-draggable-both-08ad8-150-with-dragging-constrained-to-both-axes-1-Safari.png │ │ ├── data-dragging-enabled-bar-chart-draggable-both-5625b--0-with-dragging-constrained-to-both-axes-1-Firefox.png │ │ ├── data-dragging-enabled-bar-chart-draggable-both-5fd73--0-with-dragging-constrained-to-both-axes-1-Firefox.png │ │ ├── data-dragging-enabled-bar-chart-draggable-both-990dd-150-with-dragging-constrained-to-both-axes-1-Chrome.png │ │ ├── data-dragging-enabled-bar-chart-draggable-both-990dd-150-with-dragging-constrained-to-both-axes-1-Firefox.png │ │ ├── data-dragging-enabled-bar-chart-draggable-both-990dd-150-with-dragging-constrained-to-both-axes-1-Safari.png │ │ ├── data-dragging-enabled-bar-chart-draggable-both-ae3a0--0-with-dragging-constrained-to-both-axes-1-Firefox.png │ │ ├── data-dragging-enabled-bar-chart-draggable-both-b256a-150-with-dragging-constrained-to-both-axes-1-Chrome.png │ │ ├── data-dragging-enabled-bar-chart-draggable-both-b256a-150-with-dragging-constrained-to-both-axes-1-Firefox.png │ │ ├── data-dragging-enabled-bar-chart-draggable-both-b256a-150-with-dragging-constrained-to-both-axes-1-Safari.png │ │ ├── data-dragging-enabled-bar-horizontal-chart-dra-256c7-150-with-dragging-constrained-to-both-axes-1-Chrome.png │ │ ├── data-dragging-enabled-bar-horizontal-chart-dra-256c7-150-with-dragging-constrained-to-both-axes-1-Firefox.png │ │ ├── data-dragging-enabled-bar-horizontal-chart-dra-256c7-150-with-dragging-constrained-to-both-axes-1-Safari.png │ │ ├── data-dragging-enabled-bar-horizontal-chart-dra-62fa9-150-with-dragging-constrained-to-both-axes-1-Chrome.png │ │ ├── data-dragging-enabled-bar-horizontal-chart-dra-62fa9-150-with-dragging-constrained-to-both-axes-1-Firefox.png │ │ ├── data-dragging-enabled-bar-horizontal-chart-dra-62fa9-150-with-dragging-constrained-to-both-axes-1-Safari.png │ │ ├── data-dragging-enabled-bar-horizontal-chart-dra-a2b3c--0-with-dragging-constrained-to-both-axes-1-Firefox.png │ │ ├── data-dragging-enabled-bar-horizontal-chart-dra-a2be1--0-with-dragging-constrained-to-both-axes-1-Firefox.png │ │ ├── data-dragging-enabled-bar-horizontal-chart-dra-c1c5c-150-with-dragging-constrained-to-both-axes-1-Chrome.png │ │ ├── data-dragging-enabled-bar-horizontal-chart-dra-c1c5c-150-with-dragging-constrained-to-both-axes-1-Firefox.png │ │ ├── data-dragging-enabled-bar-horizontal-chart-dra-c1c5c-150-with-dragging-constrained-to-both-axes-1-Safari.png │ │ ├── data-dragging-enabled-bar-horizontal-chart-dra-e754c--0-with-dragging-constrained-to-both-axes-1-Firefox.png │ │ ├── data-dragging-enabled-line-categorical-chart-d-09bac--0-with-dragging-constrained-to-both-axes-1-Firefox.png │ │ ├── data-dragging-enabled-line-categorical-chart-d-18fb1-150-with-dragging-constrained-to-both-axes-1-Chrome.png │ │ ├── data-dragging-enabled-line-categorical-chart-d-18fb1-150-with-dragging-constrained-to-both-axes-1-Firefox.png │ │ ├── data-dragging-enabled-line-categorical-chart-d-18fb1-150-with-dragging-constrained-to-both-axes-1-Safari.png │ │ ├── data-dragging-enabled-line-categorical-chart-d-4fa57--0-with-dragging-constrained-to-both-axes-1-Firefox.png │ │ ├── data-dragging-enabled-line-categorical-chart-d-6a5f7--0-with-dragging-constrained-to-both-axes-1-Firefox.png │ │ ├── data-dragging-enabled-line-categorical-chart-d-ab78d-150-with-dragging-constrained-to-both-axes-1-Chrome.png │ │ ├── data-dragging-enabled-line-categorical-chart-d-ab78d-150-with-dragging-constrained-to-both-axes-1-Firefox.png │ │ ├── data-dragging-enabled-line-categorical-chart-d-ab78d-150-with-dragging-constrained-to-both-axes-1-Safari.png │ │ ├── data-dragging-enabled-line-categorical-chart-d-b33d1-150-with-dragging-constrained-to-both-axes-1-Chrome.png │ │ ├── data-dragging-enabled-line-categorical-chart-d-b33d1-150-with-dragging-constrained-to-both-axes-1-Firefox.png │ │ ├── data-dragging-enabled-line-categorical-chart-d-b33d1-150-with-dragging-constrained-to-both-axes-1-Safari.png │ │ ├── data-dragging-enabled-line-linear-chart-dragga-4a7ff--0-with-dragging-constrained-to-both-axes-1-Firefox.png │ │ ├── data-dragging-enabled-line-linear-chart-dragga-72cb6-150-with-dragging-constrained-to-both-axes-1-Chrome.png │ │ ├── data-dragging-enabled-line-linear-chart-dragga-72cb6-150-with-dragging-constrained-to-both-axes-1-Firefox.png │ │ ├── data-dragging-enabled-line-linear-chart-dragga-72cb6-150-with-dragging-constrained-to-both-axes-1-Safari.png │ │ ├── data-dragging-enabled-line-linear-chart-dragga-87862-150-with-dragging-constrained-to-both-axes-1-Chrome.png │ │ ├── data-dragging-enabled-line-linear-chart-dragga-87862-150-with-dragging-constrained-to-both-axes-1-Firefox.png │ │ ├── data-dragging-enabled-line-linear-chart-dragga-87862-150-with-dragging-constrained-to-both-axes-1-Safari.png │ │ ├── data-dragging-enabled-line-linear-chart-dragga-b73b2-150-with-dragging-constrained-to-both-axes-1-Chrome.png │ │ ├── data-dragging-enabled-line-linear-chart-dragga-b73b2-150-with-dragging-constrained-to-both-axes-1-Firefox.png │ │ ├── data-dragging-enabled-line-linear-chart-dragga-b73b2-150-with-dragging-constrained-to-both-axes-1-Safari.png │ │ ├── data-dragging-enabled-line-linear-chart-dragga-cc38a--0-with-dragging-constrained-to-both-axes-1-Firefox.png │ │ ├── data-dragging-enabled-line-linear-chart-dragga-f5abb--0-with-dragging-constrained-to-both-axes-1-Firefox.png │ │ ├── data-dragging-enabled-bar-chart-draggable-both-5625b--0-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png │ │ ├── data-dragging-enabled-bar-chart-draggable-both-5625b--0-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png │ │ ├── data-dragging-enabled-bar-chart-draggable-both-5fd73--0-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png │ │ ├── data-dragging-enabled-bar-chart-draggable-both-5fd73--0-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png │ │ ├── data-dragging-enabled-bar-chart-draggable-both-ae3a0--0-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png │ │ ├── data-dragging-enabled-bar-chart-draggable-both-ae3a0--0-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png │ │ ├── data-dragging-enabled-bar-horizontal-chart-dra-a2b3c--0-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png │ │ ├── data-dragging-enabled-bar-horizontal-chart-dra-a2b3c--0-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png │ │ ├── data-dragging-enabled-bar-horizontal-chart-dra-a2be1--0-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png │ │ ├── data-dragging-enabled-bar-horizontal-chart-dra-a2be1--0-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png │ │ ├── data-dragging-enabled-bar-horizontal-chart-dra-e754c--0-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png │ │ ├── data-dragging-enabled-bar-horizontal-chart-dra-e754c--0-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png │ │ ├── data-dragging-enabled-line-categorical-chart-d-09bac--0-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png │ │ ├── data-dragging-enabled-line-categorical-chart-d-09bac--0-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png │ │ ├── data-dragging-enabled-line-categorical-chart-d-4fa57--0-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png │ │ ├── data-dragging-enabled-line-categorical-chart-d-4fa57--0-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png │ │ ├── data-dragging-enabled-line-categorical-chart-d-6a5f7--0-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png │ │ ├── data-dragging-enabled-line-categorical-chart-d-6a5f7--0-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png │ │ ├── data-dragging-enabled-line-linear-chart-dragga-4a7ff--0-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png │ │ ├── data-dragging-enabled-line-linear-chart-dragga-4a7ff--0-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png │ │ ├── data-dragging-enabled-line-linear-chart-dragga-cc38a--0-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png │ │ ├── data-dragging-enabled-line-linear-chart-dragga-cc38a--0-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png │ │ ├── data-dragging-enabled-line-linear-chart-dragga-f5abb--0-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png │ │ ├── data-dragging-enabled-line-linear-chart-dragga-f5abb--0-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png │ │ ├── data-dragging-enabled-bar-chart-draggable-both-08ad8-150-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png │ │ ├── data-dragging-enabled-bar-chart-draggable-both-08ad8-150-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png │ │ ├── data-dragging-enabled-bar-chart-draggable-both-08ad8-150-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png │ │ ├── data-dragging-enabled-bar-chart-draggable-both-5625b--0-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png │ │ ├── data-dragging-enabled-bar-chart-draggable-both-5fd73--0-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png │ │ ├── data-dragging-enabled-bar-chart-draggable-both-990dd-150-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png │ │ ├── data-dragging-enabled-bar-chart-draggable-both-990dd-150-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png │ │ ├── data-dragging-enabled-bar-chart-draggable-both-990dd-150-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png │ │ ├── data-dragging-enabled-bar-chart-draggable-both-ae3a0--0-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png │ │ ├── data-dragging-enabled-bar-chart-draggable-both-b256a-150-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png │ │ ├── data-dragging-enabled-bar-chart-draggable-both-b256a-150-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png │ │ ├── data-dragging-enabled-bar-chart-draggable-both-b256a-150-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png │ │ ├── data-dragging-enabled-bar-horizontal-chart-dra-256c7-150-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png │ │ ├── data-dragging-enabled-bar-horizontal-chart-dra-256c7-150-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png │ │ ├── data-dragging-enabled-bar-horizontal-chart-dra-256c7-150-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png │ │ ├── data-dragging-enabled-bar-horizontal-chart-dra-62fa9-150-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png │ │ ├── data-dragging-enabled-bar-horizontal-chart-dra-62fa9-150-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png │ │ ├── data-dragging-enabled-bar-horizontal-chart-dra-62fa9-150-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png │ │ ├── data-dragging-enabled-bar-horizontal-chart-dra-a2b3c--0-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png │ │ ├── data-dragging-enabled-bar-horizontal-chart-dra-a2be1--0-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png │ │ ├── data-dragging-enabled-bar-horizontal-chart-dra-c1c5c-150-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png │ │ ├── data-dragging-enabled-bar-horizontal-chart-dra-c1c5c-150-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png │ │ ├── data-dragging-enabled-bar-horizontal-chart-dra-c1c5c-150-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png │ │ ├── data-dragging-enabled-bar-horizontal-chart-dra-e754c--0-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png │ │ ├── data-dragging-enabled-line-categorical-chart-d-09bac--0-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png │ │ ├── data-dragging-enabled-line-categorical-chart-d-18fb1-150-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png │ │ ├── data-dragging-enabled-line-categorical-chart-d-18fb1-150-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png │ │ ├── data-dragging-enabled-line-categorical-chart-d-18fb1-150-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png │ │ ├── data-dragging-enabled-line-categorical-chart-d-4fa57--0-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png │ │ ├── data-dragging-enabled-line-categorical-chart-d-6a5f7--0-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png │ │ ├── data-dragging-enabled-line-categorical-chart-d-ab78d-150-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png │ │ ├── data-dragging-enabled-line-categorical-chart-d-ab78d-150-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png │ │ ├── data-dragging-enabled-line-categorical-chart-d-ab78d-150-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png │ │ ├── data-dragging-enabled-line-categorical-chart-d-b33d1-150-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png │ │ ├── data-dragging-enabled-line-categorical-chart-d-b33d1-150-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png │ │ ├── data-dragging-enabled-line-categorical-chart-d-b33d1-150-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png │ │ ├── data-dragging-enabled-line-linear-chart-dragga-4a7ff--0-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png │ │ ├── data-dragging-enabled-line-linear-chart-dragga-72cb6-150-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png │ │ ├── data-dragging-enabled-line-linear-chart-dragga-72cb6-150-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png │ │ ├── data-dragging-enabled-line-linear-chart-dragga-72cb6-150-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png │ │ ├── data-dragging-enabled-line-linear-chart-dragga-87862-150-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png │ │ ├── data-dragging-enabled-line-linear-chart-dragga-87862-150-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png │ │ ├── data-dragging-enabled-line-linear-chart-dragga-87862-150-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png │ │ ├── data-dragging-enabled-line-linear-chart-dragga-b73b2-150-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png │ │ ├── data-dragging-enabled-line-linear-chart-dragga-b73b2-150-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png │ │ ├── data-dragging-enabled-line-linear-chart-dragga-b73b2-150-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png │ │ ├── data-dragging-enabled-line-linear-chart-dragga-cc38a--0-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png │ │ └── data-dragging-enabled-line-linear-chart-dragga-f5abb--0-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png ├── __config__ │ ├── .gitignore │ ├── test.yml │ ├── commonJestConfig.ts │ └── default.yml ├── integration │ ├── types.d.ts │ ├── vue │ │ ├── vue.d.ts │ │ └── vue-chartjs.spec.ts │ ├── jest.integration.config.ts │ ├── __utils__ │ │ └── index.ts │ └── react │ │ └── react-chartjs-2.spec.tsx ├── unit │ ├── __mocks__ │ │ ├── d3-drag.ts │ │ └── d3-selection.ts │ ├── jest.unit.config.ts │ ├── __utils__ │ │ └── constants.ts │ ├── calc │ │ └── clipValue.spec.ts │ ├── __fixtures__ │ │ └── mockedEventUtils.ts │ ├── roundValue.spec.ts │ ├── applyMagnet.spec.ts │ ├── getElement.spec.ts │ └── util │ │ └── dragEndCallback.spec.ts ├── __setup__ │ ├── commonGlobalSetup.ts │ ├── jestGlobalSetup.ts │ ├── jestSetup.ts │ └── commonSetup.ts ├── __utils__ │ ├── testTypes.ts │ ├── structures │ │ ├── axisSpec.ts │ │ ├── Whitelist.ts │ │ ├── Point2D.ts │ │ ├── scenario.ts │ │ └── Offset2D.ts │ ├── types.ts │ ├── chartUtils.ts │ ├── magnet.ts │ └── cartesian.ts ├── errors │ └── IncompatibleTestConfiguration.ts ├── typings.d.ts ├── __data__ │ ├── scatter.ts │ └── gantt.ts └── __fixtures__ │ └── jest │ └── interaction.ts ├── src ├── typings.d.ts ├── util │ ├── typings.d.ts │ ├── calc │ │ ├── index.ts │ │ ├── clipValue.ts │ │ ├── radialLinear.ts │ │ └── cartesian.ts │ ├── roundValue.ts │ ├── index.ts │ ├── cloneDataPoint.ts │ ├── applyMagnet.ts │ ├── dragEndCallback.ts │ ├── updateData.ts │ ├── checkDraggingConfiguration.ts │ └── getElement.ts ├── types │ ├── ChartJSTypes.ts │ ├── index.ts │ ├── DraggingConfiguration.ts │ ├── DragDataState.ts │ ├── EventTypes.ts │ └── Configuration.ts ├── index.ts ├── plugin.ts └── typeAugmentations.ts ├── types.d.ts ├── .lintstagedrc.json ├── tsconfig.build.demos.json ├── tsconfig.build.json ├── scripts ├── setupEnv.ts ├── cleanCoverage.ts ├── openMergedCoverageReport.ts ├── utils │ └── paths.ts └── collectCoverage.ts ├── .gitignore ├── commitlint.config.ts ├── tsconfig.json ├── pages ├── src │ ├── pages │ │ ├── polar.page.ts │ │ ├── radar.page.ts │ │ ├── scatter.page.ts │ │ ├── gantt.page.ts │ │ ├── bubble.page.ts │ │ ├── bar.page.ts │ │ └── line.page.ts │ ├── types.ts │ ├── watch.ts │ └── utils.ts └── README.md ├── nyc.config.js ├── babel.config.js ├── cspell.json ├── .github ├── pull_request_template.md ├── ISSUE_TEMPLATE │ ├── enhancement_idea.md │ └── bug_report.md ├── workflows │ ├── cd.yml │ └── ci.yml └── actions │ └── setup │ └── action.yml ├── tsconfig.eslint.json ├── .release-it.json ├── .prettierrc.json ├── tsconfig.build.base.json ├── lefthook.yml ├── jest.config.ts ├── LICENSE ├── knip.ts ├── eslint.config.mjs ├── playwright.config.ts ├── rollup.config.js └── package.json /.nvmrc: -------------------------------------------------------------------------------- 1 | v22.12.0 2 | -------------------------------------------------------------------------------- /tests/e2e/.gitignore: -------------------------------------------------------------------------------- 1 | __results__ -------------------------------------------------------------------------------- /tests/__config__/.gitignore: -------------------------------------------------------------------------------- 1 | local-*.yml -------------------------------------------------------------------------------- /src/typings.d.ts: -------------------------------------------------------------------------------- 1 | import "./typeAugmentations"; 2 | -------------------------------------------------------------------------------- /src/util/typings.d.ts: -------------------------------------------------------------------------------- 1 | import "../typings.d.ts"; 2 | -------------------------------------------------------------------------------- /types.d.ts: -------------------------------------------------------------------------------- 1 | declare module "rollup-plugin-istanbul"; 2 | -------------------------------------------------------------------------------- /tests/integration/types.d.ts: -------------------------------------------------------------------------------- 1 | import "@testing-library/jest-dom"; 2 | -------------------------------------------------------------------------------- /tests/e2e/__setup__/playwrightSetup.ts: -------------------------------------------------------------------------------- 1 | import "../../__setup__/commonSetup"; 2 | -------------------------------------------------------------------------------- /.lintstagedrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "**/*.{js,jsx,ts,tsx,md,html,css}": ["prettier --write"] 3 | } 4 | -------------------------------------------------------------------------------- /tests/e2e/__utils__/types.ts: -------------------------------------------------------------------------------- 1 | export type TestSuiteIdentifier = "interaction" | "configurationChanges"; 2 | -------------------------------------------------------------------------------- /tsconfig.build.demos.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.build.base.json", 3 | "include": ["pages/src"] 4 | } 5 | -------------------------------------------------------------------------------- /tests/e2e/__fixtures__/index.ts: -------------------------------------------------------------------------------- 1 | export * from "../../__fixtures__/generic/interaction"; 2 | export * from "./setup"; 3 | -------------------------------------------------------------------------------- /src/util/calc/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./radialLinear"; 2 | export * from "./cartesian"; 3 | export * from "./clipValue"; 4 | -------------------------------------------------------------------------------- /tests/integration/vue/vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.vue" { 2 | import Vue from "vue"; 3 | export default typeof Vue; 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.build.base.json", 3 | "exclude": ["docs", "pages/dist-demos", "pages/dist-e2e"] 4 | } 5 | -------------------------------------------------------------------------------- /scripts/setupEnv.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | 3 | process.env["NODE_CONFIG_DIR"] = path.join( 4 | path.dirname(__filename), 5 | "..", 6 | "tests", 7 | "__config__", 8 | ); 9 | -------------------------------------------------------------------------------- /src/types/ChartJSTypes.ts: -------------------------------------------------------------------------------- 1 | import type { ChartType, DefaultDataPoint } from "chart.js"; 2 | 3 | export type ChartDataItemType = 4 | DefaultDataPoint[0]; 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage 2 | dist 3 | docs/assets 4 | pages/dist-demos/assets 5 | pages/dist-e2e 6 | node_modules 7 | .DS_Store 8 | .rollup.cache 9 | .nyc_output 10 | .vscode 11 | *.tgz 12 | -------------------------------------------------------------------------------- /commitlint.config.ts: -------------------------------------------------------------------------------- 1 | import type { UserConfig } from "@commitlint/types"; 2 | 3 | const config: UserConfig = { 4 | extends: ["@commitlint/config-conventional"], 5 | }; 6 | 7 | export default config; 8 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./ChartJSTypes"; 2 | export * from "./Configuration"; 3 | export * from "./EventTypes"; 4 | export * from "./DragDataState"; 5 | export * from "./DraggingConfiguration"; 6 | -------------------------------------------------------------------------------- /tests/unit/__mocks__/d3-drag.ts: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | drag: jest.fn(() => ({ 3 | container: jest.fn().mockReturnThis(), 4 | on: jest.fn().mockReturnThis(), 5 | apply: jest.fn().mockReturnThis(), 6 | })), 7 | }; 8 | -------------------------------------------------------------------------------- /tests/e2e/__utils__/paths.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | 3 | export const e2ePagesDistDirPath = path.join( 4 | path.dirname(__filename), 5 | "..", 6 | "..", 7 | "..", 8 | "pages", 9 | "dist-e2e", 10 | ); 11 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.build.json", 3 | "include": [ 4 | "tests", 5 | "*.config.ts", 6 | "knip.ts", 7 | "scripts", 8 | "types.d.ts", 9 | "pages/src", 10 | "src/typings.d.ts" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /src/util/roundValue.ts: -------------------------------------------------------------------------------- 1 | export function roundValue(value: number, pos: number | undefined) { 2 | if (pos === undefined || isNaN(pos) || pos < 0) return value; 3 | 4 | return Math.round(value * Math.pow(10, pos)) / Math.pow(10, pos); 5 | } 6 | -------------------------------------------------------------------------------- /tests/__setup__/commonGlobalSetup.ts: -------------------------------------------------------------------------------- 1 | import "../../scripts/setupEnv"; 2 | 3 | import { type TestsConfig, showConfig } from "../__utils__/testsConfig"; 4 | 5 | export default function globalSetup(which?: Array) { 6 | showConfig(which); 7 | } 8 | -------------------------------------------------------------------------------- /tests/__setup__/jestGlobalSetup.ts: -------------------------------------------------------------------------------- 1 | import commonGlobalSetup from "./commonGlobalSetup"; 2 | 3 | // run just once, not for each jest worker 4 | // eslint-disable-next-line require-await 5 | export default async function () { 6 | commonGlobalSetup(["unit", "integration"]); 7 | } 8 | -------------------------------------------------------------------------------- /tests/e2e/__setup__/playwrightGlobalSetup.ts: -------------------------------------------------------------------------------- 1 | import commonGlobalSetup from "../../__setup__/commonGlobalSetup"; 2 | 3 | // run just once, not for each playwright worker 4 | // eslint-disable-next-line require-await 5 | export default async function () { 6 | commonGlobalSetup(["e2e"]); 7 | } 8 | -------------------------------------------------------------------------------- /tests/__config__/test.yml: -------------------------------------------------------------------------------- 1 | # this file is only for CI not to show a warning that "NODE_ENV value of 'test' did not match any deployment config file names." 2 | # by default, we fall back to contents of default.yml and override these settings in local-test.yml that should be a copy of default.yml 3 | -------------------------------------------------------------------------------- /src/util/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./calc"; 2 | export * from "./applyMagnet"; 3 | export * from "./checkDraggingConfiguration"; 4 | export * from "./cloneDataPoint"; 5 | export * from "./dragEndCallback"; 6 | export * from "./getElement"; 7 | export * from "./roundValue"; 8 | export * from "./updateData"; 9 | -------------------------------------------------------------------------------- /tests/unit/jest.unit.config.ts: -------------------------------------------------------------------------------- 1 | import { Config } from "jest"; 2 | 3 | import commonJestConfig from "../__config__/commonJestConfig"; 4 | 5 | const config: Config = { 6 | ...commonJestConfig, 7 | displayName: "unit", 8 | testMatch: ["**/*.spec.[jt]s?(x)"], 9 | }; 10 | 11 | export default config; 12 | -------------------------------------------------------------------------------- /pages/src/pages/polar.page.ts: -------------------------------------------------------------------------------- 1 | import { TestPageBundleFactory } from "../types"; 2 | import { renderPage } from "../utils"; 3 | 4 | export default (({ isE2ETest }) => [ 5 | renderPage({ 6 | title: "Polar", 7 | fileName: "polar.html", 8 | isE2ETest: isE2ETest, 9 | }), 10 | ]) as TestPageBundleFactory; 11 | -------------------------------------------------------------------------------- /pages/src/pages/radar.page.ts: -------------------------------------------------------------------------------- 1 | import { TestPageBundleFactory } from "../types"; 2 | import { renderPage } from "../utils"; 3 | 4 | export default (({ isE2ETest }) => [ 5 | renderPage({ 6 | title: "Radar", 7 | fileName: "radar.html", 8 | isE2ETest: isE2ETest, 9 | }), 10 | ]) as TestPageBundleFactory; 11 | -------------------------------------------------------------------------------- /pages/src/pages/scatter.page.ts: -------------------------------------------------------------------------------- 1 | import { TestPageBundleFactory } from "../types"; 2 | import { renderPage } from "../utils"; 3 | 4 | export default (({ isE2ETest }) => [ 5 | renderPage({ 6 | title: "Scatter", 7 | fileName: "scatter.html", 8 | isE2ETest: isE2ETest, 9 | }), 10 | ]) as TestPageBundleFactory; 11 | -------------------------------------------------------------------------------- /nyc.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | all: true, 3 | exclude: [ 4 | ".rollup.cache", 5 | "tests", 6 | "scripts", 7 | "node_modules", 8 | "*.config.ts", 9 | "*.config.js", 10 | "*.config.mjs", 11 | ], 12 | include: ["src/**/*.ts"], 13 | reporter: ["lcov", "json"], 14 | excludeAfterRemap: false, 15 | }; 16 | -------------------------------------------------------------------------------- /pages/src/types.ts: -------------------------------------------------------------------------------- 1 | export type BundledPage = { 2 | html: string; 3 | outputFileName: string; 4 | }; 5 | 6 | export type TestPageBundleFactoryOptions = { 7 | isE2ETest: boolean; 8 | }; 9 | 10 | export type TestPageBundleFactory = ({ 11 | isE2ETest, 12 | }: TestPageBundleFactoryOptions) => BundledPage | BundledPage[]; 13 | -------------------------------------------------------------------------------- /tests/__utils__/testTypes.ts: -------------------------------------------------------------------------------- 1 | import Offset2D from "./structures/Offset2D"; 2 | 3 | export type DatasetPointSpec = { 4 | datasetIndex: number; 5 | index: number; 6 | additionalOffset?: Offset2D; 7 | }; 8 | 9 | export type Point2DObject = { x: number; y: number }; 10 | export type Point2DArray = [x: number, y: number]; 11 | -------------------------------------------------------------------------------- /tests/integration/jest.integration.config.ts: -------------------------------------------------------------------------------- 1 | import { Config } from "jest"; 2 | 3 | import commonJestConfig from "../__config__/commonJestConfig"; 4 | 5 | const config: Config = { 6 | ...commonJestConfig, 7 | displayName: "integration", 8 | testMatch: ["**/*.spec.[jt]s?(x)"], 9 | }; 10 | 11 | export default config; 12 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | "@babel/preset-env", 4 | "@babel/preset-typescript", 5 | "@babel/preset-react", 6 | ], 7 | env: { 8 | test: { 9 | plugins: ["@babel/plugin-transform-modules-commonjs"], 10 | presets: [["@babel/preset-env", { targets: { node: "current" } }]], 11 | }, 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /pages/src/pages/gantt.page.ts: -------------------------------------------------------------------------------- 1 | import { TestPageBundleFactory } from "../types"; 2 | import { renderPage } from "../utils"; 3 | 4 | export default (({ isE2ETest }) => [ 5 | renderPage({ 6 | title: "Gantt", 7 | fileName: "gantt.html", 8 | isE2ETest: isE2ETest, 9 | includeDateFns: true, 10 | }), 11 | ]) as TestPageBundleFactory; 12 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import ChartJSDragDataPlugin from "./plugin"; 2 | 3 | // ensure Chart.js type augmentations are bundled 4 | export type * from "./typeAugmentations"; 5 | 6 | // export all types 7 | export type * from "./types"; 8 | 9 | // export all utility functions 10 | export * from "./util"; 11 | 12 | export default ChartJSDragDataPlugin; 13 | -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-5625b--0-with-dragging-constrained-to-both-axes-1-Chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-5625b--0-with-dragging-constrained-to-both-axes-1-Chrome.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-5625b--0-with-dragging-constrained-to-both-axes-1-Safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-5625b--0-with-dragging-constrained-to-both-axes-1-Safari.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-5fd73--0-with-dragging-constrained-to-both-axes-1-Chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-5fd73--0-with-dragging-constrained-to-both-axes-1-Chrome.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-5fd73--0-with-dragging-constrained-to-both-axes-1-Safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-5fd73--0-with-dragging-constrained-to-both-axes-1-Safari.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-ae3a0--0-with-dragging-constrained-to-both-axes-1-Chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-ae3a0--0-with-dragging-constrained-to-both-axes-1-Chrome.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-ae3a0--0-with-dragging-constrained-to-both-axes-1-Safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-ae3a0--0-with-dragging-constrained-to-both-axes-1-Safari.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-a2b3c--0-with-dragging-constrained-to-both-axes-1-Chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-a2b3c--0-with-dragging-constrained-to-both-axes-1-Chrome.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-a2b3c--0-with-dragging-constrained-to-both-axes-1-Safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-a2b3c--0-with-dragging-constrained-to-both-axes-1-Safari.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-a2be1--0-with-dragging-constrained-to-both-axes-1-Chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-a2be1--0-with-dragging-constrained-to-both-axes-1-Chrome.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-a2be1--0-with-dragging-constrained-to-both-axes-1-Safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-a2be1--0-with-dragging-constrained-to-both-axes-1-Safari.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-e754c--0-with-dragging-constrained-to-both-axes-1-Chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-e754c--0-with-dragging-constrained-to-both-axes-1-Chrome.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-e754c--0-with-dragging-constrained-to-both-axes-1-Safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-e754c--0-with-dragging-constrained-to-both-axes-1-Safari.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-09bac--0-with-dragging-constrained-to-both-axes-1-Chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-09bac--0-with-dragging-constrained-to-both-axes-1-Chrome.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-09bac--0-with-dragging-constrained-to-both-axes-1-Safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-09bac--0-with-dragging-constrained-to-both-axes-1-Safari.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-4fa57--0-with-dragging-constrained-to-both-axes-1-Chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-4fa57--0-with-dragging-constrained-to-both-axes-1-Chrome.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-4fa57--0-with-dragging-constrained-to-both-axes-1-Safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-4fa57--0-with-dragging-constrained-to-both-axes-1-Safari.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-6a5f7--0-with-dragging-constrained-to-both-axes-1-Chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-6a5f7--0-with-dragging-constrained-to-both-axes-1-Chrome.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-6a5f7--0-with-dragging-constrained-to-both-axes-1-Safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-6a5f7--0-with-dragging-constrained-to-both-axes-1-Safari.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-4a7ff--0-with-dragging-constrained-to-both-axes-1-Chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-4a7ff--0-with-dragging-constrained-to-both-axes-1-Chrome.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-4a7ff--0-with-dragging-constrained-to-both-axes-1-Safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-4a7ff--0-with-dragging-constrained-to-both-axes-1-Safari.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-cc38a--0-with-dragging-constrained-to-both-axes-1-Chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-cc38a--0-with-dragging-constrained-to-both-axes-1-Chrome.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-cc38a--0-with-dragging-constrained-to-both-axes-1-Safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-cc38a--0-with-dragging-constrained-to-both-axes-1-Safari.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-f5abb--0-with-dragging-constrained-to-both-axes-1-Chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-f5abb--0-with-dragging-constrained-to-both-axes-1-Chrome.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-f5abb--0-with-dragging-constrained-to-both-axes-1-Safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-f5abb--0-with-dragging-constrained-to-both-axes-1-Safari.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-08ad8-150-with-dragging-constrained-to-both-axes-1-Chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-08ad8-150-with-dragging-constrained-to-both-axes-1-Chrome.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-08ad8-150-with-dragging-constrained-to-both-axes-1-Firefox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-08ad8-150-with-dragging-constrained-to-both-axes-1-Firefox.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-08ad8-150-with-dragging-constrained-to-both-axes-1-Safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-08ad8-150-with-dragging-constrained-to-both-axes-1-Safari.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-5625b--0-with-dragging-constrained-to-both-axes-1-Firefox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-5625b--0-with-dragging-constrained-to-both-axes-1-Firefox.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-5fd73--0-with-dragging-constrained-to-both-axes-1-Firefox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-5fd73--0-with-dragging-constrained-to-both-axes-1-Firefox.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-990dd-150-with-dragging-constrained-to-both-axes-1-Chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-990dd-150-with-dragging-constrained-to-both-axes-1-Chrome.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-990dd-150-with-dragging-constrained-to-both-axes-1-Firefox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-990dd-150-with-dragging-constrained-to-both-axes-1-Firefox.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-990dd-150-with-dragging-constrained-to-both-axes-1-Safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-990dd-150-with-dragging-constrained-to-both-axes-1-Safari.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-ae3a0--0-with-dragging-constrained-to-both-axes-1-Firefox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-ae3a0--0-with-dragging-constrained-to-both-axes-1-Firefox.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-b256a-150-with-dragging-constrained-to-both-axes-1-Chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-b256a-150-with-dragging-constrained-to-both-axes-1-Chrome.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-b256a-150-with-dragging-constrained-to-both-axes-1-Firefox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-b256a-150-with-dragging-constrained-to-both-axes-1-Firefox.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-b256a-150-with-dragging-constrained-to-both-axes-1-Safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-b256a-150-with-dragging-constrained-to-both-axes-1-Safari.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-256c7-150-with-dragging-constrained-to-both-axes-1-Chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-256c7-150-with-dragging-constrained-to-both-axes-1-Chrome.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-256c7-150-with-dragging-constrained-to-both-axes-1-Firefox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-256c7-150-with-dragging-constrained-to-both-axes-1-Firefox.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-256c7-150-with-dragging-constrained-to-both-axes-1-Safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-256c7-150-with-dragging-constrained-to-both-axes-1-Safari.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-62fa9-150-with-dragging-constrained-to-both-axes-1-Chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-62fa9-150-with-dragging-constrained-to-both-axes-1-Chrome.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-62fa9-150-with-dragging-constrained-to-both-axes-1-Firefox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-62fa9-150-with-dragging-constrained-to-both-axes-1-Firefox.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-62fa9-150-with-dragging-constrained-to-both-axes-1-Safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-62fa9-150-with-dragging-constrained-to-both-axes-1-Safari.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-a2b3c--0-with-dragging-constrained-to-both-axes-1-Firefox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-a2b3c--0-with-dragging-constrained-to-both-axes-1-Firefox.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-a2be1--0-with-dragging-constrained-to-both-axes-1-Firefox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-a2be1--0-with-dragging-constrained-to-both-axes-1-Firefox.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-c1c5c-150-with-dragging-constrained-to-both-axes-1-Chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-c1c5c-150-with-dragging-constrained-to-both-axes-1-Chrome.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-c1c5c-150-with-dragging-constrained-to-both-axes-1-Firefox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-c1c5c-150-with-dragging-constrained-to-both-axes-1-Firefox.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-c1c5c-150-with-dragging-constrained-to-both-axes-1-Safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-c1c5c-150-with-dragging-constrained-to-both-axes-1-Safari.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-e754c--0-with-dragging-constrained-to-both-axes-1-Firefox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-e754c--0-with-dragging-constrained-to-both-axes-1-Firefox.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-09bac--0-with-dragging-constrained-to-both-axes-1-Firefox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-09bac--0-with-dragging-constrained-to-both-axes-1-Firefox.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-18fb1-150-with-dragging-constrained-to-both-axes-1-Chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-18fb1-150-with-dragging-constrained-to-both-axes-1-Chrome.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-18fb1-150-with-dragging-constrained-to-both-axes-1-Firefox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-18fb1-150-with-dragging-constrained-to-both-axes-1-Firefox.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-18fb1-150-with-dragging-constrained-to-both-axes-1-Safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-18fb1-150-with-dragging-constrained-to-both-axes-1-Safari.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-4fa57--0-with-dragging-constrained-to-both-axes-1-Firefox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-4fa57--0-with-dragging-constrained-to-both-axes-1-Firefox.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-6a5f7--0-with-dragging-constrained-to-both-axes-1-Firefox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-6a5f7--0-with-dragging-constrained-to-both-axes-1-Firefox.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-ab78d-150-with-dragging-constrained-to-both-axes-1-Chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-ab78d-150-with-dragging-constrained-to-both-axes-1-Chrome.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-ab78d-150-with-dragging-constrained-to-both-axes-1-Firefox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-ab78d-150-with-dragging-constrained-to-both-axes-1-Firefox.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-ab78d-150-with-dragging-constrained-to-both-axes-1-Safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-ab78d-150-with-dragging-constrained-to-both-axes-1-Safari.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-b33d1-150-with-dragging-constrained-to-both-axes-1-Chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-b33d1-150-with-dragging-constrained-to-both-axes-1-Chrome.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-b33d1-150-with-dragging-constrained-to-both-axes-1-Firefox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-b33d1-150-with-dragging-constrained-to-both-axes-1-Firefox.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-b33d1-150-with-dragging-constrained-to-both-axes-1-Safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-b33d1-150-with-dragging-constrained-to-both-axes-1-Safari.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-4a7ff--0-with-dragging-constrained-to-both-axes-1-Firefox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-4a7ff--0-with-dragging-constrained-to-both-axes-1-Firefox.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-72cb6-150-with-dragging-constrained-to-both-axes-1-Chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-72cb6-150-with-dragging-constrained-to-both-axes-1-Chrome.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-72cb6-150-with-dragging-constrained-to-both-axes-1-Firefox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-72cb6-150-with-dragging-constrained-to-both-axes-1-Firefox.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-72cb6-150-with-dragging-constrained-to-both-axes-1-Safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-72cb6-150-with-dragging-constrained-to-both-axes-1-Safari.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-87862-150-with-dragging-constrained-to-both-axes-1-Chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-87862-150-with-dragging-constrained-to-both-axes-1-Chrome.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-87862-150-with-dragging-constrained-to-both-axes-1-Firefox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-87862-150-with-dragging-constrained-to-both-axes-1-Firefox.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-87862-150-with-dragging-constrained-to-both-axes-1-Safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-87862-150-with-dragging-constrained-to-both-axes-1-Safari.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-b73b2-150-with-dragging-constrained-to-both-axes-1-Chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-b73b2-150-with-dragging-constrained-to-both-axes-1-Chrome.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-b73b2-150-with-dragging-constrained-to-both-axes-1-Firefox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-b73b2-150-with-dragging-constrained-to-both-axes-1-Firefox.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-b73b2-150-with-dragging-constrained-to-both-axes-1-Safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-b73b2-150-with-dragging-constrained-to-both-axes-1-Safari.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-cc38a--0-with-dragging-constrained-to-both-axes-1-Firefox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-cc38a--0-with-dragging-constrained-to-both-axes-1-Firefox.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-f5abb--0-with-dragging-constrained-to-both-axes-1-Firefox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-f5abb--0-with-dragging-constrained-to-both-axes-1-Firefox.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-5625b--0-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-5625b--0-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-5625b--0-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-5625b--0-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-5fd73--0-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-5fd73--0-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-5fd73--0-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-5fd73--0-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-ae3a0--0-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-ae3a0--0-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-ae3a0--0-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-ae3a0--0-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-a2b3c--0-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-a2b3c--0-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-a2b3c--0-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-a2b3c--0-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-a2be1--0-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-a2be1--0-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-a2be1--0-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-a2be1--0-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-e754c--0-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-e754c--0-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-e754c--0-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-e754c--0-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-09bac--0-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-09bac--0-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-09bac--0-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-09bac--0-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-4fa57--0-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-4fa57--0-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-4fa57--0-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-4fa57--0-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-6a5f7--0-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-6a5f7--0-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-6a5f7--0-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-6a5f7--0-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-4a7ff--0-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-4a7ff--0-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-4a7ff--0-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-4a7ff--0-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-cc38a--0-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-cc38a--0-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-cc38a--0-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-cc38a--0-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-f5abb--0-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-f5abb--0-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-f5abb--0-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-f5abb--0-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png -------------------------------------------------------------------------------- /tests/integration/__utils__/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Integration, 3 | isTestsConfigWhitelistItemAllowed, 4 | } from "../../__utils__/testsConfig"; 5 | 6 | export function integrationAllowed(integration: Integration): boolean { 7 | return isTestsConfigWhitelistItemAllowed( 8 | "integration", 9 | "whitelistedIntegrations", 10 | integration, 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-08ad8-150-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-08ad8-150-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-08ad8-150-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-08ad8-150-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-08ad8-150-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-08ad8-150-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-5625b--0-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-5625b--0-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-5fd73--0-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-5fd73--0-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-990dd-150-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-990dd-150-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-990dd-150-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-990dd-150-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-990dd-150-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-990dd-150-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-ae3a0--0-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-ae3a0--0-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-b256a-150-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-b256a-150-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-b256a-150-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-b256a-150-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-b256a-150-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-chart-draggable-both-b256a-150-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-256c7-150-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-256c7-150-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-256c7-150-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-256c7-150-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-256c7-150-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-256c7-150-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-62fa9-150-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-62fa9-150-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-62fa9-150-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-62fa9-150-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-62fa9-150-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-62fa9-150-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-a2b3c--0-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-a2b3c--0-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-a2be1--0-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-a2be1--0-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-c1c5c-150-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-c1c5c-150-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-c1c5c-150-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-c1c5c-150-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-c1c5c-150-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-c1c5c-150-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-e754c--0-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-bar-horizontal-chart-dra-e754c--0-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-09bac--0-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-09bac--0-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-18fb1-150-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-18fb1-150-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-18fb1-150-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-18fb1-150-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-18fb1-150-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-18fb1-150-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-4fa57--0-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-4fa57--0-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-6a5f7--0-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-6a5f7--0-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-ab78d-150-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-ab78d-150-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-ab78d-150-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-ab78d-150-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-ab78d-150-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-ab78d-150-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-b33d1-150-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-b33d1-150-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-b33d1-150-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-b33d1-150-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-b33d1-150-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-categorical-chart-d-b33d1-150-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-4a7ff--0-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-4a7ff--0-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-72cb6-150-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-72cb6-150-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-72cb6-150-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-72cb6-150-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-72cb6-150-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-72cb6-150-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-87862-150-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-87862-150-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-87862-150-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-87862-150-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-87862-150-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-87862-150-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-b73b2-150-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-b73b2-150-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-b73b2-150-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-b73b2-150-with-dragging-constrained-to-both-axes-1-Mobile-Chrome.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-b73b2-150-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-b73b2-150-with-dragging-constrained-to-both-axes-1-Mobile-Safari.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-cc38a--0-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-cc38a--0-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png -------------------------------------------------------------------------------- /tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-f5abb--0-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artus9033/chartjs-plugin-dragdata/HEAD/tests/e2e/interaction.spec.ts-snapshots/data-dragging-enabled-line-linear-chart-dragga-f5abb--0-with-dragging-constrained-to-both-axes-1-Microsoft-Edge.png -------------------------------------------------------------------------------- /tests/unit/__mocks__/d3-selection.ts: -------------------------------------------------------------------------------- 1 | const callMock = jest 2 | .fn(function (...args: any[]) { 3 | var callback = args[0]; 4 | args[0] = this; 5 | (callback as any).apply(null, args); 6 | return this; 7 | }) 8 | .mockReturnThis(); 9 | 10 | module.exports = { 11 | select: jest.fn(() => ({ 12 | call: callMock, 13 | })), 14 | call: callMock, 15 | }; 16 | -------------------------------------------------------------------------------- /tests/__setup__/jestSetup.ts: -------------------------------------------------------------------------------- 1 | import "@testing-library/jest-dom/jest-globals"; 2 | import "./commonSetup"; 3 | 4 | import Chart from "chart.js/auto"; 5 | import * as matchers from "jest-extended"; 6 | import ResizeObserver from "resize-observer-polyfill"; 7 | 8 | expect.extend(matchers); 9 | 10 | global.Chart = Chart; 11 | 12 | global.ResizeObserver = ResizeObserver; 13 | -------------------------------------------------------------------------------- /cspell.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json", 3 | "version": "0.2", 4 | "language": "en-GB", 5 | "words": [ 6 | "chartjs", 7 | "datalabels", 8 | "gantt", 9 | "dragdata", 10 | "vars", 11 | "signale", 12 | "knip", 13 | "trivago" 14 | ], 15 | "ignorePaths": ["eslint.config.mjs"] 16 | } 17 | -------------------------------------------------------------------------------- /src/util/calc/clipValue.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Clips a value between a minimum and maximum value. 3 | * @param value the value to be clipped 4 | * @param min the minimum value 5 | * @param max the maximum value 6 | * @returns value in range [min, max] 7 | */ 8 | export function clipValue(value: number, min: number, max: number): number { 9 | return Math.min(Math.max(value, min), max); 10 | } 11 | -------------------------------------------------------------------------------- /tests/unit/__utils__/constants.ts: -------------------------------------------------------------------------------- 1 | import { ChartType } from "chart.js"; 2 | 3 | import { ArrayItemType } from "../../__utils__/types"; 4 | 5 | export const UNIT_TEST_CHART_TYPES = [ 6 | "line", 7 | "bar", 8 | "scatter", 9 | "bubble", 10 | "polarArea", 11 | "radar", 12 | ] satisfies Array; 13 | 14 | export type TestChartTypes = ArrayItemType; 15 | -------------------------------------------------------------------------------- /src/types/DraggingConfiguration.ts: -------------------------------------------------------------------------------- 1 | export type DraggingConfiguration = Record< 2 | | "chartDraggingDisabled" 3 | | "datasetDraggingDisabled" 4 | | "xAxisDraggingDisabled" 5 | | "yAxisDraggingDisabled" 6 | | "dataPointDraggingDisabled", 7 | boolean 8 | >; 9 | 10 | export type AxisDraggingConfiguration = Pick< 11 | DraggingConfiguration, 12 | "xAxisDraggingDisabled" | "yAxisDraggingDisabled" 13 | >; 14 | -------------------------------------------------------------------------------- /tests/errors/IncompatibleTestConfiguration.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Error that represents an invalid state when the current chart configuration is incompatible with the current test scenario. 3 | */ 4 | export class IncompatibleTestConfiguration extends Error { 5 | constructor(message: string) { 6 | super(message); 7 | this.name = "IncompatibleTestConfiguration"; 8 | } 9 | } 10 | 11 | export default IncompatibleTestConfiguration; 12 | -------------------------------------------------------------------------------- /tests/__utils__/structures/axisSpec.ts: -------------------------------------------------------------------------------- 1 | export type AxisSpec = "x" | "y" | "both"; 2 | 3 | export const ALL_AXES_SPECS: AxisSpec[] = ["both", "x", "y"]; 4 | 5 | export function getAxisDescription(axis: AxisSpec) { 6 | switch (axis) { 7 | default: 8 | return "???"; 9 | 10 | case "x": 11 | return "x-axis"; 12 | 13 | case "y": 14 | return "y-axis"; 15 | 16 | case "both": 17 | return "both axes"; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /pages/src/pages/bubble.page.ts: -------------------------------------------------------------------------------- 1 | import { TestPageBundleFactory } from "../types"; 2 | import { renderPage } from "../utils"; 3 | 4 | export default (({ isE2ETest }) => [ 5 | renderPage({ 6 | title: "Bubble", 7 | fileName: "bubble.html", 8 | isE2ETest: isE2ETest, 9 | }), 10 | renderPage({ 11 | title: "Bubble (x-only)", 12 | fileName: "bubble-x-only.html", 13 | isE2ETest: isE2ETest, 14 | }), 15 | ]) as TestPageBundleFactory; 16 | -------------------------------------------------------------------------------- /scripts/cleanCoverage.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import { Signale } from "signale"; 3 | 4 | import { coverageReportsDirPath } from "./utils/paths"; 5 | 6 | const signale = new Signale({ 7 | scope: "cleanCoverage", 8 | }); 9 | 10 | if (fs.existsSync(coverageReportsDirPath)) { 11 | signale.log(`Removing coverage reports directory: ${coverageReportsDirPath}`); 12 | 13 | fs.rmSync(coverageReportsDirPath, { recursive: true }); 14 | } 15 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Describe your changes 2 | 3 | ## Linked issues (if any) 4 | 5 | 6 | 7 | - ... 8 | - ... 9 | 10 | 11 | 12 | 13 | ## Checklist before requesting a review 14 | 15 | - [ ] E2E tests' snapshots (screenshots) are up-to-date 16 | - [ ] documentation is up-to-date 17 | -------------------------------------------------------------------------------- /src/util/cloneDataPoint.ts: -------------------------------------------------------------------------------- 1 | import type { ChartType } from "chart.js"; 2 | 3 | import { ChartDataItemType } from "../types"; 4 | 5 | export function cloneDataPoint< 6 | TType extends ChartType, 7 | T = ChartDataItemType, 8 | >(source: T): T { 9 | if (Array.isArray(source)) return [...source] as T; 10 | else if (typeof source === "object") return { ...source }; 11 | 12 | // below: typeof source === "number" 13 | return source; 14 | } 15 | -------------------------------------------------------------------------------- /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": [ 4 | "src/**/*.ts", 5 | "src/**/*.tsx", 6 | "src/**/*.js", 7 | "tests/**/*.ts", 8 | "tests/**/*.tsx", 9 | "tests/**/*.js", 10 | "scripts/**/*.ts", 11 | "scripts/**/*.tsx", 12 | "scripts/**/*.js", 13 | "pages/**/*.ts", 14 | "pages/**/*.tsx", 15 | "pages/**/*.js", 16 | "types.d.ts", 17 | "*.config.ts", 18 | "*.config.mjs", 19 | "*.config.js", 20 | "knip.ts" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /scripts/openMergedCoverageReport.ts: -------------------------------------------------------------------------------- 1 | import open from "open"; 2 | import path from "path"; 3 | import { Signale } from "signale"; 4 | 5 | import { mergedCoverageDirPath } from "./utils/paths"; 6 | 7 | const signale = new Signale({ 8 | scope: "openMergedCoverageReport", 9 | }); 10 | 11 | const htmlPath = path.join( 12 | mergedCoverageDirPath, 13 | "report", 14 | "html", 15 | "index.html", 16 | ); 17 | 18 | signale.info(`Opening coverage report: ${htmlPath}`); 19 | 20 | void open(htmlPath); 21 | -------------------------------------------------------------------------------- /tests/__utils__/types.ts: -------------------------------------------------------------------------------- 1 | export type DeepPartial = T extends object 2 | ? { 3 | [P in keyof T]?: DeepPartial; 4 | } 5 | : T; 6 | 7 | export type ArrayItemType = T extends (infer U)[] ? U : T; 8 | 9 | export type DeepFinalPropertiesOf = { 10 | [Key in keyof Obj]: `${Exclude}${DeepFinalPropertiesOf extends never ? "" : `.${DeepFinalPropertiesOf}`}`; 11 | }[keyof Obj]; 12 | -------------------------------------------------------------------------------- /.release-it.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/release-it/schema/release-it.json", 3 | "github": { 4 | "release": true 5 | }, 6 | "plugins": { 7 | "@release-it/conventional-changelog": { 8 | "preset": "conventionalcommits", 9 | "infile": "CHANGELOG.md", 10 | "header": "# chartjs-plugin-dragdata.js changelog" 11 | } 12 | }, 13 | "git": { 14 | "requireCleanWorkingDir": true, 15 | "release": true, 16 | "releaseName": "v${version}", 17 | "commitMessage": "chore: release v${version}" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "semi": true, 4 | "singleQuote": false, 5 | "trailingComma": "all", 6 | "endOfLine": "auto", 7 | "plugins": ["@trivago/prettier-plugin-sort-imports"], 8 | "importOrder": [ 9 | "^node:.*", 10 | "^react$", 11 | "^react", 12 | "", 13 | "^@.*", 14 | "^[a-zA-Z].*", 15 | "^\\.{1,2}.*" 16 | ], 17 | "importOrderSeparation": true, 18 | "importOrderSortSpecifiers": true, 19 | "importOrderParserPlugins": ["typescript", "jsx", "decorators-legacy"], 20 | "importOrderSideEffects": false 21 | } 22 | -------------------------------------------------------------------------------- /src/types/DragDataState.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | AnimationSpec, 3 | CartesianScaleOptions, 4 | ChartType, 5 | InteractionItem, 6 | } from "chart.js"; 7 | 8 | export type DragDataState = { 9 | element: InteractionItem | null; 10 | yAxisID: string; 11 | xAxisID: string; 12 | rAxisID: string; 13 | type: ChartType | undefined; 14 | stacked: CartesianScaleOptions["stacked"]; 15 | floatingBar: boolean; 16 | initValue: number; 17 | curDatasetIndex: number | undefined; 18 | curIndex: number | undefined; 19 | eventSettings: AnimationSpec | false | undefined; 20 | isDragging: boolean; 21 | }; 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/enhancement_idea.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Enhancement idea 3 | about: Create a concept of enhancement we could add to the project 4 | title: "" 5 | labels: "" 6 | assignees: "" 7 | --- 8 | 9 | **Describe the idea** 10 | Provide a clear description what you think should be added or modified in the project. 11 | 12 | **Proof-of-Concept (PoC)** 13 | Please post a short extract of code for the interface / implementation to support your idea, if possible and applicable. 14 | 15 | **Linked PRs or issues** 16 | If applicable, reference here PRs or issues that may have something to do with this idea. 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "" 5 | labels: "" 6 | assignees: "" 7 | --- 8 | 9 | **Describe the bug** 10 | A clear and concise description of what the bug is. 11 | 12 | **To Reproduce** 13 | Please use any of the available templates on either 14 | [codepen](https://codepen.io/chrispahm/pen/RwGBrEr), 15 | [jsFiddle](https://jsfiddle.net/etnLwxfa/1/), 16 | or [plunker](https://plnkr.co/edit/aJFm7Gb5uRqoekmd?open=lib%2Fscript.js) to give a reproducible example showcasing the issue. 17 | 18 | **Expected behavior** 19 | A clear and concise description of what you expected to happen. 20 | -------------------------------------------------------------------------------- /src/types/EventTypes.ts: -------------------------------------------------------------------------------- 1 | import type { ChartType } from "chart.js"; 2 | 3 | import type { ChartDataItemType } from "./ChartJSTypes"; 4 | 5 | export type DragDataEvent = MouseEvent | TouchEvent; 6 | 7 | // note: docstring located in Configuration.ts to be visible to the end users 8 | export type DragEventCallback = ( 9 | /** the interaction event */ 10 | event: DragDataEvent, 11 | /** the index of the dataset containing the dragged point */ 12 | datasetIndex: number, 13 | /** the index of the dragged point in its parent dataset */ 14 | index: number, 15 | /** the current value of the data point */ 16 | value: ChartDataItemType, 17 | ) => boolean | void; 18 | -------------------------------------------------------------------------------- /tsconfig.build.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "noFallthroughCasesInSwitch": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "noEmit": true, 16 | "jsx": "react", 17 | "experimentalDecorators": true, 18 | "emitDecoratorMetadata": true, 19 | "declaration": true, 20 | "outDir": "dist" 21 | }, 22 | "include": ["src", "src/typings.d.ts"], 23 | "exclude": ["node_modules", "dist", "scripts"] 24 | } 25 | -------------------------------------------------------------------------------- /lefthook.yml: -------------------------------------------------------------------------------- 1 | # Reference: https://github.com/evilmartians/lefthook/blob/master/docs/configuration.md 2 | 3 | pre-push: 4 | parallel: true 5 | commands: 6 | packages-audit: 7 | tags: security 8 | run: npm audit 9 | 10 | test-unit: 11 | tags: test 12 | run: npm run test:unit 13 | 14 | test-integration: 15 | tags: test 16 | run: npm run test:integration 17 | 18 | pre-commit: 19 | parallel: true 20 | commands: 21 | eslint-staged: 22 | glob: "*.{js,ts,jsx,tsx}" 23 | run: npx eslint {staged_files} 24 | 25 | knip: 26 | run: npm run knip 27 | 28 | commit-msg: 29 | parallel: true 30 | commands: 31 | commitlint: 32 | run: npx commitlint --edit 33 | -------------------------------------------------------------------------------- /tests/e2e/__utils__/constants.ts: -------------------------------------------------------------------------------- 1 | /** threshold for desktop projects above which percentage difference of pixels from baseline in the screenshot causes a failure; normalized range (0-1) */ 2 | export const SCREENSHOT_TESTING_MAX_PIXEL_DIFF_PERCENT_DESKTOP = 0.041; 3 | 4 | /** threshold for mobile projects above which percentage difference of pixels from baseline in the screenshot causes a failure; normalized range (0-1) */ 5 | export const SCREENSHOT_TESTING_MAX_PIXEL_DIFF_PERCENT_MOBILE = 0.051; 6 | 7 | /** since the bar chart does not support hit radius extension, we need to be precise 8 | * and as testing is not perfectly precise, we want a safety margin: we start 9 | * dragging from a bit lower than the edge to make sure we hit the bar */ 10 | export const BAR_SAFETY_HIT_MARGIN = 2; 11 | -------------------------------------------------------------------------------- /tests/integration/vue/vue-chartjs.spec.ts: -------------------------------------------------------------------------------- 1 | import "chart.js/auto"; 2 | 3 | import { cleanup, render } from "@testing-library/vue"; 4 | 5 | import { ChartOptions } from "chart.js/auto"; 6 | import { Line } from "vue-chartjs"; 7 | 8 | import { 9 | JestTestChartOptions, 10 | genericChartScenarioBase, 11 | } from "../../__data__/data"; 12 | import { integrationAllowed } from "../__utils__"; 13 | 14 | afterEach(() => { 15 | cleanup(); 16 | }); 17 | 18 | (integrationAllowed("vue") ? describe : describe.skip)("vue.js", () => { 19 | it("renders chart canvas in the document", () => { 20 | const wrapper = render(Line, { 21 | props: { 22 | data: genericChartScenarioBase.configuration.data, 23 | options: JestTestChartOptions as ChartOptions<"line">, 24 | }, 25 | }); 26 | 27 | const canvas = wrapper.getByRole("img"); 28 | 29 | expect(canvas).toBeTruthy(); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /tests/__config__/commonJestConfig.ts: -------------------------------------------------------------------------------- 1 | import { Config } from "jest"; 2 | 3 | export const testPathIgnorePatterns: string[] = [ 4 | "__utils__", 5 | "__fixtures__", 6 | "__mocks__", 7 | "__config__", 8 | "__data__", 9 | "__setup__", 10 | "__data__", 11 | "scripts", 12 | "node_modules", 13 | "dist", 14 | ]; 15 | 16 | const config: Config = { 17 | testPathIgnorePatterns: testPathIgnorePatterns, 18 | setupFilesAfterEnv: ["../__setup__/jestSetup.ts"], 19 | globalSetup: "../__setup__/jestGlobalSetup.ts", 20 | transform: { 21 | "^.+\\.[t|j]sx?$": [ 22 | "babel-jest", 23 | { configFile: "/../../babel.config.js" }, 24 | ], 25 | "^.+\\.vue$": "@vue/vue3-jest", 26 | }, 27 | transformIgnorePatterns: ["node_modules/(?!d3-*)"], 28 | testEnvironment: "jsdom", 29 | testEnvironmentOptions: { 30 | customExportConditions: ["node", "node-addons"], 31 | }, 32 | }; 33 | 34 | export default config; 35 | -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "jest"; 2 | 3 | import { testPathIgnorePatterns } from "./tests/__config__/commonJestConfig"; 4 | 5 | /** 6 | * Since jest projects don't inherit all properties from root config, we instead use tests/__config__/commonJestConfig.ts 7 | * thus, here we want just to define projects' configs; see https://github.com/jestjs/jest/issues/10991. 8 | * Also, as some options are not allowed in project configs, but only in the global config, they are specified here; see https://github.com/jestjs/jest/issues/13576. 9 | */ 10 | 11 | const config: Config = { 12 | projects: [ 13 | "/tests/unit/jest.unit.config.ts", 14 | "/tests/integration/jest.integration.config.ts", 15 | ], 16 | collectCoverageFrom: ["src/**/*.{js,ts,jsx,tsx}"], 17 | coverageReporters: ["lcov", "json"], 18 | coveragePathIgnorePatterns: testPathIgnorePatterns, 19 | verbose: true, 20 | }; 21 | 22 | export default config; 23 | -------------------------------------------------------------------------------- /scripts/utils/paths.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | 3 | const coverageReportsDirPath = path.resolve( 4 | path.dirname(__filename), 5 | "..", 6 | "..", 7 | "coverage", 8 | ); 9 | 10 | const unitTestsCoverageDirPath = path.join(coverageReportsDirPath, "unit"); 11 | 12 | const integrationTestsCoverageDirPath = path.join( 13 | coverageReportsDirPath, 14 | "integration", 15 | ); 16 | 17 | const e2eTestsCoverageDirPath = path.join(coverageReportsDirPath, "e2e"); 18 | 19 | const mergedCoverageDirPath = path.join(coverageReportsDirPath, "merged"); 20 | 21 | const mergedCoverageSourceReportsDirPath = path.join( 22 | mergedCoverageDirPath, 23 | "src", 24 | ), 25 | reportsSources = [ 26 | unitTestsCoverageDirPath, 27 | integrationTestsCoverageDirPath, 28 | e2eTestsCoverageDirPath, 29 | ]; 30 | 31 | export { 32 | coverageReportsDirPath, 33 | mergedCoverageDirPath, 34 | mergedCoverageSourceReportsDirPath, 35 | reportsSources, 36 | }; 37 | -------------------------------------------------------------------------------- /tests/unit/calc/clipValue.spec.ts: -------------------------------------------------------------------------------- 1 | import { clipValue } from "../../../dist/test/chartjs-plugin-dragdata-test"; 2 | import { isTestsConfigWhitelistItemAllowed } from "../../__utils__/testsConfig"; 3 | 4 | (isTestsConfigWhitelistItemAllowed( 5 | "unit", 6 | "whitelistedTestCategories", 7 | "clipValue", 8 | ) 9 | ? describe 10 | : describe.skip)("clipValue", () => { 11 | it("should return the value if it is within [min, max] range", () => { 12 | expect(clipValue(5.24, 1, 10)).toBe(5.24); 13 | expect(clipValue(1, 1, 10)).toBe(1); 14 | expect(clipValue(10, 1, 10)).toBe(10); 15 | }); 16 | 17 | it("should return the minimum value if value < min", () => { 18 | expect(clipValue(0.9999, 1, 10)).toBe(1); 19 | expect(clipValue(-5.2, 1, 10)).toBe(1); 20 | }); 21 | 22 | it("should return the maximum value if value > max", () => { 23 | expect(clipValue(10.0001, 1, 10)).toBe(10); 24 | expect(clipValue(17.74, 1, 10)).toBe(10); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /pages/src/pages/bar.page.ts: -------------------------------------------------------------------------------- 1 | import { TestPageBundleFactory } from "../types"; 2 | import { renderPage } from "../utils"; 3 | 4 | export default (({ isE2ETest }) => [ 5 | renderPage({ 6 | title: "Bar", 7 | fileName: "bar.html", 8 | isE2ETest: isE2ETest, 9 | }), 10 | renderPage({ 11 | title: "Horizontal Bar", 12 | fileName: "bar-horizontal.html", 13 | isE2ETest: isE2ETest, 14 | }), 15 | renderPage({ 16 | title: "Floating Bar", 17 | fileName: "bar-floating.html", 18 | isE2ETest: isE2ETest, 19 | }), 20 | renderPage({ 21 | title: "Horizontal Floating Bar", 22 | fileName: "bar-floating-horizontal.html", 23 | isE2ETest: isE2ETest, 24 | }), 25 | renderPage({ 26 | title: "Stacked Bar", 27 | fileName: "bar-stacked.html", 28 | isE2ETest: isE2ETest, 29 | }), 30 | renderPage({ 31 | title: "Horizontal Stacked Bar", 32 | fileName: "bar-stacked-horizontal.html", 33 | isE2ETest: isE2ETest, 34 | }), 35 | ]) as TestPageBundleFactory; 36 | -------------------------------------------------------------------------------- /tests/__utils__/structures/Whitelist.ts: -------------------------------------------------------------------------------- 1 | class Whitelist { 2 | private arr: Array | null; 3 | 4 | constructor(whitelist: T[] | null | undefined) { 5 | this.arr = whitelist?.length ? new Array(...whitelist) : null; 6 | } 7 | 8 | push(...args: T[]) { 9 | if (!this.arr) { 10 | this.arr = new Array(); 11 | } 12 | 13 | this.arr.push(...args); 14 | } 15 | 16 | /** Returns `true` if the whitelist is non-empty and contains 17 | * the `searchElement` or `false` in every other case 18 | * @param searchElement The element to check if is allowed 19 | * @returns `true` if the `searchElement` is found in the whitelist; `false` otherwise 20 | * */ 21 | isAllowed(searchElement: T): boolean { 22 | if (this.arr === null) { 23 | return true; 24 | } else { 25 | return this.arr.includes(searchElement); 26 | } 27 | } 28 | 29 | toString(): string { 30 | return `Whitelist { ${this.arr === null ? "empty - all allowed" : this.arr.join(", ")} }`; 31 | } 32 | } 33 | 34 | export default Whitelist; 35 | -------------------------------------------------------------------------------- /pages/src/pages/line.page.ts: -------------------------------------------------------------------------------- 1 | import { maxValueCustomMode } from "../../../tests/unit/__utils__/utils"; 2 | import { TestPageBundleFactory } from "../types"; 3 | import { renderPage } from "../utils"; 4 | 5 | export default (({ isE2ETest }) => [ 6 | renderPage({ 7 | title: "Line (linear)", 8 | fileName: "line-linear.html", 9 | isE2ETest: isE2ETest, 10 | }), 11 | renderPage({ 12 | title: "Line (linear, custom interaction mode)", 13 | fileName: "line-linear-custom-interaction.html", 14 | isE2ETest: isE2ETest, 15 | customJS: `Chart.Interaction.modes.maxValueCustomMode = ${maxValueCustomMode.toString()}\n`, 16 | // make only the y-axis draggable in demo mode; E2E tests are not carried out for this page 17 | demoOnlyDraggableAxis: "y", 18 | }), 19 | renderPage({ 20 | title: "Line (categorical)", 21 | fileName: "line-categorical.html", 22 | isE2ETest: isE2ETest, 23 | }), 24 | renderPage({ 25 | title: "Line (dual y-axis)", 26 | fileName: "line-dual-y-axis.html", 27 | isE2ETest: isE2ETest, 28 | }), 29 | ]) as TestPageBundleFactory; 30 | -------------------------------------------------------------------------------- /tests/__config__/default.yml: -------------------------------------------------------------------------------- 1 | # config used to tweak running tests 2 | unit: # unit tests 3 | whitelistedTestCategories: # if non-empty, only these 'groups' (types) of tests will be run 4 | # empty whitelist by default - include all available 5 | whitelistedTestedChartTypes: # if non-empty, tests will be run only against these types of charts 6 | # empty whitelist by default - include all available 7 | e2e: # end-to-end tests 8 | whitelistedBrowsers: # the browsers to be used for running tests inside (in playwright.config.ts) 9 | # empty whitelist by default - include all available 10 | testedAxes: # the axes to test dragging against 11 | x: true 12 | y: true 13 | both: true 14 | whitelistedHTMLFiles: # if non-empty, only these files will be tested 15 | # empty whitelist by default - include all available 16 | whitelistedInteractions: # if non-empty, only these interactions will be tested 17 | # empty whitelist by default - include all available 18 | whitelistedMagnetVariants: # if non-empty, only these magnet variants will be tested 19 | # empty whitelist by default - include all available 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Christoph Pahmeyer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tests/__utils__/structures/Point2D.ts: -------------------------------------------------------------------------------- 1 | export type BoundingBox = { 2 | x: number; 3 | y: number; 4 | width: number; 5 | height: number; 6 | }; 7 | 8 | class Point2D { 9 | public readonly x: number; 10 | public readonly y: number; 11 | 12 | constructor({ x, y }: { x: number; y: number }) { 13 | this.x = x; 14 | this.y = y; 15 | } 16 | 17 | /** 18 | * Creates a new Point2D instance with the the x and y values constrained to a given bounding box 19 | * @param bb the bounding box to constrain the point to 20 | * @returns a new Point2D instance with the x and y values constrained to the given bounding box 21 | */ 22 | copyConstrainedTo(bb: BoundingBox) { 23 | return new Point2D({ 24 | x: Math.max(bb.x, Math.min(bb.x + bb.width, this.x)), 25 | y: Math.max(bb.y, Math.min(bb.y + bb.height, this.y)), 26 | }); 27 | } 28 | 29 | toArray(): [x: number, y: number] { 30 | return [this.x, this.y]; 31 | } 32 | 33 | toObject() { 34 | return { 35 | x: this.x, 36 | y: this.y, 37 | }; 38 | } 39 | 40 | toString() { 41 | return `Point2D { x: ${Math.round(this.x * 10) / 10}, y: ${Math.round(this.y * 10) / 10} }`; 42 | } 43 | } 44 | 45 | export default Point2D; 46 | -------------------------------------------------------------------------------- /tests/unit/__fixtures__/mockedEventUtils.ts: -------------------------------------------------------------------------------- 1 | import { DragDataEvent } from "../../../src"; 2 | 3 | export type TestEventType = "MouseEvent" | "TouchEvent"; 4 | 5 | export function getEventX(event: DragDataEvent, eventType: TestEventType) { 6 | return eventType === "MouseEvent" 7 | ? (event as MouseEvent).clientX 8 | : (event as TouchEvent).touches[0].clientX; 9 | } 10 | 11 | export function getEventY(event: DragDataEvent, eventType: TestEventType) { 12 | return eventType === "MouseEvent" 13 | ? (event as MouseEvent).clientY 14 | : (event as TouchEvent).touches[0].clientY; 15 | } 16 | 17 | export function setEventX( 18 | event: DragDataEvent, 19 | eventType: TestEventType, 20 | x: number, 21 | ) { 22 | if (eventType === "MouseEvent") { 23 | ((event as MouseEvent).clientX as number) = x; 24 | } else { 25 | ((event as TouchEvent).touches[0].clientX as number) = x; 26 | } 27 | } 28 | 29 | export function setEventY( 30 | event: DragDataEvent, 31 | eventType: TestEventType, 32 | y: number, 33 | ) { 34 | if (eventType === "MouseEvent") { 35 | ((event as MouseEvent).clientY as number) = y; 36 | } else { 37 | ((event as TouchEvent).touches[0].clientY as number) = y; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/util/applyMagnet.ts: -------------------------------------------------------------------------------- 1 | import { Chart, ChartType } from "chart.js"; 2 | 3 | import { ChartDataItemType, OptionalPluginConfiguration } from "../types"; 4 | 5 | /** 6 | * Updates values to the nearest values 7 | * @param chartInstance the chart instance 8 | * @param datasetIndex the dataset index 9 | * @param index the data point index 10 | * @returns value after applying magnet or unchanged if not magnet is configured 11 | */ 12 | export function applyMagnet( 13 | chartInstance: Chart, 14 | datasetIndex: number, 15 | index: number, 16 | ): ChartDataItemType { 17 | const pluginOptions = chartInstance.config.options?.plugins 18 | ?.dragData as OptionalPluginConfiguration; 19 | 20 | if (pluginOptions?.magnet) { 21 | const magnet = pluginOptions?.magnet; 22 | 23 | if (typeof magnet.to === "function") { 24 | let data = chartInstance.data.datasets[datasetIndex].data[index]; 25 | data = magnet.to(data); 26 | 27 | chartInstance.data.datasets[datasetIndex].data[index] = data; 28 | 29 | chartInstance.update("none"); 30 | 31 | return data; 32 | } 33 | 34 | return null; 35 | } else { 36 | return chartInstance.data.datasets[datasetIndex].data[index]; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /.github/workflows/cd.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Delivery 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | build-demos: 10 | name: Build demos 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v4 16 | 17 | - name: Setup environment 18 | uses: ./.github/actions/setup 19 | 20 | - name: Build plugin bundles 21 | run: npm run build 22 | 23 | - name: Build E2E pages & demos 24 | run: npm run build:pages 25 | 26 | - name: Upload demos artifact 27 | uses: actions/upload-pages-artifact@v3 28 | with: 29 | path: pages/dist-demos 30 | 31 | publish-demos: 32 | name: Publish demos 33 | runs-on: ubuntu-latest 34 | needs: build-demos 35 | environment: 36 | name: github-pages 37 | url: ${{ steps.publishing.outputs.page_url }} 38 | 39 | # Grant GITHUB_TOKEN the permissions required to make a Pages deployment 40 | permissions: 41 | pages: write # to deploy to Pages 42 | id-token: write # to verify the deployment originates from an appropriate source 43 | 44 | steps: 45 | - name: Publish demos to GH Pages 46 | id: publishing 47 | uses: actions/deploy-pages@v4 48 | -------------------------------------------------------------------------------- /tests/unit/roundValue.spec.ts: -------------------------------------------------------------------------------- 1 | import { roundValue } from "../../dist/test/chartjs-plugin-dragdata-test"; 2 | import { isTestsConfigWhitelistItemAllowed } from "../__utils__/testsConfig"; 3 | 4 | (isTestsConfigWhitelistItemAllowed( 5 | "unit", 6 | "whitelistedTestCategories", 7 | "roundValue", 8 | ) 9 | ? describe 10 | : describe.skip)("roundValue", () => { 11 | test.concurrent( 12 | "should correctly round to the specified decimal places", 13 | () => { 14 | expect(roundValue(1.23456, 2)).toBe(1.23); 15 | expect(roundValue(1.23456, 3)).toBe(1.235); 16 | expect(roundValue(1.23456, 0)).toBe(1); 17 | }, 18 | ); 19 | 20 | test.concurrent("should return the original value if pos is NaN", () => { 21 | expect(roundValue(1.23456, NaN)).toBe(1.23456); 22 | }); 23 | 24 | test.concurrent("should handle NaN values", () => { 25 | expect(roundValue(-1.23456, 2)).toBe(-1.23); 26 | expect(roundValue(-1.23456, 3)).toBe(-1.235); 27 | }); 28 | 29 | test.concurrent("should handle negative numbers", () => { 30 | expect(roundValue(-1.23456, 2)).toBe(-1.23); 31 | expect(roundValue(-1.23456, 3)).toBe(-1.235); 32 | }); 33 | 34 | test.concurrent("should handle rounding up and down", () => { 35 | expect(roundValue(1.5, 0)).toBe(2); 36 | expect(roundValue(1.4, 0)).toBe(1); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /tests/integration/react/react-chartjs-2.spec.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { Chart } from "react-chartjs-2"; 4 | 5 | import { cleanup, render } from "@testing-library/react"; 6 | 7 | import ChartJS from "chart.js/auto"; 8 | 9 | import ChartJSDragDataPlugin from "../../../dist/test/chartjs-plugin-dragdata-test"; 10 | import { 11 | JestTestChartOptions, 12 | genericChartScenarioBase, 13 | } from "../../__data__/data"; 14 | import { integrationAllowed } from "../__utils__"; 15 | 16 | let chartInstance: ChartJS | null = null; 17 | 18 | beforeEach(() => { 19 | chartInstance = null; 20 | }); 21 | 22 | afterEach(() => { 23 | if (chartInstance) chartInstance.destroy(); 24 | 25 | cleanup(); 26 | }); 27 | 28 | function ChartComponent() { 29 | return ( 30 | { 32 | chartInstance = ref!; 33 | }} 34 | type="line" 35 | data={genericChartScenarioBase.configuration.data} 36 | options={{ 37 | ...JestTestChartOptions, 38 | }} 39 | plugins={[ChartJSDragDataPlugin]} 40 | /> 41 | ); 42 | } 43 | 44 | (integrationAllowed("react") ? describe : describe.skip)("react.js", () => { 45 | it("renders chart canvas in the document", () => { 46 | render(); 47 | 48 | expect(chartInstance!.canvas).toBeInTheDocument(); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /knip.ts: -------------------------------------------------------------------------------- 1 | import type { KnipConfig } from "knip"; 2 | 3 | const config: KnipConfig = { 4 | project: [ 5 | "src/**/*.ts", 6 | "src/**/*.js", 7 | "src/**/*.tsx", 8 | "src/**/*.jsx", 9 | "scripts/*.ts", 10 | "scripts/*.js", 11 | "scripts/**/*.ts", 12 | "scripts/**/*.js", 13 | ], 14 | ignoreDependencies: [ 15 | "jest-mock", // false positive 16 | "@commitlint/config-conventional", // false positive 17 | "@types/config", // false positive 18 | "@types/ejs", // false positive 19 | "@vue/vue3-jest", // false positive 20 | "babel-jest", // false positive 21 | "canvas", // false positive 22 | "chartjs-adapter-date-fns", // false positive 23 | "chartjs-plugin-datalabels", // false positive 24 | "config", // false positive 25 | "date-fns", // false positive 26 | "ejs", // false positive 27 | "jest-environment-jsdom", // false positive 28 | "lint-staged", // false positive 29 | "node-sass", // false positive 30 | "resize-observer-polyfill", // false positive 31 | "rollup-plugin-copy", // false positive 32 | "ts-node", // false positive 33 | ...(process.env.CI 34 | ? [ 35 | "lefthook-linux-x64", // false positive in CI 36 | "@rollup/rollup-linux-x64-gnu", // false positive in CI 37 | ] 38 | : []), 39 | ], 40 | ignore: ["src/util/typings.d.ts"], 41 | }; 42 | 43 | export default config; 44 | -------------------------------------------------------------------------------- /tests/typings.d.ts: -------------------------------------------------------------------------------- 1 | import type { Chart } from "chart.js"; 2 | 3 | import type Point2D from "./__utils__/structures/Point2D"; 4 | import type { AxisSpec } from "./__utils__/structures/axisSpec"; 5 | 6 | interface CustomMatchers { 7 | pointsToBeClose( 8 | p2: Point2D, 9 | maxDistance?: number, 10 | additionalInfo?: string, 11 | ): R; 12 | } 13 | 14 | declare global { 15 | interface TestChartSetupOptions { 16 | isTest: boolean; 17 | disablePlugin?: boolean; 18 | draggableAxis: AxisSpec; 19 | magnetImplSerialized?: string; 20 | roundingPrecision?: number; 21 | onDrag?: string; 22 | } 23 | 24 | // the test instance exposed by all test pages - typing for tests' code 25 | interface Window { 26 | testedChart: Chart; 27 | isTestReady?: boolean; 28 | isPluginLoaded?: boolean; 29 | setupChart: (options: TestChartSetupOptions) => void; 30 | resetData(): void; 31 | } 32 | 33 | // augment Jest matchers 34 | namespace jest { 35 | interface Expect extends CustomMatchers {} 36 | interface Matchers extends CustomMatchers {} 37 | interface InverseAsymmetricMatchers extends CustomMatchers {} 38 | } 39 | 40 | // augment Playwright matchers 41 | namespace PlaywrightTest { 42 | interface Matchers extends CustomMatchers {} 43 | interface MakeMatchers extends CustomMatchers {} 44 | } 45 | 46 | var Chart: typeof Chart; 47 | } 48 | -------------------------------------------------------------------------------- /src/util/dragEndCallback.ts: -------------------------------------------------------------------------------- 1 | import type { ChartType } from "chart.js"; 2 | import { Chart } from "chart.js"; 3 | 4 | import ChartJSDragDataPlugin from "../plugin"; 5 | import { 6 | DragDataEvent, 7 | DragDataState, 8 | OptionalPluginConfiguration, 9 | } from "../types"; 10 | import { applyMagnet } from "./applyMagnet"; 11 | 12 | export function dragEndCallback( 13 | event: DragDataEvent, 14 | chartInstance: Chart, 15 | state: DragDataState | undefined = ChartJSDragDataPlugin.statesStore.get( 16 | chartInstance.id, 17 | ), 18 | ) { 19 | if (!state) return; 20 | 21 | const callback = ( 22 | chartInstance.options?.plugins 23 | ?.dragData as OptionalPluginConfiguration 24 | )?.onDragEnd; 25 | 26 | state.curIndex = undefined; 27 | state.isDragging = false; 28 | 29 | // re-enable the tooltip animation 30 | if (chartInstance.config.options?.plugins?.tooltip) { 31 | chartInstance.config.options.plugins.tooltip.animation = 32 | state.eventSettings; 33 | chartInstance.update("none"); 34 | } 35 | 36 | if (!state.element) { 37 | return; 38 | } 39 | 40 | const datasetIndex = state.element.datasetIndex; 41 | const index = state.element.index; 42 | const value = applyMagnet(chartInstance, datasetIndex, index); 43 | 44 | if (typeof callback === "function") { 45 | return callback(event, datasetIndex, index, value); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /tests/__utils__/chartUtils.ts: -------------------------------------------------------------------------------- 1 | import type { Chart } from "chart.js"; 2 | 3 | import Offset2D from "./structures/Offset2D"; 4 | import Point2D from "./structures/Point2D"; 5 | import type { DatasetPointSpec } from "./testTypes"; 6 | 7 | export type GetChartDatasetSamplePixelPositionFunc = ( 8 | datasetIndex: number, 9 | sampleIndex: number, 10 | ) => Promise | Point2D; 11 | 12 | export type GetChartScalesFunc = () => 13 | | Promise 14 | | Chart["scales"]; 15 | 16 | /** 17 | * Function to get the location of a point on the canvas by its specs (w.r.t. the dataset) on the screen 18 | * @param getChartDatasetSamplePixelPosition function returning the position ({@see {Point2D}}) of the specified dataset sample 19 | * @param pointSpec specification on which dataset and which point to get the location of 20 | * @param canvasBB the bounding box of the canvas 21 | * @returns the coordinates of the point on the screen 22 | */ 23 | export async function getDatasetPointLocationOnScreen( 24 | getChartDatasetSamplePixelPosition: GetChartDatasetSamplePixelPositionFunc, 25 | pointSpec: DatasetPointSpec, 26 | canvasBB: DOMRect | null = null, 27 | ): Promise { 28 | const positionOnCanvas = await getChartDatasetSamplePixelPosition( 29 | pointSpec.datasetIndex, 30 | pointSpec.index, 31 | ); 32 | 33 | return new Offset2D({ 34 | xAbs: canvasBB?.left ?? 0, 35 | yAbs: canvasBB?.top ?? 0, 36 | }).translatePoint(positionOnCanvas); 37 | } 38 | -------------------------------------------------------------------------------- /tests/__data__/scatter.ts: -------------------------------------------------------------------------------- 1 | import { TestScenario } from "../__utils__/structures/scenario"; 2 | import { E2EInteraction } from "../__utils__/testsConfig"; 3 | 4 | export const scatterChartScenario = { 5 | configuration: { 6 | type: "scatter", 7 | data: { 8 | datasets: [ 9 | { 10 | label: "A dataset", 11 | data: [ 12 | { 13 | x: 0, 14 | y: -49, 15 | }, 16 | { 17 | x: 10, 18 | y: -54, 19 | }, 20 | { 21 | x: 20, 22 | y: -48, 23 | }, 24 | { 25 | x: 30, 26 | y: -42.9, 27 | }, 28 | { 29 | x: 40, 30 | y: 46, 31 | }, 32 | { 33 | x: 50, 34 | y: 44, 35 | }, 36 | { 37 | x: 60, 38 | y: -76, 39 | }, 40 | { 41 | x: 70, 42 | y: 78, 43 | }, 44 | { 45 | x: 80, 46 | y: 34, 47 | }, 48 | { 49 | x: 90, 50 | y: 49, 51 | }, 52 | ], 53 | backgroundColor: "rgba(255, 99, 132, 1)", 54 | borderWidth: 2.5, 55 | fill: false, 56 | pointRadius: 10, 57 | pointHitRadius: 25, 58 | showLine: true, 59 | }, 60 | ], 61 | }, 62 | options: { 63 | layout: { 64 | padding: { 65 | left: 20, 66 | right: 20, 67 | top: 20, 68 | bottom: 10, 69 | }, 70 | }, 71 | scales: { 72 | y: { 73 | beginAtZero: true, 74 | }, 75 | }, 76 | }, 77 | }, 78 | roundingPrecision: 4, 79 | isCategoricalX: true, 80 | isCategoricalY: false, 81 | // too complex to test in E2E, at least for now 82 | stepGroups: [], 83 | skipE2ETesting: true, 84 | } satisfies TestScenario; 85 | -------------------------------------------------------------------------------- /tests/e2e/__utils__/testHelpers.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import path from "path"; 3 | import { test } from "playwright-test-coverage"; 4 | 5 | import { TestScenarios } from "../../__data__/data"; 6 | import { isTestsConfigWhitelistItemAllowed } from "../../__utils__/testsConfig"; 7 | import { e2ePagesDistDirPath } from "./paths"; 8 | import { TestSuiteIdentifier } from "./types"; 9 | 10 | export function describeEachChartType( 11 | testGenerator: ( 12 | fileName: keyof typeof TestScenarios, 13 | scenario: (typeof TestScenarios)[keyof typeof TestScenarios], 14 | ) => void | Promise, 15 | /** used for excluding pages from specific test suites based on their configuration in tests/__data__/data.ts */ 16 | testSuiteIdentifier: TestSuiteIdentifier, 17 | ) { 18 | for (const fileName of fs 19 | .readdirSync(e2ePagesDistDirPath) 20 | .filter( 21 | (file) => 22 | !fs.lstatSync(path.join(e2ePagesDistDirPath, file)).isDirectory(), 23 | ) as (keyof typeof TestScenarios)[]) { 24 | const scenario = TestScenarios[fileName]; 25 | 26 | (path.extname(fileName) === ".html" && 27 | isTestsConfigWhitelistItemAllowed( 28 | "e2e", 29 | "whitelistedHTMLFiles", 30 | fileName, 31 | ) && 32 | scenario.skipE2ETesting !== true && 33 | !scenario.excludedTestSuites?.includes(testSuiteIdentifier) 34 | ? test.describe 35 | : test.describe.skip)(`${fileName.split(".")[0]} chart`, async () => { 36 | await testGenerator(fileName, scenario); 37 | }); 38 | } 39 | } 40 | 41 | export function hasGUI() { 42 | if (process.argv.includes("--headed") || process.argv.includes("--ui")) 43 | process.env.HEADED_MODE = "1"; 44 | 45 | return Boolean(process.env.HEADED_MODE); 46 | } 47 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | workflow_dispatch: 9 | 10 | jobs: 11 | tests-and-coverage: 12 | name: Run tests & report coverage 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v4 18 | 19 | - name: Setup environment 20 | id: setup-environment 21 | uses: ./.github/actions/setup 22 | 23 | # since playwright installs binaries of browsers with 'npx playwright install' triggered from the 'prepare' script, 24 | # it won't happen when no 'npm install' is run, which is the case for when the '...-npm-...' cache is restored 25 | # for that reason, there is a need to run this command here 26 | - name: Run 'npm run prepare' manually if the cache was used instead of npm install 27 | if: steps.setup-environment.outputs.node-modules-cache-hit == 'true' 28 | run: npm run prepare 29 | 30 | - name: Install playwright dependency libraries 31 | run: npx playwright install-deps 32 | 33 | - name: Build plugin bundles 34 | run: npm run build 35 | 36 | - name: Build E2E pages & demos 37 | run: npm run build:pages 38 | 39 | - name: Run ESLint 40 | run: npm run lint 41 | 42 | - name: Run knip 43 | run: npm run knip 44 | 45 | - name: Run tests 46 | run: npm test 47 | 48 | - name: Upload coverage reports to Codecov 49 | uses: codecov/codecov-action@v3 50 | with: 51 | fail_ci_if_error: false 52 | files: ./coverage/merged/report/merged-coverage.json 53 | token: ${{ secrets.CODECOV_TOKEN }} 54 | -------------------------------------------------------------------------------- /.github/actions/setup/action.yml: -------------------------------------------------------------------------------- 1 | name: Setup 2 | description: Setup environment for Node.JS & install dependencies, including platform-specific lefthook & rollup bindings 3 | 4 | outputs: 5 | node-modules-cache-hit: 6 | description: "Whether the node_modules cache was hit ('true' or 'false')" 7 | value: ${{ steps.cache-node-modules.outputs.cache-hit }} 8 | 9 | runs: 10 | using: composite 11 | steps: 12 | - name: Install native prerequisites 13 | shell: bash 14 | run: | 15 | sudo apt update 16 | sudo apt install build-essential libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev -y 17 | 18 | - name: Setup Node.js 19 | uses: actions/setup-node@v4 20 | id: setup-node 21 | with: 22 | node-version-file: ".nvmrc" 23 | cache: "npm" 24 | 25 | - name: Cache node_modules 26 | uses: actions/cache@v4 27 | id: cache-node-modules 28 | with: 29 | path: "node_modules" 30 | key: node-modules-${{ runner.os }}-${{ steps.setup-node.outputs.node-version }}-${{ hashFiles('package-lock.json') }} 31 | 32 | # since npm ci installs only dependencies in package-lock.json, it may be that the package-lock.json pushed 33 | # has been generated on non-linux, making the installed node_modules lack packages needed by the linux runner 34 | - name: Ensure lefthook-linux-x64 & @rollup/rollup-linux-x64-gnu are installed 35 | if: steps.cache-node-modules.outputs.cache-hit != 'true' 36 | shell: bash 37 | run: npm i lefthook-linux-x64 @rollup/rollup-linux-x64-gnu --legacy-peer-deps 38 | 39 | - name: Install npm dependencies 40 | if: steps.cache-node-modules.outputs.cache-hit != 'true' 41 | shell: bash 42 | run: npm ci --legacy-peer-deps 43 | -------------------------------------------------------------------------------- /tests/__utils__/magnet.ts: -------------------------------------------------------------------------------- 1 | import type { BubbleDataPoint, Point } from "chart.js"; 2 | 3 | type CoordType = Point | BubbleDataPoint; 4 | export type MagnetFunction = (value: T) => T; 5 | 6 | export const roundToInteger: MagnetFunction = (value) => { 7 | const scalarRound = (value: number) => Math.round(value); 8 | 9 | if (typeof value === "number") { 10 | return scalarRound(value) as any; 11 | } else { 12 | return { 13 | ...(value as CoordType), 14 | x: scalarRound(value.x), 15 | y: scalarRound(value.y), 16 | } as any; 17 | } 18 | }; 19 | 20 | export const roundToFirstDecimalPlace: MagnetFunction = (value) => { 21 | const scalarRound = (value: number) => Math.round(value * 10) / 10; 22 | 23 | if (typeof value === "number") { 24 | return scalarRound(value) as any; 25 | } else { 26 | return { 27 | ...(value as CoordType), 28 | x: scalarRound(value.x), 29 | y: scalarRound(value.y), 30 | } as any; 31 | } 32 | }; 33 | 34 | export type MagnetVariant = "none" | "toInteger" | "toFirstDecimalPlace"; 35 | 36 | export const ALL_TESTED_MAGNET_VARIANTS: MagnetVariant[] = [ 37 | "none", 38 | "toInteger", 39 | "toFirstDecimalPlace", 40 | ]; 41 | 42 | /** specifies magnet function implementations for each of magnet variants */ 43 | export const MagnetImplementations: Record< 44 | Exclude, 45 | MagnetFunction 46 | > & { none: undefined } = { 47 | none: undefined, 48 | toInteger: roundToInteger, 49 | toFirstDecimalPlace: roundToFirstDecimalPlace, 50 | }; 51 | 52 | /** describes the errors that may occur when rounding the value for each of magnet variants */ 53 | export const MagnetEstimatedErrors: Record = { 54 | none: 0, 55 | toInteger: 1, 56 | toFirstDecimalPlace: 0.1, 57 | }; 58 | -------------------------------------------------------------------------------- /pages/src/watch.ts: -------------------------------------------------------------------------------- 1 | import "../../scripts/setupEnv"; 2 | 3 | import chokidar from "chokidar"; 4 | import path from "path"; 5 | import { Signale } from "signale"; 6 | 7 | import { requireUncached } from "./utils"; 8 | 9 | const signale = new Signale({ 10 | scope: "Watcher", 11 | }); 12 | 13 | const demosSrcDirPath = path.dirname(__filename), 14 | testsDataDefFilePath = path.join( 15 | path.dirname(__filename), 16 | "..", 17 | "..", 18 | "tests", 19 | "__data__", 20 | "data.ts", 21 | ); 22 | 23 | let bundlerRunning: boolean = false; 24 | 25 | function logBundlerResult(success: boolean) { 26 | if (success) { 27 | signale.log("Bundling finished successfully"); 28 | } else { 29 | signale.fatal("Bundling failed"); 30 | } 31 | 32 | console.log(); 33 | } 34 | 35 | // eslint-disable-next-line @typescript-eslint/no-floating-promises -- this is the entrypoint 36 | (async function main() { 37 | const { assetSpecs, bundle } = requireUncached( 38 | "./bundle", 39 | ) as typeof import("./bundle"); 40 | 41 | signale.info(`Watching for changes in ${demosSrcDirPath}`); 42 | console.log(); 43 | 44 | signale.info("Bundling initially at start"); 45 | 46 | logBundlerResult(await bundle()); 47 | 48 | chokidar 49 | .watch( 50 | [ 51 | demosSrcDirPath, 52 | testsDataDefFilePath, 53 | ...assetSpecs.map(({ sourcePath }) => sourcePath), 54 | ], 55 | { ignoreInitial: true }, 56 | ) 57 | .on("all", async (event, path) => { 58 | signale.log("Event:", event, path); 59 | 60 | if (bundlerRunning) { 61 | signale.log("Bundler is already running, skipping this event"); 62 | return; 63 | } 64 | 65 | signale.log("Starting bundler"); 66 | 67 | bundlerRunning = true; 68 | 69 | const success = await bundle(); 70 | logBundlerResult(success); 71 | 72 | bundlerRunning = false; 73 | }); 74 | })(); 75 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import callstackConfig from "@callstack/eslint-config/react.flat.js"; 2 | // false-positives below: 3 | // eslint-disable-next-line import/no-unresolved 4 | import cspellConfigs from "@cspell/eslint-plugin/configs"; 5 | // eslint-disable-next-line import/no-unresolved 6 | import tsEslintParser from "@typescript-eslint/parser"; 7 | 8 | import eslintConfigPrettier from "eslint-config-prettier"; 9 | import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended"; 10 | 11 | /** @type {import('eslint').Linter.Config} */ 12 | export default [ 13 | { 14 | ignores: [ 15 | "dist", 16 | "coverage", 17 | ".nyc_output", 18 | ".github", 19 | "docs", 20 | "pages/dist-demos", 21 | "pages/dist-e2e", 22 | "src/util/typings.d.ts", 23 | ], 24 | }, 25 | ...callstackConfig, 26 | cspellConfigs.recommended, 27 | { 28 | rules: { 29 | "@cspell/spellchecker": [ 30 | "warn", 31 | { 32 | cspell: { 33 | import: ["cspell.json"], 34 | }, 35 | }, 36 | ], 37 | }, 38 | }, 39 | { 40 | files: ["*.ts", "*.tsx", "*.d.ts"], 41 | languageOptions: { 42 | parser: tsEslintParser, 43 | parserOptions: { 44 | project: "./tsconfig.eslint.json", 45 | }, 46 | }, 47 | }, 48 | eslintConfigPrettier, 49 | eslintPluginPrettierRecommended, 50 | { 51 | rules: { 52 | "import/order": "off", // handled by prettier & @trivago/prettier-plugin-sort-imports 53 | "prettier/prettier": ["error"], 54 | "import/no-extraneous-dependencies": [ 55 | "error", 56 | { 57 | devDependencies: [ 58 | "**/tests/**/*.ts", 59 | "**/tests/**/*.js", 60 | "**/*.spec.js", 61 | "**/*.spec.jsx", 62 | "**/*.spec.ts", 63 | "**/*.spec.tsx", 64 | "*.config.js", 65 | "*.config.mjs", 66 | "*.config.ts", 67 | "pages/**/*.ts", 68 | "pages/**/*.js", 69 | "scripts/**/*.ts", 70 | ], 71 | }, 72 | ], 73 | }, 74 | }, 75 | ]; 76 | -------------------------------------------------------------------------------- /src/plugin.ts: -------------------------------------------------------------------------------- 1 | import { Chart, Plugin as ChartPlugin } from "chart.js"; 2 | import { drag } from "d3-drag"; 3 | import { select } from "d3-selection"; 4 | 5 | import { DragDataState } from "./types"; 6 | import * as util from "./util"; 7 | 8 | const ChartJSDragDataPlugin = { 9 | id: "dragdata", 10 | statesStore: new Map(), 11 | afterInit(chartInstance) { 12 | const state: DragDataState = { 13 | curIndex: undefined, 14 | curDatasetIndex: undefined, 15 | element: null, 16 | eventSettings: false, 17 | floatingBar: false, 18 | initValue: 0, 19 | xAxisID: "", 20 | yAxisID: "", 21 | rAxisID: "", 22 | stacked: false, 23 | type: undefined, 24 | isDragging: false, 25 | }; 26 | 27 | ChartJSDragDataPlugin.statesStore.set(chartInstance.id, state); 28 | 29 | select(chartInstance.canvas).call( 30 | drag() 31 | .container(chartInstance.canvas) 32 | .on("start", (e) => 33 | util.getElement(e.sourceEvent, chartInstance, state), 34 | ) 35 | .on("drag", (e) => util.updateData(e.sourceEvent, chartInstance, state)) 36 | .on("end", (e) => 37 | util.dragEndCallback(e.sourceEvent, chartInstance, state), 38 | ), 39 | ); 40 | }, 41 | beforeEvent(chartInstance) { 42 | const state = ChartJSDragDataPlugin.statesStore.get(chartInstance.id); 43 | 44 | if (state?.isDragging) { 45 | (chartInstance.tooltip as any | undefined)?.update(); 46 | 47 | return false; 48 | } 49 | }, 50 | afterDestroy(chartInstance) { 51 | ChartJSDragDataPlugin.statesStore.delete(chartInstance.id); 52 | }, 53 | } as const satisfies ChartPlugin & Record; 54 | 55 | // TODO: in a future major release, stop auto-registering the plugin and require users to manually register it 56 | // see https://chartjs-plugin-datalabels.netlify.app/guide/getting-started.html#registration 57 | Chart.register(ChartJSDragDataPlugin); 58 | 59 | export default ChartJSDragDataPlugin; 60 | -------------------------------------------------------------------------------- /playwright.config.ts: -------------------------------------------------------------------------------- 1 | import "./tests/e2e/__setup__/playwrightSetup"; 2 | 3 | import { Project, defineConfig, devices } from "@playwright/test"; 4 | 5 | import path from "path"; 6 | import { Signale } from "signale"; 7 | 8 | import { isTestsConfigWhitelistItemAllowed } from "./tests/__utils__/testsConfig"; 9 | import { hasGUI } from "./tests/e2e/__utils__/testHelpers"; 10 | 11 | const signale = new Signale({ 12 | scope: "playwright.config.ts", 13 | }); 14 | 15 | if (hasGUI()) { 16 | signale.info( 17 | "Running in UI mode - testing will only happen on Chrome, regardless of YAML configuration", 18 | ); 19 | } 20 | 21 | const allAvailableRunners: Project[] = [ 22 | { 23 | name: "Chrome", 24 | use: { ...devices["Desktop Chrome"], channel: "chrome" }, 25 | }, 26 | { 27 | name: "Firefox", 28 | use: { ...devices["Desktop Firefox"] }, 29 | }, 30 | { 31 | name: "Safari", 32 | use: { ...devices["Desktop Safari"] }, 33 | }, 34 | { 35 | name: "Microsoft Edge", 36 | use: { ...devices["Desktop Edge"], channel: "msedge" }, 37 | }, 38 | { 39 | name: "Mobile Chrome", 40 | use: { ...devices["Pixel 5"] }, 41 | }, 42 | { 43 | name: "Mobile Safari", 44 | use: { ...devices["iPhone 12"] }, 45 | }, 46 | ]; 47 | 48 | export default defineConfig({ 49 | use: { 50 | launchOptions: { 51 | slowMo: hasGUI() ? 550 : undefined, 52 | }, 53 | video: "retain-on-failure", 54 | trace: "off", 55 | }, 56 | workers: "50%", 57 | maxFailures: process.env.CI ? undefined : 10, 58 | retries: 2, 59 | projects: allAvailableRunners.filter(({ name }) => 60 | isTestsConfigWhitelistItemAllowed("e2e", "whitelistedBrowsers", name!), 61 | ), 62 | testMatch: path.join(path.dirname(__filename), "tests", "e2e", "*.spec.ts"), 63 | globalSetup: path.join( 64 | path.dirname(__filename), 65 | "tests", 66 | "e2e", 67 | "__setup__", 68 | "playwrightGlobalSetup.ts", 69 | ), 70 | outputDir: path.join(path.dirname(__filename), "tests", "e2e", "__results__"), 71 | }); 72 | -------------------------------------------------------------------------------- /tests/e2e/__utils__/chartUtils.ts: -------------------------------------------------------------------------------- 1 | import type { Page } from "playwright"; 2 | 3 | import Point2D from "../../__utils__/structures/Point2D"; 4 | 5 | export async function playwrightCalcCanvasBB(page: Page): Promise { 6 | return await page.evaluate( 7 | /* istanbul ignore next - fixes fatal errors when evaluating outside of original context, see https://github.com/istanbuljs/istanbuljs/issues/499#issuecomment-580358011 */ 8 | () => window.testedChart.canvas.getBoundingClientRect(), 9 | ); 10 | } 11 | 12 | export async function playwrightGetChartDatasetSamplePixelPosition( 13 | page: Page, 14 | datasetIndex: number, 15 | sampleIndex: number, 16 | ): Promise { 17 | return new Point2D( 18 | await page.evaluate( 19 | /* istanbul ignore next - fixes fatal errors when evaluating outside of original context, see https://github.com/istanbuljs/istanbuljs/issues/499#issuecomment-580358011 */ 20 | ({ datasetIndex, sampleIndex }) => { 21 | let sampleValue = 22 | window.testedChart.getDatasetMeta(datasetIndex).data[sampleIndex], 23 | x: number = NaN, 24 | y: number = NaN; 25 | 26 | if (typeof sampleValue === "number") { 27 | y = 0; 28 | } else if (Array.isArray(sampleValue)) { 29 | [x, y] = sampleValue; 30 | } else { 31 | x = sampleValue.x; 32 | y = sampleValue.y; 33 | } 34 | 35 | if (isNaN(x)) { 36 | // the chart is probably categorical, try to find the coordinates using the scales API 37 | x = window.testedChart.scales["x"].getPixelForTick(sampleIndex); 38 | } 39 | 40 | return { 41 | x, 42 | y, 43 | }; 44 | }, 45 | { datasetIndex, sampleIndex }, 46 | ), 47 | ); 48 | } 49 | 50 | export async function playwrightGetChartScales(page: Page) { 51 | return await page.evaluate( 52 | /* istanbul ignore next - fixes fatal errors when evaluating outside of original context, see https://github.com/istanbuljs/istanbuljs/issues/499#issuecomment-580358011 */ 53 | () => window.testedChart.scales, 54 | ); 55 | } 56 | -------------------------------------------------------------------------------- /pages/src/utils.ts: -------------------------------------------------------------------------------- 1 | import ejs from "ejs"; 2 | import fs from "fs"; 3 | import path from "path"; 4 | 5 | import type { TestScenarios as TestScenariosType } from "../../tests/__data__/data"; 6 | import { AxisSpec } from "../../tests/__utils__/structures/axisSpec"; 7 | import { BundledPage } from "./types"; 8 | 9 | export function requireUncached(module: string) { 10 | delete require.cache[require.resolve(module)]; 11 | return require(module); 12 | } 13 | 14 | export function ejsFileToTemplate(ejsFilePath: string) { 15 | return ejs.compile(fs.readFileSync(ejsFilePath, "utf8").toString()); 16 | } 17 | 18 | export type RenderPageOptions = { 19 | /** Title of the page */ 20 | title: string; 21 | /** The page output file name */ 22 | fileName: keyof typeof TestScenariosType; 23 | /** 24 | * Whether this page is built for E2E testing (includes eval 25 | * operations to parse E2E test data in Playwright) or as docs demo 26 | */ 27 | isE2ETest: boolean; 28 | /** 29 | * Whether to include scripts loading date-fns & date-fns chart.js adapter 30 | */ 31 | includeDateFns?: boolean; 32 | /** 33 | * Custom JS code 34 | */ 35 | customJS?: string; 36 | /** 37 | * Value passed to setupChart for draggableAxis option **only in demo mode** 38 | */ 39 | demoOnlyDraggableAxis?: AxisSpec; 40 | }; 41 | 42 | export function renderPage({ 43 | title, 44 | fileName, 45 | isE2ETest, 46 | includeDateFns = false, 47 | customJS = "", 48 | demoOnlyDraggableAxis = "both", 49 | }: RenderPageOptions): BundledPage { 50 | const { TestScenarios } = requireUncached( 51 | "../../tests/__data__/data", 52 | ) as typeof import("../../tests/__data__/data"); 53 | 54 | const template = ejsFileToTemplate( 55 | path.join(path.dirname(__filename), "templates", "layout.html.ejs"), 56 | ), 57 | scenario = TestScenarios[fileName]; 58 | 59 | return { 60 | html: template({ 61 | title: `${title} demo`, 62 | scenarioConfiguration: JSON.stringify(scenario.configuration), 63 | isE2ETest, 64 | includeDateFns, 65 | customJS, 66 | demoOnlyDraggableAxis, 67 | }), 68 | outputFileName: fileName as string, 69 | }; 70 | } 71 | -------------------------------------------------------------------------------- /tests/unit/applyMagnet.spec.ts: -------------------------------------------------------------------------------- 1 | import { Chart, ChartType } from "chart.js"; 2 | 3 | import { applyMagnet } from "../../dist/test/chartjs-plugin-dragdata-test"; 4 | import { OptionalPluginConfiguration } from "../../src"; 5 | import { isTestsConfigWhitelistItemAllowed } from "../__utils__/testsConfig"; 6 | import { UNIT_TEST_CHART_TYPES } from "./__utils__/constants"; 7 | import { setupChartInstance } from "./__utils__/utils"; 8 | 9 | (isTestsConfigWhitelistItemAllowed( 10 | "unit", 11 | "whitelistedTestCategories", 12 | "applyMagnet", 13 | ) 14 | ? describe 15 | : describe.skip)("applyMagnet", () => { 16 | for (const chartType of UNIT_TEST_CHART_TYPES) { 17 | let chartInstance: Chart; 18 | 19 | (isTestsConfigWhitelistItemAllowed( 20 | "unit", 21 | "whitelistedTestedChartTypes", 22 | chartType, 23 | ) 24 | ? describe 25 | : describe.skip)(`${chartType} chart`, () => { 26 | beforeEach(() => { 27 | chartInstance = setupChartInstance(chartType as ChartType, { 28 | plugins: { dragData: {} }, 29 | }); 30 | }); 31 | 32 | it("should return the original data point if magnet is not configured", () => { 33 | const result = applyMagnet(chartInstance, 0, 1); 34 | expect(result).toBe(chartInstance.data.datasets[0].data[1]); 35 | }); 36 | 37 | it("should update the data point using the magnet function", () => { 38 | const magnetFunction = jest.fn().mockReturnValue(99.234); 39 | (chartInstance.config.options!.plugins! 40 | .dragData as OptionalPluginConfiguration)!.magnet = 41 | { 42 | to: magnetFunction, 43 | }; 44 | 45 | const chartUpdateSpy = jest.spyOn(chartInstance, "update"); 46 | 47 | // since applyMagnet mutates the data point, we need to store the original value 48 | const dataPointBeforeMutation = chartInstance.data.datasets[0].data[1]; 49 | 50 | const result = applyMagnet(chartInstance, 0, 1); 51 | expect(magnetFunction).toHaveBeenCalledExactlyOnceWith( 52 | dataPointBeforeMutation, 53 | ); 54 | 55 | expect(result).toBe(magnetFunction()); 56 | 57 | expect(chartUpdateSpy).toHaveBeenCalledExactlyOnceWith("none"); 58 | }); 59 | }); 60 | } 61 | }); 62 | -------------------------------------------------------------------------------- /tests/__setup__/commonSetup.ts: -------------------------------------------------------------------------------- 1 | import "../../scripts/setupEnv"; 2 | 3 | import { expect as playwrightExpect } from "playwright-test-coverage"; 4 | import { ExpectMatcherState } from "playwright/test"; 5 | 6 | import { euclideanDistance } from "../__utils__/cartesian"; 7 | import Point2D from "../__utils__/structures/Point2D"; 8 | 9 | type PlaywrightMatcherReturnType = { 10 | message: () => string; 11 | pass: boolean; 12 | name?: string; 13 | expected?: unknown; 14 | actual?: any; 15 | log?: string[]; 16 | }; 17 | type PlaywrightMatchersDef = Record< 18 | string, 19 | ( 20 | this: ExpectMatcherState, 21 | receiver: any, 22 | ...args: any[] 23 | ) => PlaywrightMatcherReturnType | Promise 24 | >; 25 | 26 | type JestExpectExtendMap = Parameters[0]; 27 | 28 | // intersection of Playwright & Jest matchers extension types 29 | type ExpectExtendMap = JestExpectExtendMap & PlaywrightMatchersDef; 30 | 31 | const customMatchers: ExpectExtendMap = { 32 | pointsToBeClose( 33 | p1: Point2D, 34 | p2: Point2D, 35 | maxDistancePx: number = 2, 36 | additionalInfo?: string, 37 | ) { 38 | const absDistance = Math.abs(euclideanDistance(p1, p2)), 39 | pass = absDistance <= maxDistancePx; 40 | 41 | if (pass) { 42 | return { 43 | message: () => 44 | `Expected the points to be absolutely-distant by at most: ${this.utils.printExpected( 45 | maxDistancePx, 46 | )}\n` + `Actual distance: ${this.utils.printReceived(absDistance)}`, 47 | pass: true, 48 | }; 49 | } 50 | 51 | return { 52 | message: () => 53 | `Expected the points to be absolutely-distant by at most: ${this.utils.printExpected( 54 | maxDistancePx, 55 | )}\n` + 56 | `Actual distance: ${this.utils.printReceived(absDistance)}\n\n` + 57 | `The points:\n\t> expected: ${this.utils.printExpected(p2.toString())}\n\t> actual: ${this.utils.printExpected(p1.toString())}` + 58 | `${additionalInfo ? `\n\nAdditional information:\n${additionalInfo}` : ""}`, 59 | pass: false, 60 | }; 61 | }, 62 | }; 63 | 64 | playwrightExpect.extend(customMatchers); 65 | 66 | if (typeof jest !== "undefined") { 67 | expect.extend(customMatchers); 68 | } 69 | -------------------------------------------------------------------------------- /scripts/collectCoverage.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import path from "path"; 3 | import { Signale } from "signale"; 4 | 5 | import { 6 | mergedCoverageDirPath, 7 | mergedCoverageSourceReportsDirPath, 8 | reportsSources, 9 | } from "./utils/paths"; 10 | 11 | const signale = new Signale({ 12 | scope: "collectCoverage.ts", 13 | }); 14 | 15 | if (fs.existsSync(mergedCoverageDirPath)) { 16 | fs.rmSync(mergedCoverageDirPath, { recursive: true }); 17 | } 18 | 19 | fs.mkdirSync(mergedCoverageSourceReportsDirPath, { recursive: true }); 20 | 21 | let counter = 0; 22 | for (const reportDir of reportsSources) { 23 | const jsonReport = path.join(reportDir, "coverage-final.json"), 24 | testsComponent = path.basename(reportDir); 25 | 26 | if (fs.existsSync(jsonReport)) { 27 | signale.log( 28 | `Including coverage report for tests component: ${testsComponent}`, 29 | ); 30 | 31 | // FIXME: remove this fix as the root cause is eliminated; tracked in https://github.com/rollup/plugins/issues/1779 32 | // below: fix for jest coverage output having wrong paths, skipping the root project's directory name 33 | const jsonContents = fs.readFileSync(jsonReport, "utf-8"); 34 | 35 | const rootProjectDirPath = path.resolve(__dirname, ".."), 36 | rootProjectDirName = path.basename(rootProjectDirPath); 37 | 38 | // replace the root project's directory name with the tests component's name 39 | const fixedJsonContents = jsonContents.replace( 40 | new RegExp( 41 | `${path.dirname(rootProjectDirPath)}(?!${rootProjectDirName})`, 42 | "g", 43 | ), 44 | rootProjectDirPath, 45 | ); 46 | 47 | fs.writeFileSync( 48 | path.join( 49 | mergedCoverageSourceReportsDirPath, 50 | `coverage-${testsComponent}.json`, 51 | ), 52 | fixedJsonContents, 53 | ); 54 | 55 | // below: 'normal' logic without the above fix 56 | // fs.cpSync( 57 | // jsonReport, 58 | // path.join( 59 | // mergedCoverageSourceReportsDirPath, 60 | // `coverage-${testsComponent}.json`, 61 | // ), 62 | // ); 63 | 64 | counter++; 65 | } else { 66 | signale.log( 67 | `Not including coverage report for tests component ${testsComponent}, since directory '${reportDir}' does not exist`, 68 | ); 69 | } 70 | } 71 | 72 | signale.log( 73 | `Placed ${counter} of ${reportsSources.length} coverage reports for merging in ${mergedCoverageDirPath}`, 74 | ); 75 | -------------------------------------------------------------------------------- /src/util/updateData.ts: -------------------------------------------------------------------------------- 1 | import type { ChartType } from "chart.js"; 2 | import { Chart } from "chart.js"; 3 | 4 | import ChartJSDragDataPlugin from "../plugin"; 5 | import { 6 | DragDataEvent, 7 | DragDataState, 8 | OptionalPluginConfiguration, 9 | } from "../types"; 10 | import { checkDraggingConfiguration } from "../util/checkDraggingConfiguration"; 11 | import { calcCartesian, calcRadialLinear } from "./calc"; 12 | import { roundValue } from "./roundValue"; 13 | 14 | export function updateData( 15 | event: DragDataEvent, 16 | chartInstance: Chart, 17 | state: DragDataState | undefined = ChartJSDragDataPlugin.statesStore.get( 18 | chartInstance.id, 19 | ), 20 | ) { 21 | if (!state) return; 22 | 23 | const pluginOptions = chartInstance.options?.plugins 24 | ?.dragData as OptionalPluginConfiguration; 25 | 26 | const callback = pluginOptions?.onDrag; 27 | 28 | if (state.element) { 29 | state.curDatasetIndex = state.element.datasetIndex; 30 | state.curIndex = state.element.index; 31 | 32 | state.isDragging = true; 33 | 34 | let dataPoint = 35 | chartInstance.data.datasets[state.curDatasetIndex].data[state.curIndex]!; 36 | 37 | const draggingConfiguration = checkDraggingConfiguration( 38 | chartInstance, 39 | state.curDatasetIndex, 40 | state.curIndex, 41 | ); 42 | 43 | if (state.type === "radar" || state.type === "polarArea") { 44 | dataPoint = calcRadialLinear( 45 | event, 46 | chartInstance, 47 | state.curIndex, 48 | state.rAxisID, 49 | state, 50 | ); 51 | } else if (state.stacked) { 52 | let cursorPos = calcCartesian( 53 | event, 54 | chartInstance, 55 | dataPoint, 56 | draggingConfiguration, 57 | state, 58 | ); 59 | dataPoint = roundValue( 60 | (cursorPos as number) - state.initValue, 61 | (pluginOptions as OptionalPluginConfiguration)?.round, 62 | ); 63 | } else { 64 | dataPoint = calcCartesian( 65 | event, 66 | chartInstance, 67 | dataPoint, 68 | draggingConfiguration, 69 | state, 70 | ); 71 | } 72 | 73 | if ( 74 | typeof callback === "function" 75 | ? callback(event, state.curDatasetIndex, state.curIndex, dataPoint) !== 76 | false 77 | : true 78 | ) { 79 | chartInstance.data.datasets[state.curDatasetIndex].data[state.curIndex] = 80 | dataPoint; 81 | chartInstance.update("none"); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/util/calc/radialLinear.ts: -------------------------------------------------------------------------------- 1 | import type { Chart, ChartType, RadialLinearScale } from "chart.js"; 2 | import { getRelativePosition } from "chart.js/helpers"; 3 | 4 | import ChartJSDragDataPlugin from "../../plugin"; 5 | import { 6 | DragDataEvent, 7 | DragDataState, 8 | OptionalPluginConfiguration, 9 | } from "../../types"; 10 | import { roundValue } from "../roundValue"; 11 | import { clipValue } from "./clipValue"; 12 | 13 | export function calcRadialLinear( 14 | event: DragDataEvent, 15 | chartInstance: Chart, 16 | curIndex: number, 17 | rAxisID: string, 18 | state: DragDataState | undefined = ChartJSDragDataPlugin.statesStore.get( 19 | chartInstance.id, 20 | ), 21 | ) { 22 | let { x: cursorX, y: cursorY } = getRelativePosition( 23 | event, 24 | chartInstance as any, 25 | ); 26 | const rScale = chartInstance.scales[rAxisID] as RadialLinearScale; 27 | let { angle: axisAngleRad } = rScale.getPointPositionForValue( 28 | // the radar chart has points draggable along primary axes that are aligned with 29 | // scales' lines; the polarArea chart, however, is draggable along lines placed in the center 30 | // between major lines, thus the +0.5 of index is added for the helper to calculate the angle 31 | // of this center guide line (the helper accept a continuous argument, in spite of the name "index") 32 | curIndex + (state?.type === "polarArea" ? 0.5 : 0), 33 | chartInstance.scales[rAxisID].max, 34 | ); 35 | const { xCenter, yCenter } = rScale; 36 | 37 | // we calculate the dot product of the vector from center to cursor & the axis direction vector 38 | // center-to-cursor vector v 39 | let vx = cursorX - xCenter; 40 | let vy = cursorY - yCenter; 41 | // axis direction vector d 42 | let dx = Math.cos(axisAngleRad); 43 | let dy = Math.sin(axisAngleRad); 44 | // dot product of v & d 45 | let dotProduct = vx * dx + vy * dy; 46 | let d = 47 | // if dot product <= 0, then the point is on the opposite side of the center than the direction of the axis 48 | dotProduct > 0 49 | ? // Euclidean distance between cursor & center 50 | Math.sqrt( 51 | Math.pow(cursorX - xCenter, 2) + Math.pow(cursorY - yCenter, 2), 52 | ) 53 | : 0; 54 | 55 | // calculate the value from distance 56 | let v = rScale.getValueForDistanceFromCenter(d); 57 | 58 | // apply rounding 59 | v = roundValue( 60 | v, 61 | ( 62 | chartInstance.config.options?.plugins 63 | ?.dragData as OptionalPluginConfiguration 64 | )?.round, 65 | ); 66 | 67 | v = clipValue( 68 | v, 69 | chartInstance.scales[rAxisID].min, 70 | chartInstance.scales[rAxisID].max, 71 | ); 72 | 73 | return v; 74 | } 75 | -------------------------------------------------------------------------------- /tests/__utils__/cartesian.ts: -------------------------------------------------------------------------------- 1 | import Point2D, { BoundingBox } from "./structures/Point2D"; 2 | import { AxisSpec } from "./structures/axisSpec"; 3 | 4 | /** 5 | * Calculates the Euclidean distance between two points in a 2D space. 6 | * @param p1 the first point 7 | * @param p2 the second point 8 | * @returns the Euclidean distance between `p1` and `p2` 9 | */ 10 | export function euclideanDistance(p1: Point2D, p2: Point2D): number { 11 | return Math.sqrt((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2); 12 | } 13 | 14 | /** 15 | * Calculates the position of the drag target based on the axis being dragged to move either of 16 | * the coordinates of `pointA` to the corresponding coordinate of `pointB`. 17 | * @param pointA the source point 18 | * @param pointB the point to take one coordinate from 19 | * @param whichAxis the axis that determines which coordinate from `pointA` to substitute with the one from `pointB` 20 | * @param draggableAxis the axis/axes that is/are allowed to be dragged by chart config; if no axis is draggable, pass in `undefined` 21 | * @param canvasBB optionally the bounding box of the canvas, e.g. its `DOMRect` 22 | * @returns the new point, constrained to the `draggableAxis` 23 | */ 24 | export function calcDragTargetPosition( 25 | pointA: Point2D, 26 | pointB: Point2D, 27 | whichAxis: AxisSpec, 28 | draggableAxis?: AxisSpec, 29 | canvasBB?: BoundingBox, 30 | ): Point2D { 31 | let desiredPoint: Point2D; 32 | 33 | switch (whichAxis) { 34 | case "x": 35 | // drag desired to happen on the x axis 36 | desiredPoint = new Point2D({ 37 | x: pointB.x, 38 | y: pointA.y, 39 | }); 40 | break; 41 | 42 | case "y": 43 | // drag desired to happen on the y axis 44 | desiredPoint = new Point2D({ 45 | x: pointA.x, 46 | y: pointB.y, 47 | }); 48 | break; 49 | 50 | case "both": 51 | // drag desired to happen on both axes 52 | desiredPoint = new Point2D({ x: pointB.x, y: pointB.y }); 53 | break; 54 | 55 | default: 56 | throw new Error(`Unknown whichAxis: ${whichAxis}`); 57 | } 58 | 59 | switch (draggableAxis) { 60 | case "x": 61 | // constrain the drag to the x axis 62 | desiredPoint = new Point2D({ x: desiredPoint.x, y: pointA.y }); 63 | break; 64 | 65 | case "y": 66 | // constrain the drag to the y axis 67 | desiredPoint = new Point2D({ x: pointA.x, y: desiredPoint.y }); 68 | break; 69 | 70 | case "both": 71 | // no constraints 72 | break; 73 | 74 | case undefined: 75 | // no axis is draggable 76 | desiredPoint = new Point2D({ x: pointA.x, y: pointA.y }); 77 | break; 78 | 79 | default: 80 | throw new Error(`Unknown draggableAxis: ${draggableAxis}`); 81 | } 82 | 83 | if (canvasBB) { 84 | desiredPoint = desiredPoint.copyConstrainedTo(canvasBB); 85 | } 86 | 87 | return desiredPoint; 88 | } 89 | -------------------------------------------------------------------------------- /tests/e2e/__fixtures__/setup.ts: -------------------------------------------------------------------------------- 1 | import { type Page } from "@playwright/test"; 2 | 3 | import path from "path"; 4 | import { test } from "playwright-test-coverage"; 5 | 6 | import { TestScenarios } from "../../__data__/data"; 7 | import { MagnetImplementations, MagnetVariant } from "../../__utils__/magnet"; 8 | import { AxisSpec } from "../../__utils__/structures/axisSpec"; 9 | 10 | export function sleep(ms: number) { 11 | return new Promise((resolve) => setTimeout(resolve, ms)); 12 | } 13 | 14 | export type SetupTestOptions = { 15 | fileName: keyof typeof TestScenarios; 16 | draggableAxis: AxisSpec; 17 | disablePlugin?: boolean; 18 | magnet?: MagnetVariant; 19 | }; 20 | 21 | export async function setupE2ETest( 22 | options: SetupTestOptions, 23 | page: Page, 24 | isMobile: boolean, 25 | ) { 26 | await test.step(`Load & initialize chart '${options.fileName}'`, async () => { 27 | options.disablePlugin = options.disablePlugin ?? false; 28 | 29 | const scenario = TestScenarios[options.fileName]; 30 | 31 | const testChartSetupOptions: TestChartSetupOptions = { 32 | ...options, 33 | isTest: true, 34 | roundingPrecision: scenario.roundingPrecision, 35 | onDrag: scenario.onDrag, 36 | }; 37 | 38 | if (isMobile) { 39 | let viewport = page.viewportSize(); 40 | 41 | // switch to landscape so that the chart takes full screen size 42 | if (viewport?.width && viewport.width < viewport.height) { 43 | await page.setViewportSize({ 44 | width: viewport!.height, 45 | height: viewport!.width, 46 | }); 47 | } 48 | } 49 | 50 | for (const key of Object.keys(options) as (keyof SetupTestOptions)[]) { 51 | switch (key) { 52 | case "fileName": 53 | default: 54 | // skip these properties - they are spread to testChartSetupOptions above but do not have any side effects 55 | continue; 56 | 57 | case "magnet": 58 | if (options.magnet) { 59 | const magnetImpl = MagnetImplementations[options.magnet]; 60 | 61 | if (magnetImpl) { 62 | testChartSetupOptions.magnetImplSerialized = 63 | magnetImpl.toString(); 64 | } 65 | } 66 | 67 | break; 68 | } 69 | } 70 | 71 | await page.goto( 72 | `file://${path.dirname(__filename)}/../../../pages/dist-e2e/${options.fileName}?isTest=true`, 73 | ); 74 | await page.waitForLoadState("load"); 75 | 76 | // run the actual setup function in browser context 77 | await page.evaluate( 78 | ({ testChartSetupOptions }) => { 79 | window.setupChart(testChartSetupOptions); 80 | }, 81 | { testChartSetupOptions: testChartSetupOptions as any }, 82 | ); 83 | 84 | // since the plugin script is lazily loaded based on URL search params, 85 | // the below is to stably check whether the page is ready for real 86 | await page.waitForFunction(() => window.isTestReady); 87 | }); 88 | } 89 | -------------------------------------------------------------------------------- /src/util/checkDraggingConfiguration.ts: -------------------------------------------------------------------------------- 1 | import type { BubbleDataPoint, ChartType, Point } from "chart.js"; 2 | import { Chart } from "chart.js"; 3 | 4 | import ChartJSDragDataPlugin from "../plugin"; 5 | import { DragDataState } from "../types"; 6 | import { OptionalPluginConfiguration } from "../types/Configuration"; 7 | import { DraggingConfiguration } from "../types/DraggingConfiguration"; 8 | 9 | export function checkDraggingConfiguration( 10 | chartInstance: Chart, 11 | datasetIndex: number, 12 | dataPointIndex: number, 13 | state: DragDataState | undefined = ChartJSDragDataPlugin.statesStore.get( 14 | chartInstance.id, 15 | ), 16 | ): DraggingConfiguration { 17 | if (!state) 18 | return { 19 | chartDraggingDisabled: true, 20 | datasetDraggingDisabled: true, 21 | xAxisDraggingDisabled: true, 22 | yAxisDraggingDisabled: true, 23 | dataPointDraggingDisabled: true, 24 | }; 25 | 26 | const dataset = chartInstance.data.datasets[datasetIndex]; 27 | 28 | /** per-chart option */ 29 | const chartDraggingDisabled = 30 | chartInstance.config.options?.plugins?.dragData === false; 31 | 32 | /** per-dataset option */ 33 | const datasetDraggingDisabled = 34 | chartDraggingDisabled || dataset.dragData === false; 35 | 36 | /** x-axis option (per-axis); dragging on the x-axis is disabled by default */ 37 | const _xAxisDraggingPerAxisOptionValue = 38 | chartInstance.config.options?.scales?.[state.xAxisID]?.dragData; 39 | 40 | /** x-axis option (per-axis); dragging on the x-axis is disabled by default */ 41 | let xAxisDraggingDisabled = true; 42 | 43 | if ( 44 | !datasetDraggingDisabled && 45 | (_xAxisDraggingPerAxisOptionValue === true || // finally, dragging can be enabled on the x-axis by the plugin options, 46 | // unless it's explicitly disabled in x-axis options 47 | (( 48 | chartInstance.config.options?.plugins 49 | ?.dragData as OptionalPluginConfiguration 50 | )?.dragX === true && 51 | _xAxisDraggingPerAxisOptionValue !== false)) 52 | ) { 53 | xAxisDraggingDisabled = false; 54 | } 55 | 56 | /** y-axis option (per-axis); dragging on the y-axis is enabled by default */ 57 | const yAxisDraggingDisabled = 58 | datasetDraggingDisabled || 59 | ( 60 | chartInstance.config.options?.plugins 61 | ?.dragData as OptionalPluginConfiguration 62 | )?.dragY === false || 63 | chartInstance.config.options?.scales?.[state.yAxisID]?.dragData === false; 64 | 65 | /** per-data-point option */ 66 | const dataPointDraggingDisabled = 67 | datasetDraggingDisabled || 68 | (dataset.data[dataPointIndex] as Point | BubbleDataPoint)?.dragData === 69 | false; 70 | 71 | return { 72 | chartDraggingDisabled, 73 | datasetDraggingDisabled, 74 | xAxisDraggingDisabled, 75 | yAxisDraggingDisabled, 76 | dataPointDraggingDisabled, 77 | }; 78 | } 79 | -------------------------------------------------------------------------------- /src/typeAugmentations.ts: -------------------------------------------------------------------------------- 1 | // this file is not a .d.ts in src/ file so as for rollup to bundle it along with other ts files 2 | import { ChartType, Plugin } from "chart.js"; 3 | 4 | import { 5 | DataPointDraggingConfiguration, 6 | DatasetDraggingConfiguration, 7 | PluginConfiguration, 8 | ScaleDraggingConfiguration, 9 | } from "./types/Configuration"; 10 | 11 | declare module "chart.js" { 12 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 13 | export interface ChartDatasetProperties { 14 | /** 15 | * Configuration controlling the dragdata plugin behaviour for a given dataset. 16 | * 17 | * For information regarding the mechanics, please consult `PluginConfiguration`. 18 | * 19 | * @see PluginConfiguration 20 | */ 21 | dragData?: DatasetDraggingConfiguration | boolean; 22 | } 23 | 24 | export interface PluginOptionsByType { 25 | /** 26 | * Configuration controlling the dragdata plugin behaviour. 27 | * The scope depends on where the configuration is applied. The possible locations are: 28 | * - per-chart (inside `plugins` section in chart configuration) 29 | * - per-scale (inside a scale's configuration) 30 | * - per-dataset (inside a dataset's configuration) 31 | * - per-data-point (inside a data point's object, for object data points only) 32 | * 33 | * Each next level from the listing above overrides any preceding configuration (if applicable with respect to the configuration options available). 34 | * 35 | * To entirely disable the plugin, pass `false`. By default, the plugin is enabled for every data point but only 36 | * on the y-axis, unless a lower-level configuration specifies otherwise. 37 | */ 38 | dragData?: PluginConfiguration | boolean; 39 | } 40 | 41 | export interface CoreScaleOptions { 42 | /** 43 | * Configuration controlling the dragdata plugin behaviour for a given scale. 44 | * If `true`, the plugin is enabled for this scale, otherwise it is disabled. 45 | * 46 | * The default value depends on the scale: for the y-axis, it is `true` by default, 47 | * while it is `false` by default otherwise (in which case `true` needs to be 48 | * explicitly specified to enable dragging on the axis). 49 | */ 50 | dragData?: ScaleDraggingConfiguration | boolean; 51 | } 52 | 53 | export interface Point { 54 | /** 55 | * Configuration controlling the dragdata plugin behaviour for a given data point. 56 | * 57 | * For information regarding the mechanics, please consult `PluginConfiguration`. 58 | * 59 | * @see PluginConfiguration 60 | */ 61 | dragData?: DataPointDraggingConfiguration | false; 62 | } 63 | } 64 | 65 | declare const ChartJSDragDataPlugin: Plugin; 66 | 67 | export default ChartJSDragDataPlugin; 68 | -------------------------------------------------------------------------------- /tests/unit/getElement.spec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | InteractionMode, 3 | InteractionOptions, 4 | PointElement, 5 | Chart as TChart, 6 | } from "chart.js"; 7 | 8 | import ChartJSDragDataPlugin, { 9 | getElement, 10 | } from "../../dist/test/chartjs-plugin-dragdata-test"; 11 | import { isTestsConfigWhitelistItemAllowed } from "../__utils__/testsConfig"; 12 | import { maxValueCustomMode, setupChartInstance } from "./__utils__/utils"; 13 | 14 | const DEFAULT_GET_ELEMENTS_AT_EVENT_MOCK_RETURN_VALUE = [ 15 | { index: 0, datasetIndex: 0, element: new PointElement({}) }, 16 | { index: 0, datasetIndex: 1, element: new PointElement({}) }, 17 | ]; 18 | 19 | (isTestsConfigWhitelistItemAllowed( 20 | "unit", 21 | "whitelistedTestCategories", 22 | "getElement", 23 | ) 24 | ? describe 25 | : describe.skip)("getElement", () => { 26 | describe("line chart with custom interaction mode", () => { 27 | let chartInstance: TChart<"line">; 28 | let interactionMode: InteractionMode; 29 | let interactionOptions: InteractionOptions; 30 | 31 | beforeEach(() => { 32 | Chart.Interaction.modes.maxValueCustomMode = maxValueCustomMode; 33 | 34 | chartInstance = setupChartInstance("line", { 35 | interaction: { 36 | mode: "maxValueCustomMode" as any, 37 | }, 38 | }); 39 | 40 | // mock getElementsAtEventForMode to always return items at first data index from both datasets 41 | chartInstance.getElementsAtEventForMode = jest.fn( 42 | (_e, _mode, _options) => 43 | DEFAULT_GET_ELEMENTS_AT_EVENT_MOCK_RETURN_VALUE, 44 | ); 45 | 46 | Chart.register(ChartJSDragDataPlugin); 47 | 48 | interactionMode = 49 | chartInstance.config.options?.interaction?.mode ?? "nearest"; 50 | interactionOptions = chartInstance.config.options?.interaction ?? { 51 | intersect: true, 52 | }; 53 | }); 54 | 55 | afterEach(() => { 56 | jest.restoreAllMocks(); // undo spyOn() calls 57 | jest.clearAllMocks(); // clear mocks 58 | }); 59 | 60 | it("getElement should properly call getElementsAtEventForMode & select first returned point", () => { 61 | const evtMock = {} as any; 62 | 63 | getElement(evtMock, chartInstance); 64 | 65 | expect( 66 | chartInstance.getElementsAtEventForMode, 67 | ).toHaveBeenCalledExactlyOnceWith( 68 | evtMock, 69 | interactionMode, 70 | interactionOptions, 71 | false, 72 | ); 73 | 74 | expect( 75 | ChartJSDragDataPlugin.statesStore.get(chartInstance.id)?.element, 76 | ).toBe(DEFAULT_GET_ELEMENTS_AT_EVENT_MOCK_RETURN_VALUE[0]); 77 | }); 78 | 79 | it("getElement should result in selecting null if callback returns false", () => { 80 | const evtMock = {} as any; 81 | 82 | getElement(evtMock, chartInstance, (() => false) as any); 83 | 84 | expect( 85 | chartInstance.getElementsAtEventForMode, 86 | ).toHaveBeenCalledExactlyOnceWith( 87 | evtMock, 88 | interactionMode, 89 | interactionOptions, 90 | false, 91 | ); 92 | expect( 93 | ChartJSDragDataPlugin.statesStore.get(chartInstance.id)?.element, 94 | ).toBe(null); 95 | }); 96 | }); 97 | }); 98 | -------------------------------------------------------------------------------- /pages/README.md: -------------------------------------------------------------------------------- 1 | # Demos for chartjs-plugin-dragdata.js 2 | 3 | This directory contains scripts & templates for the demos that present the possible configurations and examples of the plugin. They are served alongside the [docs](TODO) for users and are also used for testing. Please find the compiled files in [/pages/dist-demos/](/pages/dist-demos). To compile the demos locally, run `npm run build:pages` or `build:pages:watch` to run watch-mode development bundler that will observe for changes in files & rebuild when any of them are changed. 4 | 5 | Each demo exposes various global variables (see [/tests/typings.d.ts](/tests/typings.d.ts)) that are used by Playwright to perform end-to-end testing on the demos: 6 | 7 | - `testedChart`: This is the instance of the Chart that is currently being tested in the demo page. 8 | 9 | - `isTestReady`: This is a `boolean` flag that indicates whether the test setup is complete and the test is ready to be run. If `isTestReady` is `true`, the test can be run. If it is undefined or false, the test setup is not yet complete. 10 | 11 | - `setupChart`: This is a function that is used to set up the test environment or demo. It takes an object of type `TestChartSetupOptions` as an argument, which can include the following properties in test environment builds: 12 | 13 | - `isTest`: A `boolean` that indicates whether the current environment is a test environment. 14 | 15 | - `disablePlugin`: An optional `boolean` that, if `true`, disables the dragData plugin for the test. 16 | 17 | - `draggableAxis`: An AxisSpec object that specifies which axes of the chart should be draggable. 18 | 19 | - `magnetImplSerialized`: An optional `string` that represents a serialized implementation of the magnet function. This function is used to adjust the data point's value after it has been dragged. 20 | 21 | - `roundingPrecision`: An optional `number` that specifies the number of decimal places to which values should be rounded after they have been adjusted by the magnet function. 22 | 23 | - `resetData`: a function that resets the data to the original shape passed in to `setupChart()`; invoked in-between groups of test steps 24 | 25 | Note: from the below properties, only `disablePlugin`, `draggableAxis`, `roundingPrecision` are honored in demo mode. Their values are fixed and passed automatically by the script when the page loads. 26 | 27 | The key files in this module are located in the `src` directory, as follows: 28 | 29 | - `templates/layout.html.ejs` - an [EJS](https://ejs.co/) template that is core for each of the demos 30 | - `pages/.page.ts` - each of the demos themselves, using the aforementioned EJS template as its core & specifying bundles, e.g. for `line.page.ts` it specifies 2 bundles: one for a linear chart & one for a categorical X axis chart; **IMPORTANT:** each of these files **must** have a corresponding entry in `TestScenarios` inside [tests/\_\_data\_\_/data.ts](/tests/__data__/data.ts)) 31 | - `bundle.ts` - exposes a function to process all bundles exported from all `pages/*.page.ts` files; also works as an entrypoint if executed directly to run bundling just once 32 | - `watch.ts` - entrypoint for a dev script that watches all [assets (/dist)](/dist), [demos & E2E test pages (pages/src)](/pages/src/) and [tests/\_\_data\_\_/data.ts](/tests/__data__/data.ts) for changes & runs the bundler function exported from `bundle.ts` 33 | 34 | All bundle outputs along with assets copied from [/dist](/dist) (the compiled dragdata plugin) & `node_modules` (chart.js, lodash for testing code inside them) will be placed in `/pages/dist-demos`. 35 | -------------------------------------------------------------------------------- /src/util/calc/cartesian.ts: -------------------------------------------------------------------------------- 1 | import type { Chart, ChartType, Point } from "chart.js"; 2 | import { getRelativePosition } from "chart.js/helpers"; 3 | 4 | import ChartJSDragDataPlugin from "../../plugin"; 5 | import { 6 | ChartDataItemType, 7 | DragDataEvent, 8 | DragDataState, 9 | OptionalPluginConfiguration, 10 | } from "../../types"; 11 | import { AxisDraggingConfiguration } from "../../types/DraggingConfiguration"; 12 | import { cloneDataPoint } from "../cloneDataPoint"; 13 | import { roundValue } from "../roundValue"; 14 | import { clipValue } from "./clipValue"; 15 | 16 | export function calcCartesian( 17 | event: DragDataEvent, 18 | chartInstance: Chart, 19 | data: NonNullable>, 20 | { xAxisDraggingDisabled, yAxisDraggingDisabled }: AxisDraggingConfiguration, 21 | state: DragDataState | undefined = ChartJSDragDataPlugin.statesStore.get( 22 | chartInstance.id, 23 | ), 24 | ): NonNullable> { 25 | if (!state) return data; 26 | 27 | const dataPoint = cloneDataPoint(data)!; 28 | 29 | let { x: cursorX, y: cursorY } = getRelativePosition( 30 | event, 31 | chartInstance as any, 32 | ); 33 | 34 | let x = chartInstance.scales[state.xAxisID].getValueForPixel(cursorX); 35 | let y = chartInstance.scales[state.yAxisID].getValueForPixel(cursorY); 36 | 37 | const rounding = ( 38 | chartInstance.config.options?.plugins 39 | ?.dragData as OptionalPluginConfiguration 40 | )?.round; 41 | 42 | x = roundValue(x!, rounding); 43 | y = roundValue(y!, rounding); 44 | 45 | x = clipValue( 46 | x, 47 | chartInstance.scales[state.xAxisID].min, 48 | chartInstance.scales[state.xAxisID].max, 49 | ); 50 | y = clipValue( 51 | y, 52 | chartInstance.scales[state.yAxisID].min, 53 | chartInstance.scales[state.yAxisID].max, 54 | ); 55 | 56 | if (state.floatingBar) { 57 | // x contains the new value for one end of the floating bar 58 | // dataPoint contains the old interval [left, right] of the floating bar 59 | // calculate difference between the new value and both sides 60 | // the side with the smallest difference from the new value was the one that was dragged 61 | // return an interval with new value on the dragged side and old value on the other side 62 | let newVal; 63 | // choose the right variable based on the orientation of the graph (vertical, horizontal) 64 | if (chartInstance.config.options?.indexAxis === "y") { 65 | newVal = x; 66 | } else { 67 | newVal = y; 68 | } 69 | const diffFromLeft = Math.abs(newVal - (dataPoint as [number, number])[0]); 70 | const diffFromRight = Math.abs(newVal - (dataPoint as [number, number])[1]); 71 | 72 | if (diffFromLeft <= diffFromRight) { 73 | (dataPoint as [number, number])[0] = newVal; 74 | } else { 75 | (dataPoint as [number, number])[1] = newVal; 76 | } 77 | 78 | return dataPoint; 79 | } 80 | 81 | if ((dataPoint as Point).x !== undefined && !xAxisDraggingDisabled) { 82 | (dataPoint as Point).x = x; 83 | } 84 | 85 | if ((dataPoint as Point).y !== undefined) { 86 | if (!yAxisDraggingDisabled) { 87 | (dataPoint as Point).y = y; 88 | } 89 | return dataPoint; 90 | } else { 91 | if (chartInstance.config.options?.indexAxis === "y") { 92 | if (!xAxisDraggingDisabled) { 93 | return x; 94 | } else { 95 | return dataPoint; 96 | } 97 | } else { 98 | if (!yAxisDraggingDisabled) { 99 | return y; 100 | } else { 101 | return dataPoint; 102 | } 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /tests/__fixtures__/jest/interaction.ts: -------------------------------------------------------------------------------- 1 | import { fireEvent } from "@testing-library/react"; 2 | 3 | import type { Chart } from "chart.js"; 4 | 5 | import Offset2D from "../../__utils__/structures/Offset2D"; 6 | import Point2D from "../../__utils__/structures/Point2D"; 7 | import { getAxisDescription } from "../../__utils__/structures/axisSpec"; 8 | import { describeDatasetPointSpecOrPoint } from "../../__utils__/structures/scenario"; 9 | import { 10 | GenericDragTestParams, 11 | _genericTestDrag, 12 | } from "../generic/interaction"; 13 | 14 | export async function performDragWithoutTesting({ 15 | chart, 16 | magnet, 17 | ...parameters 18 | }: { 19 | chart: Chart; 20 | } & Pick< 21 | GenericDragTestParams, 22 | | "dragPointSpec" 23 | | "dragDestPointSpecOrStartPointOffset" 24 | | "whichAxis" 25 | | "draggableAxis" 26 | | "magnet" 27 | >) { 28 | const canvasBB = chart.canvas.getBoundingClientRect(); 29 | 30 | const getChartScales = () => chart.scales; 31 | 32 | return await _genericTestDrag({ 33 | ...parameters, 34 | canvasBB, 35 | additionalInfo: `${describeDatasetPointSpecOrPoint(parameters.dragPointSpec)} -> ${describeDatasetPointSpecOrPoint(parameters.dragDestPointSpecOrStartPointOffset)} on ${getAxisDescription(parameters.whichAxis)}`, 36 | performDrag: ({ dragStartPoint, dragDestPoint }) => { 37 | fireEvent.mouseDown(chart.canvas, { 38 | clientX: canvasBB.top + dragStartPoint.x, 39 | clientY: canvasBB.left + dragStartPoint.y, 40 | cancelable: true, 41 | bubbles: true, 42 | view: window, 43 | }); 44 | fireEvent.mouseMove(chart.canvas, { 45 | clientX: canvasBB.top + dragDestPoint.x, 46 | clientY: canvasBB.left + dragDestPoint.y, 47 | cancelable: true, 48 | bubbles: true, 49 | view: window, 50 | }); 51 | fireEvent.mouseUp(chart.canvas, { 52 | view: window, 53 | }); 54 | }, 55 | getChartDatasetSamplePixelPosition: (datasetIndex, sampleIndex) => { 56 | const meta = chart.getDatasetMeta(datasetIndex).data[sampleIndex]; 57 | 58 | return new Point2D({ 59 | x: meta.x, 60 | y: meta.y, 61 | }); 62 | }, 63 | getChartScales, 64 | isDragDataPluginEnabled: (chart.options.plugins as any).dragData, 65 | // in Jest environment, rendering on the canvas does not work as 66 | // expected & positions of data points are calculated wrong, 67 | // thus it does not make sense to test this behaviour - such tests are done during E2E testing 68 | bExpectResult: false, 69 | ...(magnet 70 | ? { 71 | magnet, 72 | // eslint-disable-next-line require-await 73 | getDataFromPointOnScreen: async (pointOnScreen, canvasBB) => { 74 | pointOnScreen = new Offset2D({ 75 | xAbs: -canvasBB.x, 76 | yAbs: -canvasBB.y, 77 | }).translatePoint(pointOnScreen); 78 | 79 | const { x, y } = { 80 | x: window.testedChart.scales["x"].getValueForPixel( 81 | pointOnScreen.x, 82 | ), 83 | y: window.testedChart.scales["y"].getValueForPixel( 84 | pointOnScreen.y, 85 | ), 86 | }; 87 | 88 | return x === undefined 89 | ? y === undefined 90 | ? undefined 91 | : y 92 | : y === undefined 93 | ? x 94 | : new Point2D({ x, y }); 95 | }, 96 | } 97 | : { magnet: undefined, getDataFromPointOnScreen: undefined }), 98 | // eslint-disable-next-line require-await 99 | getCoordinateOnScaleForAxis: async (data, axis) => 100 | isNaN(data) ? NaN : chart.scales[axis].getPixelForValue(data), 101 | }); 102 | } 103 | -------------------------------------------------------------------------------- /tests/__utils__/structures/scenario.ts: -------------------------------------------------------------------------------- 1 | import { ChartConfiguration } from "chart.js"; 2 | 3 | import { GenericDragTestParams } from "../../e2e/__fixtures__"; 4 | import { TestSuiteIdentifier } from "../../e2e/__utils__/types"; 5 | import { DatasetPointSpec } from "../testTypes"; 6 | import Offset2D from "./Offset2D"; 7 | import Point2D from "./Point2D"; 8 | import { AxisSpec } from "./axisSpec"; 9 | 10 | export type TestScenarioStepsGroup = { 11 | /** the name of this group of interaction steps */ 12 | groupName: GroupNameType; 13 | /** definitions of interaction steps */ 14 | steps: TestScenarioStep[]; 15 | /** whether this group of interactions should be skipped */ 16 | shouldBeSkipped: boolean; 17 | /** whether tests in this group should involve screenshot snapshot testing */ 18 | shouldAssertScreenshot: boolean; 19 | }; 20 | 21 | export type TestScenarioStep = { 22 | axisSpec: AxisSpec; 23 | } & Pick< 24 | GenericDragTestParams, 25 | | "dragPointSpec" 26 | | "dragDestPointSpecOrStartPointOffset" 27 | | "expectedDestPointSpecOverride" 28 | >; 29 | 30 | export type TestScenario = { 31 | /** 32 | * The configuration to pass to the tested chart instance 33 | */ 34 | configuration: Partial; 35 | /** 36 | * Definitions of groups of interaction steps 37 | */ 38 | stepGroups: TestScenarioStepsGroup[]; 39 | /** 40 | * Precision for rounding the values on the grid 41 | */ 42 | roundingPrecision: number; 43 | /** 44 | * Whether data.ts should run post-processing on the 'configuration' 45 | * object (e.g. calculate min/max limits for scales) 46 | */ 47 | postprocessConfiguration?: boolean; 48 | /** 49 | * Whether x is categorical (not linear, thus not draggable) 50 | */ 51 | isCategoricalX?: boolean; 52 | /** 53 | * Whether y is categorical (not linear, thus not draggable) 54 | */ 55 | isCategoricalY?: boolean; 56 | /** 57 | * Browsers that are not supported by this test scenario 58 | */ 59 | unsupportedBrowsers?: Array<"chromium" | "firefox" | "webkit" | "mobile">; 60 | /** 61 | * Whether to skip E2E testing and just use the scenario as data source for HTML demo 62 | */ 63 | skipE2ETesting?: boolean; 64 | /** 65 | * Custom onDrag callback to be stringified & eval-ed on page side 66 | */ 67 | onDrag?: bSealed extends true 68 | ? string 69 | : ( 70 | e: MouseEvent, 71 | datasetIndex: number, 72 | index: number, 73 | value: [number, number], 74 | ) => void; 75 | /** 76 | * Forces an axis to be the only supported draggable axis, effectively skipping 77 | * all generated test cases when a different / incompatible axis could be tested for. 78 | * 79 | * @default undefined ("both") 80 | */ 81 | forceDraggableAxis?: AxisSpec; 82 | /** 83 | * Specifies whether this page should be excluded from a given test suite. 84 | */ 85 | excludedTestSuites?: TestSuiteIdentifier[]; 86 | }; 87 | 88 | export function describeDatasetPointSpecOrPoint( 89 | datasetPointSpecOrPoint: DatasetPointSpec | Point2D | Offset2D, 90 | ): string { 91 | if ( 92 | datasetPointSpecOrPoint instanceof Point2D || 93 | datasetPointSpecOrPoint instanceof Offset2D 94 | ) { 95 | return datasetPointSpecOrPoint.toString(); 96 | } else { 97 | return `dataset #${datasetPointSpecOrPoint.datasetIndex} point #${datasetPointSpecOrPoint.index}${datasetPointSpecOrPoint.additionalOffset?.shouldBeLogged ? ` (with ${datasetPointSpecOrPoint.additionalOffset.toString()})` : ""}`; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /tests/unit/util/dragEndCallback.spec.ts: -------------------------------------------------------------------------------- 1 | import { Chart, InteractionItem } from "chart.js"; 2 | 3 | // somehow, types for jest-extended matchers in this subdirectory don't work without an explicit import 4 | import "jest-extended"; 5 | 6 | import ChartJSDragDataPlugin, { 7 | type DragDataEvent, 8 | type DragEventCallback, 9 | PluginConfiguration, 10 | dragEndCallback, 11 | } from "../../../dist/test/chartjs-plugin-dragdata-test"; 12 | import { isTestsConfigWhitelistItemAllowed } from "../../__utils__/testsConfig"; 13 | import { setupChartInstance } from "../__utils__/utils"; 14 | 15 | const rAxisID = "y"; 16 | 17 | (isTestsConfigWhitelistItemAllowed( 18 | "unit", 19 | "whitelistedTestCategories", 20 | "dragEndCallback", 21 | ) 22 | ? describe 23 | : describe.skip)("dragEndCallback", () => { 24 | let chartInstance: Chart<"line">; 25 | let callback: jest.Mock< 26 | ReturnType>, 27 | Parameters> 28 | >; 29 | 30 | beforeEach(() => { 31 | callback = jest.fn(); 32 | 33 | chartInstance = setupChartInstance("line", { 34 | plugins: { 35 | dragData: { 36 | round: 4, 37 | onDragEnd: callback, 38 | }, 39 | }, 40 | scales: { 41 | [rAxisID]: { 42 | max: 100, 43 | min: 0, 44 | reverse: false, 45 | }, 46 | }, 47 | }); 48 | }); 49 | 50 | it("should return early if state is undefined", () => { 51 | dragEndCallback<"line">({} as DragDataEvent, { id: 99, config: {} } as any); 52 | 53 | expect(callback).not.toHaveBeenCalled(); 54 | }); 55 | 56 | it("should re-enable the tooltip animation", () => { 57 | chartInstance.config.options = { 58 | plugins: { 59 | tooltip: { animation: {} }, 60 | }, 61 | }; 62 | 63 | const chartUpdateFunctionSpy = jest.spyOn(chartInstance, "update"); 64 | 65 | dragEndCallback<"line">({} as DragDataEvent, chartInstance); 66 | expect(chartInstance.config.options.plugins?.tooltip?.animation).toBe( 67 | ChartJSDragDataPlugin.statesStore.get(chartInstance.id)?.eventSettings, 68 | ); 69 | 70 | expect(chartUpdateFunctionSpy).toHaveBeenCalledWith("none"); 71 | }); 72 | 73 | for (const withMagnet of [true, false]) { 74 | // eslint-disable-next-line jest/valid-title 75 | describe(withMagnet ? "with magnet" : "without magnet", () => { 76 | it("should invoke callback with correct arguments and update isDragging to false", () => { 77 | const event = {} as DragDataEvent; 78 | const mockedMagnetReturnValue = 42; 79 | 80 | if (withMagnet) { 81 | (chartInstance.options.plugins! 82 | .dragData as PluginConfiguration<"line">)!.magnet ??= { 83 | to: () => mockedMagnetReturnValue, 84 | }; 85 | } 86 | 87 | let element: InteractionItem = { 88 | datasetIndex: 0, 89 | index: 1, 90 | element: {} as any, 91 | }; 92 | 93 | // since no previous interaction had happened, we need to set the element in state manually 94 | // note that the state before the below was a valid object, but element was null 95 | ChartJSDragDataPlugin.statesStore.set(chartInstance.id, { 96 | ...ChartJSDragDataPlugin.statesStore.get(chartInstance.id)!, 97 | element, 98 | isDragging: true, // we want to set this to true to test if it was properly set to false by dragEndCallback 99 | }); 100 | 101 | const expectedValue = withMagnet 102 | ? mockedMagnetReturnValue 103 | : chartInstance.data.datasets[element.datasetIndex].data[ 104 | element.index 105 | ]; 106 | 107 | dragEndCallback<"line">(event, chartInstance); 108 | 109 | expect(callback).toHaveBeenCalledWith( 110 | event, 111 | element.datasetIndex, 112 | element.index, 113 | expectedValue, 114 | ); 115 | expect( 116 | ChartJSDragDataPlugin.statesStore.get(chartInstance.id)?.isDragging, 117 | ).toBe(false); 118 | }); 119 | }); 120 | } 121 | }); 122 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const path = require("path"); 3 | 4 | const commonjs = require("@rollup/plugin-commonjs"); 5 | const resolve = require("@rollup/plugin-node-resolve"); 6 | const terser = require("@rollup/plugin-terser"); 7 | const istanbul = require("rollup-plugin-istanbul"); 8 | const typescript = require("@rollup/plugin-typescript"); 9 | 10 | const pkg = require("./package.json"); 11 | 12 | const banner = `/*! 13 | * ${pkg.name} v${pkg.version} 14 | * ${pkg.repository.url} 15 | * (c) 2018-${new Date().getFullYear()} ${pkg.name} contributors 16 | * Released under the ${pkg.license} license 17 | */`; 18 | 19 | /** 20 | * Create a rollup configuration for a given file 21 | * @param {string} options.file the input file 22 | * @param {import("rollup").ModuleFormat} options.format the format of the output module 23 | * @param {boolean} options.terse whether to run terser plugin 24 | * @param {boolean} options.bTestBuild whether to run istanbul plugin (if true) for coverage or to strip testing exports (if false) 25 | * @param {boolean} options.bBundleD3 whether to bundle D3 plugins or to reference them as externals 26 | * @returns {import('rollup').RollupOptions} the built options 27 | */ 28 | function bundleDragDataPlugin({ 29 | file, 30 | format, 31 | terse, 32 | bTestBuild = false, 33 | bBundleD3 = true, 34 | }) { 35 | /** @type {import('rollup').RollupOptions} */ 36 | const customOptions = { 37 | input: "src/index.ts", 38 | external: [ 39 | "chart.js", 40 | "chart.js/helpers", 41 | ...(bBundleD3 ? [] : ["d3-drag", "d3-selection"]), 42 | ], 43 | output: { 44 | exports: "named", 45 | banner, 46 | name: "ChartJSDragDataPlugin", 47 | file, 48 | format, 49 | globals: { 50 | "chart.js": "Chart", 51 | "chart.js/helpers": "Chart.helpers", 52 | }, 53 | }, 54 | plugins: [ 55 | ...(format === "umd" ? [commonjs()] : []), 56 | resolve({ 57 | browser: true, 58 | }), 59 | ...(bTestBuild 60 | ? process.env.DISABLE_ISTANBUL_COVERAGE_AT_BUILD !== "true" 61 | ? [ 62 | // in a test build, inject istanbul and keep testing exports 63 | istanbul({ 64 | exclude: ["node_modules/**/*"], 65 | }), 66 | ] 67 | : [] 68 | : []), 69 | terse ? terser() : undefined, 70 | typescript({ 71 | tsconfig: "./tsconfig.build.json", 72 | compilerOptions: { 73 | outDir: path.dirname(file), 74 | }, 75 | }), 76 | { 77 | // copy index.d.ts to file matching the bundle filename for jest tests to pick up typings 78 | closeBundle() { 79 | if (bTestBuild) { 80 | const dir = path.dirname(file); 81 | fs.mkdirSync(dir, { recursive: true }); 82 | 83 | fs.copyFileSync( 84 | path.join(dir, "index.d.ts"), 85 | file.replace(".js", ".d.ts"), 86 | ); 87 | } 88 | }, 89 | }, 90 | ], 91 | }; 92 | 93 | return customOptions; 94 | } 95 | 96 | /** @type {import('rollup').RollupOptions[]} */ 97 | const config = [ 98 | bundleDragDataPlugin({ 99 | file: pkg.main, 100 | format: "umd", 101 | terse: false, 102 | }), 103 | 104 | bundleDragDataPlugin({ 105 | file: pkg.browser, 106 | format: "umd", 107 | terse: true, 108 | }), 109 | 110 | bundleDragDataPlugin({ 111 | file: pkg.module, 112 | format: "esm", 113 | terse: true, 114 | }), 115 | 116 | // bundle for E2E testing: istanbul + bundled D3 (for browser) 117 | bundleDragDataPlugin({ 118 | file: pkg.main 119 | .replace(".js", "-test-browser.js") 120 | .replace("dist/", "dist/test/"), 121 | format: "umd", 122 | terse: false, 123 | bTestBuild: true, 124 | }), 125 | 126 | // bundle for unit/integration testing: istanbul + external D3 127 | bundleDragDataPlugin({ 128 | file: pkg.main.replace(".js", "-test.js").replace("dist/", "dist/test/"), 129 | format: "es", 130 | terse: false, 131 | bTestBuild: true, 132 | bBundleD3: false, 133 | }), 134 | ]; 135 | 136 | module.exports = config; 137 | -------------------------------------------------------------------------------- /src/types/Configuration.ts: -------------------------------------------------------------------------------- 1 | import type { ChartType } from "chart.js"; 2 | 3 | import { ChartDataItemType } from "./ChartJSTypes"; 4 | import { DragEventCallback } from "./EventTypes"; 5 | 6 | export type OptionalPluginConfiguration = 7 | | PluginConfiguration 8 | | undefined; 9 | 10 | /** 11 | * Core configuration, used as a common base for other specialized configuration types. 12 | */ 13 | type CoreConfiguration = { 14 | /** 15 | * Rounds the values to `round` decimal places. 16 | * 17 | * Example: `1`, would cause `0.1234` to be rounded to `0.1`. 18 | */ 19 | round: number; 20 | 21 | /** 22 | * Whether to show the tooltip while dragging. 23 | * 24 | * @default true 25 | */ 26 | showTooltip: boolean; 27 | }; 28 | 29 | // per-scale configuration; docstring located in typeAugmentations.d.ts to be visible to the end users 30 | export type ScaleDraggingConfiguration = boolean; 31 | 32 | // per-dataset configuration; docstring located in typeAugmentations.d.ts to be visible to the end users 33 | export type DatasetDraggingConfiguration = boolean; 34 | 35 | // per-data-point configuration; docstring located in typeAugmentations.d.ts to be visible to the end users 36 | export type DataPointDraggingConfiguration = boolean; 37 | 38 | // plugin (per-chart) configuration; docstring located in typeAugmentations.d.ts to be visible to the end users 39 | export type PluginConfiguration = 40 | CoreConfiguration & { 41 | /** 42 | * Whether to allow for dragging on the x-axis. 43 | * 44 | * **Note**: This solely works for continuous, numerical x-axis scales (no categories or dates)! 45 | * **Disabled by default.** 46 | * 47 | * @default `false` 48 | */ 49 | dragX: boolean; 50 | 51 | /** 52 | * Whether to allow for dragging on the y-axis. 53 | * 54 | * @default `true` 55 | */ 56 | dragY: boolean; 57 | 58 | /** 59 | * Callback fired during the drag numerous times. 60 | * 61 | * If the callback returns `false`, the drag is prevented and the new value is discarded. 62 | * 63 | * May be used e.g. to process the data point value or to prevent the drag. 64 | * 65 | * **Note: this solely works for continuous, numerical x-axis scales (no categories or dates)** 66 | * 67 | * @example 68 | * { 69 | * dragData: { 70 | * onDragStart: () => { 71 | * if (element.datasetIndex === 0 && element.index === 0) { 72 | * // this would prohibit dragging the first datapoint in the first 73 | * // dataset entirely 74 | * return false 75 | * } 76 | * } 77 | * } 78 | * } 79 | */ 80 | onDragStart: DragEventCallback; 81 | 82 | /** 83 | * Callback fired during the drag numerous times. 84 | * 85 | * If the callback returns `false`, the drag is prevented and the previous 86 | * value of the data point is still effective while the new one is discarded. 87 | * 88 | * May be used e.g. to process the data point value or to prevent the drag. 89 | * 90 | * @example 91 | * { 92 | * dragData: { 93 | * onDragStart: () => { 94 | * if (element.datasetIndex === 0 && element.index === 0) { 95 | * // you may control the range in which data points are allowed to be 96 | * // dragged by returning `false` in this callback 97 | * if (value < 0) return false // this only allows positive values 98 | * if (datasetIndex === 0 && index === 0 && value > 20) return false 99 | * } 100 | * } 101 | * } 102 | * } 103 | */ 104 | onDrag: DragEventCallback; 105 | 106 | /** 107 | * Callback fired after a drag completes. 108 | * 109 | * May be used e.g. to store the final data point value (after dragging) 110 | * or perform any other desired side effects. 111 | */ 112 | onDragEnd: DragEventCallback; 113 | 114 | /** 115 | * The 'magnet' function to apply to the dragged value. Can be used to round the values, e.g. snap them to the grid. 116 | */ 117 | magnet: { 118 | to: (value: ChartDataItemType) => ChartDataItemType; 119 | }; 120 | }; 121 | -------------------------------------------------------------------------------- /src/util/getElement.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | CartesianScaleOptions, 3 | ChartConfiguration, 4 | ChartType, 5 | } from "chart.js"; 6 | import { Chart } from "chart.js"; 7 | 8 | import ChartJSDragDataPlugin from "../plugin"; 9 | import { 10 | DragDataEvent, 11 | DragDataState, 12 | OptionalPluginConfiguration, 13 | } from "../types"; 14 | import { checkDraggingConfiguration } from "../util/checkDraggingConfiguration"; 15 | import { calcCartesian } from "./calc"; 16 | 17 | export function getElement( 18 | event: DragDataEvent, 19 | chartInstance: Chart, 20 | state: DragDataState | undefined = ChartJSDragDataPlugin.statesStore.get( 21 | chartInstance.id, 22 | ), 23 | ) { 24 | const callback = ( 25 | chartInstance.options?.plugins 26 | ?.dragData as OptionalPluginConfiguration 27 | )?.onDragStart; 28 | 29 | if (!state) return; 30 | 31 | const searchMode = 32 | chartInstance.config.options?.interaction?.mode ?? "nearest", 33 | searchOptions = chartInstance.config.options?.interaction ?? { 34 | intersect: true, 35 | }; 36 | 37 | state.element = chartInstance.getElementsAtEventForMode( 38 | event, 39 | searchMode, 40 | searchOptions, 41 | false, 42 | )[0]; 43 | 44 | if (state.element) { 45 | let datasetIndex = state.element.datasetIndex; 46 | let index = state.element.index; 47 | 48 | // note: type may be absent if config is ChartConfigurationCustomTypesPerDataset, in which case we pull this value from the dataset 49 | state.type = 50 | (chartInstance.config as ChartConfiguration).type ?? 51 | chartInstance.data.datasets[datasetIndex].type ?? 52 | undefined; 53 | 54 | // save element settings 55 | state.eventSettings = 56 | chartInstance.config.options?.plugins?.tooltip?.animation; 57 | 58 | const dataset = chartInstance.data.datasets[datasetIndex]; 59 | const datasetMeta = chartInstance.getDatasetMeta(datasetIndex); 60 | let curValue = dataset.data[index]; 61 | // get the id of the datasets scale 62 | state.xAxisID = datasetMeta.xAxisID!; 63 | state.yAxisID = datasetMeta.yAxisID!; 64 | state.rAxisID = datasetMeta.rAxisID!; 65 | 66 | const draggingConfiguration = checkDraggingConfiguration( 67 | chartInstance, 68 | datasetIndex, 69 | index, 70 | ), 71 | { 72 | datasetDraggingDisabled, 73 | xAxisDraggingDisabled, 74 | yAxisDraggingDisabled, 75 | dataPointDraggingDisabled, 76 | } = draggingConfiguration; 77 | 78 | // check if dragging the dataset or datapoint is prohibited 79 | if ( 80 | datasetDraggingDisabled || 81 | // dragging disabled on all scales 82 | (xAxisDraggingDisabled && yAxisDraggingDisabled) || 83 | dataPointDraggingDisabled 84 | ) { 85 | state.element = null; 86 | return; 87 | } 88 | 89 | if (state.type === "bar") { 90 | // note: stacked may be missing in RadialLinearScaleOptions 91 | state.stacked = 92 | ( 93 | chartInstance.config.options?.scales?.[ 94 | state.xAxisID 95 | ] as CartesianScaleOptions 96 | )?.stacked ?? undefined; 97 | 98 | // if a bar has a data point that is an array of length 2, it's a floating bar 99 | const samplePoint = chartInstance.data.datasets[0].data[0]; 100 | state.floatingBar = 101 | samplePoint !== null && 102 | Array.isArray(samplePoint) && 103 | samplePoint.length >= 2; 104 | 105 | let dataPoint = chartInstance.data.datasets[datasetIndex].data[index]!; 106 | let newPos = calcCartesian( 107 | event, 108 | chartInstance, 109 | dataPoint, 110 | draggingConfiguration, 111 | state, 112 | ); 113 | state.initValue = (newPos as number) - (curValue as number); 114 | } 115 | 116 | // disable the tooltip animation 117 | const showTooltipOptionValue = ( 118 | chartInstance.config.options?.plugins 119 | ?.dragData as OptionalPluginConfiguration 120 | )?.showTooltip; 121 | if ( 122 | showTooltipOptionValue === undefined || 123 | showTooltipOptionValue === true 124 | ) { 125 | chartInstance.config.options ??= {} as any; 126 | chartInstance.config.options!.plugins ??= {} as any; 127 | chartInstance.config.options!.plugins!.tooltip ??= {} as any; 128 | 129 | chartInstance.config.options!.plugins!.tooltip!.animation = false; 130 | } 131 | 132 | if (typeof callback === "function" && state.element) { 133 | if (callback(event, datasetIndex, index, curValue) === false) { 134 | state.element = null; 135 | } 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /tests/__data__/gantt.ts: -------------------------------------------------------------------------------- 1 | import dateFns from "date-fns"; 2 | 3 | import { TestScenario } from "../__utils__/structures/scenario"; 4 | import { E2EInteraction } from "../__utils__/testsConfig"; 5 | 6 | const projectSteps = [ 7 | "Hypothesis", 8 | "Researching", 9 | "Outlining", 10 | "Drafting", 11 | "Finalizing", 12 | ]; 13 | 14 | const colors = ["#69d2e7", "#a7dbd8", "#e0e4cc", "#f38630", "#fa6900"]; 15 | 16 | const DEMO_START_DATE_STR = "2024-04-09T21:05:48.060Z"; 17 | 18 | function createNewProject(daysFromNow: number, daysPerStepProj: number[]) { 19 | const startProject = dateFns.addDays( 20 | new Date(DEMO_START_DATE_STR), 21 | daysFromNow, 22 | ); 23 | 24 | const datesProj = projectSteps.reduce<[Date, Date][]>((arr, s, i) => { 25 | if (!arr.length) { 26 | const end = dateFns.addDays(startProject, daysPerStepProj[i]); 27 | arr.push([startProject, end]); 28 | } else { 29 | const lastEntry = arr[arr.length - 1]; 30 | const start = lastEntry[1]; 31 | const end = dateFns.addDays(start, daysPerStepProj[i]); 32 | arr.push([start, end]); 33 | } 34 | return arr; 35 | }, []); 36 | 37 | return datesProj; 38 | } 39 | 40 | const projectsStartDates = [0, 10, 40, 50, 55, 70, 90, 94]; 41 | const projectsTasksLengths = [ 42 | [19, 6, 25, 11, 39], 43 | [40, 9, 38, 21, 22], 44 | [39, 32, 10, 11, 25], 45 | [21, 26, 10, 32, 25], 46 | [28, 19, 26, 14, 16], 47 | [18, 9, 6, 40, 40], 48 | [7, 19, 34, 30, 39], 49 | [36, 26, 21, 18, 29], 50 | ]; 51 | const dataPerProject = projectsStartDates.map((daysFromStart, projectIndex) => 52 | createNewProject(daysFromStart, projectsTasksLengths[projectIndex]), 53 | ); 54 | 55 | export const ganttChartScenario = { 56 | configuration: { 57 | type: "bar", 58 | data: { 59 | labels: projectsStartDates.map((p, i) => `Project ${i + 1}`), 60 | datasets: projectSteps.map((step, i) => ({ 61 | label: step, 62 | data: dataPerProject.map((d) => d[i]) as any, 63 | backgroundColor: colors[i], 64 | })), 65 | }, 66 | options: { 67 | indexAxis: "y", 68 | responsive: true, 69 | plugins: { 70 | tooltip: { 71 | enabled: false, 72 | }, 73 | datalabels: { 74 | display: true, 75 | color: "white", 76 | }, 77 | }, 78 | scales: { 79 | x: { 80 | type: "time", 81 | // @ts-ignore: this is valid since chartjs-adapter-date-fns is used 82 | min: new Date(DEMO_START_DATE_STR) as any, 83 | }, 84 | y: { 85 | stacked: true, 86 | }, 87 | }, 88 | }, 89 | }, 90 | roundingPrecision: 0, 91 | postprocessConfiguration: false, 92 | onDrag: (_e, _datasetIndex, index, _value) => { 93 | // const duration = dateFns.differenceInDays(value[1], value[0]); 94 | window.testedChart.data.datasets.forEach((segment, i) => { 95 | const curDuration = dateFns.differenceInDays( 96 | // @ts-ignore: this is valid since chartjs-adapter-date-fns is used 97 | segment.data[index][1], 98 | // @ts-ignore: this is valid since chartjs-adapter-date-fns is used 99 | segment.data[index][0], 100 | ); 101 | // @ts-ignore: this is valid since chartjs-adapter-date-fns is used 102 | const thisStart = new Date(segment.data[0]); 103 | // @ts-ignore: this is valid since chartjs-adapter-date-fns is used 104 | const thisEnd = new Date(segment.data[1]); 105 | 106 | if (window.testedChart.data.datasets[i + 1] && i !== 0) { 107 | const nextStart = new Date( 108 | // @ts-ignore: this is valid since chartjs-adapter-date-fns is used 109 | window.testedChart.data.datasets[i + 1].data[index][0], 110 | ); 111 | if (nextStart !== thisEnd) { 112 | // @ts-ignore: this is valid since chartjs-adapter-date-fns is used 113 | segment.data[index] = [thisStart, nextStart]; 114 | } 115 | } 116 | if (i > 0) { 117 | const prevEnd = new Date( 118 | // @ts-ignore: this is valid since chartjs-adapter-date-fns is used 119 | window.testedChart.data.datasets[i - 1].data[index][1], 120 | ); 121 | if (thisStart !== prevEnd) { 122 | // retain current segment length 123 | const newEnd = dateFns.addDays(prevEnd, curDuration); 124 | // @ts-ignore: this is valid since chartjs-adapter-date-fns is used 125 | segment.data[index] = [prevEnd, newEnd]; 126 | } 127 | } 128 | }); 129 | window.testedChart.update("none"); 130 | }, 131 | isCategoricalX: true, 132 | isCategoricalY: false, 133 | // too complex to test in E2E, at least for now 134 | stepGroups: [], 135 | skipE2ETesting: true, 136 | } satisfies TestScenario; 137 | -------------------------------------------------------------------------------- /tests/e2e/__fixtures__/interaction.ts: -------------------------------------------------------------------------------- 1 | import type { Page } from "playwright"; 2 | import { expect, test } from "playwright-test-coverage"; 3 | 4 | import { 5 | GenericDragTestParams, 6 | _genericTestDrag, 7 | } from "../../__fixtures__/generic/interaction"; 8 | import Offset2D from "../../__utils__/structures/Offset2D"; 9 | import Point2D, { BoundingBox } from "../../__utils__/structures/Point2D"; 10 | import { getAxisDescription } from "../../__utils__/structures/axisSpec"; 11 | import { describeDatasetPointSpecOrPoint } from "../../__utils__/structures/scenario"; 12 | import { 13 | playwrightCalcCanvasBB, 14 | playwrightGetChartDatasetSamplePixelPosition, 15 | playwrightGetChartScales, 16 | } from "../__utils__/chartUtils"; 17 | import { hasGUI } from "../__utils__/testHelpers"; 18 | 19 | export type PlaywrightTestDragParams = { 20 | page: Page; 21 | isDragDataPluginDisabled?: boolean; 22 | additionalInfo?: string; 23 | } & Pick< 24 | GenericDragTestParams, 25 | | "dragPointSpec" 26 | | "dragDestPointSpecOrStartPointOffset" 27 | | "whichAxis" 28 | | "draggableAxis" 29 | | "isCategoricalX" 30 | | "isCategoricalY" 31 | | "expectedDestPointSpecOverride" 32 | | "magnet" 33 | | "getDataFromPointOnScreen" 34 | >; 35 | 36 | export async function playwrightTestDrag({ 37 | page, 38 | isDragDataPluginDisabled = false, 39 | magnet, 40 | additionalInfo, 41 | ...parameters 42 | }: PlaywrightTestDragParams) { 43 | await test.step(`Test drag on axis: ${parameters.whichAxis} from ${parameters.dragPointSpec} to ${parameters.expectedDestPointSpecOverride ?? parameters.dragDestPointSpecOrStartPointOffset}`, async () => { 44 | const canvasBB = await playwrightCalcCanvasBB(page), 45 | windowBB: BoundingBox = await page.evaluate(() => ({ 46 | x: 0, 47 | y: 0, 48 | width: window.innerWidth, 49 | height: window.innerHeight, 50 | })); 51 | 52 | return await _genericTestDrag({ 53 | ...parameters, 54 | canvasBB, 55 | additionalInfo: 56 | `${describeDatasetPointSpecOrPoint(parameters.dragPointSpec)} -> ${describeDatasetPointSpecOrPoint(parameters.dragDestPointSpecOrStartPointOffset)} on ${getAxisDescription(parameters.whichAxis)}${additionalInfo ?? ""}\n` + 57 | JSON.stringify( 58 | { 59 | isCategoricalX: parameters.isCategoricalX, 60 | isCategoricalY: parameters.isCategoricalY, 61 | }, 62 | undefined, 63 | 4, 64 | ), 65 | performDrag: async ({ dragStartPoint, dragDestPoint }) => { 66 | // playwright "loops" the cursor when leaving the window bounds, thus we want to clip the coordinates 67 | dragStartPoint = dragStartPoint.copyConstrainedTo(windowBB); 68 | dragDestPoint = dragDestPoint.copyConstrainedTo(windowBB); 69 | 70 | if (hasGUI()) { 71 | console.log( 72 | `[playwrightTestDragSignale] Dragging ${dragStartPoint.toString()} -> ${dragDestPoint.toString()}`, 73 | ); 74 | } 75 | 76 | await page.mouse.move(...dragStartPoint.toArray(), { 77 | steps: hasGUI() ? 8 : undefined, 78 | }); 79 | await page.mouse.down(); 80 | 81 | await page.mouse.move(...dragDestPoint.toArray(), { 82 | steps: hasGUI() ? 8 : undefined, 83 | }); 84 | await page.mouse.up(); 85 | }, 86 | getChartDatasetSamplePixelPosition: (datasetIndex, sampleIndex) => 87 | playwrightGetChartDatasetSamplePixelPosition( 88 | page, 89 | datasetIndex, 90 | sampleIndex, 91 | ), 92 | getChartScales: () => playwrightGetChartScales(page), 93 | isDragDataPluginEnabled: !isDragDataPluginDisabled, 94 | bExpectResult: true, 95 | expect: expect as any, 96 | ...(magnet 97 | ? { 98 | magnet, 99 | getDataFromPointOnScreen: async (pointOnScreen, canvasBB) => { 100 | pointOnScreen = new Offset2D({ 101 | xAbs: -canvasBB.x, 102 | yAbs: -canvasBB.y, 103 | }).translatePoint(pointOnScreen); 104 | 105 | const { x, y } = await page.evaluate( 106 | (pointOnScreen) => ({ 107 | x: window.testedChart.scales["x"].getValueForPixel( 108 | pointOnScreen.x, 109 | ), 110 | y: window.testedChart.scales["y"].getValueForPixel( 111 | pointOnScreen.y, 112 | ), 113 | }), 114 | pointOnScreen, 115 | ); 116 | 117 | return x === undefined 118 | ? y === undefined 119 | ? undefined 120 | : y 121 | : y === undefined 122 | ? x 123 | : new Point2D({ x, y }); 124 | }, 125 | } 126 | : { magnet: undefined, getDataFromPointOnScreen: undefined }), 127 | getCoordinateOnScaleForAxis: async (data, axis) => { 128 | return await page.evaluate( 129 | ({ data, axis }) => 130 | isNaN(data) 131 | ? NaN 132 | : window.testedChart.scales[axis].getPixelForValue(data), 133 | { data, axis }, 134 | ); 135 | }, 136 | }); 137 | }); 138 | } 139 | -------------------------------------------------------------------------------- /tests/__utils__/structures/Offset2D.ts: -------------------------------------------------------------------------------- 1 | import Point2D from "./Point2D"; 2 | 3 | export type Offset2DScale = { x: number; y: number }; 4 | 5 | type Offset2DParameters = { 6 | /** absolute offset in px (or other unit, e.g. value of chart) on the X axis */ 7 | xAbs?: number; 8 | /** absolute offset in px (or other unit, e.g. value of chart) on the Y axis */ 9 | yAbs?: number; 10 | /** relative normalized (`0` to `1`) offset of an arbitrary value to be handled elsewhere on the X axis */ 11 | xRelative?: number; 12 | /** relative normalized (`0` to `1`) offset of an arbitrary value to be handled elsewhere on the Y axis */ 13 | yRelative?: number; 14 | /** additional flag that can be utilized for external means */ 15 | shouldBeLogged?: boolean; 16 | /** whether scaledCopy() has any effect */ 17 | scalable?: boolean; 18 | }; 19 | 20 | type Offset2DOptions = Partial<{ 21 | nonScalableParameters: Array; 22 | }>; 23 | 24 | /** 25 | * Represents a 2D offset (relative to the values of a given point or absolute in an arbitrary unit) in a cartesian plane 26 | * 27 | * **NOTE**: the coordinates of this offset are counted in cartesian plane where `(0, 0)` is located 28 | * in the bottom-left corner, contrary to the HTML window coordinates, where `(0, 0)` is in the top-left corner 29 | */ 30 | class Offset2D { 31 | public readonly xAbs: number | undefined; 32 | public readonly yAbs: number | undefined; 33 | public readonly xRelative: number | undefined; 34 | public readonly yRelative: number | undefined; 35 | public readonly shouldBeLogged: boolean; 36 | public readonly scalable: boolean; 37 | 38 | constructor( 39 | { 40 | xAbs, 41 | yAbs, 42 | xRelative, 43 | yRelative, 44 | shouldBeLogged = true, 45 | scalable = true, 46 | }: Offset2DParameters, 47 | private options?: Offset2DOptions, 48 | ) { 49 | this.xAbs = xAbs; 50 | this.yAbs = yAbs; 51 | this.xRelative = xRelative; 52 | this.yRelative = yRelative; 53 | this.shouldBeLogged = shouldBeLogged; 54 | this.scalable = scalable; 55 | } 56 | 57 | /** 58 | * Translates a point by the offset 59 | * 60 | * **NOTE**: the coordinates of this offset are counted in cartesian plane where `(0, 0)` is located 61 | * in the bottom-left corner, contrary to the HTML window coordinates, where `(0, 0)` is in the top-left corner 62 | * @param point the point to translate 63 | * @returns the translated point by this offset 64 | */ 65 | translatePoint(point: Point2D): Point2D { 66 | return new Point2D({ 67 | x: point.x - ((this.xAbs ?? 0) + (this.xRelative ?? 0) * point.x), 68 | y: point.y - ((this.yAbs ?? 0) + (this.yRelative ?? 0) * point.y), 69 | }); 70 | } 71 | 72 | /** 73 | * Returns a plain object (POJO) representation of this instance 74 | * @returns the POJO 75 | */ 76 | toObject() { 77 | return { 78 | x: this.xAbs, 79 | y: this.yAbs, 80 | xRelative: this.xRelative, 81 | yRelative: this.yRelative, 82 | }; 83 | } 84 | 85 | /** 86 | * Returns a new `Offset2D` instance with the same values, but scaled by the given `scale` 87 | * @param scale the scale to apply 88 | * @returns the scaled offset (new instance) 89 | */ 90 | scaledCopy(scale: Offset2DScale): Offset2D { 91 | if (this.scalable) { 92 | return new Offset2D({ 93 | xAbs: this.options?.nonScalableParameters?.includes("xAbs") 94 | ? this.xAbs 95 | : this.xAbs === undefined 96 | ? undefined 97 | : this.xAbs * scale.x, 98 | yAbs: this.options?.nonScalableParameters?.includes("yAbs") 99 | ? this.yAbs 100 | : this.yAbs === undefined 101 | ? undefined 102 | : this.yAbs * scale.y, 103 | xRelative: this.options?.nonScalableParameters?.includes("xRelative") 104 | ? this.xRelative 105 | : this.xRelative === undefined 106 | ? undefined 107 | : this.xRelative * scale.x, 108 | yRelative: this.options?.nonScalableParameters?.includes("yRelative") 109 | ? this.yRelative 110 | : this.yRelative === undefined 111 | ? undefined 112 | : this.yRelative * scale.y, 113 | }); 114 | } else { 115 | return new Offset2D(this); 116 | } 117 | } 118 | 119 | summedCopy(offset: Offset2D): Offset2D { 120 | return new Offset2D({ 121 | xAbs: (this.xAbs ?? 0) + (offset.xAbs ?? 0), 122 | yAbs: (this.yAbs ?? 0) + (offset.yAbs ?? 0), 123 | xRelative: (this.xRelative ?? 0) + (offset.xRelative ?? 0), 124 | yRelative: (this.yRelative ?? 0) + (offset.yRelative ?? 0), 125 | shouldBeLogged: this.shouldBeLogged || offset.shouldBeLogged, 126 | scalable: 127 | this.scalable === false || offset.scalable === false ? false : true, 128 | }); 129 | } 130 | 131 | toString() { 132 | return `Offset2D { x abs. ${this.xAbs ?? 0}, x rel. ${Math.round((this.xRelative ?? 0) * 100)}%, y abs. ${this.yAbs ?? 0}, y rel. ${Math.round((this.yRelative ?? 0) * 100)}% }`; 133 | } 134 | } 135 | 136 | export default Offset2D; 137 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chartjs-plugin-dragdata", 3 | "version": "2.3.1", 4 | "description": "Draggable data points for Chart.js", 5 | "exports": { 6 | "import": "./dist/chartjs-plugin-dragdata.esm.js", 7 | "require": "./dist/chartjs-plugin-dragdata.js", 8 | "types": "./dist/typeAugmentations.d.ts" 9 | }, 10 | "main": "dist/chartjs-plugin-dragdata.js", 11 | "module": "dist/chartjs-plugin-dragdata.esm.js", 12 | "browser": "dist/chartjs-plugin-dragdata.min.js", 13 | "types": "dist/typeAugmentations.d.ts", 14 | "scripts": { 15 | "build": "npm run rollup:base", 16 | "build:no-coverage": "npx cross-env DISABLE_ISTANBUL_COVERAGE_AT_BUILD=true npm run rollup:base", 17 | "build:watch": "npm run rollup:base -- --watch", 18 | "build:watch:no-coverage": "npx cross-env DISABLE_ISTANBUL_COVERAGE_AT_BUILD=true npm run rollup:base -- --watch", 19 | "build:pages": "npx tsx pages/src/bundle.ts", 20 | "build:pages:watch": "npx tsx pages/src/watch.ts", 21 | "cleanCoverage": "npx tsx scripts/cleanCoverage.ts", 22 | "collectCoverage": "npx tsx scripts/collectCoverage.ts && npx nyc merge coverage/merged/src coverage/merged/report/merged-coverage.json && npx nyc report --report-dir=coverage/merged/report/html --reporter=html -t coverage/merged/report && npx nyc report -t coverage/merged/report --reporter=text-summary", 23 | "lint": "npx eslint .", 24 | "lint:fix": "npx eslint . --fix", 25 | "pretest": "npm run build && npm run build:pages && npm run cleanCoverage", 26 | "prepack": "npm run build:no-coverage", 27 | "prepare": "npx playwright install && npx lefthook install", 28 | "posttest": "npm run collectCoverage && npx tsx scripts/openMergedCoverageReport.ts", 29 | "rollup:base": "rollup --config rollup.config.js", 30 | "test": "npm run test:unit && npm run test:integration && npm run test:e2e", 31 | "test:unit": "npx jest --coverage --coverageDirectory=coverage/unit --config jest.config.ts --selectProjects unit", 32 | "test:integration": "npx jest --coverage --coverageDirectory=coverage/integration --config jest.config.ts --selectProjects integration", 33 | "test:e2e": "npm run test:e2e:base && npm run test:e2e:posttest", 34 | "test:e2e:base": "playwright test", 35 | "test:e2e:ui": "npm run test:e2e:base -- --ui && npm run test:e2e:posttest", 36 | "test:e2e:headed": "npm run test:e2e:base --headed && npm run test:e2e:posttest", 37 | "test:e2e:updateSnapshots": "npm run test:e2e -- -- --update-snapshots && npm run test:e2e:posttest", 38 | "test:e2e:posttest": "npx nyc report --reporter=json --report-dir=coverage/e2e && npx nyc report --reporter=html --report-dir=coverage/e2e/report/html", 39 | "release": "release-it", 40 | "knip": "knip" 41 | }, 42 | "repository": { 43 | "type": "git", 44 | "url": "https://github.com/artus9033/chartjs-plugin-dragdata.git" 45 | }, 46 | "keywords": [ 47 | "chartjs", 48 | "plugin", 49 | "drag", 50 | "draggable", 51 | "data" 52 | ], 53 | "author": "Christoph Pahmeyer ", 54 | "license": "MIT", 55 | "peerDependencies": { 56 | "chart.js": "^3.9.1 || ^4.0.1" 57 | }, 58 | "dependencies": { 59 | "d3-drag": "^3.0.0", 60 | "d3-selection": "^3.0.0", 61 | "playwright-test-coverage": "^1.2.12" 62 | }, 63 | "devDependencies": { 64 | "@babel/core": "^7.28.0", 65 | "@babel/plugin-transform-modules-commonjs": "^7.27.1", 66 | "@babel/preset-env": "^7.28.0", 67 | "@babel/preset-react": "^7.27.1", 68 | "@babel/preset-typescript": "^7.27.1", 69 | "@callstack/eslint-config": "^15.0.0", 70 | "@commitlint/config-conventional": "^19.8.1", 71 | "@cspell/eslint-plugin": "^9.1.5", 72 | "@playwright/test": "^1.54.1", 73 | "@release-it/conventional-changelog": "^10.0.1", 74 | "@rollup/plugin-commonjs": "^28.0.6", 75 | "@rollup/plugin-node-resolve": "^16.0.1", 76 | "@rollup/plugin-terser": "^0.4.4", 77 | "@rollup/plugin-typescript": "^12.1.4", 78 | "@testing-library/dom": "^10.4.0", 79 | "@testing-library/jest-dom": "^6.6.3", 80 | "@testing-library/react": "^16.3.0", 81 | "@testing-library/vue": "^8.1.0", 82 | "@trivago/prettier-plugin-sort-imports": "^5.2.2", 83 | "@types/config": "^3.3.5", 84 | "@types/d3-drag": "^3.0.7", 85 | "@types/d3-selection": "^3.0.11", 86 | "@types/ejs": "^3.1.5", 87 | "@types/jest": "^30.0.0", 88 | "@types/lodash": "^4.17.20", 89 | "@types/node": "^24.0.14", 90 | "@types/react": "^19.1.8", 91 | "@types/signale": "^1.4.7", 92 | "@typescript-eslint/parser": "^8.37.0", 93 | "@vue/vue3-jest": "^29.2.6", 94 | "babel-jest": "^30.0.4", 95 | "canvas": "^3.1.2", 96 | "chart.js": "^4.5.0", 97 | "chartjs-adapter-date-fns": "^3.0.0", 98 | "chartjs-plugin-datalabels": "^2.2.0", 99 | "chokidar": "^4.0.3", 100 | "commitlint": "^19.8.1", 101 | "config": "^4.0.0", 102 | "cross-env": "^7.0.3", 103 | "d3-drag": "^3.0.0", 104 | "d3-selection": "^3.0.0", 105 | "date-fns": "^4.1.0", 106 | "ejs": "^3.1.10", 107 | "eslint": "^9.31.0", 108 | "eslint-config-prettier": "^10.1.5", 109 | "eslint-plugin-prettier": "^5.5.1", 110 | "jest": "^30.0.4", 111 | "jest-environment-jsdom": "^30.0.4", 112 | "jest-extended": "^6.0.0", 113 | "knip": "^5.61.3", 114 | "lefthook": "^1.12.2", 115 | "lint-staged": "^16.1.2", 116 | "lodash": "^4.17.21", 117 | "node-sass": "^9.0.0", 118 | "nyc": "^17.1.0", 119 | "open": "^10.2.0", 120 | "playwright": "^1.54.1", 121 | "prettier": "^3.6.2", 122 | "react": "^19.1.0", 123 | "react-chartjs-2": "^5.3.0", 124 | "react-dom": "^19.1.0", 125 | "release-it": "^19.0.3", 126 | "resize-observer-polyfill": "^1.5.1", 127 | "rollup": "^4.45.0", 128 | "rollup-plugin-copy": "^3.5.0", 129 | "rollup-plugin-istanbul": "^5.0.0", 130 | "signale": "^1.4.0", 131 | "ts-node": "^10.9.2", 132 | "tslib": "^2.8.1", 133 | "tsx": "^4.20.3", 134 | "typescript": "^5.8.3", 135 | "vue": "^3.5.17", 136 | "vue-chartjs": "^5.3.2" 137 | }, 138 | "files": [ 139 | "dist/**/*", 140 | "!dist/test/**/*", 141 | "package.json", 142 | "LICENSE", 143 | "README.md", 144 | "CHANGELOG.md" 145 | ] 146 | } 147 | --------------------------------------------------------------------------------