TL;DR: Be taught so as to add scrollbars to Syncfusion Flutter Charts utilizing the Vary Slider and Vary Selector widgets. This information gives step-by-step directions and code examples to boost chart navigation. Excellent for seamless zooming and panning. Test it out!
Syncfusion Flutter Charts comprises a wealthy gallery of 30+ charts and graphs, starting from line to monetary charts, that cater to all charting eventualities.
On this weblog, we’ll discover tips on how to add a scrollbar in Flutter Charts to trace the zoom and pan progress and its limits. The scrollbar characteristic isn’t constructed into our Flutter Charts; nonetheless, we will add it utilizing the SfRangeSlider and SfRangeSelector widgets.
Let’s stroll by the steps to attain the identical.
Including a mini-map scrollbar
The SfRangeSelector widget is used to pick out a variety of values between the set minimal and most values, and it will probably settle for any widget as its baby. One distinctive characteristic of the SfRangeSelector is its means to map the vary choice in SfCartesianChart, making it perform as a mini-map scrollbar. That is achieved by the usage of the RangeController.
The SfRangeSelector has a controller property of sort RangeController, which updates each time the vary of the SfRangeSelector adjustments by interplay. However, the SfCartesianChart helps a built-in choice to take heed to and replace the RangeController by the rangeController property of the Numeric and DateTime axes. At any time when the vary of the CartesianSeries adjustments on account of zooming, panning, or direct range-related APIs, the RangeController is up to date internally within the Charts, just like the SfRangeSelector.
The RangeController is a notifier to which the chart and vary selector hear. Due to this fact, each time the beginning and finish values change within the RangeController, the chart and vary selector replace themselves.
Let’s create a chart and initialize the information supply. Right here, I’ve at present ready a working pattern with random knowledge.
num _yValue() { if (_random.nextDouble() > 0.5) { _baseValue += _random.nextDouble(); return _baseValue; } else { _baseValue -= _random.nextDouble(); return _baseValue; } } @override void initState() { DateTime date = DateTime(2020); _chartData = Record.generate(_dataCount + 1, (int index) { closing Record<num> values = [_yValue(), _yValue(), _yValue(), _yValue()]; values.kind(); return ChartData( x: date.add(Period(days: index)), excessive: values[0], low: values[3], open: values[1], shut: values[2], ); }); _startRange = _chartData[0].x; _endRange = _chartData[_dataCount].x; _rangeController = RangeController( begin: _startRange, finish: _endRange, ); tremendous.initState(); } @override Widget construct(BuildContext context) { ... SfCartesianChart( margin: EdgeInsets.zero, primaryXAxis: const DateTimeAxis(), primaryYAxis: const NumericAxis( opposedPosition: true, ), collection: <CartesianSeries<ChartData, DateTime>>[ CandleSeries( dataSource: _chartData, xValueMapper: (ChartData data, int index) => data.x, highValueMapper: (ChartData data, int index) => data.high, lowValueMapper: (ChartData data, int index) => data.low, openValueMapper: (ChartData data, int index) => data.open, closeValueMapper: (ChartData data, int index) => data.close, ), ], ), ... }
The y-axis vary can be calculated from 0 because the default vary padding is ChartRangePadding.regular. Set the vary padding of the NumericAxis to ChartRangePadding.spherical, which calculates and shows the y-axis vary just for the accessible knowledge factors.
primaryYAxis: const NumericAxis( ... rangePadding: ChartRangePadding.spherical, )
Add the ZoomPanBehavior to zoom and pan the chart. Right here, the zoom mode is ready to X as a result of the vary selector has no vertical orientation, so we will use it for one-direction(horizontal) zooming.
late ZoomPanBehavior _zoomPanBehavior; @override void initState() { _zoomPanBehavior = ZoomPanBehavior( enablePanning: true, zoomMode: ZoomMode.x, ); ... } SfCartesianChart( ... zoomPanBehavior: _zoomPanBehavior, ... )
Now, create the SfRangeSelector and set the min and max values from the chart knowledge supply. For the reason that SfRangeSelector doesn’t have built-in auto interval calculation help, we have to set the interval, dateFormat, and dateIntervalType properties manually. Within the following code instance, we’ll customise the Jan label with its 12 months utilizing the labelFormatterCallback occasion for higher visible enchantment.
For the reason that SfRangeSelector acts as a mini-map, we will add any cartesian collection (primarily based on want) as its baby and map the identical knowledge supply used above.
SfRangeSelectorTheme( knowledge: SfRangeSelectorThemeData( thumbRadius: 0, overlayRadius: 0, activeRegionColor: colorScheme.main.withOpacity(0.12), inactiveRegionColor: Colours.clear, ), baby: SfRangeSelector( min: _startRange, max: _endRange, showTicks: true, showLabels: true, interval: 1, dateIntervalType: DateIntervalType.months, dateFormat: DateFormat.MMM(), labelPlacement: LabelPlacement.betweenTicks, labelFormatterCallback: (dynamic actualValue, String formattedText) { if (formattedText.comprises('Jan')) { closing 12 months = DateFormat('yyyy').format(actualValue); return ' $12 months $formattedText'; } return formattedText; }, baby: SfCartesianChart( ... ), ), )
Now, wrap the Flutter Charts and the Vary Selector widgets in a Column widget. Then, create a variety controller and assign it to the chart and the vary selector.
RangeController? _rangeController; @override void initState() { ... _rangeController = RangeController( begin: _startRange, finish: _endRange, ); tremendous.initState(); } @override Widget construct(BuildContext context) { ... Column( kids: <Widget>[ Expanded( child: SfCartesianChart( margin: EdgeInsets.zero, primaryXAxis: DateTimeAxis( rangeController: _rangeController, ), ... ), ), Container( height: 150, padding: const EdgeInsets.only(bottom: 10), child: SfRangeSelectorTheme( ... child: SfRangeSelector( min: _startRange, max: _endRange, controller: _rangeController, ... ), ), ), ], ) ... }
That’s it. Now, each time the axis vary adjustments within the Flutter Chart, the Vary Selector will replace accordingly and alter the Chart’s visible vary by interplay with the Vary Selector.
Seek advice from the next picture.
Including a scrollbar on X-axis
This may be achieved by utilizing the SfRangeSelector with an empty SizedBox as its baby and putting it on the x-axis utilizing the Flutter Charts annotation property. Let’s have the precise Chart’s knowledge level vary because the vary of the SfRangeSelector and make it a scrollbar on the X-axis.
To show the default scrollbar UI, take away the thumb and overlay from the SfRangeSelector utilizing the SfRangeSelectorTheme.
Initialize the information supply and discover its minimal and most values. Set these values because the vary for the SfRangeSelector.
Seek advice from the next code instance.
late Record<ChartData> _chartData; late DateTime _xScrollbarStartRange; late DateTime _xScrollbarEndRange; RangeController? _xScrollbarController; @override void initState() { ... _xScrollbarStartRange = _chartData[0].x; _xScrollbarEndRange = _chartData[_dataCount].x; _xScrollbarController = RangeController( begin: _xScrollbarStartRange, finish: _xScrollbarEndRange, ); tremendous.initState(); } SfCartesianChart( ... collection: <CartesianSeries<ChartData, DateTime>>[ HiloOpenCloseSeries( dataSource: _chartData, ... ), ], zoomPanBehavior: _zoomPanBehavior, ... ) SfRangeSelectorTheme( knowledge: const SfRangeSelectorThemeData( thumbRadius: 0, overlayRadius: 0, ), baby: SfRangeSelector( min: _xScrollbarStartRange, max: _xScrollbarEndRange, baby: const SizedBox(top: 0), ), )
Create a variety controller and assign it to each the chart’s axis and the vary selector so they are going to map to one another and get up to date when the vary adjustments.
void initState() { ... _xScrollbarController = RangeController( begin: _xScrollbarStartRange, finish: _xScrollbarEndRange, ); tremendous.initState(); } @override Widget construct(BuildContext context) { SfCartesianChart( margin: EdgeInsets.zero, primaryXAxis: DateTimeAxis( rangeController: _xScrollbarController, ), ... ) SfRangeSelector( min: _xScrollbarStartRange, max: _xScrollbarEndRange, controller: _xScrollbarController, showTicks: true, ... )
Now, add an annotation to the chart and place the SfRangeSelector as a baby of the annotation.
SfCartesianChart( ... annotations: [ CartesianChartAnnotation( widget: SfRangeSelectorTheme( data: const SfRangeSelectorThemeData( thumbRadius: 0, overlayRadius: 0, ), child: SfRangeSelector( min: _xScrollbarStartRange, max: _xScrollbarEndRange, controller: _xScrollbarController, child: const SizedBox(height: 0), ), ), ), ], )
With the intention to place the vary selector on the x-axis, we have to decide the highest left place of the x-axis, which is identical as the underside left place of the plot space. To get the plot space (collection) dimension, write a customized collection renderer for the CartesianSeries and override its performLayout technique. After calling the tremendous.performLayout technique, you possibly can receive the dimensions of the collection.
Seek advice from the next code instance.
collection: <CartesianSeries<ChartData, DateTime>>[ HiloOpenCloseSeries( ... onCreateRenderer: (ChartSeries<ChartData, DateTime> series) { return _HiloOpenCloseSeriesRenderer(this); }, ), ] class _HiloOpenCloseSeriesRenderer extends HiloOpenCloseSeriesRenderer<ChartData, DateTime> { _HiloOpenCloseSeriesRenderer(this._state); closing _ChartWithRangeSliderState _state; @override void performLayout() { tremendous.performLayout(); _state._updateScrollBarSize(dimension); } }
After acquiring the dimensions, modify the annotation place by the postFrameCallback. When utilizing the collection’ backside left place because the annotation’s x and y coordinates, the annotation can be positioned within the middle of the given place by default as a result of the default horizontalAlignment and verticalAlignment of the annotation is middle.
Nonetheless, in our case, the annotation should think about the place as middle left, which could be executed by setting the chart’s horizontalAlignment as close to and verticalAlignment as middle. Regardless of this adjustment, the scrollbar nonetheless didn’t stretch to the whole axis size, so set the collection width to the scrollbar utilizing the SizedBox.
Seek advice from the next code instance.
Dimension _scrollbarSize = Dimension.zero; Offset _verticalScrollbarStart = Offset.zero; void _updateScrollBarSize(Dimension dimension) { SchedulerBinding.occasion.addPostFrameCallback((Period timeStamp) { if (dimension != _scrollbarSize) { setState(() { _scrollbarSize = dimension; _horizontalScrollbarStart = Offset(0, dimension.top); }); } }); } SfCartesianChart( ... annotations: [ CartesianChartAnnotation( x: _horizontalScrollbarStart.dx, y: _horizontalScrollbarStart.dy, coordinateUnit: CoordinateUnit.logicalPixel, horizontalAlignment: ChartAlignment.near, widget: SizedBox( width: _scrollbarSize.width, ... ), ), ], );
Including a scrollbar on Y-axis
This may be achieved utilizing the vertical SfRangeSlider widget as an annotation on the Flutter Charts widget. Now we have used the precise chart knowledge level vary for the x-axis scrollbar vary; now, we’ll implement a distinct technique for the y-axis scrollbar.
Let’s assume the scrollbar’s minimal worth is 0 and the utmost is 1. Primarily based on the vary chosen within the chart, the scrollbar vary will should be up to date. Let’s discover tips on how to accomplish this.
To show the precise scrollbar UI, take away the thumb and overlay from the SfRangeSlider utilizing the SfRangeSliderTheme.
Just like the x-axis scrollbar, place the scrollbar on the y-axis. Receive the collection dimension utilizing a customized collection renderer and place the vertical SfRangeSlider, stretching its top to the whole axis top.
If the y-axis is positioned on the left by default, we will merely place the scrollbar at Offset.zero. Whether it is positioned on the correct facet (the other place is true), we should always get the collection dimension and reposition it by the postFrameCallback.
Seek advice from the next code instance.
void _updateScrollBarSize(Dimension dimension) { SchedulerBinding.occasion.addPostFrameCallback((Period timeStamp) { if (dimension != _scrollbarSize) { setState(() { _scrollbarSize = dimension; _verticalScrollbarStart = Offset(dimension.width, dimension.top); }); } }); } SfCartesianChart( ... annotations: [ CartesianChartAnnotation( x: _verticalScrollbarStart.dx, y: _verticalScrollbarStart.dy, coordinateUnit: CoordinateUnit.logicalPixel, verticalAlignment: ChartAlignment.far, widget: SizedBox( width: 6, // Max size from the active and inactive track. height: _scrollbarSize.height, child: SfRangeSliderTheme( data: const SfRangeSliderThemeData( thumbRadius: 0, overlayRadius: 0, ), child: SfRangeSlider.vertical( min: 0, max: 1, values: values, ... ), ), ), ), ], )
The SfRangeSlider can be up to date solely when the widget rebuilds with new values. Due to this fact, wrap it in a ValueListenableBuilder and replace the listenable worth within the chart’s onActualRangeChanged callback by changing the seen vary values into a variety from 0 to 1. When altering the listenable worth, the ValueListenableBuilder rebuilds its baby utilizing the builder callback. Inside this callback, use the brand new vary values that have been up to date within the onActualRangeChanged callback. This can make sure that the SfRangeSlider is up to date to mirror the brand new seen vary.
late num _yAxisActualMin; late num _yAxisActualMax; SfCartesianChart( ... onActualRangeChanged: (ActualRangeChangedArgs args) { if (args.axisName == 'primaryYAxis') { _yAxisActualMin = args.actualMin; _yAxisActualMax = args.actualMax; SchedulerBinding.occasion.addPostFrameCallback((Period timeStamp) { closing num actualRange = args.actualMax - args.actualMin; double visibleMinNormalized = (args.visibleMin - args.actualMin) / actualRange; double visibleMaxNormalized = (args.visibleMax - args.actualMin) / actualRange; _yScrollbarSelectedValues.worth = SfRangeValues(visibleMinNormalized, visibleMaxNormalized); }); } }, annotations: [ CartesianChartAnnotation( ... widget: SizedBox( width: 6, height: _scrollbarSize.height, child: ValueListenableBuilder<SfRangeValues>( valueListenable: _yScrollbarSelectedValues, builder: (BuildContext context, SfRangeValues values, Widget? child) { return SfRangeSliderTheme( ... child: SfRangeSlider.vertical( min: 0, max: 1, values: values, ... ), ); }, ), ), ), ], )
That’s it. Now, the y-axis scrollbar can be up to date each time the y-axis vary adjustments by chart interactions or direct APIs. Yet one more factor: if it’s worthwhile to replace the chart vary when dragging and updating the y-axis scrollbar (vary slider), convert the onChanged new values to precise values and replace them to the chart axis controller’s seen min and max properties.
late NumericAxisController _yAxisController; SfCartesianChart( ... primaryYAxis: NumericAxis( ... onRendererCreated: (NumericAxisController controller) { _yAxisController = controller; }, ), ) SfRangeSlider.vertical( min: 0, max: 1, values: values, onChanged: (SfRangeValues newValues) { _yAxisController.visibleMinimum = lerpDouble( _yAxisActualMin, _yAxisActualMax, newValues.begin); _yAxisController.visibleMaximum = lerpDouble( _yAxisActualMin, _yAxisActualMax, newValues.finish); }, )
Seek advice from the next picture.
GitHub reference
For extra particulars, consult with including scrollbars within the Flutter Charts GitHub demo.
Strive It Free
Conclusion
Thanks for studying! On this weblog, we realized tips on how to add and synchronize scrollbars within the Syncfusion Flutter Charts widget. With this, you possibly can seamlessly zoom and pan within the charts to view the information factors intimately and get insights. Give it a attempt, and go away your suggestions within the remark part beneath.
Take a look at different options of our Flutter Charts and Sliders within the consumer information and discover our Flutter Charts and Sliders widget samples. Moreover, take a look at our demo apps accessible on completely different platforms: Android, iOS, internet, Home windows, and Linux.
When you want a brand new widget for the Flutter framework or new options in our present widgets, you possibly can contact us by our help boards, help portal, or suggestions portal. As all the time, we’re glad to help you!