Add text widget to getDrawingHorizontalLine in fl_chart - flutter

This is my fl_chart bar chart.
I need to add the 'AVG' text to the line as in the image below.
Any ideas? Really stuck. The line is returned under "getDrawingHorizontalLine" section below.
I could try and figure out where this line is and use a stack but doesn't sound too promising 🤔
return BarChart(
BarChartData(
barGroups: graphData,
alignment: BarChartAlignment.center,
groupsSpace: isPortrait ? null : 70,
minY: 0,
maxY: maxUsage,
gridData: FlGridData(
horizontalInterval: average,
checkToShowHorizontalLine: (value) => value == average, <----- draw when avg
drawHorizontalLine: true,
drawVerticalLine: false,
getDrawingHorizontalLine: (value) => FlLine( <------ draws the line
color: Colors.white,
strokeWidth: 1,
dashArray: [5, 5],
),
),

I ended up adding it to the right titles and showing it when value == the value I want it to show.
static AxisTitles getRightTitles(isOverlay, maxUsage, averageUsage) => AxisTitles(
sideTitles: SideTitles(
interval: 0.9,
showTitles: isOverlay,
reservedSize: 34,
getTitlesWidget: (value, meta) {
final String avg = averageUsage.toStringAsFixed(0);
final String val = value.toStringAsFixed(0);
final bool show = (avg == val);
return isOverlay && show
? const Padding(
padding: EdgeInsets.only(left: 8.0, top: 3),
child: Text(
'AVG',
maxLines: 1,
softWrap: false,
style: TextStyle(
color: Colors.white,
fontSize: 11,
fontWeight: FontWeight.bold,
),
),
)
: const SizedBox();
},
),
);

Related

how to make this type gauge in flutter with dynamic section

return SfRadialGauge(
title: GaugeTitle(
text: 'Speedometer',
textStyle:
const TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold)),
axes: <RadialAxis>[
RadialAxis(
minimum: 0,
maximum: 320,
startAngle: 180,
endAngle: 0,
// showLabels: false,
maximumLabels: 320,
onLabelCreated: _handleLabelCreated,
canRotateLabels: true,
labelsPosition: ElementsPosition.outside,
ticksPosition: ElementsPosition.outside,
showTicks: false,
radiusFactor: 1.1,
axisLineStyle: AxisLineStyle(
thickness: 5,
dashArray: <double>[(320+100) / 5, 3],
// color: Color(0xFF66BB6A)
gradient: SweepGradient(colors: <Color>[
Color(0xFFE7627D),
Color(0xFF231557),
Color(0xFF44107A),
Color(0xFFFF1361),
Color(0xFFFFF800),
], stops: <double>[
0,
100,
190,
240,
320
])
),
/* ranges: <GaugeRange>[
GaugeRange(
startValue: 0,
endValue: 100,
color: Colors.green,
),
GaugeRange(
startValue: 100,
endValue: 210,
color: Colors.orange,
),
GaugeRange(
startValue: 210,
endValue: 320,
color: Colors.red,
)
],*/
annotations: <GaugeAnnotation>[
GaugeAnnotation(
angle: 180,
horizontalAlignment: GaugeAlignment.near,
positionFactor: 0.78,
verticalAlignment: GaugeAlignment.near,
widget: Text(
"0",
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.bold,
fontSize: 14,
),
)),
GaugeAnnotation(
angle: 0,
horizontalAlignment: GaugeAlignment.far,
positionFactor: 0.85,
verticalAlignment: GaugeAlignment.near,
widget: Text(
"320",
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.bold,
fontSize: 14,
),
)),
GaugeAnnotation(
axisValue: 100,
positionFactor: 0.5,
angle: 90,
widget: Column(children: [
Center(
child: RichText(
textAlign: TextAlign.center,
text: TextSpan(
text: '223\n',
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.bold,
fontSize: 30,
),
children: [
TextSpan(
text: 'Points earned this month',
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.normal,
fontSize: 12,
),
)
],
)),
)
]))
],
pointers: <GaugePointer>[
MarkerPointer(
value: 223,
markerHeight: 15,
markerWidth: 15,
enableDragging: true,
overlayRadius: 12,
borderColor: Colors.green,
borderWidth: 2,
color: Colors.white,
markerType: MarkerType.circle)
],
)
]);
void _handleLabelCreated(AxisLabelCreatedArgs args) {
if (args.text == '30') {
args.text = '£0 - £4';
} else if (args.text == '100') {
args.text = '£4 - £8';
} else if (args.text == '165') {
args.text = '£8 - £12';
} else if (args.text == '235') {
args.text = '£12 - £16';
} else if (args.text == '290') {
args.text = '£16 - £20';
} else {
args.text = '';
}
I have to develop this type of UI using plugin.but there is some point remaining like color we are not achieve after pointer and how equally segment divided and how responsive for all device that need to check.
I have done below part from my side and remaining thing need to add that i mention .So suggest me how can i achieve .
Your requirement can be achieved with help of the ranges property, MarkerPointer onValueChanging callback in the SfRadialGauge. Added _buildRanges method to find the segment length based on the max value and return the list of the GaugeRange, and added _buildGaugeRange method to create GaugeRange based on the given start, end, and _pointerValue value. The minimum value to the marker range is considered an active range, and the maximum value to the marker range is considered an inactive range in the _buildGaugeRange method. The GaugeRange is colored based on the active and inactive range.
We have prepared and shared a code snippet below for your reference.
class _MyHomePageState extends State<MyHomePage> {
double _pointerValue = 256;
int _segmentsCount = 5;
late List<LabelDetails> _labels;
#override
void initState() {
_labels = [];
_calculateLabelsPosition();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
SfRadialGauge(
title: const GaugeTitle(
text: 'Speedometer',
textStyle:
TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold)),
axes: <RadialAxis>[
RadialAxis(
minimum: 0,
maximum: 320,
startAngle: 180,
endAngle: 0,
maximumLabels: 320,
canScaleToFit: true,
interval: 1,
onLabelCreated: _handleLabelCreated,
canRotateLabels: true,
labelsPosition: ElementsPosition.outside,
showTicks: false,
radiusFactor: 1.1,
pointers: <GaugePointer>[
MarkerPointer(
onValueChanging: (dynamic args) {
setState(() {
_pointerValue = args.value;
});
},
value: _pointerValue,
enableDragging: true,
color: Colors.white,
borderColor: Colors.green,
borderWidth: 3,
markerType: MarkerType.circle,
markerHeight: 15,
markerWidth: 15,
overlayRadius: 0,
)
],
ranges: _buildRanges(),
annotations: <GaugeAnnotation>[
const GaugeAnnotation(
angle: 180,
horizontalAlignment: GaugeAlignment.far,
positionFactor: 0.75,
verticalAlignment: GaugeAlignment.near,
widget: Padding(
padding: EdgeInsets.only(top: 5),
child: Text(
"0",
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.bold,
fontSize: 14,
),
),
)),
const GaugeAnnotation(
angle: 0,
horizontalAlignment: GaugeAlignment.far,
positionFactor: 0.85,
verticalAlignment: GaugeAlignment.near,
widget: Padding(
padding: EdgeInsets.only(top: 5),
child: Text(
"320",
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.bold,
fontSize: 14,
),
),
)),
GaugeAnnotation(
widget:
Column(mainAxisSize: MainAxisSize.min, children: [
Center(
child: RichText(
textAlign: TextAlign.center,
text: TextSpan(
text: '${_pointerValue.round()}\n',
style: const TextStyle(
color: Colors.black,
fontWeight: FontWeight.bold,
fontSize: 30,
),
children: const [
TextSpan(
text: 'Points earned this month',
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.normal,
fontSize: 12,
),
)
],
)),
)
]))
],
),
]),
ElevatedButton(
onPressed: () {
setState(() {
_segmentsCount = 7;
_calculateLabelsPosition();
});
},
child: const Text('Update Segment count'))
],
),
);
}
void _calculateLabelsPosition() {
_labels.clear();
// Length of the each segment.
double segmentLength = 320 / _segmentsCount;
double start = segmentLength / 2;
for (int i = 0; i < _segmentsCount; i++) {
_labels.add(LabelDetails(start.toInt(), '£${i * 4} - £${(i + 1) * 4}'));
start += segmentLength;
}
}
void _handleLabelCreated(AxisLabelCreatedArgs args) {
for (int i = 0; i < _segmentsCount; i++) {
LabelDetails details = _labels[i];
if (details.labelPoint == int.parse(args.text)) {
args.text = details.customizedLabel;
return;
}
}
args.text = '';
}
// Return the list of gauge range
List<GaugeRange> _buildRanges() {
List<GaugeRange> ranges = [];
// Gap value between two range
int gap = 2;
// Length of the each segment without gap.
double segmentLength =
(320 - ((_segmentsCount - 1) * gap)) / _segmentsCount;
double start = 0;
for (int i = 0; i < _segmentsCount; i++) {
_buildGaugeRange(start, start + segmentLength, ranges);
start += segmentLength + gap;
}
return ranges;
}
// Method to create a GaugeRange based on start, end and pointerValue and assigned
// color based on active and inactive range.
void _buildGaugeRange(double start, double end, List<GaugeRange> ranges) {
if (_pointerValue >= start && _pointerValue <= end) {
ranges.add(GaugeRange(
startValue: start, endValue: _pointerValue, color: Colors.green));
ranges.add(GaugeRange(
startValue: _pointerValue,
endValue: end,
color: const Color.fromARGB(255, 82, 86, 97),
));
} else if (_pointerValue >= end) {
ranges.add(GaugeRange(
startValue: start,
endValue: end,
color: Colors.green,
));
} else {
ranges.add(GaugeRange(
startValue: start,
endValue: end,
color: const Color.fromARGB(255, 82, 86, 97),
));
}
}
}
class LabelDetails {
LabelDetails(this.labelPoint, this.customizedLabel);
int labelPoint;
String customizedLabel;
}
Screenshot:

Remove suffix icon from DropdownSearch in flutter

I want to remove suffix icon from DropdownSearch widget. I am using dropdown_search plugin.
I have tried giving size to icon and also added visibility false. but any of these didn't work.
Even I have tried using dropdown builder too..!!
Please help..!!
DropdownSearch<String>(
dropdownBuilder: (context, str) {
return Text(str ?? "-");
},
dropdownDecoratorProps: DropDownDecoratorProps(
textAlign: TextAlign.left,
textAlignVertical: TextAlignVertical.center,
dropdownSearchDecoration: _appTheme.dropDownDecoration()),
dropdownButtonProps: const DropdownButtonProps(
alignment: Alignment.centerRight,
padding: EdgeInsets.all(0),
icon: SizedBox(
width: 0,
child: Visibility(
visible: false,
child: Icon(
Icons.arrow_downward,
size: 0,
)),
),
isVisible: false,
constraints: BoxConstraints(maxWidth: 0, maxHeight: 0),
iconSize: 0),
popupProps: PopupProps.menu(
textStyle: _appTheme.textFiledStyle,
showSelectedItems: true,
showSearchBox: true,
),
items: _registrationBloc.countries,
onChanged: (value) {
_registrationBloc.selectedCountry = value;
},
selectedItem: _registrationBloc.selectedCountry,
)
set the isVisible property to false it will work.
or you can use other plugin. one is searchfield which i have used.
There is a 'dropDownButton' property for DropdownSearch, use it as follows
dropDownButton : Container(),
hope it works

Fl Chart Flutter with date data

I want to show fl line chart using this plugin https://pub.dev/packages/fl_chart.
data is double value and date in format of yyyy-MM-dd. Now I tried to show this data in chart.
Y axis showing data correctly but date calculations are not may be correct it showing lots of dates.
and If I add minX and maxX app got stuck due to lots of dates calculation between min and max X date values.
this is my code: in this data x is Date value and Y is double value.
I got the idea from this link https://github.com/imaNNeoFighT/fl_chart/issues/471
class AnalyticsChart extends StatefulWidget {
const AnalyticsChart({Key? key}) : super(key: key);
#override
_AnalyticsChartState createState() => _AnalyticsChartState();
}
class _AnalyticsChartState extends State<AnalyticsChart> {
List<Color> gradientColors = [
const Color(0xff099f53),
const Color(0xfffefffe),
];
bool showAvg = false;
final dateList = [];
#override
Widget build(BuildContext context) {
double windowHeight = MediaQuery.of(context).size.height - 370;
return Container(
height: windowHeight,
decoration: const BoxDecoration(
borderRadius: BorderRadius.all(
Radius.circular(0),
),
color: whiteColor),
child: Padding(
padding:
const EdgeInsets.only(right: 18.0, left: 12.0, top: 15, bottom: 12),
child: BlocBuilder<AnalyticsChartBloc, AnalyticsChartState>(
builder: (BuildContext context, AnalyticsChartState state) {
if (state is LoadAnalyticsStatesState) {
if (state.totalSales != null &&
(state.totalSales?.isNotEmpty ?? false)) {
List<FlSpot>? flSpots = [];
dateList.clear();
flSpots = state.totalSales?.map((e) {
dateList.add(e.x);
return FlSpot(
DateTime.parse(e.x ?? '') // e.date = "2020-10-24"
.millisecondsSinceEpoch
.toDouble(),
double.parse(e.y ?? '0'));
}).toList();
return LineChart(
mainData(flSpots),
);
}
}
return _buildNoChart();
}),
),
);
}
Widget bottomTitleWidgets(double value, TitleMeta meta) {
const style = TextStyle(
height: 1,
color: placeholderColor,
fontWeight: weight500,
fontSize: fontsize12,
);
final DateTime date = DateTime.fromMillisecondsSinceEpoch(value.toInt());
final parts = date.toIso8601String().split("T");
return SideTitleWidget(
space: 12,
child: Text(parts.first, style: style),
axisSide: meta.axisSide,
);
}
Widget leftTitleWidgets(double value, TitleMeta meta) {
const style = TextStyle(
color: placeholderColor,
fontWeight: weight100,
fontSize: fontsize9,
);
return Container(
padding: const EdgeInsets.only(right: 10),
height: 50.0,
child: Text(
value.toStringAsFixed(0),
style: style,
));
}
LineChartData mainData(List<FlSpot>? flSpots) {
final List<double> xList = [];
final List<double> yList = [];
flSpots?.map((e) {
xList.add(e.x);
yList.add(e.y);
}).toList();
final minX = xList.reduce(min); //
final maxX = xList.reduce(max); //
final minY = yList.reduce(min); //
final maxY = ((yList.reduce(max)) + 1000).toInt().toDouble(); //
debugPrint('minX/maxX $minX $maxX ');
debugPrint('minY/maxY $minY $maxY ');
debugPrint('maxX-minX ${maxX - minX} ');
return LineChartData(
lineTouchData: LineTouchData(
enabled: true,
touchTooltipData: LineTouchTooltipData(
tooltipBgColor: primaryColorGreen,
tooltipRoundedRadius: 4.0,
showOnTopOfTheChartBoxArea: false,
fitInsideHorizontally: true,
getTooltipItems: (touchedSpots) {
return touchedSpots.map(
(LineBarSpot touchedSpot) {
const textStyle = TextStyle(
fontSize: fontsize13,
fontWeight: weight500,
color: whiteColor,
height: 1.4,
);
return LineTooltipItem(
'Total Sales \n 17,500',
textStyle,
);
},
).toList();
},
),
),
gridData: FlGridData(
show: true,
drawVerticalLine: true,
horizontalInterval: 1,
verticalInterval: 1,
getDrawingHorizontalLine: (value) {
return FlLine(
color: borderColor,
strokeWidth: 1,
);
},
getDrawingVerticalLine: (value) {
return FlLine(
color: Colors.transparent,
strokeWidth: 1,
);
},
),
titlesData: FlTitlesData(
show: true,
rightTitles: AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
topTitles: AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
interval: (flSpots![flSpots.length - 1].x - flSpots[0].x),
getTitlesWidget: bottomTitleWidgets,
))),
maxX: maxX,
minX: minX,
maxY: maxY,
minY: minY,
borderData: FlBorderData(
show: true, border: Border.all(color: Colors.transparent, width: 0)),
lineBarsData: [
LineChartBarData(
spots: flSpots.asMap().entries.map((e) {
return FlSpot(e.key.toDouble(), e.value.y);
}).toList(),
isCurved: false,
color: primaryColorGreen,
barWidth: 2,
isStrokeCapRound: true,
dotData: FlDotData(
show: true,
),
belowBarData: BarAreaData(
show: true,
gradient: LinearGradient(
colors: gradientColors
.map((color) => color.withOpacity(0.3))
.toList(),
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
),
),
],
);
}
Widget _buildNoChart() => const SizedBox(
child: Center(
child: Text('No chart data found'),
),
);
}
///////////////////////

Changing LineChart's line color dynamically

I'm currently building a patient tracking application where patients can save their blood pressures any time they measure. And from the data they enter, a graph is built. On the same graph, I'm showing both diastole and systole. What I want is to change graph line's color if it falls below or goes beyond a threshold value. For ex. for diastole if it falls below 60 or goes beyond 100, I want the line to turn red from that point until a new data is entered which is between 60-100.
Sorry if the code is hard to read but it's still in the prototype phase. Here is my current code:
class Graph extends StatefulWidget {
#override
_GraphState createState() => _GraphState();
}
class _GraphState extends State<Graph> {
final kTansiyon = [80, 70, 63, 76, 82, 90];
final kTimes = [7, 13.6, 15.8, 17, 20, 22];
final bTansiyon = [121, 136, 117, 120, 110, 112];
final bTimes = [10, 11, 12, 20, 21, 22];
List<FlSpot> createSpots(List<int> tansiyon, List<int> times) {
List<FlSpot> retVal = [];
if (tansiyon.length != times.length) {
return null;
} else {
for (int i = 0; i < tansiyon.length; i++) {
retVal.add(FlSpot(
times.elementAt(i).toDouble(), tansiyon.elementAt(i).toDouble()));
}
}
return retVal;
}
List<Color> kgradientColors = [
const Color(0xff23b6e6),
];
List<Color> bgradientColors = [Colors.grey];
#override
Widget build(BuildContext context) {
return LineChart(
LineChartData(
minX: 0,
minY: 0,
maxX: 24,
maxY: 150,
titlesData: FlTitlesData(
show: true,
bottomTitles: SideTitles(
showTitles: true,
reservedSize: 35,
getTextStyles: (BuildContext context, value) => const TextStyle(
color: Colors.grey,
fontWeight: FontWeight.bold,
fontSize: 10),
getTitles: (value) {
switch (value.toInt()) {
case 4:
return '4:00';
break;
case 8:
return '8:00';
break;
case 12:
return '12:00';
break;
case 16:
return '16:00';
break;
case 20:
return '20:00';
break;
case 24:
return '00:00';
break;
}
return '';
}),
leftTitles: SideTitles(
showTitles: true,
getTextStyles: (BuildContext context, value) => const TextStyle(
color: Colors.grey,
fontWeight: FontWeight.bold,
fontSize: 10),
getTitles: (value) {
if (value % 10 == 0) {
return '${value.toInt()}';
}
return '';
},
),
),
gridData: FlGridData(
show: false,
drawVerticalLine: true,
getDrawingHorizontalLine: (value) {
return FlLine(
color: const Color(0xff37434d),
strokeWidth: 1,
);
},
getDrawingVerticalLine: (value) {
return FlLine(
color: const Color(0xff37434d),
strokeWidth: 1,
);
},
),
borderData: FlBorderData(
show: true,
border: Border.all(color: const Color(0xff37434d), width: 1),
),
lineBarsData: [
LineChartBarData(
spots: [
FlSpot(9, 80),
FlSpot(12, 77),
FlSpot(15, 80),
FlSpot(18, 90),
FlSpot(21, 62),
],
isCurved: true,
colors: kgradientColors,
barWidth: 5,
isStrokeCapRound: true,
dotData: FlDotData(
show: false,
),
belowBarData: BarAreaData(
show: true,
colors: kgradientColors
.map((color) => color.withOpacity(0.3))
.toList(),
),
),
LineChartBarData(
spots: [
FlSpot(7, 120),
FlSpot(9, 130),
FlSpot(11, 116),
FlSpot(22, 128),
FlSpot(23, 123),
],
isCurved: true,
colors: bgradientColors,
barWidth: 5,
isStrokeCapRound: true,
dotData: FlDotData(
show: false,
),
belowBarData: BarAreaData(
show: true,
colors: bgradientColors
.map((color) => color.withOpacity(0.3))
.toList(),
),
),
LineChartBarData(
spots: [],
isCurved: true,
colors: bgradientColors,
barWidth: 5,
isStrokeCapRound: true,
dotData: FlDotData(
show: false,
),
belowBarData: BarAreaData(
show: true,
colors: bgradientColors
.map((color) => color.withOpacity(0.3))
.toList(),
),
)
]),
);
}
}
And this is how it looks at the moment:
graph
Thanks in advance.
So this is the current code for one of the lines:
LineChartBarData(
spots: [
FlSpot(9, 80),
FlSpot(12, 77),
FlSpot(15, 80),
FlSpot(18, 90),
FlSpot(21, 62),
],
isCurved: true,
colors: kgradientColors,
barWidth: 5,
isStrokeCapRound: true,
dotData: FlDotData(
show: false,
),
belowBarData: BarAreaData(
show: true,
colors: kgradientColors
.map((color) => color.withOpacity(0.3))
.toList(),
),
),
The first thing you would need to do is to factor out the placeholder values for the spots into a variable we can reference in order to determine what line color to use.
List<FlSpot> spots = [
FlSpot(9, 80),
FlSpot(12, 77),
FlSpot(15, 80),
FlSpot(18, 90),
FlSpot(21, 62),
];
Then instead of kgradientColors lets define two different color sets, one to use for a red line and one for the blue line.
List<Color> redColors = [
Colors.red,
];
List<Color> blueColors = [
Colors.blue,
];
Then we can define a helper function that picks the correct color set based on if the last value in spots is between 60 and 100:
List<Color> get lineColors => spots.last.y <= 100 && spots.last.y >= 60
? blueColors
: redColors;
Then use it in the LineChartBarData:
LineChartBarData(
spots: spots,
isCurved: true,
colors: lineColors,
barWidth: 5,
isStrokeCapRound: true,
dotData: FlDotData(
show: false,
),
belowBarData: BarAreaData(
show: true,
colors: lineColors
.map((color) => color.withOpacity(0.3))
.toList(),
),
),

How can I call OnvalueChanged in flutter?

I want to add page ==> Like tab bar
I want to add like 2 pages - in this SegmentsBar...
How can I achieve it?
this is my code ---
[
final _segments = <String, String>{
'freelancher': 'Freelancer',
'employer': 'Employer',
};
var _value = 'freelancher';
AdvancedSegment(
sliderOffset: 5,
sliderColor: Colors.white,
backgroundColor: Colors.white24,
shadow: [
BoxShadow(
color: Colors.transparent,
),
],
activeStyle: TextStyle(
fontFamily: AppTheme.medium,
color: AppTheme.primaryColor,
),
inactiveStyle: TextStyle(
fontFamily: AppTheme.medium,
color: AppTheme.white,
),
itemPadding: EdgeInsets.symmetric(
horizontal: 45,
vertical: 10,
),
segments: _segments,
controller: _advanceC,
onValueChanged: (value) {
_value == value ? ChatPage() : ProfilePage();
},
value: _value,
),
I need help please.
Thanks Buddy.
I don't know what is AdvancedSegment, use instead AnimatedPositioned
onValueChanged:
(value) {
setState(() {
_value == value ? ChatPage() : ProfilePage();
});
}