didvan-app/lib/views/widgets/mini_chart.dart

198 lines
5.4 KiB
Dart

import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:didvan/models/statistic_data/data.dart';
import 'package:didvan/services/network/request.dart';
import 'package:didvan/services/network/request_helper.dart';
class MiniChart extends StatefulWidget {
final String label;
final double width;
final double height;
final Color? lineColor;
final double? changePercent;
final String? trend;
const MiniChart({
super.key,
required this.label,
this.width = 100,
this.height = 40,
this.lineColor,
this.changePercent,
this.trend,
});
@override
State<MiniChart> createState() => _MiniChartState();
}
class _MiniChartState extends State<MiniChart> {
List<Data> chartData = [];
bool isLoading = true;
bool hasError = false;
@override
void initState() {
super.initState();
_loadChartData();
}
Future<void> _loadChartData() async {
try {
final service = RequestService(
RequestHelper.statisticDetails(widget.label, 'weekly'),
);
await service.httpGet();
if (service.isSuccess) {
final result = service.result['data'];
final List<Data> data = [];
for (var i = 0; i < result.length; i++) {
data.add(Data.fromList(result[i]));
}
if (mounted) {
setState(() {
chartData = data.reversed.take(10).toList();
isLoading = false;
hasError = false;
});
}
} else {
if (mounted) {
setState(() {
isLoading = false;
hasError = true;
});
}
}
} catch (e) {
if (mounted) {
setState(() {
isLoading = false;
hasError = true;
});
}
}
}
double _stringToDouble(String value) {
try {
return double.parse(value.replaceAll(',', ''));
} catch (e) {
return 0.0;
}
}
@override
Widget build(BuildContext context) {
if (isLoading) {
return SizedBox(
width: widget.width,
height: widget.height,
child: const Center(
child: SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(strokeWidth: 2),
),
),
);
}
if (hasError || chartData.isEmpty) {
return SizedBox(
width: widget.width,
height: widget.height,
child: Container(
decoration: BoxDecoration(
color: Colors.grey.withOpacity(0.1),
borderRadius: BorderRadius.circular(4),
),
child: const Center(
child: Icon(
Icons.show_chart,
size: 16,
color: Colors.grey,
),
),
),
);
}
final spots = <FlSpot>[];
double minY = double.infinity;
double maxY = double.negativeInfinity;
for (int i = 0; i < chartData.length; i++) {
final value = _stringToDouble(chartData[i].p);
spots.add(FlSpot(i.toDouble(), value));
if (value < minY) minY = value;
if (value > maxY) maxY = value;
}
final padding = (maxY - minY) * 0.1;
minY -= padding;
maxY += padding;
final bool shouldShowShadow = widget.changePercent != null && widget.changePercent != 0;
final bool isPositive = shouldShowShadow && widget.trend == 'high';
final bool isNegative = shouldShowShadow && widget.trend != 'high';
return SizedBox(
width: widget.width,
height: widget.height,
child: LineChart(
LineChartData(
lineTouchData: const LineTouchData(enabled: false),
gridData: const FlGridData(show: false),
titlesData: const FlTitlesData(show: false),
borderData: FlBorderData(show: false),
minX: 0,
maxX: (chartData.length - 1).toDouble(),
minY: minY,
maxY: maxY,
lineBarsData: [
LineChartBarData(
spots: spots,
isCurved: false,
color: widget.lineColor ?? Theme.of(context).colorScheme.primary,
barWidth: 1.5,
isStrokeCapRound: false,
dotData: const FlDotData(show: false),
belowBarData: isPositive
? BarAreaData(
show: true,
gradient: LinearGradient(
colors: [
Colors.green.withOpacity(0.3),
const Color.fromARGB(43, 255, 255, 255),
],
stops: const [0.4, 1.0],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
)
: BarAreaData(show: false),
aboveBarData: isNegative
? BarAreaData(
show: true,
gradient: LinearGradient(
colors: [
Colors.red.withOpacity(0.3),
const Color.fromARGB(43, 255, 255, 255),
],
stops: const [0.4, 1.0],
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
),
)
: BarAreaData(show: false),
),
],
),
),
);
}
}