How to synchronize crosshair in multiple Flutter SfCartesianChart ?
In this article, we described how to synchronize the crosshair in multiple charts.
In Flutter Cartesian Chart widget, you can synchronize the crosshair of multiple charts using the available callback events and public methods. A callback event is a callback function or method, which you can pass as an argument to another function or method. It can perform an action when you require it, and public methods are methods that can be called by using the class object where they are defined. The callback event used for synchronizing crosshair is the onChartTouchInteractionMove, onChartTouchInteractionDown, onChartTouchInteractionUp and the show public method of the crosshair is used for activating the crosshair in multiple charts in a synchronized fashion on user interaction.
The following steps explains how to synchronize the crosshair in multiple charts.
Step 1: Define two CrosshairBehavior variables with the required properties globally for the first and second charts respectively to use the public methods of crosshair.
// Crosshair behavior for first chart.
late CrosshairBehavior crosshair1;
// Crosshair behavior for second chart.
late CrosshairBehavior crosshair2;
@override
void initState() {
CrosshairBehavior crosshair1 = CrosshairBehavior(
enable: true,
activationMode: ActivationMode.singleTap,
);
CrosshairBehavior crosshair2 = CrosshairBehavior(
enable: true,
activationMode: ActivationMode.singleTap,
);
super.initState();
}
Step 2: Declare two ChartSeriesController variables to get the series details when tapping on the chart and two position variables to get the tapped position of the chart.
// Initialize the variable to get the series details for the first chart.
ChartSeriesController? _firstChartController;
// Initialize the variable to get the series details for the second chart.
ChartSeriesController? _secondChartController;
// Initialize the variable to get the tapped position for the first chart.
Offset? _firstPosition;
// Initialize the variable to get the tapped position for the second chart.
Offset? _secondPosition;
Step 3: Initialize a Cartesian chart (First chart) as an individual StatefulWidget with all the necessary properties and also set the globally defined crosshair behavior variable (crosshair1) of the first chart in the crosshairBehaviour property available in the chart.
class FirstChart extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return FirstChartState();
}
}
class FirstChartState extends State<FirstChart> {
@override
Widget build(BuildContext context) {
return SfCartesianChart(
primaryXAxis: CategoryAxis(),
title: ChartTitle(text: 'Chart 1'),
crosshairBehavior: crosshair1, // Crosshair behavior variable of the first chart.
series: <CartesianSeries<ChartSampleData, String>>[
LineSeries<ChartSampleData, String>(
dataSource: charData,
xValueMapper: (ChartSampleData data, int index) => data.x,
yValueMapper: (ChartSampleData data, int index) => data.y,
),
],
);
}
}
Step 4: Initialize another Cartesian chart (Second chart) as an individual StatefulWidget with all the necessary properties and also set the globally defined crosshair behavior variable (crosshair2) of the second chart in the crosshairBehaviour property available in the chart.
class SecondChart extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return SecondChartState();
}
}
class SecondChartState extends State<SecondChart> {
@override
Widget build(BuildContext context) {
return SfCartesianChart(
primaryXAxis: CategoryAxis(),
title: ChartTitle(text: 'Chart 2'),
crosshairBehavior: crosshair2, // Crosshair behavior variable of the second chart.
series: <CartesianSeries<ChartSampleData, String>>[
LineSeries<ChartSampleData, String>(
dataSource: charData,
xValueMapper: (ChartSampleData data, int index) => data.x,
yValueMapper: (ChartSampleData data, int index) => data.y,
),
],
);
}
Step 5: The synchronization of crosshair can be done with the help of the onChartTouchInteractionDown, onChartTouchInteractionMove, onChartTouchInteractionUp callback events and the crosshair’s show public method in the chart.
In the first chart, with the help of the pixel position arguments are obtained from the onChartTouchInteractionDown, onChartTouchInteractionMove event on user interaction, call the crosshair show method for the second chart with the obtained pixel position arguments (dx and dy) as the parameters.
class FirstChartState extends State<FirstChart> {
// Declare variable to avoid calling of the first chart's crosshair.
bool _isInteractive = false;
@override
Widget build(BuildContext context) {
return SfCartesianChart(
onChartTouchInteractionDown: (ChartTouchInteractionArgs tapArgs) {
// Set to true when touched on the chart.
_isInteractive = true;
final CartesianChartPoint chartPoint = _secondChartController!.pixelToPoint(tapArgs.position);
_secondPosition = _secondChartController!.pointToPixel(chartPoint);
// Calls the second chart’s crosshair show method with pixel position arguments as parameters.
_crosshair2.show(_secondPosition!.dx, _secondPosition!.dy, 'pixel');
},
onChartTouchInteractionUp: (ChartTouchInteractionArgs tapArgs) {
// Set to false when touched out of the chart.
_isInteractive = false;
_crosshair2.hide();
},
onChartTouchInteractionMove: (ChartTouchInteractionArgs tapArgs) {
if (_isInteractive) {
final CartesianChartPoint chartPoint = _secondChartController!.pixelToPoint(tapArgs.position);
_secondPosition = _secondChartController!.pointToPixel(chartPoint);
// Calls the second chart’s crosshair show method with pixel position arguments as parameters.
_crosshair2.show(_secondPosition!.dx, _secondPosition!.dy, 'pixel');
}
},
crosshairBehavior: _crosshair1, // Crosshair behavior variable of the first chart.
title: const ChartTitle(text: 'Chart 1'),
backgroundColor: Colors.white,
plotAreaBorderWidth: 0,
series: <CartesianSeries<ChartData, double>>[
LineSeries<ChartData, double>(
dataSource: <ChartData>[
ChartData(1, 15),
ChartData(2, 25),
ChartData(3, 35),
ChartData(4, 20),
ChartData(5, 40),
ChartData(6, 30),
ChartData(7, 45),
ChartData(8, 25),
ChartData(9, 50),
ChartData(10, 30)
],
xValueMapper: (ChartData data, int index) => data.x,
yValueMapper: (ChartData data, int index) => data.y,
onRendererCreated: (ChartSeriesController controller) {
_firstChartController = controller; // Get the series details for the first chart.
},
),
],
);
}
}
Similarly, in the second chart, call the crosshair show method for the first chart with the obtained pixel position arguments (dx and dy) from the onChartTouchInteractionDown, onChartTouchInteractionMove as the parameters.
class SecondChartState extends State<SecondChart> {
// Declare variable to avoid calling of the first chart's crosshair.
bool _isInteractive = false;
@override
Widget build(BuildContext context) {
return SfCartesianChart(
onChartTouchInteractionDown: (ChartTouchInteractionArgs tapArgs) {
// Set to true when touched on the chart.
_isInteractive = true;
final CartesianChartPoint chartPoint = _secondChartController!.pixelToPoint(tapArgs.position);
_firstPosition = _firstChartController!.pointToPixel(chartPoint);
// Calls the first chart’s crosshair show method with pixel position arguments as parameters.
crosshair1.show(_firstPosition!.dx, _firstPosition!.dy, 'pixel');
},
onChartTouchInteractionUp: (ChartTouchInteractionArgs tapArgs) {
// Set to false when touched out of the chart.
_isInteractive = false;
// Hide the crosshair when touched out of the chart.
crosshair1.hide();
},
onChartTouchInteractionMove: (ChartTouchInteractionArgs tapArgs) {
if (_isInteractive) {
final CartesianChartPoint chartPoint = _firstChartController!.pixelToPoint(tapArgs.position);
_firstPosition = _secondChartController!.pointToPixel(chartPoint);
// Calls the first chart’s crosshair show method with pixel position arguments as parameters.
crosshair1.show(_firstPosition!.dx, _firstPosition!.dy, 'pixel');
}
},
backgroundColor: Colors.white,
primaryXAxis: const CategoryAxis(),
title: const ChartTitle(text: 'Chart 1'),
crosshairBehavior: crosshair2, // Crosshair behavior variable of the second chart.
series: <CartesianSeries<ChartData, String>>[
LineSeries<ChartData, String>(
dataSource: <ChartData>[
ChartData(1, 15),
ChartData(2, 25),
ChartData(3, 35),
ChartData(4, 20),
ChartData(5, 40),
ChartData(6, 30),
ChartData(7, 45),
ChartData(8, 25),
ChartData(9, 50),
ChartData(10, 30)
],
xValueMapper: (ChartData data, int index) => data.x,
yValueMapper: (ChartData data, int index) => data.y,
onRendererCreated: (ChartSeriesController controller) {
_secondChartController = controller; // Get the series details for the first chart.
},
),
],
);
}
}
By following the above-provided steps, you can easily synchronize the crosshairs across multiple charts.
Note: For category and datetime category axes, crosshair synchronization currently renders based on indexed points.
Conclusion
I hope you enjoyed learning about How to synchronize crosshair in multiple Flutter SfCartesianChart. You can refer to our Flutter CartesianChart feature tour page to know about its other groundbreaking feature representations. You can also explore our documentation to understand how to create and manipulate data.
For current customers, you can check out our components from the License and Downloads page. If you are new to Syncfusion, you can try our 30-day free trial to check out our other controls.
If you have any queries or require clarifications, please let us know in the comments section below. You can also contact us through our support forums, Direct-Trac, or feedback portal. We are always happy to assist you!