This is SfCartesianChart and I want to make it dynamic as per the rest API data given below but when I put the dynamic data to it, the graph shows only null in the legend text and no data shown but I've posted the required data. Kindly help me it's right or not.
SfCartesianChart _getSpacingColumnChart() {
return SfCartesianChart(
// borderColor: Colors.red,
// borderWidth: 2,
// Sets 15 logical pixels as margin for all the 4 sides.
margin: EdgeInsets.all(0),
plotAreaBorderWidth: 0,
title: ChartTitle(
// text: 'Inventory - Finished Products',
// textStyle: TextStyle(
// fontSize: 18.0,
// color: Colors.blueAccent,
// ),
text: widget.title,
// backgroundColor: Colors.lightGreen,
// borderColor: Colors.blue,
borderWidth: 2,
// Aligns the chart title to left
alignment: ChartAlignment.center,
// ignore: deprecated_member_use
textStyle: ChartTextStyle(
color: Colors.blueAccent,
// fontFamily: 'Roboto',
// fontStyle: FontStyle.italic,
fontSize: 11,
)),
primaryXAxis: CategoryAxis(
majorGridLines: MajorGridLines(width: 0),
),
primaryYAxis: NumericAxis(
// maximum: 150,
// minimum: 0,
interval: 25,
axisLine: AxisLine(width: 0),
majorTickLines: MajorTickLines(size: 0)),
palette: <Color>[
Color.fromRGBO(15, 207, 105, 1.0),
Color.fromRGBO(242, 209, 106, 1.0),
Color.fromRGBO(0, 72, 205, 1.0)
],
series: _getDefaultColumn(),
legend: Legend(
isVisible: true,
// Legend will be placed at the bottom
position: LegendPosition.bottom,
// Overflowing legend content will be wraped
overflowMode: LegendItemOverflowMode.wrap),
// tooltipBehavior: TooltipBehavior(
// enable: true,
// canShowMarker: true,
// header: '',
// format: 'point.y marks in point.x'),
tooltipBehavior: TooltipBehavior(enable: true),
);
}
List<ColumnSeries<ColumnChartDataModel, String>> _getDefaultColumn() {
List<ColumnChartDataModel> chartData = <ColumnChartDataModel>[];
for (Map i in widget.data)
chartData
.add(ColumnChartDataModel.fromJson(i) // Deserialization step #3
);
print('chartDataNewchartDataNew=>${widget.data}');
return <ColumnSeries<ColumnChartDataModel, String>>[
ColumnSeries<ColumnChartDataModel, String>(
/// To apply the column width here.
width: isCardView ? 0.8 : _columnWidth,
/// To apply the spacing betweeen to two columns here.
spacing: isCardView ? 0.2 : _columnSpacing,
animationDuration: 3000,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(2), topRight: Radius.circular(2)),
dataSource: chartData,
// color: const Color.fromRGBO(252, 216, 20, 1),
xValueMapper: (ColumnChartDataModel sales, _) => sales.x,
yValueMapper: (ColumnChartDataModel sales, _) => sales.y,
dataLabelSettings: DataLabelSettings(
isVisible: true,
labelAlignment: ChartDataLabelAlignment.top,
textStyle: TextStyle(fontSize: 10, color: Colors.white)),
name: 'In'),
ColumnSeries<ColumnChartDataModel, String>(
dataSource: chartData,
width: isCardView ? 0.8 : _columnWidth,
spacing: isCardView ? 0.2 : _columnSpacing,
animationDuration: 3000,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(2), topRight: Radius.circular(2)),
// color: const Color.fromRGBO(169, 169, 169, 1),
xValueMapper: (ColumnChartDataModel sales, _) => sales.x,
yValueMapper: (ColumnChartDataModel sales, _) =>
sales.secondSeriesYValue,
dataLabelSettings: DataLabelSettings(
isVisible: true,
labelAlignment: ChartDataLabelAlignment.top,
textStyle: TextStyle(fontSize: 10, color: Colors.white)),
name: 'Out'),
ColumnSeries<ColumnChartDataModel, String>(
dataSource: chartData,
width: isCardView ? 0.8 : _columnWidth,
spacing: isCardView ? 0.2 : _columnSpacing,
animationDuration: 3000,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(2), topRight: Radius.circular(2)),
// color: const Color.fromRGBO(205, 127, 50, 1),
xValueMapper: (ColumnChartDataModel sales, _) => sales.x,
yValueMapper: (ColumnChartDataModel sales, _) =>
sales.thirdSeriesYValue,
dataLabelSettings: DataLabelSettings(
isVisible: true,
labelAlignment: ChartDataLabelAlignment.top,
textStyle: TextStyle(fontSize: 10, color: Colors.white)),
name: 'Stock')
];
}
this page is the model data
Model Data
/// Package import
import 'package:flutter/material.dart';
/// Base class of the sample's stateful widget class
abstract class ColumnChartModel extends StatefulWidget {
/// base class constructor of sample's stateful widget class
const ColumnChartModel({Key key}) : super(key: key);
}
/// Base class of the sample's state class
abstract class ColumnChartModelState extends State<ColumnChartModel> {
/// Holds the information of current page is card view or not
bool isCardView;
#override
void initState() {
isCardView = true;
super.initState();
}
/// Get the settings panel content.
Widget buildSettings(BuildContext context) {
return null;
}
}
///Chart sample data
class ColumnChartDataModel {
/// Holds the datapoint values like x, y, etc.,
ColumnChartDataModel(
this.x,
this.y,
this.xValue,
this.yValue,
this.secondSeriesYValue,
this.thirdSeriesYValue,
this.pointColor,
this.size,
this.text,
this.open,
this.close,
this.low,
this.high,
this.volume);
/// Holds x value of the datapoint
final dynamic x;
/// Holds y value of the datapoint
final num y;
/// Holds x value of the datapoint
final dynamic xValue;
/// Holds y value of the datapoint
final num yValue;
/// Holds y value of the datapoint(for 2nd series)
final num secondSeriesYValue;
/// Holds y value of the datapoint(for 3nd series)
final num thirdSeriesYValue;
/// Holds point color of the datapoint
final Color pointColor;
/// Holds size of the datapoint
final num size;
/// Holds datalabel/text value mapper of the datapoint
final String text;
/// Holds open value of the datapoint
final num open;
/// Holds close value of the datapoint
final num close;
/// Holds low value of the datapoint
final num low;
/// Holds high value of the datapoint
final num high;
/// Holds open value of the datapoint
final num volume;
// factory ColumnChartDataModel.fromJson(Map<String, dynamic> json) => ColumnChartDataModel(
// x: json["x"],
// y: json["y"],
// secondSeriesYValue: json["secondSeriesYValue"],
// thirdSeriesYValue: json["thirdSeriesYValue"],
// );
factory ColumnChartDataModel.fromJson(Map<String, dynamic> parsedJson) {
return ColumnChartDataModel(
parsedJson['x'].toString(),
parsedJson['y'] as num,
parsedJson['secondSeriesYValue'] as num,
parsedJson['thirdSeriesYValue'] as num,
parsedJson['xValue'] as dynamic,
parsedJson['yValue'] as num,
parsedJson['pointColor'] as Color,
parsedJson['size'] as num,
parsedJson['text'] as String,
parsedJson['open'] as num,
parsedJson['close'] as num,
parsedJson['low'] as num,
parsedJson['high'] as num,
parsedJson['volume'] as num);
}
}
widget.data == [{productName: abcd-4, totalIn: 10, totalOut: 40, currentStock: 270}, {productName: afegwt-10 Pairs, totalIn: 110, totalOut: 80, currentStock: 530}]
widget.title == "some text"
Please help me..
Thankx in advance.
In the json data the key names should be matched as per the data model created. or you have to put separate instead of .fromJson().
Related
I have tried creating this bar graph using SfCartesianChart, and have been able to complete 95% of it, but unable to remove the middle labels.
...
Lobo,
Greetings from Syncfusion.
We have validated your query and we have achieved your requirement by using the axisLabelFormatter callback in axis. We have rendered the first and last axis labels alone using this axisLabelFormatter callback, and its invoked while rendering each axis label in the chart. Please refer the following code snippet.
Code snippet :
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Syncusion flutter charts'),
centerTitle: true,
),
body: SfCartesianChart(
primaryXAxis: CategoryAxis(
axisLabelFormatter: (AxisLabelRenderDetails details) {
String text = details.value == 0
? 'Jun 2022'
: details.value == chartData.length - 1
? 'Mar 2022'
: "";
return ChartAxisLabel(text, details.textStyle);
},
majorGridLines: const MajorGridLines(width: 0),
majorTickLines: const MajorTickLines(width: 0),
),
primaryYAxis: NumericAxis(
isVisible: false,
majorTickLines: const MajorTickLines(width: 0),
majorGridLines: const MajorGridLines(width: 0),
),
series: <ChartSeries<ChartSampleData, String>>[
ColumnSeries<ChartSampleData, String>(
dataSource: chartData,
dataLabelSettings: const DataLabelSettings(isVisible: true),
xValueMapper: (ChartSampleData sales, _) => sales.x,
yValueMapper: (ChartSampleData sales, _) => sales.y),
],
),
),
);
}
}
class ChartSampleData {
ChartSampleData(this.x, this.y);
final String? x;
final double? y;
}
final List<ChartSampleData> chartData = [
ChartSampleData('jan', 50.56),
ChartSampleData('Feb', 49.42),
ChartSampleData('Mar', 53.21),
ChartSampleData('Apr', 64.78),
ChartSampleData('May', 59.97),
];
ScreenShot:
Syncfusion Custom Axis Label
Regards,
Lokesh Palani.
Need to show no data available text in place of bar which has zero value , y axis labels are
string and x axis are integers
please refer below for the 1st image is acutal result and 2nd image is expected result . 1st image in which empty place which has zero value instead of empty place need to show no data available text
SfCartesianChart(
crosshairBehavior: _crosshairBehavior,
primaryXAxis: CategoryAxis(
majorTickLines: MajorTickLines(size: 20, width: 0),
axisBorderType: AxisBorderType.withoutTopAndBottom,
labelAlignment: LabelAlignment.center,
borderWidth: 2.0,
labelStyle: TextStyle(
fontFamily: 'Roboto',
fontSize: 14,
fontStyle: FontStyle.normal,
fontWeight: FontWeight.w500),
//maximumLabelWidth: 100,
labelPosition: ChartDataLabelPosition.outside,
borderColor: Colors.blueGrey[800]),
primaryYAxis: CategoryAxis(
plotBands: <PlotBand>[
PlotBand(
isVisible: widget.isFleetAverageVisible,
start: 30,
end: 29,
borderWidth: 0.3,
borderColor: Colors.black,
)
],
interval: 25,
maximumLabels: 4,
maximum: 100,
minimum: 0,
majorTickLines: MajorTickLines(size: 15, width: 0),
majorGridLines: MajorGridLines(dashArray: [5, 5]),
),
series: stackedWidget(sortSelected != null
? sortSelected == true
? SortingOrder.ascending
: SortingOrder.descending
: SortingOrder.none),
legend: Legend(
isVisible: true,
position: LegendPosition.bottom,
overflowMode: LegendItemOverflowMode.wrap,
),
onLegendTapped: (LegendTapArgs args) {
},
),
List<ChartSeries> stackedWidget(SortingOrder sortingOrder) {
return <ChartSeries>[
StackedBarSeries<ChartData, String>(
sortingOrder: sortingOrder,
legendIconType: LegendIconType.horizontalLine,
legendItemText: "series1",
width: 0.3,
spacing: 0.2,
dataSource: chartData,
//list of data
xValueMapper: (ChartData data, _) => data.x,
yValueMapper: (ChartData data, _) => data.y1,
sortFieldValueMapper: (ChartData sales, _) => sales.y1,
color: Color.fromRGBO(51, 102, 255, 1))
];
}
Try giving the DataLabelSettings() to the dataLabelSettings: property on StackedBarSeries():
List<ChartSeries> stackedWidget(SortingOrder sortingOrder) {
return <ChartSeries>[
StackedBarSeries<ChartData, String>(
dataLabelSettings: DataLabelSettings(
builder: (
data,
point,
series,
pointIndex,
seriesIndex,
) {
if (chartData[pointIndex].y == 0 || chartData[pointIndex].y == null) {
return Row(children: const [
Icon(Icons.warning_amber_outlined),
Text("No data available"),
]);
} else {
return const SizedBox();
}
},
isVisible: true,
),
sortingOrder: sortingOrder,
legendIconType: LegendIconType.horizontalLine,
legendItemText: "series1",
width: 0.3,
spacing: 0.2,
dataSource: chartData,
//list of data
xValueMapper: (ChartData data, _) => data.x,
yValueMapper: (ChartData data, _) => data.y1,
sortFieldValueMapper: (ChartData sales, _) => sales.y1,
color: Color.fromRGBO(51, 102, 255, 1))
];
}
I am using the syncfusion_flutter_charts package to create a chart. I need to make a value check and display the column with the largest value in red. Tell me how to put a condition / loop correctly in order to check the values of the y-axis and recolor the larger value in red? I will be grateful for help.
chart
class ChartWidget extends StatefulWidget {
const ChartWidget({Key? key}) : super(key: key);
#override
State<ChartWidget> createState() => _ChartWidget();
}
class _ChartWidget extends State<ChartWidget> {
late List<_ChartData> data;
TooltipBehavior? _tooltipBehavior;
#override
void initState() {
data = [
_ChartData('6:00', 18),
_ChartData('7:00', 11),
_ChartData('8:00', 14),
_ChartData('9:00', 5),
_ChartData('10:00', 16),
_ChartData('11:00', 13),
_ChartData('12:00', 15),
_ChartData('13:00', 1),
_ChartData('14:00', 2),
_ChartData('15:00', 15),
_ChartData('16:00', 18),
_ChartData('17:00', 11),
_ChartData('18:00', 14),
_ChartData('19:00', 5),
_ChartData('20:00', 16),
_ChartData('21:00', 13),
_ChartData('22:00', 20),
_ChartData('23:00', 1),
_ChartData('24:00', 2),
];
super.initState();
}
#override
Widget build(BuildContext context) {
return _buildColumnChart();
}
SfCartesianChart _buildColumnChart() {
return SfCartesianChart(
plotAreaBorderWidth: 0,
zoomPanBehavior: ZoomPanBehavior(enablePanning: true),
primaryXAxis: CategoryAxis(
interval: 3,
visibleMaximum: 16,
axisLine: const AxisLine(width: 0),
labelStyle: constants.Styles.xxTinyLtStdTextStyleWhite,
majorTickLines: const MajorTickLines(width: 0),
majorGridLines: const MajorGridLines(width: 0),
),
primaryYAxis:
NumericAxis(isVisible: false, minimum: 0, maximum: 20, interval: 1),
tooltipBehavior: _tooltipBehavior,
series: <ChartSeries<_ChartData, String>>[
ColumnSeries<_ChartData, String>(
dataSource: data,
color: constants.Colors.greyMiddle,
borderColor: constants.Colors.greyChart,
borderWidth: 1,
borderRadius: BorderRadius.circular(4),
xValueMapper: (_ChartData data, _) => data.x,
yValueMapper: (_ChartData data, _) => data.y,
name: 'Test'),
],
);
}
}
class _ChartData {
_ChartData(this.x, this.y);
final String x;
final double y;
}
This is the desired result
You can use pointColorMapper
find the max value
double maxValue = 0;
//loop list of _ChartData to compare its y value and find the max
data.forEach((data){
if(data.y>maxValue) {
maxValue = data.y;
}
});
Then instead of using color use pointColorMapper
//Instead of
color: constants.Colors.greyMiddle,
//use
pointColorMapper: (_ChartData data,_){
if(data.y == maxValue){
return constants.Colors.YourRedColor;
}
else{
return constants.Colors.greyMiddle;
}
},
This was the code I worked with, the final output being 'largest value is 20 and index: 16'
void main() {
var data = [
_ChartData('6:00', 18),
_ChartData('7:00', 11),
_ChartData('8:00', 14),
_ChartData('9:00', 5),
_ChartData('10:00', 16),
_ChartData('11:00', 13),
_ChartData('12:00', 15),
_ChartData('13:00', 1),
_ChartData('14:00', 2),
_ChartData('15:00', 15),
_ChartData('16:00', 18),
_ChartData('17:00', 11),
_ChartData('18:00', 14),
_ChartData('19:00', 5),
_ChartData('20:00', 16),
_ChartData('21:00', 13),
_ChartData('22:00', 20),
_ChartData('23:00', 1),
_ChartData('24:00', 2),
];
double largest_val = 0.0;
int largest_val_index = 0;
for(int k = 0; k < data.length; k++){
if(data[k].y >largest_val){
largest_val = data[k].y;
largest_val_index = k;
}
}
print('largest value is ${largest_val} and index: ${largest_val_index}');
}
class _ChartData {
_ChartData(this.x, this.y);
final String x;
final double y;
}
variable largest_val_index can be used to get the index in list data while largest_val gives the largest value.
void main() should be removed while execution.
To use a specific color to a point, you can make use of pointColorMapper property. Find the maximum y-value from your data source and based on that you can apply color to each point using the pointColorMapper. We have attached the code below
late double yMaximum = 0;
#override
void initState() {
data = [
//Your data
];
getMax(data);
super.initState();
}
void getMax(List<_ChartData> data) {
for (int i = 0; i < data.length; i++)
if (data[i].y > yMaximum) yMaximum = data[i].y;
}
SfCartesianChart(
//Other properties
series: <ChartSeries<_ChartData, String>>[
ColumnSeries<_ChartData, String>(
pointColorMapper: (_ChartData data, _) =>
data.y == yMaximum ? constants.Colors.YourRedColor : constants.Colors.greyMiddle,
),
],
)
Already we have a demo sample to apply the color based on the y-value using the pointColorMapper, which can be found below.
Demo
UG
I want to update the data of my chart, but it doesn't work. I used the chart I saw on a YouTube Video, which is using the fl_chart package. This is the whole code of the Youtube Video: https://github.com/JohannesMilke/fl_bar_chart_example. I think I have to use a setState somewhere, but I don't know where... I tried some options, but all failed, so I saw no other way than asking you. The weekChartDataGlobal is the varaiable which I want to be able to change, so my goal is to make the chart dynamic. I just putted you more or less the whole code into the question, because I don't know exactly what you need in order to answer my question. If you need more code please write me. Thanks in advance for helping me out with this big, big question.
class BarChartWidget extends StatefulWidget {
#override
_BarChartWidgetState createState() => _BarChartWidgetState();
}
class _BarChartWidgetState extends State<BarChartWidget> {
final double barWidth = 22;
#override
Widget build(BuildContext context) {
return BarChart(
BarChartData(
alignment: BarChartAlignment.center,
maxY: 20,
minY: 0,
groupsSpace: MediaQuery.of(context).size.width * 0.05,
barTouchData: BarTouchData(enabled: true),
titlesData: FlTitlesData(
bottomTitles: BarTitles.getTopBottomTitles(),
leftTitles: BarTitles.getSideTitles(),
),
gridData: FlGridData(
checkToShowHorizontalLine: (value) => value % BarData.interval == 0,
getDrawingHorizontalLine: (value) {
if (value == 0) {
return FlLine(
color: const Color(0xff363753),
strokeWidth: 3,
);
} else {
return FlLine(
color: const Color(0xff2a2747),
strokeWidth: 0.8,
);
}
},
),
barGroups: BarData.barData
.map(
(data) => BarChartGroupData(
x: data.id,
barRods: [
BarChartRodData(
y: data.y,
width: barWidth,
colors: [data.color],
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10),
topRight: Radius.circular(10),
)),
],
),
)
.toList(),
),
);
}
}
class BarTitles {
static SideTitles getTopBottomTitles() => SideTitles(
showTitles: true,
getTextStyles: (value) =>
const TextStyle(color: Colors.white, fontSize: 12),
margin: 0,
getTitles: (double id) => BarData.barData
.firstWhere((element) => element.id == id.toInt())
.name,
);
static SideTitles getSideTitles() => SideTitles(
showTitles: true,
getTextStyles: (value) =>
const TextStyle(color: Colors.white, fontSize: 12),
rotateAngle: 90,
interval: BarData.interval.toDouble(),
margin: 0,
reservedSize: 30,
getTitles: (double value) => value == 0 ? '0' : '${value.toInt()}',
);
}
class BarData {
static int interval = 5;
static List<Data> barData = [
Data(
id: 0,
name: 'Mon',
y: weekChartDataGlobal[0]['dayTime'].toDouble(),
color: Color(0xff19bfff),
),
Data(
name: 'Tue',
id: 1,
y: weekChartDataGlobal[1]['dayTime'].toDouble(),
color: Color(0xffff4d94),
),
Data(
name: 'Wed',
id: 2,
y: weekChartDataGlobal[2]['dayTime'].toDouble(),
color: Color(0xff2bdb90),
),
Data(
name: 'Thu',
id: 3,
y: weekChartDataGlobal[3]['dayTime'].toDouble(),
color: Color(0xffffdd80),
),
Data(
name: 'Fri',
id: 4,
y: weekChartDataGlobal[4]['dayTime'].toDouble(),
color: Color(0xff2bdb90),
),
Data(
name: 'Sat',
id: 5,
y: weekChartDataGlobal[5]['dayTime'].toDouble(),
color: Color(0xffffdd80),
),
Data(
name: 'Sun',
id: 6,
y: weekChartDataGlobal[6]['dayTime'].toDouble(),
color: Color(0xffff4d94),
),
];
}
class Data {
// for ordering in the graph
final int id;
final String name;
final double y;
final Color color;
const Data({
#required this.name,
#required this.id,
#required this.y,
#required this.color,
});
}
Well the only way I got to update this chart is to put the chart inside a list variable, put this list inside the build method and replace the chart using a function everytime I want to update the chart. Let me try to explain it. Follow the steps in order to understand what i'm doing here and be patient. To test it right away just copy the code, delete my comments and run it. Don't forget to use pub get for fl_chart dependencies in pubspec.yaml and importing the library with the import 'package:fl_chart/fl_chart.dart';
// STEP 1 - create the global variables - this is to adapt your code to this approach.
//Put all these variables outside and above any function of your BarChartWidget class.
late BuildContext sameContext; //we will initialize this variable later, its the build's method context variable
late _BarChartWidgetState thisState; //we will initialize this variable later, its the class State variable, wich contains the setState method
double theChanges = 0; // this is a placeholder for your weekChartDataGlobal variable since i don't know what is this weekChartDataGlobal but i know its the info that will be displayed and updated in the Y axis of the chart so i replaced it with this double variable to ilustrate this approach
final double barWidth = 22; // your barWidth variable made global
// STEP 4 - here is the method to get a new chart. It's the exact same code of our theChart list
//but once we throw the old BarChart widget away we need to get another and this new one will call all
//its methods again and be updated with the new Y values from theChanges in new Data objects.
//So this method just return the BarChart widget.
Widget getTheChart(){
return BarChart(
BarChartData(
alignment: BarChartAlignment.center,
maxY: 20,
minY: 0,
groupsSpace: MediaQuery.of(sameContext).size.width * 0.05,
barTouchData: BarTouchData(enabled: true),
titlesData: FlTitlesData(
bottomTitles: BarTitles.getTopBottomTitles(),
leftTitles: BarTitles.getSideTitles(),
),
gridData: FlGridData(
checkToShowHorizontalLine: (value) => value % BarData.interval == 0,
getDrawingHorizontalLine: (value) {
if (value == 0) {
return FlLine(
color: const Color(0xff363753),
strokeWidth: 3,
);
} else {
return FlLine(
color: const Color(0xff2a2747),
strokeWidth: 0.8,
);
}
},
),
barGroups: BarData.barData
.map(
(data) => BarChartGroupData(
x: data.id,
barRods: [
BarChartRodData(
y: data.y,
width: barWidth,
colors: [data.color],
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10),
topRight: Radius.circular(10),
)),
],
),
).toList(),
),
);
}
// STEP 2 - Here i putted all the widgets of your page inside a list variable called theChart,
//this is to make things easier to control, so we can update the BarChart widget easily.
//In this list i'm putting your BarChart widget and a Button widget below it for you to be able to see
//the chart being updated. I created this list too as a global variable.
//Note that since this list is being declared outside the build method, we are using
//sameContext variable instead of context and thisState.setState((){}) instead of setState((){}).
//As you can see in the list, the BarChart widget is placed at 0 and the Container holding the
//MaterialButton is placed at 1.
List<Widget> theChart = <Widget>[
BarChart(
BarChartData(
alignment: BarChartAlignment.center,
maxY: 20,
minY: 0,
//sameContext variable being used here
groupsSpace: MediaQuery.of(sameContext).size.width * 0.05,
barTouchData: BarTouchData(enabled: true),
titlesData: FlTitlesData(
bottomTitles: BarTitles.getTopBottomTitles(),
leftTitles: BarTitles.getSideTitles(),
),
gridData: FlGridData(
checkToShowHorizontalLine: (value) => value % BarData.interval == 0,
getDrawingHorizontalLine: (value) {
if (value == 0) {
return FlLine(
color: const Color(0xff363753),
strokeWidth: 3,
);
} else {
return FlLine(
color: const Color(0xff2a2747),
strokeWidth: 0.8,
);
}
},
),
barGroups: BarData.barData
.map(
(data) => BarChartGroupData(
x: data.id,
barRods: [
BarChartRodData(
y: data.y,
width: barWidth,
colors: [data.color],
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10),
topRight: Radius.circular(10),
)),
],
),
).toList(),
),
),
//this is the button widget, click to increase Y (theChanges variable) by 5,
//long press to decrease Y (theChanges variable) by 5
Container(
margin: EdgeInsets.fromLTRB(0, 10, 0, 0),
child: MaterialButton(
color: Colors.lightBlue,
textColor: Colors.white,
child: Text("Press me!"),
onPressed: () {
//when you press the button we enter this method. Here we are outside the build method wich contains
//the context and the state variables so we need to use our state variable to call the setState
//method. This is the key point of the whole thing of updating the chart. We change the variable
//holding the Y value in the chart, here i'm using my theChanges variable but in your code it's that weekChartDataGlobal.
//Then we call the function BarData.updateData() (check step 3) to update the BarData static list variable wich contains the actual
//objects holding the Y values in the chart, and then we REPLACE THE BarChart WIDGET. That's right we throw
//the old widget away and replace it with a new one with the new values. Widgets atributes are final, we
//can't change them once they're settled so we need to replace them, the sooner you accept it the sooner you'll understand all this.
thisState.setState((){
theChanges += 5;
BarData.updateData();
theChart.removeAt(0);
theChart.insert(0, getTheChart()); // check step 4
});
},
onLongPress: (){
thisState.setState((){
theChanges -= 5;
BarData.updateData();
theChart.removeAt(0);
theChart.insert(0, getTheChart());
});
},
),
),
].toList();
class BarChartWidget extends StatefulWidget {
#override
_BarChartWidgetState createState() => _BarChartWidgetState();
}
class _BarChartWidgetState extends State<BarChartWidget> {
#override
Widget build(BuildContext context) {
// at this method your page will actually be built, here is where all the action will happen
//indeed so we
sameContext = context; // initialize our global context variable
thisState = this; // initialize our global State variable
//here we put theChart list variable inside a ListView and the ListView inside a container.
//The build method will get each widget inside the list and place it in order
return Container(
child: ListView.builder(
itemCount: theChart.length,
itemBuilder: (context, index){
return theChart[index];
},
),
);
}
}
class BarTitles {
static SideTitles getTopBottomTitles() => SideTitles(
showTitles: true,
getTextStyles: (value) =>
const TextStyle(color: Colors.white, fontSize: 12),
margin: 0,
getTitles: (double id) => BarData.barData
.firstWhere((element) => element.id == id.toInt())
.name,
);
static SideTitles getSideTitles() => SideTitles(
showTitles: true,
getTextStyles: (value) =>
const TextStyle(color: Colors.white, fontSize: 12),
rotateAngle: 90,
interval: BarData.interval.toDouble(),
margin: 0,
reservedSize: 30,
getTitles: (double value) => value == 0 ? '0' : '${value.toInt()}',
);
}
// STEP 3 here i replaced your weekChartDataGlobal with my theChanges variable to ilustrate the
//update and created the updateData() method.
class BarData {
static int interval = 5;
static List<Data> barData = [
Data(
id: 0,
name: 'Mon',
//y: weekChartDataGlobal[0]['dayTime'].toDouble(),
y: theChanges,
color: Color(0xff19bfff),
),
Data(
name: 'Tue',
id: 1,
//y: weekChartDataGlobal[1]['dayTime'].toDouble(),
y: theChanges,
color: Color(0xffff4d94),
),
Data(
name: 'Wed',
id: 2,
//y: weekChartDataGlobal[2]['dayTime'].toDouble(),
y: theChanges,
color: Color(0xff2bdb90),
),
Data(
name: 'Thu',
id: 3,
//y: weekChartDataGlobal[3]['dayTime'].toDouble(),
y: theChanges,
color: Color(0xffffdd80),
),
Data(
name: 'Fri',
id: 4,
//y: weekChartDataGlobal[4]['dayTime'].toDouble(),
y: theChanges,
color: Color(0xff2bdb90),
),
Data(
name: 'Sat',
id: 5,
//y: weekChartDataGlobal[5]['dayTime'].toDouble(),
y: theChanges,
color: Color(0xffffdd80),
),
Data(
name: 'Sun',
id: 6,
//y: weekChartDataGlobal[6]['dayTime'].toDouble(),
y: theChanges,
color: Color(0xffff4d94),
),
];
//this is another key point. Note that here i'm just clearing the whole static list and filling it
//again with the Data objects. I just copied the code above to create the static list and
//pasted down there. Why? Widgets attributes are final, their Y parameter won't change even if
//you update the theChanges variable so you need to throw them all away and place new ones
//with the new value of theChanges variable.
static void updateData(){
barData.clear();
barData = [
Data(
id: 0,
name: 'Mon',
//y: weekChartDataGlobal[0]['dayTime'].toDouble(),
y: theChanges,
color: Color(0xff19bfff),
),
Data(
name: 'Tue',
id: 1,
//y: weekChartDataGlobal[1]['dayTime'].toDouble(),
y: theChanges,
color: Color(0xffff4d94),
),
Data(
name: 'Wed',
id: 2,
//y: weekChartDataGlobal[2]['dayTime'].toDouble(),
y: theChanges,
color: Color(0xff2bdb90),
),
Data(
name: 'Thu',
id: 3,
//y: weekChartDataGlobal[3]['dayTime'].toDouble(),
y: theChanges,
color: Color(0xffffdd80),
),
Data(
name: 'Fri',
id: 4,
//y: weekChartDataGlobal[4]['dayTime'].toDouble(),
y: theChanges,
color: Color(0xff2bdb90),
),
Data(
name: 'Sat',
id: 5,
//y: weekChartDataGlobal[5]['dayTime'].toDouble(),
y: theChanges,
color: Color(0xffffdd80),
),
Data(
name: 'Sun',
id: 6,
//y: weekChartDataGlobal[6]['dayTime'].toDouble(),
y: theChanges,
color: Color(0xffff4d94),
),
];
}
}
class Data {
// for ordering in the graph
final int id;
final String name;
final double y;
final Color color;
const Data({
required this.name,
required this.id,
required this.y,
required this.color,
});
}
In case anyone doesn't want to do such a complex workaround, I simply added a ValueKey to my graph constructor. Then attached this to my selector widget, which changes the timeframe my graph is showing, e.g. day, month, week.
e.g.
String _yourVariableThatChangesHere = "monthView";
GTBarChart(
key: ValueKey(_yourVariableThatChangesHere),
data: ...
)
Then when your _yourVariableThatChangesHere changes, the graph gets rebuilt when you call setState.
I have one data type in my app which contains two lists:
class DeviceData {
List<double> frequency;
List<double> value;
DeviceData({this.frequency, this.value});
}
I don't want to separate the data types in my app into individual nums due to it being reused everywhere from displaying raw data to writing/reading from the database. I need to plot the two lists in Flutter with frequency on the y axis and value on x.
All the libraries/packages I found on pub.dev use a data model containing pure int/double values to plot the data which they then convert into a list of that data model. I want to use lists containing all of x and all of y data, not one containing one x frequency corresponding to one y value. How does one do that? What if I want to use a map to plot the data?
An example:
SfCartesianChart(
plotAreaBorderWidth: 0,
primaryXAxis: NumericAxis(
edgeLabelPlacement: EdgeLabelPlacement.shift,
interval: 2,
majorGridLines: MajorGridLines(width: 0)),
primaryYAxis: NumericAxis(
labelFormat: '{value}',
axisLine: AxisLine(width: 0),
majorTickLines: MajorTickLines(color: Colors.transparent)),
series: <LineSeries<DeviceData, List<num>>>[
LineSeries<DeviceData, List<num>>(
animationDuration: 1500,
dataSource: _result, //Contains List<DeviceData>
xValueMapper: (DeviceData data, _) => data.frequency,
yValueMapper: (DeviceData data, _) => data.value,
width: 2,
markerSettings: MarkerSettings(isVisible: true))
],
tooltipBehavior: TooltipBehavior(enable: true),
);
What am I doing wrong here?
You could easily map your current devideData into a List<SingleDeviceData>:
dataSource: List.generate(
deviceData.frequency.length,
(index) => SingleDeviceData(
frequency: deviceData.frequency[index],
value: deviceData.value[index],
),
).toList(),
Full source code for easy copy-paste:
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:syncfusion_flutter_charts/charts.dart';
void main() {
runApp(
MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
home: Scaffold(
body: MyWidget(),
),
),
);
}
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return SfCartesianChart(
plotAreaBorderWidth: 0,
primaryXAxis: NumericAxis(
edgeLabelPlacement: EdgeLabelPlacement.shift,
interval: 2,
majorGridLines: MajorGridLines(width: 0)),
primaryYAxis: NumericAxis(
labelFormat: '{value}',
axisLine: AxisLine(width: 0),
majorTickLines: MajorTickLines(color: Colors.transparent)),
series: dataList
.map((deviceData) => LineSeries<SingleDeviceData, double>(
animationDuration: 1500,
dataSource: List.generate(
deviceData.frequency.length,
(index) => SingleDeviceData(
frequency: deviceData.frequency[index],
value: deviceData.value[index],
),
).toList(),
xValueMapper: (SingleDeviceData data, _) => data.frequency,
yValueMapper: (SingleDeviceData data, _) => data.value,
width: 2,
markerSettings: MarkerSettings(isVisible: true)))
.toList(),
tooltipBehavior: TooltipBehavior(enable: true),
);
}
}
class DeviceData {
List<double> frequency;
List<double> value;
DeviceData({this.frequency, this.value});
}
class SingleDeviceData {
final double frequency;
final double value;
SingleDeviceData({this.frequency, this.value});
}
final Random random = Random();
final List<DeviceData> dataList = List.generate(
10,
(_) => DeviceData(
frequency: List.generate(100, (_) => random.nextDouble() * 100)..sort(),
value: List.generate(100, (_) => random.nextDouble() * 10 - 3)
.fold([0], (prev, curr) => prev..add(prev.last + curr)),
),
);