Articles in this section
Category / Section

How to Vertically Align Multiple Axis in Flutter Cartesian Chart?

21 mins read

To keep y-axis margins consistent across multiple Flutter Cartesian Chart, we use specialized functions that respond when charts are created or when their data ranges change. These functions help measure the size of each chart’s y-axis. By comparing these sizes, we adjust the margins between charts to maintain balance. This ensures that even if charts have different data ranges or scales, their vertical alignments remain uniform for a neat and professional appearance.

Step 1: Initialize the list variables to store the dataSource. Then create two SfCartesianChart widgets with the Cartesian Series and assign the chartData to the dataSource property, mapping the x and y values to xValueMapper, yValueMapper properties respectively.

final List<SalesData> chartData1 = [
     SalesData(DateTime(2024, 1, 1, 0), 150.02),
     SalesData(DateTime(2024, 1, 1, 1), 150),
     SalesData(DateTime(2024, 1, 1, 2), 150.01),
     SalesData(DateTime(2024, 1, 1, 3), 149.999),
   ];

   final List<SalesData> chartData2 = [
     SalesData(DateTime(2024, 1, 1, 0), 12),
     SalesData(DateTime(2024, 1, 1, 1), 22),
     SalesData(DateTime(2024, 1, 1, 1, 30), 21),
     SalesData(DateTime(2024, 1, 1, 2), 9),
     SalesData(DateTime(2024, 1, 1, 3), 7),
   ];

   final List<SalesData> chartData3 = [
     SalesData(DateTime(2024, 1, 1, 0), 5),
     SalesData(DateTime(2024, 1, 1, 1), 2),
     SalesData(DateTime(2024, 1, 1, 3), 7),
   ];

   final List<SalesData> chartData4 = [
     SalesData(DateTime(2024, 1, 1, 0), 1),
     SalesData(DateTime(2024, 1, 1, 1), 8),
     SalesData(DateTime(2024, 1, 1, 2), 4),
     SalesData(DateTime(2024, 1, 1, 3), 2),
   ];

   return MaterialApp(
     home: Scaffold(
       body: SingleChildScrollView(
         child: Padding(
           padding: const EdgeInsets.all(10.0),
           child: Column(
             crossAxisAlignment: CrossAxisAlignment.start,
             children: [
               SfCartesianChart(
                 
                 series: <CartesianSeries>[
                   ColumnSeries<SalesData, DateTime>(
                     animationDuration: 0,
                     dataSource: chartData1,
                     xValueMapper: (SalesData sales, int index) => sales.year,
                     yValueMapper: (SalesData sales, int index) => sales.sales,
                   ),
                   SplineSeries<SalesData, DateTime>(
                     animationDuration: 0,
                     dataSource: chartData2,
                     xValueMapper: (SalesData sales, int index) => sales.year,
                     yValueMapper: (SalesData sales, int index) => sales.sales,
                   ),
                   LineSeries<SalesData, DateTime>(
                     animationDuration: 0,
                     dataSource: chartData3,
                     xValueMapper: (SalesData sales, int index) => sales.year,
                     yValueMapper: (SalesData sales, int index) => sales.sales,
                   ),
                 ],
               ),
               SfCartesianChart(
                 series: <CartesianSeries>[
                   ColumnSeries<SalesData, DateTime>(
                     animationDuration: 0,
                     dataSource: chartData4,
                     xValueMapper: (SalesData sales, int index) => sales.year,
                     yValueMapper: (SalesData sales, int index) => sales.sales,
                   ),
                 ],
               ),
             ],
           ),
         ),
       ),
     ),
   );
 }


class SalesData {
 SalesData(this.year, this.sales);

 final DateTime year;
 final double sales;
} 

Step 2: Declare _axisSize1 and _axisSize2 as nullable doubles to store axis sizes, and _xAxisController1 and _xAxisController2 as nullable NumericAxisController instances for axis control. Initialize margin1 and margin2 with 10-pixel margins on all sides using EdgeInsets. Utilize the SfCartesianChart widget from Syncfusion® for chart rendering. Implement an onRendererCreated callback to initialize _xAxisController1 upon chart renderer creation. After rendering, update _axisSize1 with the width of _xAxisController1, and potentially adjust margins using _adjustMargins() if both axis sizes are available.


 double? _axisSize1;
 double? _axisSize2;
 NumericAxisController? _xAxisController1;
 NumericAxisController? _xAxisController2;
 EdgeInsets margin1 = const EdgeInsets.all(10);
 EdgeInsets margin2 = const EdgeInsets.all(10);

 @override
 Widget build(BuildContext context) {
   return MaterialApp(
     home: Scaffold(
       body: SingleChildScrollView(
         child: Padding(
           padding: const EdgeInsets.all(10.0),
           child: Column(
             crossAxisAlignment: CrossAxisAlignment.start,
             children: [
               SfCartesianChart(
                 margin: margin1,
                 backgroundColor: Colors.white,
                 primaryXAxis: const DateTimeAxis(),
                 primaryYAxis: NumericAxis(
                   onRendererCreated: (NumericAxisController controller) {
                     _xAxisController1 = controller;
                     WidgetsBinding.instance.addPostFrameCallback((_) {
                       if (mounted) {
                         setState(() {
                           _axisSize1 = _xAxisController1!.axis.size.width;

                           if (_axisSize1 != null && _axisSize2 != null) {
                             _adjustMargins();
                           }
                         });
                       }
                     });
                   },
                 ),
                 legend: const Legend(
                     isVisible: true, position: LegendPosition.top),
               ),
               SfCartesianChart(
                 margin: margin2,
                 backgroundColor: Colors.white,
                 primaryXAxis: const DateTimeAxis(),
                 primaryYAxis: NumericAxis(
                   onRendererCreated: (NumericAxisController controller) {
                     _xAxisController2 = controller;
                     WidgetsBinding.instance.addPostFrameCallback((_) {
                       if (mounted) {
                         setState(() {
                           _axisSize2 = _xAxisController2!.axis.size.width;

                           if (_axisSize1 != null && _axisSize2 != null) {
                             _adjustMargins();
                           }
                         });
                       }
                     });
                   },
                 ),
                 legend: const Legend(
                     isVisible: true, position: LegendPosition.top),
               ),
             ],
           ),
         ),
       ),
     ),
   );
 }


 void _adjustMargins() {
   double difference = (_axisSize1! - _axisSize2!).abs();
     if (_axisSize1! > _axisSize2!) {
       setState(() {
         margin2 = EdgeInsets.only(left: 10 + difference);
       });
     } else {
       setState(() {
         margin1 = EdgeInsets.only(left: 10 + difference);
       });
     }
   }
 } 

Step 3: Declare onActualRangeChanged to handle chart range changes, ensuring UI updates post-rendering using setState. It adjusts margins using _axisSize1 fetched from _xAxisController1, verifying widget presence to maintain state integrity.


              SfCartesianChart(
                 margin: margin1,
                 backgroundColor: Colors.white,
                 primaryXAxis: const DateTimeAxis(),
                 primaryYAxis: NumericAxis(
                   onRendererCreated: (NumericAxisController controller) {
                     _xAxisController1 = controller;
                     WidgetsBinding.instance.addPostFrameCallback((_) {
                       if (mounted) {
                         setState(() {
                           _axisSize1 = _xAxisController1!.axis.size.width;

                           if (_axisSize1 != null && _axisSize2 != null) {
                             _adjustMargins();
                           }
                         });
                       }
                     });
                   },
                 ),
                 onActualRangeChanged: (ActualRangeChangedArgs args) {
                   WidgetsBinding.instance.addPostFrameCallback((_) {
                     if (mounted) {
                       setState(() {
                         _axisSize1 = _xAxisController1!.axis.size.width;

                         if (_axisSize1 != null && _axisSize2 != null) {
                           _adjustMargins();
                         }
                       });
                     }
                   });
                 },
                
                 legend: const Legend(
                     isVisible: true, position: LegendPosition.top),
                 series: <CartesianSeries>[
                   ColumnSeries<SalesData, DateTime>(
                     animationDuration: 0,
                     dataSource: chartData1,
                     xValueMapper: (SalesData sales, _) => sales.year,
                     yValueMapper: (SalesData sales, _) => sales.sales,
                   ),
                   SplineSeries<SalesData, DateTime>(
                     animationDuration: 0,
                     dataSource: chartData2,
                     xValueMapper: (SalesData sales, _) => sales.year,
                     yValueMapper: (SalesData sales, _) => sales.sales,
                   ),
                   LineSeries<SalesData, DateTime>(
                     animationDuration: 0,
                     dataSource: chartData3,
                     xValueMapper: (SalesData sales, _) => sales.year,
                     yValueMapper: (SalesData sales, _) => sales.sales,
                   ),
                 ],
               ),
               SfCartesianChart(                
                 onActualRangeChanged: (ActualRangeChangedArgs args) {
                   WidgetsBinding.instance.addPostFrameCallback((_) {
                     if (mounted) {
                       setState(() {
                         _axisSize2 = _xAxisController2!.axis.size.width;

                         if (_axisSize1 != null && _axisSize2 != null) {
                           _adjustMargins();
                         }
                       });
                     }
                   });
                 },
                 margin: margin2,
                 backgroundColor: Colors.white,
                 primaryXAxis: const DateTimeAxis(),
                 primaryYAxis: NumericAxis(
                   onRendererCreated: (NumericAxisController controller) {
                     _xAxisController2 = controller;
                     WidgetsBinding.instance.addPostFrameCallback((_) {
                       if (mounted) {
                         setState(() {
                           _axisSize2 = _xAxisController2!.axis.size.width;

                           if (_axisSize1 != null && _axisSize2 != null) {
                             _adjustMargins();
                           }
                         });
                       }
                     });
                   },
                 ),
                 
                 legend: const Legend(
                     isVisible: true, position: LegendPosition.top),
                 series: <CartesianSeries>[
                   ColumnSeries<SalesData, DateTime>(
                     animationDuration: 0,
                     dataSource: chartData4,
                     xValueMapper: (SalesData sales, _) => sales.year,
                     yValueMapper: (SalesData sales, _) => sales.sales,
                   ),
                 ],
               ),
             ],
           ),
         ),
       ),
     ),
   );
 }

 void _adjustMargins() {
   double difference = (_axisSize1! - _axisSize2!).abs();
   if (difference == 0) {
     setState(() {
       margin1 = const EdgeInsets.only(left: 10);
       margin2 = const EdgeInsets.only(left: 10);
     });
   } else {
     if (_axisSize1! > _axisSize2!) {
       setState(() {
         margin2 = EdgeInsets.only(left: 10 + difference);
       });
     } else {
       setState(() {
         margin1 = EdgeInsets.only(left: 10 + difference);
       });
     }
   }
 }
} 

Now, the vertical axis is uniformly aligned across the multiple charts as shown below.

KBDemo-ezgifcom-video-to-gif-converter_1.gif

View the Github Sample here..

Conclusion
I hope you enjoyed learning how to vertically align multiple axis in Flutter Cartesian Chart.
You can refer to our Flutter Cartesian Chart feature tour page to learn about its other groundbreaking feature representations and documentation, and how to quickly get started for configuration specifications. You can also explore our Flutter Cartesian Chart example 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!

Did you find this information helpful?
Yes
No
Help us improve this page
Please provide feedback or comments
Comments (0)
Please  to leave a comment
Access denied
Access denied