// tools/statistical_charts.dart import 'package:flutter/material.dart'; import 'package:fl_chart/fl_chart.dart'; class StatisticalChartsPage extends StatefulWidget { @override _StatisticalChartsPageState createState() => _StatisticalChartsPageState(); } class _StatisticalChartsPageState extends State { int _currentChartIndex = 0; final List _chartTypes = ['条形图', '折线图', '饼图', '雷达图']; // 数据控制器 final List _dataControllers = []; final List _labelControllers = []; // 图表数据 List _dataValues = []; List _dataLabels = []; @override void initState() { super.initState(); // 初始化默认数据 _initializeDefaultData(); } void _initializeDefaultData() { // 清空现有控制器 _dataControllers.clear(); _labelControllers.clear(); // 添加默认数据 for (int i = 0; i < 5; i++) { _dataControllers.add(TextEditingController(text: '${(i + 1) * 10}')); _labelControllers.add(TextEditingController(text: '数据${i + 1}')); } _updateChartData(); } void _updateChartData() { setState(() { _dataValues = []; _dataLabels = []; for (int i = 0; i < _dataControllers.length; i++) { if (_dataControllers[i].text.isNotEmpty && _labelControllers[i].text.isNotEmpty) { try { double value = double.parse(_dataControllers[i].text); _dataValues.add(value); _dataLabels.add(_labelControllers[i].text); } catch (e) { // 忽略无效数据 } } } }); } void _addDataField() { setState(() { _dataControllers.add(TextEditingController(text: '0')); _labelControllers.add(TextEditingController(text: '新数据')); }); } void _removeDataField(int index) { if (_dataControllers.length > 1) { setState(() { _dataControllers.removeAt(index); _labelControllers.removeAt(index); _updateChartData(); }); } } Widget _buildDataInputPanel() { return Card( elevation: 4, margin: EdgeInsets.all(16), child: Padding( padding: EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '数据输入', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), SizedBox(height: 16), Row( children: [ Expanded( flex: 2, child: Text( '标签', style: TextStyle(fontWeight: FontWeight.bold), textAlign: TextAlign.center, ), ), SizedBox(width: 16), Expanded( flex: 2, child: Text( '数值', style: TextStyle(fontWeight: FontWeight.bold), textAlign: TextAlign.center, ), ), SizedBox(width: 16), Expanded( flex: 1, child: Text( '操作', style: TextStyle(fontWeight: FontWeight.bold), textAlign: TextAlign.center, ), ), ], ), SizedBox(height: 8), ...List.generate(_dataControllers.length, (index) { return Padding( padding: EdgeInsets.symmetric(vertical: 8), child: Row( children: [ Expanded( flex: 2, child: TextField( controller: _labelControllers[index], decoration: InputDecoration( hintText: '输入标签', border: OutlineInputBorder(), contentPadding: EdgeInsets.symmetric(horizontal: 12), ), onChanged: (value) => _updateChartData(), ), ), SizedBox(width: 16), Expanded( flex: 2, child: TextField( controller: _dataControllers[index], decoration: InputDecoration( hintText: '输入数值', border: OutlineInputBorder(), contentPadding: EdgeInsets.symmetric(horizontal: 12), ), keyboardType: TextInputType.number, onChanged: (value) => _updateChartData(), ), ), SizedBox(width: 16), Expanded( flex: 1, child: IconButton( icon: Icon(Icons.delete, color: Colors.red), onPressed: () => _removeDataField(index), ), ), ], ), ); }), SizedBox(height: 16), Row( children: [ ElevatedButton( onPressed: _addDataField, child: Text('添加数据'), style: ElevatedButton.styleFrom( backgroundColor: Colors.blue, foregroundColor: Colors.white, ), ), SizedBox(width: 16), ElevatedButton( onPressed: _initializeDefaultData, child: Text('重置数据'), style: ElevatedButton.styleFrom( backgroundColor: Colors.grey, foregroundColor: Colors.white, ), ), ], ), ], ), ), ); } Widget _buildChartTypeSelector() { return Card( elevation: 4, margin: EdgeInsets.symmetric(horizontal: 16), child: Padding( padding: EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '图表类型', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), SizedBox(height: 12), Wrap( spacing: 8, runSpacing: 8, children: List.generate(_chartTypes.length, (index) { return ChoiceChip( label: Text(_chartTypes[index]), selected: _currentChartIndex == index, onSelected: (selected) { setState(() { _currentChartIndex = index; }); }, selectedColor: Colors.blue, labelStyle: TextStyle( color: _currentChartIndex == index ? Colors.white : Colors.black, ), ); }), ), ], ), ), ); } Widget _buildChart() { if (_dataValues.isEmpty) { return Container( height: 300, child: Center( child: Text( '请输入有效数据', style: TextStyle(fontSize: 18, color: Colors.grey), ), ), ); } switch (_currentChartIndex) { case 0: // 条形图 return _buildBarChart(); case 1: // 折线图 return _buildLineChart(); case 2: // 饼图 return _buildPieChart(); case 3: // 雷达图 return _buildRadarChart(); default: return _buildBarChart(); } } Widget _buildBarChart() { return Card( elevation: 4, margin: EdgeInsets.all(16), child: Padding( padding: EdgeInsets.all(16), child: Column( children: [ Text( '条形图', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), SizedBox(height: 16), Container( height: 300, child: BarChart( BarChartData( alignment: BarChartAlignment.spaceAround, maxY: _dataValues.isEmpty ? 100 : _dataValues.reduce((a, b) => a > b ? a : b) * 1.2, barTouchData: BarTouchData( enabled: true, // 简化 tooltip 配置 touchTooltipData: BarTouchTooltipData(), ), titlesData: FlTitlesData( show: true, bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, getTitlesWidget: (value, meta) { int index = value.toInt(); if (index >= 0 && index < _dataLabels.length) { return Padding( padding: const EdgeInsets.only(top: 8.0), child: Text( _dataLabels[index], style: TextStyle(fontSize: 10), maxLines: 2, overflow: TextOverflow.ellipsis, ), ); } return Text(''); }, ), ), leftTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: 40, ), ), ), gridData: FlGridData(show: true), borderData: FlBorderData(show: true), barGroups: List.generate(_dataValues.length, (index) { return BarChartGroupData( x: index, barRods: [ BarChartRodData( toY: _dataValues[index], color: Colors.primaries[index % Colors.primaries.length], width: 16, ), ], ); }), ), ), ), ], ), ), ); } // 在类顶部添加曲线类型状态变量 bool _isLineCurved = true; Widget _buildLineChart() { return Card( elevation: 4, margin: EdgeInsets.all(16), child: Padding( padding: EdgeInsets.all(16), child: Column( children: [ Text( '折线图', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), SizedBox(height: 16), // 添加曲线类型选择器 Container( padding: EdgeInsets.symmetric(horizontal: 16), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( '曲线类型:', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500), ), Row( children: [ ChoiceChip( label: Text('直线'), selected: !_isLineCurved, onSelected: (selected) { setState(() { _isLineCurved = false; }); }, selectedColor: Colors.blue, labelStyle: TextStyle( color: !_isLineCurved ? Colors.white : Colors.black, ), ), SizedBox(width: 8), ChoiceChip( label: Text('光滑曲线'), selected: _isLineCurved, onSelected: (selected) { setState(() { _isLineCurved = true; }); }, selectedColor: Colors.blue, labelStyle: TextStyle( color: _isLineCurved ? Colors.white : Colors.black, ), ), ], ), ], ), ), SizedBox(height: 16), Container( height: 300, child: LineChart( LineChartData( lineTouchData: LineTouchData(enabled: true), gridData: FlGridData(show: true), titlesData: FlTitlesData( show: true, bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, getTitlesWidget: (value, meta) { int index = value.toInt(); if (index >= 0 && index < _dataLabels.length) { return Padding( padding: const EdgeInsets.only(top: 8.0), child: Text( _dataLabels[index], style: TextStyle(fontSize: 10), ), ); } return Text(''); }, ), ), leftTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: 40, ), ), ), borderData: FlBorderData(show: true), minX: 0, maxX: _dataValues.length > 0 ? (_dataValues.length - 1).toDouble() : 1, minY: 0, maxY: _dataValues.isEmpty ? 100 : _dataValues.reduce((a, b) => a > b ? a : b) * 1.2, lineBarsData: [ LineChartBarData( spots: List.generate(_dataValues.length, (index) { return FlSpot(index.toDouble(), _dataValues[index]); }), isCurved: _isLineCurved, color: Colors.blue, barWidth: 4, belowBarData: BarAreaData(show: false), dotData: FlDotData( show: true, getDotPainter: (spot, percent, barData, index) { return FlDotCirclePainter( radius: 4, color: Colors.blue, strokeWidth: 2, strokeColor: Colors.white, ); }, ), ), ], ), ), ), ], ), ), ); } Widget _buildPieChart() { double total = _dataValues.fold(0, (a, b) => a + b); return Card( elevation: 4, margin: EdgeInsets.all(16), child: Padding( padding: EdgeInsets.all(16), child: Column( children: [ Text( '饼图', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), SizedBox(height: 16), Container( height: 300, child: Row( children: [ Expanded( flex: 2, child: PieChart( PieChartData( sectionsSpace: 4, centerSpaceRadius: 40, sections: List.generate(_dataValues.length, (index) { return PieChartSectionData( color: Colors.primaries[index % Colors.primaries.length], value: _dataValues[index], title: '${(_dataValues[index] / total * 100).toStringAsFixed(1)}%', radius: 50, titleStyle: TextStyle( fontSize: 12, fontWeight: FontWeight.bold, color: Colors.white, ), ); }), ), ), ), Expanded( flex: 1, child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: List.generate(_dataValues.length, (index) { return Padding( padding: EdgeInsets.symmetric(vertical: 4), child: Row( children: [ Container( width: 12, height: 12, color: Colors.primaries[index % Colors.primaries.length], ), SizedBox(width: 8), Flexible( child: Text( _dataLabels[index], style: TextStyle(fontSize: 12), overflow: TextOverflow.ellipsis, ), ), ], ), ); }), ), ), ], ), ), ], ), ), ); } Widget _buildRadarChart() { if (_dataValues.length < 3) { return Card( elevation: 4, margin: EdgeInsets.all(16), child: Padding( padding: EdgeInsets.all(16), child: Column( children: [ Text( '雷达图', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), SizedBox(height: 16), Container( height: 300, child: Center( child: Text( '雷达图需要至少3个数据点\n请添加更多数据', textAlign: TextAlign.center, style: TextStyle(fontSize: 16, color: Colors.grey), ), ), ), ], ), ), ); } return Card( elevation: 4, margin: EdgeInsets.all(16), child: Padding( padding: EdgeInsets.all(16), child: Column( children: [ Text( '雷达图', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), SizedBox(height: 16), Container( height: 300, child: RadarChart( RadarChartData( dataSets: [ RadarDataSet( dataEntries: List.generate(_dataValues.length, (index) { return RadarEntry( value: _dataValues[index], ); }), fillColor: Colors.blue.withOpacity(0.3), borderColor: Colors.blue, borderWidth: 2, ), ], radarBackgroundColor: Colors.transparent, radarBorderData: BorderSide(color: Colors.grey, width: 1), tickBorderData: BorderSide(color: Colors.grey, width: 1), gridBorderData: BorderSide(color: Colors.grey, width: 1), ticksTextStyle: TextStyle(color: Colors.black, fontSize: 10), tickCount: 5, getTitle: (index, angle) { if (index >= 0 && index < _dataLabels.length) { return RadarChartTitle( text: _dataLabels[index], angle: angle, ); } return RadarChartTitle(text: ''); }, titleTextStyle: TextStyle( color: Colors.black, fontSize: 12, fontWeight: FontWeight.bold, ), titlePositionPercentageOffset: 0.15, ), swapAnimationDuration: Duration(milliseconds: 500), swapAnimationCurve: Curves.easeInOut, ), ), SizedBox(height: 16), Container( height: 20, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( width: 12, height: 12, decoration: BoxDecoration( color: Colors.blue.withOpacity(0.3), border: Border.all(color: Colors.blue, width: 2), ), ), SizedBox(width: 8), Text( '数据分布', style: TextStyle(fontSize: 12, color: Colors.grey), ), ], ), ), ], ), ), ); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('统计图表绘制', style: TextStyle(color: Colors.black)), backgroundColor: Colors.white, elevation: 1, ), body: SingleChildScrollView( child: Column( children: [ _buildChartTypeSelector(), _buildDataInputPanel(), _buildChart(), ], ), ), ); } @override void dispose() { // 清理控制器 for (var controller in _dataControllers) { controller.dispose(); } for (var controller in _labelControllers) { controller.dispose(); } super.dispose(); } }