functionality/graph
#54
Merged
Haroon6138
merged 2 commits from functionality/graph into master 1 month ago
@ -0,0 +1,14 @@
|
||||
|
||||
|
||||
///class used to provide value for the [DynamicResultChart] to plot the values
|
||||
class DataPoint {
|
||||
///values that is displayed on the graph and dot is plotted on this
|
||||
final double value;
|
||||
///label shown on the bottom of the graph
|
||||
String label;
|
||||
|
||||
DataPoint(
|
||||
{required this.value,
|
||||
required this.label,
|
||||
});
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
import 'dart:ui' show Color;
|
||||
|
||||
class ThresholdRange {
|
||||
final String label;
|
||||
final double value;
|
||||
final Color color;
|
||||
final Color lineColor;
|
||||
final String? actualValue;
|
||||
|
||||
ThresholdRange(
|
||||
{required this.label,
|
||||
required this.value,
|
||||
required this.color,
|
||||
required this.lineColor,
|
||||
this.actualValue});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ThresholdRange(label: $label, value: $value, color: ${color.value.toRadixString(16)}, lineColor: ${lineColor.value.toRadixString(16)})';
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,287 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:hmg_patient_app_new/core/common_models/data_points.dart';
|
||||
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
|
||||
import 'package:hmg_patient_app_new/theme/colors.dart';
|
||||
///
|
||||
/// CustomGraph(dataPoints: sampleData, scrollDirection: Axis.horizontal,height: 200,maxY: 100, maxX:2.5,
|
||||
/// leftLabelFormatter: (value){
|
||||
/// Widget buildLabel(String label) {
|
||||
/// return Padding(
|
||||
/// padding: const EdgeInsets.only(right: 8),
|
||||
/// child: Text(
|
||||
/// label,
|
||||
/// style: TextStyle(
|
||||
/// fontSize: 8.fSize, color: AppColors.textColor,
|
||||
/// fontFamily:
|
||||
/// FontUtils.getFontFamilyForLanguage(false)
|
||||
/// ),
|
||||
/// textAlign: TextAlign.right,
|
||||
/// ),
|
||||
/// );
|
||||
/// }
|
||||
/// switch (value.toInt()) {
|
||||
///
|
||||
/// case 20:
|
||||
/// return buildLabel("Critical Low");
|
||||
/// case 40:
|
||||
/// return buildLabel("Low");
|
||||
/// case 60:
|
||||
/// return buildLabel("Normal");
|
||||
/// case 80:
|
||||
/// return buildLabel("High");
|
||||
/// case 100:
|
||||
/// return buildLabel("Critical High");
|
||||
/// }
|
||||
/// return const SizedBox.shrink();
|
||||
/// },
|
||||
///
|
||||
/// ),
|
||||
class CustomGraph extends StatelessWidget {
|
||||
final List<DataPoint> dataPoints;
|
||||
final double? width;
|
||||
final double height;
|
||||
final double? maxY;
|
||||
final double? maxX;
|
||||
final Color spotColor;
|
||||
final Color graphColor;
|
||||
final Color graphShadowColor;
|
||||
final Color graphGridColor;
|
||||
final Color bottomLabelColor;
|
||||
final double? bottomLabelSize;
|
||||
final FontWeight? bottomLabelFontWeight;
|
||||
|
||||
///creates the left label and provide it to the chart as it will be used by other part of the application so the label will be different for every chart
|
||||
final Widget Function(double value) leftLabelFormatter;
|
||||
|
||||
final Axis scrollDirection;
|
||||
final bool showBottomTitleDates;
|
||||
final bool isFullScreeGraph;
|
||||
|
||||
const CustomGraph({
|
||||
super.key,
|
||||
required this.dataPoints,
|
||||
required this.leftLabelFormatter,
|
||||
this.width,
|
||||
required this.scrollDirection,
|
||||
required this.height,
|
||||
this.maxY,
|
||||
this.maxX,
|
||||
this.showBottomTitleDates = true,
|
||||
this.isFullScreeGraph = false,
|
||||
this.spotColor = AppColors.bgGreenColor,
|
||||
this.graphColor = AppColors.bgGreenColor,
|
||||
this.graphShadowColor = AppColors.graphGridColor,
|
||||
this.graphGridColor = AppColors.graphGridColor,
|
||||
this.bottomLabelColor = AppColors.textColor,
|
||||
this.bottomLabelFontWeight = FontWeight.w500,
|
||||
this.bottomLabelSize,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// var maxY = 0.0;
|
||||
double interval = 20;
|
||||
if ((maxY ?? 0) > 10 && (maxY ?? 0) <= 20) {
|
||||
interval = 2;
|
||||
} else if ((maxY ?? 0) > 5 && (maxY ?? 0) <= 10) {
|
||||
interval = 1;
|
||||
} else if ((maxY ?? 0) >= 0 && (maxY ?? 0) <= 5) {
|
||||
interval = .4;
|
||||
}
|
||||
return Material(
|
||||
color: Colors.white,
|
||||
child: SizedBox(
|
||||
width: width,
|
||||
height: height,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0, bottom: 8),
|
||||
child: LineChart(
|
||||
LineChartData(
|
||||
minY: 0,
|
||||
maxY:
|
||||
((maxY?.ceilToDouble() ?? 0.0) + interval).floorToDouble(),
|
||||
// minX: dataPoints.first.labelValue - 1,
|
||||
maxX: maxX,
|
||||
minX: -0.2,
|
||||
lineTouchData: LineTouchData(
|
||||
getTouchLineEnd: (_, __) => 0,
|
||||
getTouchedSpotIndicator: (barData, indicators) {
|
||||
// Only show custom marker for touched spot
|
||||
return indicators.map((int index) {
|
||||
return TouchedSpotIndicatorData(
|
||||
FlLine(color: Colors.transparent),
|
||||
FlDotData(
|
||||
show: true,
|
||||
getDotPainter: (spot, percent, barData, idx) {
|
||||
return FlDotCirclePainter(
|
||||
radius: 8,
|
||||
color: spotColor,
|
||||
strokeWidth: 2,
|
||||
strokeColor: Colors.white,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}).toList();
|
||||
},
|
||||
enabled: true,
|
||||
touchTooltipData: LineTouchTooltipData(
|
||||
getTooltipColor: (_) => Colors.white,
|
||||
getTooltipItems: (touchedSpots) {
|
||||
if (touchedSpots.isEmpty) return [];
|
||||
// Only show tooltip for the first touched spot, hide others
|
||||
return touchedSpots.map((spot) {
|
||||
if (spot == touchedSpots.first) {
|
||||
final dataPoint = dataPoints[spot.x.toInt()];
|
||||
|
||||
return LineTooltipItem(
|
||||
// '${dataPoint.label} ${spot.y.toStringAsFixed(2)}',
|
||||
'${dataPoint.value} ',
|
||||
TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 12.fSize,
|
||||
fontWeight: FontWeight.w500),
|
||||
);
|
||||
}
|
||||
return null; // hides the rest
|
||||
}).toList();
|
||||
},
|
||||
),
|
||||
),
|
||||
titlesData: FlTitlesData(
|
||||
leftTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
reservedSize: 77,
|
||||
interval: .1, // Let fl_chart handle it
|
||||
getTitlesWidget: (value, _) {
|
||||
return leftLabelFormatter(value);
|
||||
},
|
||||
),
|
||||
),
|
||||
bottomTitles: AxisTitles(
|
||||
axisNameSize: 60,
|
||||
sideTitles: SideTitles(
|
||||
showTitles: showBottomTitleDates,
|
||||
reservedSize: 50,
|
||||
getTitlesWidget: (value, _) {
|
||||
if ((value.toDouble() >= 0) &&
|
||||
(value.toDouble() < (maxX ?? dataPoints.length))) {
|
||||
var label = dataPoints[value.toInt()].label;
|
||||
|
||||
return buildBottomLabel(label);
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
interval: 1, // ensures 1:1 mapping with spots
|
||||
),
|
||||
),
|
||||
topTitles: AxisTitles(),
|
||||
rightTitles: AxisTitles(),
|
||||
),
|
||||
borderData: FlBorderData(
|
||||
show: true,
|
||||
border: const Border(
|
||||
bottom: BorderSide.none,
|
||||
left: BorderSide(color: Colors.grey, width: .5),
|
||||
right: BorderSide.none,
|
||||
top: BorderSide.none,
|
||||
),
|
||||
),
|
||||
lineBarsData: _buildColoredLineSegments(dataPoints),
|
||||
gridData: FlGridData(
|
||||
show: true,
|
||||
drawVerticalLine: false,
|
||||
horizontalInterval: 20,
|
||||
checkToShowHorizontalLine: (value) =>
|
||||
value >= 0 && value <= 100,
|
||||
getDrawingHorizontalLine: (value) {
|
||||
return FlLine(
|
||||
color: AppColors.graphGridColor,
|
||||
strokeWidth: 1,
|
||||
dashArray: [5, 5],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
List<LineChartBarData> _buildColoredLineSegments(List<DataPoint> dataPoints) {
|
||||
final List<FlSpot> allSpots = dataPoints.asMap().entries.map((entry) {
|
||||
return FlSpot(entry.key.toDouble(), entry.value.value);
|
||||
}).toList();
|
||||
|
||||
var data = [
|
||||
LineChartBarData(
|
||||
spots: allSpots,
|
||||
isCurved: true,
|
||||
isStrokeCapRound: true,
|
||||
isStrokeJoinRound: true,
|
||||
barWidth: 4,
|
||||
gradient: LinearGradient(
|
||||
colors: [graphColor, graphColor],
|
||||
begin: Alignment.centerLeft,
|
||||
end: Alignment.centerRight,
|
||||
),
|
||||
dotData: FlDotData(
|
||||
show: false,
|
||||
),
|
||||
belowBarData: BarAreaData(
|
||||
show: true,
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
graphShadowColor,
|
||||
Colors.transparent,
|
||||
],
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
),
|
||||
),
|
||||
)
|
||||
];
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
// Widget buildLabel(String label) {
|
||||
// return Padding(
|
||||
// padding: const EdgeInsets.only(right: 8),
|
||||
// child: Text(
|
||||
// label,
|
||||
// style: TextStyle(
|
||||
// fontSize: leftLabelSize ?? 8.fSize, color: leftLabelColor),
|
||||
// textAlign: TextAlign.right,
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
|
||||
Widget buildBottomLabel(String label) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: bottomLabelSize ?? 8.fSize, color: bottomLabelColor),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final List<DataPoint> sampleData = [
|
||||
DataPoint(
|
||||
value: 20,
|
||||
label: 'Jan 2024',
|
||||
),
|
||||
DataPoint(
|
||||
value: 36,
|
||||
label: 'Feb 2024',
|
||||
),
|
||||
DataPoint(
|
||||
value: 80,
|
||||
label: 'This result',
|
||||
),
|
||||
];
|
||||
Loading…
Reference in New Issue