chart.js

Annotations will stop drag once mouse moved too fast or going over another element


I am using ChartJS to display a chart and would like to interact with the annotations via the annotationsplugin.

In addition the annotations are draggable, so the user can drag them around freely. I am using the drag documentation for that.

This works fine, however, I need to make sure that the mouse is moved/dragged very slowly. If I move it slightly too fast, it will stop.

I thought I remove the mouseout event handler of the example. However, this does not work and once I move the mouse too fast and the mouse is slightly off the edges of that annotation, it stops.

It is also noticable on the example of the annotation plugin in the link above.

Unfortunately, I have very thin lines I would like to drag, so I need to move the mouse in slow motion in order to make this work. In order to test the problem, you can simply try to move the example at the very edge of the annotations.


Solution

  • Using the code from the example you linked to (and that is just an example with minimal features, not a production-ready dragging api), one can overcome the issue with dragging being stopped when the pointer enters another annotation by preventing the default functionality of enter and leave while dragging:

       enter(ctx) {
          if(lastEvent){
             return;
          }
          element = ctx.element;
       },
       leave() {
          if(lastEvent){
             return
          }
          element = undefined;
          // lastEvent = undefined;
       }
    

    lastEvent is defined (truthy) while dragging is on. That seems to also take care of the fast moving mouse, since dragging is not canceled if the pointer temporarily gets outside the element before its position was updated.

    Demo snippet

    //code mostly from https://www.chartjs.org/chartjs-plugin-annotation/3.1.0/samples/interaction/dragging.html
    const data = {
       labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
       datasets: [{
          type: 'line',
          label: 'Dataset 1',
          borderColor: 'rgb(54, 162, 235)',
          borderWidth: 2,
          fill: false,
          data: Array.from({length: 100}, ()=>Math.round(Math.random() * 100))
       }]
    };
    
    const annotation1 = {
       type: 'box',
       backgroundColor: 'rgba(165, 214, 167, 0.2)',
       borderColor: 'rgb(165, 214, 167)',
       borderWidth: 2,
       label: {
          display: true,
          content: ['Box annotation', 'to drag'],
          textAlign: 'center'
       },
       xMax: 'May',
       xMin: 'April',
       xScaleID: 'x',
       yMax: 75,
       yMin: 25,
       yScaleID: 'y'
    };
    
    const annotation2 = {
       type: 'label',
       backgroundColor: 'rgba(255, 99, 132, 0.25)',
       borderWidth: 3,
       borderColor: 'black',
       content: ['Label annotation', 'to drag'],
       callout: {
          display: true,
          borderColor: 'black',
       },
       position: {
          x: "25%",
          y: "start"
       },
       xValue: 1,
       yValue: 40
    };
    
    const annotation3 = {
       type: 'point',
       backgroundColor: 'rgba(0, 255, 255, 0.4)',
       borderWidth: 2,
       borderColor: 'black',
       radius: 20,
       xValue: 'March', // oe 2
       yValue: 50
    };
    
    const annotation4 = {
       type: 'polygon',
       backgroundColor: 'rgba(150, 0, 0, 0.25)',
       borderWidth: 2,
       borderColor: 'black',
       radius: 50,
       sides: 6,
       xValue: 5,// or 'June',
       yValue: 20
    };
    
    
    let element;
    let lastEvent;
    
    const drag = function(moveX, moveY) {
       element.x += moveX;
       element.y += moveY;
       element.x2 += moveX;
       element.y2 += moveY;
       element.centerX += moveX;
       element.centerY += moveY;
       if (element.elements && element.elements.length) {
          for (const subEl of element.elements) {
             subEl.x += moveX;
             subEl.y += moveY;
             subEl.x2 += moveX;
             subEl.y2 += moveY;
             subEl.centerX += moveX;
             subEl.centerY += moveY;
             subEl.bX += moveX;
             subEl.bY += moveY;
          }
       }
    };
    
    const handleElementDragging = function(event) {
       if (!lastEvent || !element) {
          return;
       }
       const moveX = event.x - lastEvent.x;
       const moveY = event.y - lastEvent.y;
       drag(moveX, moveY);
       lastEvent = event;
       return true;
    };
    
    const handleDrag = function(event) {
       if (element) {
          switch (event.type) {
             case 'mousemove':
                return handleElementDragging(event);
             case 'mouseout':
             case 'mouseup':
                lastEvent = undefined;
                break;
             case 'mousedown':
                lastEvent = event;
                break;
             default:
          }
       }
    };
    
    const config = {
       type: 'line',
       plugins: [{
          beforeEvent(chart, args) {
             if (handleDrag(args.event)) {
                args.changed = true;
                return;
             }
          }
       }],
       data,
       options: {
          events: ['mousedown', 'mouseup', 'mousemove', 'mouseout'],
          scales: {
             y: {
                beginAtZero: true,
                min: 0,
                max: 100
             }
          },
          plugins: {
             annotation: {
                enter(ctx) {
                   if(lastEvent){
                      return;
                   }
                   element = ctx.element;
                },
                leave() {
                   if(lastEvent){
                      return
                   }
                   element = undefined;
                   // lastEvent = undefined;
                },
                annotations: {
                   annotation1,
                   annotation2,
                   annotation3,
                   annotation4
                }
             }
          }
       }
    };
    
    new Chart('myChart', config);
    <div style="height: 300px">
        <canvas id="myChart">
        </canvas>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/chartjs-plugin-annotation/3.1.0/chartjs-plugin-annotation.min.js"></script>

    Please let me know if this doesn't cover all the issues of this category.