So I have a 2 bar chart widgets within a Stack, upon pressing a button, the page switches between these 2 charts. The problem is when I switch to the 2nd bar chart, the x-axis values disappear. I get the error
════════ Exception caught by rendering library ═════════════════════════════════
Bad state: No element
The relevant error-causing widget was
BarChart
Here is the first bar chart widget output
2nd bar chart widget output
Here are my bar chart Widgets
BarChartWidget
class BarChartWidget extends StatelessWidget {
final double barWidth = 22;
#override
Widget build(BuildContext context) => BarChart(
BarChartData(
alignment: BarChartAlignment.center,
maxY: 50,
groupsSpace: 12,
barTouchData: BarTouchData(enabled: true),
titlesData: FlTitlesData(
bottomTitles: BarTitles.getTopBottomTitles(),
leftTitles: BarTitles.getSideTitles(),
),
gridData: FlGridData(
checkToShowHorizontalLine: (value) =>
value % BarData.interval == 0,
checkToShowVerticalLine: (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,
);
}
},
getDrawingVerticalLine: (value) {
if (value == 0) {
return FlLine(
color: const Color(0xff363753),
strokeWidth: 0.8,
);
} else {
return FlLine(
color: const Color(0xff2a2747),
strokeWidth: 3,
);
}
}),
barGroups: BarData.barData
.map(
(data) => BarChartGroupData(
x: data.id,
barRods: [
BarChartRodData(
y: data.y,
width: barWidth,
colors: [data.color],
borderRadius: data.y > 0
? BorderRadius.only(
topLeft: Radius.circular(6),
topRight: Radius.circular(6),
)
: BorderRadius.only(
bottomLeft: Radius.circular(6),
bottomRight: Radius.circular(6),
),
),
],
),
)
.toList(),
),
);
}
BarChartWidget2
class BarChartWidget2 extends StatelessWidget {
final double barWidth = 22;
#override
Widget build(BuildContext context) => BarChart(
BarChartData(
alignment: BarChartAlignment.center,
maxY: 50,
groupsSpace: 12,
barTouchData: BarTouchData(enabled: true),
titlesData: FlTitlesData(
bottomTitles: BarTitles.getTopBottomTitles(),
leftTitles: BarTitles.getSideTitles(),
),
gridData: FlGridData(
checkToShowHorizontalLine: (value) =>
value % BarData2.interval == 0,
checkToShowVerticalLine: (value) =>
value % BarData2.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,
);
}
},
getDrawingVerticalLine: (value) {
if (value == 0) {
return FlLine(
color: const Color(0xff363753),
strokeWidth: 0.8,
);
} else {
return FlLine(
color: const Color(0xff2a2747),
strokeWidth: 3,
);
}
}),
barGroups: BarData2.barData2
.map(
(data) => BarChartGroupData(
x: data.id,
barRods: [
BarChartRodData(
y: data.y,
width: barWidth,
colors: [data.color],
borderRadius: data.y > 0
? BorderRadius.only(
topLeft: Radius.circular(6),
topRight: Radius.circular(6),
)
: BorderRadius.only(
bottomLeft: Radius.circular(6),
bottomRight: Radius.circular(6),
),
),
],
),
)
.toList(),
),
);
}
I'm not sure what I did wrong, the error is pointing to BarChartWidget2's BarChart. Any help is much appreciated
Related
I have a List<Map<String,Widget>> and one function to remove items from it, and one function to add to it, and both setState the console is printing that the item removed is actually correct, but on the interface, it's only the last item is removed, and somehow the widget that is to be removed is still there on the interface.
Here are the two functions:
addPage() {
String i = pageHolder.ins.pages[pageHolder.ins.pages.length - 2].keys.first;
pageHolder.ins.pages.insert(pageHolder.ins.pages.length - 1, {
"${int.parse(i) + 1}":
PageWidget(callback: removePage, index: int.parse(i) + 1)
});
setState(() {});
}
removePage(String index) {
print("remove page index recerived: $index");
var i = (pageHolder.ins.pages)
.indexWhere((element) => element.keys.first == index);
print("should be removing at: $i");
pageHolder.ins.pages.removeAt(i);
setState(() {});
}
and here is what i get in the console:
[{0: PageWidget}, {1: PageWidget}, {2: PageWidget}, {7: PageWidget}, {8: PageWidget}, {9: PageWidget}, {: Adder}]
remove page index recerived: 7
should be removing at: 3
[{0: PageWidget}, {1: PageWidget}, {2: PageWidget}, {8: PageWidget}, {9: PageWidget}, {: Adder}]
here is the widget code:
import 'package:file_picker/file_picker.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:just_audio/just_audio.dart';
import 'package:teacher_page/classes/pageHolder.dart';
import 'classes/audiostreamer.dart';
class PageWidget extends StatefulWidget {
final index;
final callback;
const PageWidget({super.key, this.index, this.callback});
#override
State<PageWidget> createState() => _PageWidgetState();
}
class _PageWidgetState extends State<PageWidget>
with AutomaticKeepAliveClientMixin {
#override
void initState() {
print("init state is called again and index is:${widget.index}");
super.initState();
}
bool imageAvailable = false;
late Uint8List imageBytes;
late Uint8List AudioBytes;
final player = AudioPlayer();
bool audioAvailable = false;
#override
Widget build(BuildContext context) {
return pageContainer();
}
placeholder() {
return Container(
color: Colors.purple.withOpacity(0.5),
child: const Center(
child: Text(
"Pick your image",
style: TextStyle(color: Colors.white),
)),
);
}
pageContainer() {
return Container(
margin: const EdgeInsets.only(left: 30, top: 100, right: 30, bottom: 50),
height: double.infinity,
width: double.infinity,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(10),
topRight: Radius.circular(10),
bottomLeft: Radius.circular(10),
bottomRight: Radius.circular(10)),
boxShadow: [
BoxShadow(
color: Colors.purple.withOpacity(0.5),
spreadRadius: 3,
blurRadius: 7,
offset: const Offset(0, 3), // changes position of shadow
),
],
),
child: Column(
children: [
Expanded(
child: InkWell(
child: imageAvailable ? Image.memory(imageBytes) : placeholder(),
onTap: () async {
FilePickerResult? result = await FilePicker.platform.pickFiles(
type: FileType.custom,
allowedExtensions: ['jpg', 'png'],
);
if (result != null) {
setState(() {
imageBytes = result.files[0].bytes!;
imageAvailable = true;
});
// File file = File(result.files.single.path);
} else {
// User canceled the picker
}
},
),
),
Row(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: ElevatedButton.icon(
style: ElevatedButton.styleFrom(
backgroundColor: audioAvailable
? Colors.purple
: Colors.grey, // This is what you need!
),
onPressed: () async {
if (audioAvailable) {
await player.setAudioSource(JABytesSource(AudioBytes));
player.play();
}
},
icon: const Icon(Icons.play_arrow),
label: const Text("Play")),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: ElevatedButton.icon(
onPressed: () async {
FilePickerResult? result =
await FilePicker.platform.pickFiles(
type: FileType.custom,
allowedExtensions: ['mp3', 'wav'],
);
if (result != null) {
setState(() {
AudioBytes = result.files[0].bytes!;
audioAvailable = true;
});
// File file = File(result.files.single.path);
} else {
// User canceled the picker
}
},
icon: const Icon(Icons.file_upload),
label: const Text("Pick an audio file")),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: (widget.index > 2)
? ElevatedButton.icon(
onPressed: () async {
// print("remove is called: ${widget.index}");
widget.callback(widget.index.toString());
},
icon: const Icon(Icons.delete),
label: Text("${widget.index}"))
: null,
),
],
),
TextField(
onChanged: (text) {
print("THe text is:$text");
print(pageHolder.ins.pages);
// pageHolder.ins.pages;
},
style: const TextStyle(color: Colors.purple, fontSize: 24),
decoration: const InputDecoration(
fillColor: Colors.white,
filled: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(8))),
labelText: 'Word or the sentence for the picture',
labelStyle: TextStyle(
color: Colors.purple, fontSize: 24, fontFamily: "sans-serif"),
floatingLabelStyle: TextStyle(
color: Color.fromARGB(255, 191, 116, 204), fontSize: 24),
),
),
],
),
);
}
#override
// TODO: implement wantKeepAlive
bool get wantKeepAlive => true;
}
I have SfCartesianChart and trackball inside, when I try to click or move a mouse on the SfCartesianChart then nothing happens, my trackball doesn't appear.
It appears only one second then immediately disappears, I fond out it by clicking and moving a mouse on SfCartesianChart almost a minute...
How to fix that?
This is my SfCartesianChart, there is no trackball when I click or move on blue dots:
And this is my code:
class HomeWidget extends StatefulWidget {
const HomeWidget({super.key});
#override
State<HomeWidget> createState() => _HomeWidgetState();
}
class _HomeWidgetState extends State<HomeWidget> {
late TabController _tabController;
void initState() {
_tabController = TabController(vsync: this, length: 3);
super.initState();
}
#override
void dispose() {
_tabController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return SizedBox(
height: 220.h,
child: TabBarView(
controller: _tabController,
children: [
DaysChart(
monitoringData: state.chartData,
precision: 1
),
DaysChart(
monitoringData: state.chartData,
precision: 2
),
DaysChart(
monitoringData: state.chartData,
precision: 3
),
],
)
);
}
}
Chartdata looks like this:
state.chartData = List<TimeSeriesValues> dayMonitoringData = [
TimeSeriesValues(DateTime(2007, 2, 1, 8, 40), 30),
TimeSeriesValues(DateTime(2007, 2, 1, 12, 40), 80),
TimeSeriesValues(DateTime(2007, 2, 1, 18, 40), 50),
];
And TimeSeriesValues class:
class TimeSeriesValues {
final DateTime time;
final int values;
TimeSeriesValues(this.time, this.values);
}
And this is DaysChart:
class DaysChart extends StatefulWidget {
const DaysChart({
Key? key,
required this.monitoringData,
required this.precision,
}) : super(key: key);
final List<TimeSeriesValues> monitoringData;
final int precision;
#override
State<DaysChart> createState() => _DaysChartState();
}
class _DaysChartState extends State<DaysChart> {
late TrackballBehavior _trackballBehavior;
#override
void initState() {
_trackballBehavior = TrackballBehavior(
enable: true,
shouldAlwaysShow: true,
lineColor: const Color(0xFF454545),
activationMode: ActivationMode.singleTap,
tooltipDisplayMode: TrackballDisplayMode.nearestPoint,
tooltipSettings: const InteractiveTooltip(
arrowLength: 0,
arrowWidth: 0,
canShowMarker: false,
color: Colors.transparent,
),
builder: (context, TrackballDetails trackballDetails) {
var tag = Localizations.maybeLocaleOf(context)?.toLanguageTag();
return SizedBox(
height: 50,
child: Column(
children: [
Text(
"${trackballDetails.point!.yValue.round().toString()}%",
style: TextStyle(
color: Colors.white,
fontSize: 16.sp,
)
),
Text(
DateFormat.MMMMd(tag).format(trackballDetails.point!.x),
style: TextStyle(
color: Colors.white,
fontSize: 10.sp,
),
)
],
)
);
}
);
super.initState();
}
#override
Widget build(BuildContext context) {
final List<double> stops = <double>[];
stops.add(0.1);
stops.add(1.0);
return SizedBox(
height: 190.h,
width: 320.w,
child: SfCartesianChart(
plotAreaBorderWidth: 0,
plotAreaBorderColor: Colors.white24,
trackballBehavior: _trackballBehavior,
primaryXAxis: DateTimeCategoryAxis(
majorTickLines: const MajorTickLines(width: 0),
axisLine: const AxisLine(
color: Colors.white24,
dashArray: <double>[5,5]
),
minimum: widget.monitoringData.first.time,
maximum: widget.monitoringData.last.time,
intervalType: widget.precision == 1
? DateTimeIntervalType.minutes
: widget.precision == 2
? DateTimeIntervalType.days
: DateTimeIntervalType.months,
dateFormat: widget.precision == 1
? DateFormat.Hm()
: widget.precision == 2
? DateFormat.E()
: DateFormat.MMMd(),
borderColor: Colors.transparent,
majorGridLines: const MajorGridLines(
width: 0.5,
color: Colors.transparent,
),
),
primaryYAxis: NumericAxis(
majorGridLines: const MajorGridLines(width: 0.5, color: Colors.white24, dashArray: <double>[5, 5]),
majorTickLines: const MajorTickLines(width: 0),
axisLine: const AxisLine(
width: 0
),
labelStyle: const TextStyle(
fontSize: 0
),
minimum: 0,
maximum: 100
),
series: <ChartSeries<TimeSeriesValues, DateTime>>[
AreaSeries<TimeSeriesValues, DateTime>(
borderWidth: 2,
animationDuration: 0,
borderColor: const Color(0xFF409CFF),
dataSource: widget.monitoringData,
markerSettings: const MarkerSettings(
isVisible: true,
color: Color(0xFF409CFF),
height: 11,
width: 11,
borderWidth: 3,
borderColor: Colors.transparent,
),
xValueMapper: (TimeSeriesValues sales, _) => sales.time,
yValueMapper: (TimeSeriesValues sales, _) => sales.values,
gradient: LinearGradient(
colors: const [Color(0xFF121212), Color(0xFF10273F)],
stops: stops,
begin: Alignment.bottomCenter,
end: Alignment.topCenter),
)
]
);
}
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'),
),
);
}
///////////////////////
I am trying to get the values passed from my TextformField with their units (ie cm or inches) to my form in order to adjust values submitted on the form before they are sent to a calculator. For example: if someone submits 77 inches it would convert to 196 cm since my calculator relies on metric system.
My_unit.dart
import 'textfield_entry.dart';
class MyUnit extends ValueNotifier<Units> {
//You want to check when the enum Units change, so that will be your ValueNotifier
final String _imperial;
final String _metric;
MyUnit(
{Units unit = Units.unitImperial,
String imperial = 'inches',
String metric = 'cm'})
: _imperial = imperial,
_metric = metric,
super(unit);
String get unitType => value == Units.unitImperial
? _imperial
: _metric; //The labels you define, just like unit1 and unit2 in TextFieldEntry
Units get unit => value; //the enum value
set unit(Units newUnit) =>
value = newUnit; //when this change, it will rebuild the listeners
}
TextFormField
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:form_builder_validators/form_builder_validators.dart';
import 'my_unit.dart';
enum Units { unitImperial, unitMetric }
typedef OnSaved(value);
class TextFieldEntry extends StatefulWidget {
TextFieldEntry(
{required this.name, required this.onSaved, this.formCallBackFunction});
final String name;
final OnSaved onSaved;
final Function? formCallBackFunction;
final myDecorationField = InputDecoration(
labelText: 'textFieldName',
labelStyle: TextStyle(fontSize: 20, color: Colors.pink),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.only(
topRight: Radius.circular(0),
bottomRight: Radius.circular(0),
),
borderSide: BorderSide(
width: 1,
color: Colors.pink,
),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.only(
topRight: Radius.circular(0),
bottomRight: Radius.circular(0),
),
borderSide: BorderSide(
color: Colors.pink,
width: 1,
),
),
);
#override
State<TextFieldEntry> createState() => _TextFieldEntryState();
}
class _TextFieldEntryState extends State<TextFieldEntry> {
var _validator;
var _keyboard;
var _myUnit;
String unitSelected = 'unit Error';
Widget build(BuildContext context) {
if (widget.name == 'height') {
final heightValidate = FormBuilderValidators.compose([
FormBuilderValidators.required(context),
FormBuilderValidators.numeric(context),
FormBuilderValidators.max(context, 200),
FormBuilderValidators.min(context, 30)
]);
_validator = heightValidate;
_keyboard = TextInputType.number;
_myUnit = MyUnit();
} else if (widget.name == 'weight') {
final weightValidate = FormBuilderValidators.compose([
FormBuilderValidators.required(context),
FormBuilderValidators.numeric(context),
FormBuilderValidators.max(context, 450),
FormBuilderValidators.min(context, 30)
]);
_validator = weightValidate;
_keyboard = TextInputType.number;
_myUnit = MyUnit(imperial: 'lbs', metric: 'kg');
} else {
_validator = null;
_myUnit = null;
}
return Padding(
padding: const EdgeInsets.all(2.0),
child: Column(
children: [
Container(
constraints: BoxConstraints(
maxWidth: 375,
maxHeight: widget.name == 'Description' ? 100 : 50),
//- Textformfield code
child: Row(
children: [
Expanded(
child: FormBuilderTextField(
name: widget.name,
maxLines: widget.name == 'Description' ? 5 : 1,
decoration: widget.myDecorationField.copyWith(
labelText: widget.name,
labelStyle: TextStyle(
color: Theme.of(context).colorScheme.primary),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).colorScheme.primary)),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).colorScheme.primary))),
onSaved: widget.onSaved,
validator: _validator,
keyboardType: _keyboard,
),
),
Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary,
border: Border.all(
color: Theme.of(context).colorScheme.primary,
width: _myUnit != null ? 2 : 0)),
//- Text for the unit changing button (i.e. inches)
child: Row(
children: [
if (_myUnit != null)
Container(
padding: EdgeInsets.all(5),
child: Center(
child: ValueListenableBuilder<Units>(
valueListenable: _myUnit,
builder: (context, unit, _) => AutoSizeText(
_myUnit.unitType,
style: TextStyle(
color: Theme.of(context)
.colorScheme
.onPrimary,
fontSize: 20,
fontWeight: FontWeight.w500),
)),
),
),
Builder(
builder: (context) {
if (widget.name == 'height' ||
widget.name == 'weight' ) {
return Container(
constraints:
BoxConstraints(maxHeight: 50, maxWidth: 60),
//- need to callback this information to adjust values accordingly (i.e. change inches into cm)
child: GestureDetector(
onTap: () {
Units unit = _myUnit.unit;
_myUnit.unit != null
? _myUnit.unit =
unit == Units.unitImperial
? Units.unitMetric
: Units.unitImperial
: '';
//- using this method returns with an error of "type 'Units' is not a subtype of type 'String'"
return widget.formCallBackFunction!(unit);
},
child: Center(
child: Icon(Icons.loop,
size: 30,
color: Theme.of(context)
.colorScheme
.onPrimary),
),
),
);
} else {
return Container();
}
},
),
],
),
),
],
),
),
SizedBox(
height: 12,
)
],
),
);
}
}
Implementation in a form
class Input {
var title;
var unit;
Input({
this.title,
this.unit,
});
}
class BMIDosing extends StatefulWidget {
#override
State<BMIDosing> createState() => _BMIDosingState();
}
class _BMIDosingState extends State<BMIDosing> {
final _formKey = GlobalKey<FormState>();
List<Input> _traditionalList = [
Input(
title: 'height',
),
Input(
title: 'weight',
),
Input(title: 'age'),];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: CustomAppBar(title: 'Traditional'),
drawer: MainDrawer(),
body: SingleChildScrollView(
child: Form(
key: _formKey,
child: Center(
child: Column(
children: [
SizedBox(
height: 20,
),
Container(
constraints:
BoxConstraints(maxHeight: 475, maxWidth: 375), //Needed
child: ListView.builder(
itemCount: _traditionalList.length,
itemBuilder: (BuildContext inp, index) {
return _traditionalList[index].title ==
'height' ||
_traditionalList[index]
.title ==
'weight' ||
_traditionalList[index]
.title ==
'age'
? TextFieldEntry(
name: _traditionalList[index]
.title,
onSaved: (value) {
_traditionalList[index]
.title = value;
},
);
}),
),
ElevatedButton(
child: Text('Calculate'),
onPressed: () {
// if (_formKey.currentState!.validate()) {
_formKey.currentState?.save();
}
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(),
),
),