| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692 |
- // 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<StatisticalChartsPage> {
- int _currentChartIndex = 0;
- final List<String> _chartTypes = ['条形图', '折线图', '饼图', '雷达图'];
-
- // 数据控制器
- final List<TextEditingController> _dataControllers = [];
- final List<TextEditingController> _labelControllers = [];
-
- // 图表数据
- List<double> _dataValues = [];
- List<String> _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();
- }
- }
|