Search code examples
javascripthtmlhighchartsannotations

Highcharts draggable annotaion how to move by steps?


I would like to make the annotations move by a certain step and not stop in between two columns in axis.

For example, the x axis is [0, 10, 20, 30]. When dragging the annotation, I want its point to change directly from {x: 10, y: 10000} to {x: 20, y: 10000} without going to x: 15. i.e. make the annotation a bit sticky to the column and not sitting between columns.

I also need to get the current annotation point so I can update some other element.

I tried the solution from stackoverflow but it doesn't work.

Here is my current code. CodePen

enter code here

Edit 1:
Thanks to the solution from @ppotaczek, the annotation can move by steps now. Here is the updated code JSFiddle. What needs to be refined is when dragging the annotation too fast, it cannot keep up with the mouse. Is it because of the performance of Highcharts.redraw() method and how can we solve this?

Another unsolved question is -> how to get the current point of the annotation? It seems the annotation object doesn't provide any value about that. All I can think of is keep a record of the initial point, then everytime the annotation is moved by a step, update the record. Is there a better way?

//this code can deal with vertical and horizontal annotations now.
Highcharts.Annotation.prototype.onDrag = function(e) {
if (
  this.chart.isInsidePlot(
    e.chartX - this.chart.plotLeft,
    e.chartY - this.chart.plotTop
  )
) {
  var translation = this.mouseMoveToTranslation(e),
    xAxis = this.chart.xAxis[0],
    yAxis = this.chart.yAxis[0],
    xStep = 1,
    yStep = 1000,
    pxStep = xAxis.toPixels(xStep) - xAxis.toPixels(0),
    pyStep = yAxis.toPixels(yStep) - yAxis.toPixels(0);

  if (!Highcharts.defined(this.potentialTranslation)) { //not defined
    this.potentialTranslation = 0;
  }

  if (this.options.draggable === 'x') {
    this.potentialTranslation += translation.x;
    translation.y = 0;
    if (Math.abs(this.potentialTranslation) >= Math.abs(pxStep)) {
      translation.x = (this.potentialTranslation > 0) ? pxStep : -pxStep;
      this.potentialTranslation = 0;
      //this.potentialTranslation = undefined;

      if (this.points.length) {
        this.translate(translation.x, translation.y);
      } else {
        this.shapes.forEach(function(shape) {
          shape.translate(translation.x, translation.y);
        });
        this.labels.forEach(function(label) {
          label.translate(translation.x, translation.y);
        });
      }
    }
  }
  if (this.options.draggable === 'y') {
    this.potentialTranslation += translation.y;
    translation.x = 0;
    if (Math.abs(this.potentialTranslation) >= Math.abs(pyStep)) {
      translation.y = (this.potentialTranslation > 0) ? -pyStep : pyStep;
      this.potentialTranslation = 0;

      if (this.points.length) {
        this.translate(translation.x, translation.y);
      } else {
        this.shapes.forEach(function(shape) {
          shape.translate(translation.x, translation.y);
        });
        this.labels.forEach(function(label) {
          label.translate(translation.x, translation.y);
        });
      }
    }
  }

  this.redraw(false);
  }
}

Edit 2: Thanks to @ppotaczek again! The "mouse moving too fast" issue is solved now. I apply his updates and also my idea of how to get the annotation value into JSFiddle.


Solution

  • You can overwrite Highcharts.Annotation.prototype.onDrag method:

    (function(H) {
        H.Annotation.prototype.onDrag = function(e) {
            if (
                this.chart.isInsidePlot(
                    e.chartX - this.chart.plotLeft,
                    e.chartY - this.chart.plotTop
                )
            ) {
                var translation = this.mouseMoveToTranslation(e),
                    xAxis = this.chart.xAxis[0],
                    step = 0.5,
                    pxStep = xAxis.toPixels(step) - xAxis.toPixels(0);
    
                if (!H.defined(this.potentialTranslation)) {
                    this.potentialTranslation = 0;
                }
    
                this.potentialTranslation += translation.x;
    
                if (Math.abs(this.potentialTranslation) >= pxStep) {
                    translation.y = 0;
                    translation.x = (this.potentialTranslation > 0) ? pxStep : -pxStep;
                    this.potentialTranslation = 0;
    
                    if (this.points.length) {
                        this.translate(translation.x, translation.y);
                    } else {
                        this.shapes.forEach(function(shape) {
                            shape.translate(translation.x, translation.y);
                        });
                        this.labels.forEach(function(label) {
                            label.translate(translation.x, translation.y);
                        });
                    }
                }
    
                this.redraw(false);
            }
        }
    }(Highcharts));
    

    Live demo: http://jsfiddle.net/BlackLabel/vmon2chx/

    Docs: https://www.highcharts.com/docs/extending-highcharts