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.