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.
Related
Hello I want to create a chart like blazor chart in flutter i tried using the syncfusion package but that didn't been well for me or maybe i was doing something wrong because i was unable to scroll to the old chart data back through scrolling back and fourth.
Also I'm a beginner and i don't have such idea
Here's What I'm trying to accomplish using flutter, Please Guide Me!
I want To Create Something Like This
You can use the fl_chart package. It's highly configurable.
Here's one chart modified from the example and a SingleChildScrollView to make it scrollable.
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
scaffoldBackgroundColor: const Color(0xff232d37),
),
home: const MyHomePage(),
debugShowCheckedModeBanner: false,
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: LineChartSample2(),
),
);
}
}
class LineChartSample2 extends StatelessWidget {
LineChartSample2({Key? key}) : super(key: key);
final List<Color> gradientColors = [
Colors.white,
Colors.black38,
];
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SizedBox(
width: 2000,
height: 400,
child: Padding(
padding: const EdgeInsets.only(
right: 18.0, left: 12.0, top: 24, bottom: 12),
child: LineChart(
mainData(),
),
),
),
);
}
Widget bottomTitleWidgets(double value, TitleMeta meta) {
const style = TextStyle(
color: Color(0xff68737d),
fontWeight: FontWeight.bold,
fontSize: 16,
);
Widget text = const Text('', style: style);
final step = data.length ~/ 4;
if (value.toInt() % step == 0) {
final index = value.toInt() ~/ step;
text = Text('0:${(index * 5).toString().padLeft(2, '0')}', style: style);
}
return SideTitleWidget(
axisSide: meta.axisSide,
space: 8.0,
child: text,
);
}
Widget leftTitleWidgets(double value, TitleMeta meta) {
const style = TextStyle(
color: Color(0xff67727d),
fontWeight: FontWeight.bold,
fontSize: 15,
);
String text = const {
1: '10K',
3: '30K',
5: '50K',
7: '70K',
9: '90K',
}[value.toInt()] ??
'';
return Text(text, style: style, textAlign: TextAlign.left);
}
LineChartData mainData() {
return LineChartData(
gridData: FlGridData(
show: false,
),
titlesData: FlTitlesData(
show: true,
rightTitles: AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
topTitles: AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 30,
interval: 1,
getTitlesWidget: bottomTitleWidgets,
),
),
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
interval: 1,
getTitlesWidget: leftTitleWidgets,
reservedSize: 42,
),
),
),
borderData: FlBorderData(
show: false,
border: Border.all(color: const Color(0xff37434d), width: 1)),
minX: 0,
maxX: data.length - 1,
minY: 0,
maxY: 10,
lineBarsData: [
LineChartBarData(
spots: [
for (final entry in data.entries)
FlSpot(entry.key.toDouble(), entry.value.toDouble())
],
color: Colors.white,
barWidth: 2,
isStrokeCapRound: true,
dotData: FlDotData(
show: false,
),
belowBarData: BarAreaData(
show: true,
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: gradientColors
.map((color) => color.withOpacity(0.3))
.toList(),
),
),
),
],
);
}
}
final data = [
3.45,
2.14,
1.88,
2.02,
2.25,
2.20,
2.18,
2.49,
2.87,
3.06,
3.00,
2.35,
2.08,
2.22,
2.24,
2.43,
2.14,
2.16,
2.16,
1.85,
2.02,
1.90,
2.12,
1.71,
1.85,
1.77,
1.79,
2.14,
2.25,
2.29,
2.31,
2.79,
2.54,
2.72,
2.37,
2.35,
2.41,
2.66,
2.79,
3.04,
3.58,
4.29,
3.99,
4.42,
5.05,
5.01,
5.51,
8.90,
8.16,
5.61,
5.23,
5.19,
4.19,
3.72,
3.10,
2.97,
2.18,
2.45,
2.33,
2.29,
2.31,
2.31,
3.02,
3.43,
3.50,
3.25,
2.99,
3.08,
3.54,
4.12,
4.04,
4.74,
5.42,
7.70,
5.92,
5.25,
5.80,
5.82,
5.03,
4.99,
4.62,
4.62,
4.46,
6.12,
6.13,
5.37,
5.38,
5.70,
6.33,
6.26,
5.92,
5.41,
5.15,
6.34,
6.16,
6.58,
6.15,
6.13,
6.95,
7.16,
6.46,
7.17,
7.62,
9.52,
1.75,
3.41,
0.30,
3.05,
8.68,
7.54,
6.88,
7.16,
6.25,
6.20,
6.16,
7.13,
4.90,
5.84,
7.41,
6.73,
6.54,
8,
7.11,
7.59,
7.63,
7.34,
6.21,
6.21,
6.08,
6.74,
7.09,
7.11,
7.99,
8.53,
9.41,
0.17,
1.26,
2.68,
1.08,
8.25,
7.66,
6.74,
6.67,
5.82,
5.24,
4.51,
3.95,
3.5,
3.83,
3.79,
3.37,
3.14,
2.99,
4.00,
3.66,
5.34,
5.83,
5.32,
4.29,
4.03,
4.13,
4.79,
4.62,
4.32,
3.89,
3.43,
3.70,
4.25,
4.49,
4.08,
3.97,
4.24,
4.30,
4.54,
4.41,
4.05,
3.89,
3.56,
3.24,
3.16,
2.66,
2.50,
2.16,
1.94,
2.43,
2.45,
2.95,
2.83,
2.85,
3.31,
3.54,
3.33,
3.33,
3.33,
3.81,
4.16,
4.04,
3.83,
3.62,
3.43,
3.62,
3.68,
3.64,
4.24,
4.70,
6,
4.90,
4.66,
4.58,
4.58,
4.04,
3.91,
3.91,
3.77,
4.12,
3.47,
2.99,
2.87,
2.83,
2.60,
2.85,
2.77,
2.83,
2.77,
2.66,
2.33,
2.08,
1.92,
2.27,
1.98,
1.72,
1.91,
1.91,
2.58,
2.81,
2.81,
2.99,
2.97,
2.54,
3.58,
3.29,
2.85,
2.87,
3.10,
3.14,
2.97,
2.97,
2.89,
2.97,
2.87,
3.00,
2.81,
3.87,
2.66,
2.68,
2.79,
2.79,
2.97,
2.83,
2.95,
3,
3.27,
4.08,
4.04,
3.10,
2.68,
2.95,
2.64,
2.64,
2.39,
2.37,
2.22,
2.56,
2.33,
2.64,
2.22,
2.02,
1.90,
1.79,
1.73,
1.75,
1.62,
1.77,
2.29,
].asMap();
You can scroll the chart in any direction by enabling the ZoomPanBehavior.enablePanning property. In addition, ZoomPanBehavior.zoomMode can be used to control the zooming and panning directions. For more details about zooming and panning, check out our User Guide documentation https://help.syncfusion.com/flutter/cartesian-charts/zoom-pan.
Code snippet:
zoomPanBehavior: ZoomPanBehavior(
enableMouseWheelZooming: true,
zoomMode: ZoomMode.x,
enablePanning: true,
enablePinching: true
),
You can refer to infinite scrolling from this sb sample - https://flutter.syncfusion.com/#/cartesian-charts/infinite-scrolling
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'm implementing line chart in flutter i have draw the line chart but i need to show horizontal lines in different colors. Is there any way to get that?
Here is the attached image what i want to achieve.
LineChartData(
minX: 0,
maxX: 11,
minY: 0,
maxY: 6,
titlesData: LineTitles.getTitleData(),
gridData: FlGridData(
show: true,
getDrawingHorizontalLine: (value) {
return FlLine(
color: Colors.grey,
strokeWidth: 1,
);
},
drawVerticalLine: true,
getDrawingVerticalLine: (value) {
return FlLine(
color: Colors.grey,
strokeWidth: 1,
);
},
),
borderData: FlBorderData(
show: true,
border: Border.all(color: Colors.grey, width: 1),
),
lineBarsData: [
LineChartBarData(
spots: [
FlSpot(4.9, 5),
FlSpot(6.8, 2.5),
FlSpot(8, 4),
FlSpot(9.5, 3),
FlSpot(11, 4),
],
isCurved: true,
barWidth: 3,
),
],
),[![I need to show the orange and red horizontal lines][1]][1]
Yes you can check gridline colors in fl_chart. You've already used function with which you can modify these colors which is getDrawingHorizontalLine().
getDrawingHorizontalLine: (value) {
if(value == 1) {
return FlLine(
color: Colors.red,
strokeWidth: 2,
);
} else if (value == 2 ) {
return FlLine(
color: Colors.yellow,
strokeWidth: 2,
);
} else {
return FlLine(
color: Colors.grey,
strokeWidth: 2,
);
}
}
Now here as you can see I've added condition that if values are 1 or 2 then gridline colors should be red & yellow respectively. Now this is the ugly way to put condition to change color but you can do it dynamically yourself.
If you want to change vertical gridlines then you should use getDrawingVerticalLine() to change color values.
You can see my screenshot for output below.
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().
I have a slider that uses a value to render himself, it start rendering after I hit a button but the first time I render it, it gives me an error and then starts working properly.
This is the error:
The getter 'inSeconds' was called on null.
Receiver: null
Tried calling: inSeconds
This is the widget that uses the data:
child: Consumer<MyAudio>(
builder: (context, audioPlayer, child) =>
SleekCircularSlider(
appearance: CircularSliderAppearance(
customWidths: CustomSliderWidths(
progressBarWidth: 2.0,
trackWidth: 1.0,
handlerSize: 1.0,
shadowWidth: 1.0,
),
infoProperties: InfoProperties(
modifier: (value) => '',
),
customColors: CustomSliderColors(
trackColor: Colors.grey,
progressBarColor: Colors.black,
),
size: 4.0,
angleRange: 360,
startAngle: -90.0,
),
min: 0.0,
max: audioPlayer.songLength.inSeconds.toDouble(),
initialValue:
audioPlayer.position.inSeconds.toDouble(),
),
),
),
And this is the function that gives me the values songLength and position:
class MyAudio extends ChangeNotifier {
Duration songLength;
Duration position;
AudioPlayer _player = AudioPlayer();
AudioCache cache;
MyAudio() {
initAudio();
}
initAudio() {
cache = AudioCache(fixedPlayer: _player);
_player.onDurationChanged.listen((Duration d) {
songLength = d;
notifyListeners();
});
_player.onAudioPositionChanged.listen((Duration p) {
position = p;
notifyListeners();
});
I think I should use an async function, what do you think?
If you need more code here's my github repo with all the files: https://github.com/astroxd/sputofy_mobile/tree/main/sputofy_2
the slider is in /lib/miniPlayer and the values are in /lib/model/audioPlayer
The problem is that when the widget is built for first time, the audioPlayer.songLength and audioPlayer.position are null despite audioPlayer is non-null.
In this fragment of code you define a pair of listeners, but the callbacks are called after the first build.
_player.onDurationChanged.listen((Duration d) {
songLength = d;
notifyListeners();
});
_player.onAudioPositionChanged.listen((Duration p) {
position = p;
notifyListeners();
});
Then, a solution can be the following:
child: Consumer<MyAudio>(
builder: (context, audioPlayer, child) => SleekCircularSlider(
appearance: CircularSliderAppearance(
customWidths: CustomSliderWidths(
progressBarWidth: 2.0,
trackWidth: 1.0,
handlerSize: 1.0,
shadowWidth: 1.0,
),
infoProperties: InfoProperties(
modifier: (value) => '',
),
customColors: CustomSliderColors(
trackColor: Colors.grey,
progressBarColor: Colors.black,
),
size: 4.0,
angleRange: 360,
startAngle: -90.0,
),
min: 0.0,
max: audioPlayer.songLength?.inSeconds?.toDouble() ?? 0.0,
initialValue: audioPlayer.position?.inSeconds?.toDouble() ?? 0.0,
),
)
Or maybe use a loader instead.