Tracking Cursor’s position via API

This tutorial will explain how you can track Cursor's position within plot area, as well as map it to real values.

Prerequisites

If you haven't done so already, we strongly suggest you take a look at the "Chart Cursor" article, which walks through every step and aspect of using this chart helper.

We will also be relying on event handlers in this tutorial, so it might be a good idea to familiarize yourself with the concept by checking out "Event Listeners" article.

Tracking Cursor's position

Getting position

To track Cursor's position we will be using its cursorpositionchanged event.

It will be invoked every time a cursor's position changes over the plot area.

It goes something like this:

chart.cursor.events.on("cursorpositionchanged", function(ev) {
  // ...
});
chart.cursor.events.on("cursorpositionchanged", function(ev) {
  // ...
});
{
  // ...
  "cursor": {
    "events": {
      "cursorpositionchanged": function(ev) {
        // ..
      }
    }
  }
}

Now, whenever you glide your mouse over or touch chart's plot area, our event handle will fire. At this point it does nothing since we haven't added any code to it, yet.

Let's see how we can make it useful.

The event handler function will receive a parameter object which, among other properties, will have target property that holds reference to cursor itself.

Since we now have Cursor's object, we can examine its reference and find out that it has properties xPosition and yPosition that respectively hold cursor's relative horizontal and vertical positions within plot area.

chart.cursor.events.on("cursorpositionchanged", function(ev) {
  console.log("x: ", ev.target.xPosition);
  console.log("y: ", ev.target.yPosition);
});
chart.cursor.events.on("cursorpositionchanged", function(ev) {
  console.log("x: ", ev.target.xPosition);
  console.log("y: ", ev.target.yPosition);
});
{
  // ...
  "cursor": {
    "events": {
      "cursorpositionchanged": function(ev) {
        console.log("x: ", ev.target.xPosition);
        console.log("y: ", ev.target.yPosition);
      }
    }
  }
}

As we mentioned earlier, the position properties will show cursor's relative position within plot area, where 0 indicates left/top edge, 0.5 - the middle, and 1 - right/bottom edge.

Not very useful, huh?

Accounting for zoom

There's a catch. It's important. Pay close attention to the next few paragraphs.

A Cursor's xPosition and yPosition corespond to its horizontal and vertical position within plot area (plotContainer). Plot area's dimensions are constant and never change.

This might not be the case with chart's axes. While chart is fully zoomed out, the position within plot area (cursor's position) and position within chart's axes are identical. However, when you zoom in, position within axis and plot area become misaligned.

E.g. Cursor's position 0.2 will always point to the 20% from the left of the plot area. However, same value position in horizontal axis scope might point to a wholly different place, even to a point that's currently outside plot area.

This is why you should always convert Cursor's position to Axis' position, using the latter's toAxisPosition().

This Axis' method converts Cursor's (plot area) position into particular axis position, taking zoom into account.

We'll show you how to use it in the next chapter.

Converting to real value, date, or category

The next obvious step is to convert relative "position" to a real value within real axis scale.

Lucky for us, each type of axis has helper functions that will convert position to a real value within it's scale.

Below are listed the functions that will help us:

Axis type Conversion function Comment
All axis types getPositionLabel(position) Returns an actual label that is or would be at that specific position on axis.
CategoryAxis positionToIndex(position) Returns an index of the category that corresponds relative position.
DateAxis positionToDate(position) Returns a Date object that corresponds to relative position.
ValueAxis positionToValue(position) Returns a numeric value that corresponds to the relative position.

So, if our chart has a horizontal Date axis, and a vertical Value axis, we could use positionToDate and positionToValue respectively to convert Cursor's relative xPosition and yPosition to real values.

chart.cursor.events.on("cursorpositionchanged", function(ev) {
  let xAxis = ev.target.chart.xAxes.getIndex(0);
  let yAxis = ev.target.chart.yAxes.getIndex(0);
  console.log("x: ", xAxis.positionToDate(xAxis.toAxisPosition(ev.target.xPosition)));
  console.log("y: ", yAxis.positionToValue(yAxis.toAxisPosition(ev.target.yPosition)));
});
chart.cursor.events.on("cursorpositionchanged", function(ev) {
  var xAxis = ev.target.chart.xAxes.getIndex(0);
  var yAxis = ev.target.chart.yAxes.getIndex(0);
  console.log("x: ", xAxis.positionToDate(xAxis.toAxisPosition(ev.target.xPosition)));
  console.log("y: ", yAxis.positionToValue(yAxis.toAxisPosition(ev.target.yPosition)));
});
{
  // ...
  "cursor": {
    "events": {
      "cursorpositionchanged": function(ev) {
        var xAxis = ev.target.chart.xAxes.getIndex(0);
        var yAxis = ev.target.chart.yAxes.getIndex(0);
        console.log("x: ", xAxis.positionToDate(xAxis.toAxisPosition(ev.target.xPosition)));
        console.log("y: ", yAxis.positionToValue(yAxis.toAxisPosition(ev.target.yPosition)));
      }
    }
  }
}

Now, when you will glide the Cursor of the plot area, you will be getting meaningful x and y coordinates, expressed in values that correspond to the axis type and scale.

Logging last position

The last thing remaining is to log this data somewhere, so that we can access it later, when we need it, perhaps on click.

For that we can define a variable that holds it:

let cursorPosition = {
  x: null,
  y: null
};
chart.cursor.events.on("cursorpositionchanged", function(ev) {
  let xAxis = ev.target.chart.xAxes.getIndex(0);
  let yAxis = ev.target.chart.yAxes.getIndex(0);
  cursorPosition.x = xAxis.positionToDate(xAxis.toAxisPosition(ev.target.xPosition));
  cursorPosition.y = yAxis.positionToValue(yAxis.toAxisPosition(ev.target.yPosition));
});
var cursorPosition = {
  x: null,
  y: null
};
chart.cursor.events.on("cursorpositionchanged", function(ev) {
  var xAxis = ev.target.chart.xAxes.getIndex(0);
  var yAxis = ev.target.chart.yAxes.getIndex(0);
  cursorPosition.x = xAxis.positionToDate(xAxis.toAxisPosition(ev.target.xPosition));
  cursorPosition.y = yAxis.positionToValue(yAxis.toAxisPosition(ev.target.yPosition));
});
{
  // ...
  "cursor": {
    "events": {
      "cursorpositionchanged": function(ev) {
        var xAxis = ev.target.chart.xAxes.getIndex(0);
        var yAxis = ev.target.chart.yAxes.getIndex(0);
        cursorPosition.x = xAxis.positionToDate(xAxis.toAxisPosition(ev.target.xPosition));
        cursorPosition.y = yAxis.positionToValue(yAxis.toAxisPosition(ev.target.yPosition));
      }
    }
  }
}

As simple, as that.

Using tracked info

Obviously, just tracking stuff is not useful in itself. Let's put it to good use.

Let's do something when user clicks on the plot area.

To register click/tap on a XY chart's plot area, we can use hit event on chart's plotContainer:

chart.plotContainer.events.on("hit", function(ev) {
  console.log("Clicked on", cursorPosition);
});
chart.plotContainer.events.on("hit", function(ev) {
  console.log("Clicked on", cursorPosition);
});
{
  // ...
  "plotContainer": {
    "events": {
      "hit": function(ev) {
        console.log("Clicked on", cursorPosition);
      }
    }
  }
}

Now, whenever user clicks anywhere within plot area, we'll get coordinates of that event, expressed in real values.

Here's the full working chart:

See the Pen amCharts V4: Tracking cursor position in axes by amCharts (@amcharts) on CodePen.24419