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.