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