forked from eatvio/eatvio_app
131 lines
3.7 KiB
Dart
131 lines
3.7 KiB
Dart
import 'dart:math' as math;
|
||
|
||
import 'package:eatvio_app/features/insights/presentation/widgets/insights_colors.dart';
|
||
import 'package:flutter/material.dart';
|
||
import 'package:syncfusion_flutter_charts/charts.dart';
|
||
|
||
/// Точка графика: подпись оси X (`dd.MM`) и значение.
|
||
class InsightsPoint {
|
||
const InsightsPoint(this.label, this.value);
|
||
|
||
final String label;
|
||
final double value;
|
||
}
|
||
|
||
/// Склеивает `labels[]` и `values[]` (по минимальной длине).
|
||
List<InsightsPoint> buildPoints(List<String> labels, List<double> values) {
|
||
final count = math.min(labels.length, values.length);
|
||
return [for (var i = 0; i < count; i++) InsightsPoint(labels[i], values[i])];
|
||
}
|
||
|
||
/// Границы оси Y с отступом, чтобы линия не прижималась к краям (и ось не начиналась с нуля).
|
||
({double min, double max}) paddedRange(
|
||
Iterable<double> values, {
|
||
double minPadding = 1,
|
||
}) {
|
||
if (values.isEmpty) {
|
||
return (min: 0, max: 1);
|
||
}
|
||
final lowest = values.reduce(math.min);
|
||
final highest = values.reduce(math.max);
|
||
final padding = math.max((highest - lowest) * 0.2, minPadding);
|
||
return (
|
||
min: (lowest - padding).floorToDouble(),
|
||
max: (highest + padding).ceilToDouble(),
|
||
);
|
||
}
|
||
|
||
const TextStyle insightsAxisLabelStyle = TextStyle(
|
||
fontSize: 11,
|
||
color: InsightsColors.subtitle,
|
||
);
|
||
|
||
CategoryAxis buildInsightsCategoryAxis() {
|
||
return const CategoryAxis(
|
||
arrangeByIndex: true,
|
||
labelStyle: insightsAxisLabelStyle,
|
||
majorGridLines: MajorGridLines(width: 0),
|
||
majorTickLines: MajorTickLines(size: 0),
|
||
axisLine: AxisLine(color: InsightsColors.border, width: 1),
|
||
edgeLabelPlacement: EdgeLabelPlacement.shift,
|
||
labelIntersectAction: AxisLabelIntersectAction.hide,
|
||
);
|
||
}
|
||
|
||
NumericAxis buildInsightsValueAxis({
|
||
required String unit,
|
||
required double min,
|
||
required double max,
|
||
List<PlotBand> plotBands = const [],
|
||
}) {
|
||
return NumericAxis(
|
||
minimum: min,
|
||
maximum: max,
|
||
labelFormat: '{value} $unit',
|
||
labelStyle: insightsAxisLabelStyle,
|
||
majorTickLines: const MajorTickLines(size: 0),
|
||
axisLine: const AxisLine(width: 0),
|
||
majorGridLines: MajorGridLines(
|
||
color: InsightsColors.border.withValues(alpha: 0.8),
|
||
dashArray: const [4, 4],
|
||
),
|
||
plotBands: plotBands,
|
||
);
|
||
}
|
||
|
||
TrackballBehavior buildInsightsTrackball() {
|
||
return TrackballBehavior(
|
||
enable: true,
|
||
activationMode: ActivationMode.singleTap,
|
||
tooltipDisplayMode: TrackballDisplayMode.groupAllPoints,
|
||
lineType: TrackballLineType.vertical,
|
||
tooltipSettings: const InteractiveTooltip(),
|
||
);
|
||
}
|
||
|
||
MarkerSettings buildInsightsMarker(Color color) {
|
||
return MarkerSettings(
|
||
isVisible: true,
|
||
width: 4,
|
||
height: 4,
|
||
color: Colors.white,
|
||
borderColor: color,
|
||
borderWidth: 2,
|
||
);
|
||
}
|
||
|
||
LinearGradient buildInsightsAreaGradient(Color color) {
|
||
return LinearGradient(
|
||
begin: Alignment.topCenter,
|
||
end: Alignment.bottomCenter,
|
||
colors: [color.withValues(alpha: 0.18), color.withValues(alpha: 0)],
|
||
);
|
||
}
|
||
|
||
/// Пустое состояние графика (нет данных).
|
||
class InsightsChartPlaceholder extends StatelessWidget {
|
||
const InsightsChartPlaceholder({super.key, required this.text});
|
||
|
||
final String text;
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return SizedBox(
|
||
height: insightsChartHeight,
|
||
child: Center(
|
||
child: Padding(
|
||
padding: const EdgeInsets.symmetric(horizontal: 32),
|
||
child: Text(
|
||
text,
|
||
textAlign: TextAlign.center,
|
||
style: const TextStyle(
|
||
fontSize: 14,
|
||
color: InsightsColors.subtitle,
|
||
),
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
}
|