Getting data items within cursor selection

This tutorial will show how we can grab data items that fall within the selection of a cursor on a scatter (XYChart) chart.

Setup

Our chart setup will use a ValueAxis as its Y-axis, and a DateAxis for its X-axis, just so we can look at how we can extract selection boundaries for both types of axes.

Our chart cursor's behavior will be set to "selectXY".

The code

We'll use cursor's "selectended" event to grab data items within the selection.

cursor.events.on("selectended", function(ev) {
  
  // Get actors
  let cursor = ev.target;
  
  // Get selection boundaries
  let x1 = xAxis.positionToDate(
    xAxis.toAxisPosition(cursor.getPrivate("downPositionX"))
  ).getTime();
  let x2 = xAxis.positionToDate(
    xAxis.toAxisPosition(cursor.getPrivate("positionX"))
  ).getTime();
  let y1 = yAxis.positionToValue(
    yAxis.toAxisPosition(cursor.getPrivate("downPositionY"))
  );
  let y2 = yAxis.positionToValue(
    yAxis.toAxisPosition(cursor.getPrivate("positionY"))
  );
  
  // Account for centering of bullets on a DateAxis
  let baseInterval = xAxis.getPrivate("baseInterval");
  let baseDuration = am5.time.getDuration(baseInterval.timeUnit, baseInterval.count) * series.get("locationX");
  x1 -= baseDuration;
  x2 -= baseDuration;
  
  // Assemble bounds
  let bounds = {
    left: x1 > x2 ? x2 : x1,
    right: x1 > x2 ? x1 : x2,
    top: y1 < y2 ? y1 : y2,
    bottom: y1 < y2 ? y2 : y1
  };
  
  // Filter data items within boundaries
  let results = [];
  am5.array.each(series.dataItems, function(dataItem) {
    let x = dataItem.get("valueX");
    let y = dataItem.get("valueY");
    if (am5.math.inBounds({ x: x, y: y }, bounds)) {
      results.push(dataItem);
    }
  });
  
  // Results
  console.log(results)
});
cursor.events.on("selectended", function(ev) {
  
  // Get actors
  var cursor = ev.target;
  
  // Get selection boundaries
  var x1 = xAxis.positionToDate(
    xAxis.toAxisPosition(cursor.getPrivate("downPositionX"))
  ).getTime();
  var x2 = xAxis.positionToDate(
    xAxis.toAxisPosition(cursor.getPrivate("positionX"))
  ).getTime();
  var y1 = yAxis.positionToValue(
    yAxis.toAxisPosition(cursor.getPrivate("downPositionY"))
  );
  var y2 = yAxis.positionToValue(
    yAxis.toAxisPosition(cursor.getPrivate("positionY"))
  );
  
  // Account for centering of bullets on a DateAxis
  var baseInterval = xAxis.getPrivate("baseInterval");
  var baseDuration = am5.time.getDuration(baseInterval.timeUnit, baseInterval.count) * series.get("locationX");
  x1 -= baseDuration;
  x2 -= baseDuration;
  
  // Assemble bounds
  var bounds = {
    left: x1 > x2 ? x2 : x1,
    right: x1 > x2 ? x1 : x2,
    top: y1 < y2 ? y1 : y2,
    bottom: y1 < y2 ? y2 : y1
  };
  
  // Filter data items within boundaries
  var results = [];
  am5.array.each(series.dataItems, function(dataItem) {
    var x = dataItem.get("valueX");
    var y = dataItem.get("valueY");
    if (am5.math.inBounds({ x: x, y: y }, bounds)) {
      results.push(dataItem);
    }
  });
  
  // Results
  console.log(results)
});

Let's look at the code above.

Retrieving the boundaries

To retrieve boundaries of the selection, we grab cursor's private (read-only) settings: downPositionX and downPositionY (those hold relative position of where selection started), and positionX and positionY (that indicate position of the end of selection).

We convert those positions to positions on the respective axes using their toAxisPosition() methods.

This step is needed to convert the positions in cases chart is zoomed.

Finally, we convert X positions to actual dates using date axis' positionToDate(), and Y positions to positionToValue() methods.

Adjusting for bullet centering

Before we can use the boundaries, there's one crucial step left.

Date axis would make series center its bullets within its base interval. E.g. so the bullet that has timestamp of a start of the day, would be centered within the day.

The following code takes that into account when calculating final boundaries.

// Account for centering of bullets on a DateAxis
let baseInterval = xAxis.getPrivate("baseInterval");
let baseDuration = am5.time.getDuration(baseInterval.timeUnit, baseInterval.count) * series.get("locationX");
x1 -= baseDuration;
x2 -= baseDuration;
// Account for centering of bullets on a DateAxis
var baseInterval = xAxis.getPrivate("baseInterval");
var baseDuration = am5.time.getDuration(baseInterval.timeUnit, baseInterval.count) * series.get("locationX");
x1 -= baseDuration;
x2 -= baseDuration;

Filtering data items

All that's left is to iterate through series' data items and check whether they fall within selection boundaries.

We can use amCharts 5 built-in array function am5.array.each() to iterate through series data items, as well as am5.math.inBounds() to quickly check whether data item is within the boundaries.

Example

See the Pen
Getting data items within cursor selection
by amCharts team (@amcharts)
on CodePen.0

Posted in Uncategorized