Version propre - Dashboard enhanced
This commit is contained in:
@@ -0,0 +1,282 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import '../../../../shared/theme/app_theme.dart';
|
||||
import '../../../../shared/theme/design_system.dart';
|
||||
|
||||
/// Graphique linéaire professionnel avec animations et interactions
|
||||
class ProfessionalLineChart extends StatefulWidget {
|
||||
const ProfessionalLineChart({
|
||||
super.key,
|
||||
required this.data,
|
||||
required this.title,
|
||||
this.subtitle,
|
||||
this.showGrid = true,
|
||||
this.showDots = true,
|
||||
this.showArea = false,
|
||||
this.animationDuration = const Duration(milliseconds: 1500),
|
||||
this.lineColor,
|
||||
this.gradientColors,
|
||||
});
|
||||
|
||||
final List<LineDataPoint> data;
|
||||
final String title;
|
||||
final String? subtitle;
|
||||
final bool showGrid;
|
||||
final bool showDots;
|
||||
final bool showArea;
|
||||
final Duration animationDuration;
|
||||
final Color? lineColor;
|
||||
final List<Color>? gradientColors;
|
||||
|
||||
@override
|
||||
State<ProfessionalLineChart> createState() => _ProfessionalLineChartState();
|
||||
}
|
||||
|
||||
class _ProfessionalLineChartState extends State<ProfessionalLineChart>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late AnimationController _animationController;
|
||||
late Animation<double> _animation;
|
||||
List<int> _showingTooltipOnSpots = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_animationController = AnimationController(
|
||||
duration: widget.animationDuration,
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
_animation = Tween<double>(
|
||||
begin: 0.0,
|
||||
end: 1.0,
|
||||
).animate(CurvedAnimation(
|
||||
parent: _animationController,
|
||||
curve: DesignSystem.animationCurve,
|
||||
));
|
||||
|
||||
_animationController.forward();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_animationController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildHeader(),
|
||||
SizedBox(height: DesignSystem.spacingLg),
|
||||
Expanded(
|
||||
child: _buildChart(),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHeader() {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
widget.title,
|
||||
style: DesignSystem.titleLarge.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
if (widget.subtitle != null) ...[
|
||||
SizedBox(height: DesignSystem.spacingXs),
|
||||
Text(
|
||||
widget.subtitle!,
|
||||
style: DesignSystem.bodyMedium.copyWith(
|
||||
color: AppTheme.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildChart() {
|
||||
return AnimatedBuilder(
|
||||
animation: _animation,
|
||||
builder: (context, child) {
|
||||
return LineChart(
|
||||
LineChartData(
|
||||
lineTouchData: LineTouchData(
|
||||
touchTooltipData: LineTouchTooltipData(
|
||||
tooltipBgColor: AppTheme.textPrimary.withOpacity(0.9),
|
||||
tooltipRoundedRadius: DesignSystem.radiusSm,
|
||||
tooltipPadding: EdgeInsets.all(DesignSystem.spacingSm),
|
||||
getTooltipItems: (List<LineBarSpot> touchedBarSpots) {
|
||||
return touchedBarSpots.map((barSpot) {
|
||||
final data = widget.data[barSpot.x.toInt()];
|
||||
return LineTooltipItem(
|
||||
'${data.label}\n${barSpot.y.toInt()}',
|
||||
DesignSystem.labelMedium.copyWith(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
);
|
||||
}).toList();
|
||||
},
|
||||
),
|
||||
handleBuiltInTouches: true,
|
||||
getTouchedSpotIndicator: (LineChartBarData barData, List<int> spotIndexes) {
|
||||
return spotIndexes.map((index) {
|
||||
return TouchedSpotIndicatorData(
|
||||
FlLine(
|
||||
color: widget.lineColor ?? AppTheme.primaryColor,
|
||||
strokeWidth: 2,
|
||||
dashArray: [3, 3],
|
||||
),
|
||||
FlDotData(
|
||||
getDotPainter: (spot, percent, barData, index) =>
|
||||
FlDotCirclePainter(
|
||||
radius: 6,
|
||||
color: widget.lineColor ?? AppTheme.primaryColor,
|
||||
strokeWidth: 2,
|
||||
strokeColor: Colors.white,
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList();
|
||||
},
|
||||
),
|
||||
gridData: FlGridData(
|
||||
show: widget.showGrid,
|
||||
drawVerticalLine: false,
|
||||
horizontalInterval: _getMaxY() / 5,
|
||||
getDrawingHorizontalLine: (value) {
|
||||
return FlLine(
|
||||
color: AppTheme.borderColor.withOpacity(0.3),
|
||||
strokeWidth: 1,
|
||||
);
|
||||
},
|
||||
),
|
||||
titlesData: FlTitlesData(
|
||||
show: true,
|
||||
rightTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(showTitles: false),
|
||||
),
|
||||
topTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(showTitles: false),
|
||||
),
|
||||
bottomTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
getTitlesWidget: _buildBottomTitles,
|
||||
reservedSize: 42,
|
||||
),
|
||||
),
|
||||
leftTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
getTitlesWidget: _buildLeftTitles,
|
||||
reservedSize: 40,
|
||||
),
|
||||
),
|
||||
),
|
||||
borderData: FlBorderData(show: false),
|
||||
minX: 0,
|
||||
maxX: widget.data.length.toDouble() - 1,
|
||||
minY: 0,
|
||||
maxY: _getMaxY() * 1.2,
|
||||
lineBarsData: [
|
||||
_buildLineBarData(),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
LineChartBarData _buildLineBarData() {
|
||||
final spots = widget.data.asMap().entries.map((entry) {
|
||||
final index = entry.key;
|
||||
final data = entry.value;
|
||||
return FlSpot(index.toDouble(), data.value * _animation.value);
|
||||
}).toList();
|
||||
|
||||
return LineChartBarData(
|
||||
spots: spots,
|
||||
isCurved: true,
|
||||
curveSmoothness: 0.3,
|
||||
color: widget.lineColor ?? AppTheme.primaryColor,
|
||||
barWidth: 3,
|
||||
isStrokeCapRound: true,
|
||||
dotData: FlDotData(
|
||||
show: widget.showDots,
|
||||
getDotPainter: (spot, percent, barData, index) => FlDotCirclePainter(
|
||||
radius: 4,
|
||||
color: widget.lineColor ?? AppTheme.primaryColor,
|
||||
strokeWidth: 2,
|
||||
strokeColor: Colors.white,
|
||||
),
|
||||
),
|
||||
belowBarData: widget.showArea ? BarAreaData(
|
||||
show: true,
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: widget.gradientColors ?? [
|
||||
(widget.lineColor ?? AppTheme.primaryColor).withOpacity(0.3),
|
||||
(widget.lineColor ?? AppTheme.primaryColor).withOpacity(0.05),
|
||||
],
|
||||
),
|
||||
) : BarAreaData(show: false),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBottomTitles(double value, TitleMeta meta) {
|
||||
if (value.toInt() >= widget.data.length) return const SizedBox.shrink();
|
||||
|
||||
final data = widget.data[value.toInt()];
|
||||
return SideTitleWidget(
|
||||
axisSide: meta.axisSide,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(top: DesignSystem.spacingXs),
|
||||
child: Text(
|
||||
data.label,
|
||||
style: DesignSystem.labelSmall.copyWith(
|
||||
color: AppTheme.textSecondary,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLeftTitles(double value, TitleMeta meta) {
|
||||
return SideTitleWidget(
|
||||
axisSide: meta.axisSide,
|
||||
child: Text(
|
||||
value.toInt().toString(),
|
||||
style: DesignSystem.labelSmall.copyWith(
|
||||
color: AppTheme.textSecondary,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
double _getMaxY() {
|
||||
if (widget.data.isEmpty) return 10;
|
||||
return widget.data.map((e) => e.value).reduce((a, b) => a > b ? a : b);
|
||||
}
|
||||
}
|
||||
|
||||
/// Modèle de données pour le graphique linéaire
|
||||
class LineDataPoint {
|
||||
const LineDataPoint({
|
||||
required this.label,
|
||||
required this.value,
|
||||
});
|
||||
|
||||
final String label;
|
||||
final double value;
|
||||
}
|
||||
Reference in New Issue
Block a user