Files
eatvio_app/lib/features/insights/presentation/charts/insights_chart_common.dart
2026-11-09 15:35:10 +03:00

131 lines
3.7 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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,
),
),
),
),
);
}
}