目录
0. 项目简介
项目想法脱胎于2023年服务外包大赛A18题 随手买(详情)
整个APP思路如下:
这篇博客主要服务于管理员界面的数据概览功能
1. 效果展示
饼图
折线图
柱状图
日历热图
2. 代码
依赖如下
dev_dependencies:
flutter_test:
sdk: flutter
# The "flutter_lints" package below contains a set of recommended lints to
# encourage good coding practices. The lint set provided by the package is
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_screenutil: ^3.1.0
flutter_echart: ^1.0.0
fl_chart: 0.55.2
相关文件如下
'dataPage.dart'
'CircleView.dart'
'BarView.dart'
'DateView.dart'
'LineViewNew.dart'
// 数据准备
'draw_grid.dart';
'utils_date.dart';
dataPage.dart
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'CircleView.dart';
import 'BarView.dart';
import 'DateView.dart';
import 'LineViewNew.dart';
class dataPage extends StatefulWidget {
const dataPage({Key? key}) : super(key: key);
State<dataPage> createState() => _dataPageState();
}
class _dataPageState extends State<dataPage> with SingleTickerProviderStateMixin{
TabController? tabController;
void initState() {
// TODO: implement initState
tabController = TabController(
length: 4,
vsync: this,
);
super.initState();
}
void dispose() {
// TODO: implement dispose
tabController!.dispose();
super.dispose();
}
Widget build(BuildContext context) {
ScreenUtil.init(context, allowFontScaling: false);
return Scaffold(
// backgroundColor: Colors.grey.shade300,
backgroundColor: Color(0xff232d37),
appBar: AppBar(
title: TabBar(
indicatorColor: Colors.white,
indicatorSize: TabBarIndicatorSize.label,
unselectedLabelStyle: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w500,
),
unselectedLabelColor: Colors.white54,
labelColor: Colors.white,
labelStyle: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500
),
controller: tabController,
tabs: <Widget>[
Tab(text: '用户主体',),
Tab(text: 'APP访问量',),
Tab(text: '交易量',),
Tab(text: '广告播放量',)
],
),
),
body: TabBarView(
controller: tabController,
children: <Widget>[
CircleView(),
LineChartSample2(),
BarView(),
DateView(),
],
),
);
}
}
CircleView.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/painting.dart';
import 'package:flutter_echart/flutter_echart.dart';
void main() {
runApp(
MaterialApp(
debugShowCheckedModeBanner: false,
home: CircleView(),
),
);
}
class CircleView extends StatefulWidget {
const CircleView({Key? key}) : super(key: key);
State<CircleView> createState() => _CircleViewState();
}
// //定义一个全局的内容主颜色
// Color mainColor = Colors.transparent;
class _CircleViewState extends State<CircleView> {
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
//页面的主内容 先来个居中
body: Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: Stack(
alignment: Alignment.center,
children: [
Container(
//来个高度
height: 230,
//宽度填充
width: MediaQuery.of(context).size.width,
// //设置一下背景
// color: mainColor,
//封装一个方法构建左右排列的
child: buildPieChatWidget(),
),
],
),
),
);
}
List<EChartPieBean> _dataList = [
EChartPieBean(title: "18岁以下", number: 2471, color: Colors.lightBlueAccent),
EChartPieBean(title: "19~30岁", number: 44658, color: Colors.deepOrangeAccent),
EChartPieBean(title: "31~40岁", number: 28617, color: Colors.green),
EChartPieBean(title: "41~50岁", number: 15632, color: Colors.amber),
EChartPieBean(title: "51~60岁", number: 4584, color: Colors.orange),
EChartPieBean(title: "60岁以上", number: 8451, color: Colors.deepPurpleAccent),
];
PieChatWidget buildPieChatWidget() {
return PieChatWidget(
dataList: _dataList,
//是否输出日志
isLog: true,
//是否需要背景
isBackground: true,
//是否画直线
isLineText: true,
//背景
bgColor: Colors.white,
//是否显示最前面的内容
isFrontgText: true,
//默认选择放大的块
initSelect: 1,
//初次显示以动画方式展开
openType: OpenType.ANI,
//旋转类型
loopType: LoopType.AUTO_LOOP,
// //点击回调
// clickCallBack: (int value) {
// print("当前点击显示 $value");
// },
);
}
}
BarView.dart
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'dart:math' as math;
class AppUtils {
factory AppUtils() {
return _singleton;
}
AppUtils._internal();
static final AppUtils _singleton = AppUtils._internal();
double degreeToRadian(double degree) {
return degree * math.pi / 180;
}
double radianToDegree(double radian) {
return radian * 180 / math.pi;
}
}
class BarView extends StatefulWidget {
const BarView({super.key});
State<StatefulWidget> createState() => BarViewState();
}
class BarViewState extends State<BarView> {
static const double barWidth = 50;
static const shadowOpacity = 0.2;
static const mainItems = <int, List<double>>{
0: [2, 3, 2.5, 8, 7, 0.2, 0.3, 1.1],
1: [1.8, 2.7, 3, 6.5, 0.9, 0.2, 0.3, 5],
2: [1.5, 2, 3.5, 6, 6.5, 0.2, 9, 3],
3: [1.5, 1.5, 4, 6.5, 6.5, 2, 0.3, 3],
4: [2, 2, 5, 9, 6.5, 0.2, 3, 1.1],
5: [1.2, 1.5, 4.3, 10, 6.5, 0.2, 0.3, 8],
6: [1.2, 8, 5, 7, 6.5, 1, 0.3, 1.1],
7: [1, 4.8, 5, 5, 7.5, 0.2, 0.3, 1.1],
8: [1.2, 3, 5, 1, 6.5, 7, 0.3, 1.1],
9: [1.2, 4.8, 3, 5, 2.5, 0.2, 7, 1.1],
10: [1.2, 2, 5, 3, 6.5, 2, 0.3, 1.1],
11: [1.2, 4.8, 5, 5, 1.5, 0.2, 4, 1.1],
};
int touchedIndex = -1;
void initState() {
super.initState();
}
Widget bottomTitles(double value, TitleMeta meta) {
const style = TextStyle(color: Colors.white, fontSize: 10);
String text;
switch (value.toInt()%7) {
case 0:
text = 'Mon';
break;
case 1:
text = 'Tue';
break;
case 2:
text = 'Wed';
break;
case 3:
text = 'Thu';
break;
case 4:
text = 'Fri';
break;
case 5:
text = 'Sat';
break;
case 6:
text = 'Sun';
break;
default:
text = '';
break;
}
return SideTitleWidget(
axisSide: meta.axisSide,
child: Text(text, style: style),
);
}
Widget topTitles(double value, TitleMeta meta) {
const style = TextStyle(color: Colors.white, fontSize: 10);
String text;
switch (value.toInt()%7) {
case 0:
text = 'Mon';
break;
case 1:
text = 'Tue';
break;
case 2:
text = 'Wed';
break;
case 3:
text = 'Thu';
break;
case 4:
text = 'Fri';
break;
case 5:
text = 'Sat';
break;
case 6:
text = 'Sun';
break;
default:
return Container();
}
return SideTitleWidget(
axisSide: meta.axisSide,
child: Text(text, style: style),
);
}
Widget leftTitles(double value, TitleMeta meta) {
const style = TextStyle(color: Colors.white, fontSize: 10);
String text;
if (value == 0) {
text = '0';
} else {
text = '${value.toInt()}0k';
}
return SideTitleWidget(
angle: AppUtils().degreeToRadian(value < 0 ? -45 : 45),
axisSide: meta.axisSide,
space: 4,
child: Text(
text,
style: style,
textAlign: TextAlign.center,
),
);
}
Widget rightTitles(double value, TitleMeta meta) {
const style = TextStyle(color: Colors.white, fontSize: 10);
String text;
if (value == 0) {
text = '0';
} else {
text = '${value.toInt()}0k';
}
return SideTitleWidget(
angle: AppUtils().degreeToRadian(value > 0 ? -45 : 45),
axisSide: meta.axisSide,
space: 0,
child: Text(
text,
style: style,
textAlign: TextAlign.center,
),
);
}
BarChartGroupData generateGroup(
int x,
double value1,
double value2,
double value3,
double value4,
double value5,
double value6,
double value7,
double value8,
) {
// final isTop = value1 > 0;
final sum1 = value1 + value2 + value3 + value4;
final sum2 = value5 + value6 + value7 + value8;
// final abs_sum = value1.abs() + value2.abs() + value3.abs() + value4.abs();
final isTop = sum1 > sum2;
final isTouched = touchedIndex == x;
return BarChartGroupData(
x: x,
groupVertically: true,
showingTooltipIndicators: isTouched ? [0] : [],
barRods: [
BarChartRodData(
toY: isTop
?sum1
:-sum2,
width: barWidth,
borderRadius: isTop
? const BorderRadius.only(
topLeft: Radius.circular(6),
topRight: Radius.circular(6),
)
: const BorderRadius.only(
bottomLeft: Radius.circular(6),
bottomRight: Radius.circular(6),
),
rodStackItems: [
BarChartRodStackItem(
0,
isTop? value1:-value5,
const Color(0xff2bdb90),
BorderSide(
color: Colors.white,
width: isTouched ? 2 : 0,
),
),
BarChartRodStackItem(
isTop? value1:-value5,
isTop? (value1+value2):-(value5+value6),
const Color(0xffffdd80),
BorderSide(
color: Colors.white,
width: isTouched ? 2 : 0,
),
),
BarChartRodStackItem(
isTop? (value1+value2):-(value5+value6),
isTop? (value1+value2+value3):-(value5+value6+value7),
const Color(0xffff4d94),
BorderSide(
color: Colors.white,
width: isTouched ? 2 : 0,
),
),
BarChartRodStackItem(
isTop? (value1+value2+value3):-(value5+value6+value7),
isTop? (value1+value2+value3+value4):-(value5+value6+value7+value8),
const Color(0xff19bfff),
BorderSide(
color: Colors.white,
width: isTouched ? 2 : 0,
),
),
],
),
BarChartRodData(
toY: isTop
?-sum2
:sum1,
width: barWidth,
color: Colors.transparent,
borderRadius: isTop
? const BorderRadius.only(
bottomLeft: Radius.circular(6),
bottomRight: Radius.circular(6),
)
: const BorderRadius.only(
topLeft: Radius.circular(6),
topRight: Radius.circular(6),
),
rodStackItems: [
BarChartRodStackItem(
0,
isTop? -(value5):(value1),
const Color(0xff2bdb90)
.withOpacity(isTouched ? shadowOpacity * 2 : shadowOpacity),
const BorderSide(color: Colors.transparent),
),
BarChartRodStackItem(
isTop? -(value5):(value1),
isTop? -(value5+value6):(value1+value2),
const Color(0xffffdd80)
.withOpacity(isTouched ? shadowOpacity * 2 : shadowOpacity),
const BorderSide(color: Colors.transparent),
),
BarChartRodStackItem(
isTop? -(value5+value6):(value1+value2),
isTop? -(value5+value6+value7):(value1+value2+value3),
const Color(0xffff4d94)
.withOpacity(isTouched ? shadowOpacity * 2 : shadowOpacity),
const BorderSide(color: Colors.transparent),
),
BarChartRodStackItem(
isTop? -(value5+value6+value7):(value1+value2+value3),
isTop? -(value5+value6+value7+value8):(value1+value2+value3+value4),
const Color(0xff19bfff)
.withOpacity(isTouched ? shadowOpacity * 2 : shadowOpacity),
const BorderSide(color: Colors.transparent),
),
],
),
],
);
}
bool isShadowBar(int rodIndex) => rodIndex == 1;
Widget build(BuildContext context) {
return AspectRatio(
aspectRatio: 0.8,
child: Card(
elevation: 4,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(6)),
color: const Color(0xff020227),
child: Padding(
padding: const EdgeInsets.only(top: 16),
child: BarChart(
BarChartData(
alignment: BarChartAlignment.center,
maxY: 20,
minY: -20,
groupsSpace: 12,
barTouchData: BarTouchData(
handleBuiltInTouches: false,
touchCallback: (FlTouchEvent event, barTouchResponse) {
if (!event.isInterestedForInteractions ||
barTouchResponse == null ||
barTouchResponse.spot == null) {
setState(() {
touchedIndex = -1;
});
return;
}
final rodIndex = barTouchResponse.spot!.touchedRodDataIndex;
if (isShadowBar(rodIndex)) {
setState(() {
touchedIndex = -1;
});
return;
}
setState(() {
touchedIndex = barTouchResponse.spot!.touchedBarGroupIndex;
});
},
),
titlesData: FlTitlesData(
show: true,
topTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 32,
getTitlesWidget: topTitles,
),
),
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 32,
getTitlesWidget: bottomTitles,
),
),
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
getTitlesWidget: leftTitles,
interval: 5,
reservedSize: 42,
),
),
rightTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
getTitlesWidget: rightTitles,
interval: 5,
reservedSize: 42,
),
),
),
gridData: FlGridData(
show: true,
checkToShowHorizontalLine: (value) => value % 5 == 0,
getDrawingHorizontalLine: (value) {
if (value == 0) {
return FlLine(
color: const Color(0xff363753),
strokeWidth: 3,
);
}
return FlLine(
color: const Color(0xff2a2747),
strokeWidth: 0.8,
);
},
),
borderData: FlBorderData(
show: false,
),
barGroups: mainItems.entries
.map(
(e) => generateGroup(
e.key,
e.value[0],
e.value[1],
e.value[2],
e.value[3],
e.value[4],
e.value[5],
e.value[6],
e.value[7],
),
)
.toList(),
),
),
),
),
);
}
}
DateView.dart
import 'dart:math';
import 'package:flutter/material.dart';
import 'draw_grid.dart';
import 'utils_date.dart';
class DateView extends StatefulWidget {
const DateView({Key? key}) : super(key: key);
State<DateView> createState() => _DateViewState();
}
class _DateViewState extends State<DateView> {
Widget build(BuildContext context) {
List<double> datas = [
0, 0, 0,
61, 81, 96, 56, 56, 68, 52,
69, 84, 76, 69, 66, 48, 59,
80, 96, 84, 80, 79, 90, 84,
91, 108, 111, 91, 92, 90, 81,
81, 119, 106, 96, 91, 98, 98,
100, 96, 102, 89, 101, 156, 141,
110, 111, 148, 147, 98, 109, 120,
91, 100, 146, 154, 99, 101, 105,
102, 120, 169, 167, 94, 96, 103,
129, 135, 189, 241, 137, 134, 140,
139, 145, 301, 121, 110, 148, 198,
145, 141, 268, 267, 189, 142, 158,
159, 149, 234, 294, 294, 167, 167,
149, 140, 249, 231, 147, 147, 134,
170, 180, 254, 267, 195, 196, 96,
171, 190, 209, 201, 185, 187, 180,
120, 91, 199, 291, 135, 197, 471,
201, 196, 272, 270, 197, 194, 200,
240, 232, 299, 290, 270, 215, 260,
300, 303, 394, 400, 289, 347, 315,
402, 419, 464, 498, 394, 514, 415,
401, 380, 480, 394, 340, 399, 140,
420, 429, 510, 607, 321, 409, 441,
320, 431, 520, 314, 324, 411, 412,
];
return CalenderHeatMap(datas: datas);
}
}
class CalenderHeatMap extends StatelessWidget {
final List<double> datas;
const CalenderHeatMap({
required this.datas,
});
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
return CustomPaint(
painter: CalenderHeatMapPainter(datas: datas),
size: constraints.biggest,
);
},
);
}
}
class CalenderHeatMapPainter extends CustomPainter {
final List<double> datas;
CalenderHeatMapPainter({
required this.datas,
});
final double gap = 5.0;
final double blockWidth = 25.0;
final double blockHeight = 25.0;
final double blockRadius = 5.0;
List<Color> _generateBlockColor({double opacity = 1}) {
if (opacity == 0) {
return [Colors.black12, Colors.black12, Colors.black12, Colors.black12];
}
return [Color.fromRGBO(45, 255, 93, opacity), Color.fromRGBO(255, 45, 93, opacity), Color.fromRGBO(45, 93, 255, opacity), Colors.purpleAccent.withOpacity(opacity), Colors.yellow.withOpacity(opacity), Colors.cyan.withOpacity(opacity)];
}
void _drawBlock(Canvas canvas, Offset offset, Color color) {
final Paint paint = Paint()
..color = color
..isAntiAlias = true
..style = PaintingStyle.fill;
final Rect rect =
Rect.fromLTWH(offset.dx, offset.dy, blockWidth, blockHeight);
final Radius radius = Radius.circular(blockRadius);
final rrect = RRect.fromRectAndRadius(rect, radius);
canvas.drawRRect(rrect, paint);
}
void _drawWeekDayTexts(Canvas canvas, Size size) {
canvas.save();
canvas.translate(0, size.height / 10);
Offset start = Offset(30.0, 0.0);
weekDayTextEn.asMap().forEach((index, text) {
TextPainter(
textAlign: TextAlign.center,
textDirection: TextDirection.ltr,
)
..text = TextSpan(
text: text,
style: TextStyle(
fontSize: 24.0,
fontWeight: FontWeight.bold,
color: Color(0xffc5d1da)
),
)
..layout(
minWidth: 0.0,
maxWidth: 100.0,
)
..paint(canvas, start);
start += Offset(0, gap + blockHeight);
});
canvas.restore();
}
void paint(Canvas canvas, Size size) {
drawGrid(canvas, size);
_drawWeekDayTexts(canvas, size);
canvas.save();
canvas.translate(0, size.height / 10);
Offset start = Offset(100.0, 0.0);
for (int i = 0; i < datas.length; i++) {
Offset move;
if (i == 0) {
move = Offset.zero;
} else {
if (i % 7 == 0) {
move = Offset(gap + blockWidth, -start.dy);
} else {
move = Offset(0, gap + blockHeight);
}
}
start += move;
final double val = datas[i];
final double maxVal = datas.reduce(max);
final percent = val / maxVal;
double opacity;
opacity = percent;
// if (percent > .8) {
// opacity = 1;
// } else if (percent > .6) {
// opacity = 0.8;
// } else if (percent > .4) {
// opacity = 0.6;
// } else if (percent < .2) {
// opacity = 0.4;
// } else {
// opacity = 0.2;
// }
_drawBlock(canvas, start, _generateBlockColor(opacity: opacity)[i~/31]);
}
canvas.restore();
}
bool shouldRepaint(CalenderHeatMapPainter oldDelegate) => false;
bool shouldRebuildSemantics(CalenderHeatMapPainter oldDelegate) => false;
}
LineViewNew.dart
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
class LineChartSample2 extends StatefulWidget {
const LineChartSample2({super.key});
State<LineChartSample2> createState() => _LineChartSample2State();
}
class _LineChartSample2State extends State<LineChartSample2> {
List<Color> gradientColors = [
const Color(0xff23b6e6),
const Color(0xff02d39a),
];
bool showAvg = false;
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
AspectRatio(
aspectRatio: 3.5,
child: DecoratedBox(
decoration: const BoxDecoration(
borderRadius: BorderRadius.all(
Radius.circular(18),
),
color: Color(0xff232d37),
),
child: Padding(
padding: const EdgeInsets.only(
right: 30,
left: 30,
top: 40,
bottom: 12,
),
child: LineChart(
showAvg ? avgData() : mainData(),
),
),
),
),
SizedBox(
width: 200,
height: 34,
child: TextButton(
onPressed: () {
setState(() {
showAvg = !showAvg;
});
},
child: Text(
'平均',
style: TextStyle(
fontSize: 12,
color: showAvg ? Colors.white.withOpacity(0.5) : Colors.white,
),
),
),
),
],
);
}
Widget bottomTitleWidgets(double value, TitleMeta meta) {
const style = TextStyle(
color: Color(0xff68737d),
fontWeight: FontWeight.bold,
fontSize: 16,
);
Widget text;
text = Text(((value.toInt()+1)%12).toString()+'月', style: style);
return SideTitleWidget(
axisSide: meta.axisSide,
child: text,
);
}
Widget leftTitleWidgets(double value, TitleMeta meta) {
const style = TextStyle(
color: Color(0xff67727d),
fontWeight: FontWeight.bold,
fontSize: 15,
);
String text;
switch (value.toInt()) {
case 1:
text = '10K';
break;
case 3:
text = '30k';
break;
case 5:
text = '50k';
break;
case 7:
text = '70K';
break;
case 9:
text = '90k';
break;
case 11:
text = '110k';
break;
default:
return Container();
}
return Text(text, style: style, textAlign: TextAlign.left);
}
LineChartData mainData() {
return LineChartData(
gridData: FlGridData(
show: true,
drawVerticalLine: true,
horizontalInterval: 1,
verticalInterval: 1,
getDrawingHorizontalLine: (value) {
return FlLine(
color: const Color(0xff37434d),
strokeWidth: 1,
);
},
getDrawingVerticalLine: (value) {
return FlLine(
color: const Color(0xff37434d),
strokeWidth: 1,
);
},
),
titlesData: FlTitlesData(
show: true,
rightTitles: AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
topTitles: AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 30,
interval: 1,
getTitlesWidget: bottomTitleWidgets,
),
),
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
interval: 1,
getTitlesWidget: leftTitleWidgets,
reservedSize: 42,
),
),
),
borderData: FlBorderData(
show: true,
border: Border.all(color: const Color(0xff37434d)),
),
minX: 0,
maxX: 20,
minY: 0,
maxY: 11,
lineBarsData: linesBarData1()
);
}
List<LineChartBarData> linesBarData1() {
final LineChartBarData lineChartBarData1 = LineChartBarData(
spots: [
FlSpot(0, 0.1),
FlSpot(1, 0.2),
FlSpot(2, 0.4),
FlSpot(3, 0.9),
FlSpot(4, 0.95),
FlSpot(5, 0.99),
FlSpot(6, 1.5),
FlSpot(7, 1.9),
FlSpot(8, 2),
FlSpot(9, 2.5),
FlSpot(10, 2.1),
FlSpot(11, 2.3),
FlSpot(12, 2.0),
FlSpot(13, 1.8),
FlSpot(14, 2.1),
FlSpot(15, 2.7),
FlSpot(16, 2.9),
FlSpot(17, 2.97),
FlSpot(18, 3.7),
FlSpot(19, 4),
FlSpot(20, 4.3),
],
isCurved: true,
gradient: LinearGradient(
colors: gradientColors,
),
barWidth: 2,
isStrokeCapRound: true,
dotData: FlDotData(
show: false,
),
belowBarData: BarAreaData(
show: true,
gradient: LinearGradient(
colors: gradientColors
.map((color) => color.withOpacity(0.3))
.toList(),
),
),
);
final LineChartBarData lineChartBarData2 = LineChartBarData(
spots: [
FlSpot(0, 0.01),
FlSpot(1, 0.02),
FlSpot(2, 0.04),
FlSpot(3, 0.09),
FlSpot(4, 0.5),
FlSpot(5, 1),
FlSpot(6, 1.1),
FlSpot(7, 1.2),
FlSpot(8, 1.2),
FlSpot(9, 1.5),
FlSpot(10, 1.6),
FlSpot(11, 1.9),
FlSpot(12, 2.04),
FlSpot(13, 1.33),
FlSpot(14, 2.2),
FlSpot(15, 2.6),
FlSpot(16, 4.4),
FlSpot(17, 4.5),
FlSpot(18, 5.2),
FlSpot(19, 8.7),
FlSpot(20, 10.2),
],
isCurved: true,
gradient: LinearGradient(
colors: [
ColorTween(begin: gradientColors[0], end: gradientColors[1])
.lerp(0.2)!,
ColorTween(begin: gradientColors[0], end: gradientColors[1])
.lerp(0.2)!,
],
),
barWidth: 2,
isStrokeCapRound: true,
dotData: FlDotData(
show: false,
),
belowBarData: BarAreaData(
show: true,
gradient: LinearGradient(
colors: [
ColorTween(begin: gradientColors[0], end: gradientColors[1])
.lerp(0.2)!
.withOpacity(0.1),
ColorTween(begin: gradientColors[0], end: gradientColors[1])
.lerp(0.2)!
.withOpacity(0.1),
],
),
),
);
return [lineChartBarData1, lineChartBarData2];
}
LineChartData avgData() {
return LineChartData(
lineTouchData: LineTouchData(enabled: false),
gridData: FlGridData(
show: true,
drawHorizontalLine: true,
verticalInterval: 1,
horizontalInterval: 1,
getDrawingVerticalLine: (value) {
return FlLine(
color: const Color(0xff37434d),
strokeWidth: 1,
);
},
getDrawingHorizontalLine: (value) {
return FlLine(
color: const Color(0xff37434d),
strokeWidth: 1,
);
},
),
titlesData: FlTitlesData(
show: true,
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 30,
getTitlesWidget: bottomTitleWidgets,
interval: 1,
),
),
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
getTitlesWidget: leftTitleWidgets,
reservedSize: 42,
interval: 1,
),
),
topTitles: AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
rightTitles: AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
),
borderData: FlBorderData(
show: true,
border: Border.all(color: const Color(0xff37434d)),
),
minX: 0,
maxX: 20,
minY: 0,
maxY: 11,
lineBarsData: linesBarData2()
);
}
List<LineChartBarData> linesBarData2(){
final LineChartBarData lineChartBarData1 = LineChartBarData(
spots:const [
FlSpot(0, 2.8791),
FlSpot(1, 2.8791),
FlSpot(2, 2.8791),
FlSpot(3, 2.8791),
FlSpot(4, 2.8791),
FlSpot(5, 2.8791),
FlSpot(6, 2.8791),
FlSpot(7, 2.8791),
FlSpot(8, 2.8791),
FlSpot(9, 2.8791),
FlSpot(10, 2.8791),
FlSpot(11, 2.8791),
FlSpot(12, 2.8791),
FlSpot(13, 2.8791),
FlSpot(14, 2.8791),
FlSpot(15, 2.8791),
FlSpot(16, 2.8791),
FlSpot(17, 2.8791),
FlSpot(18, 2.8791),
FlSpot(19, 2.8791),
FlSpot(20, 2.8791),
],
isCurved: true,
gradient: LinearGradient(
colors: [
ColorTween(begin: gradientColors[0], end: gradientColors[1])
.lerp(0.2)!,
ColorTween(begin: gradientColors[0], end: gradientColors[1])
.lerp(0.2)!,
],
),
barWidth: 5,
isStrokeCapRound: true,
dotData: FlDotData(
show: false,
),
belowBarData: BarAreaData(
show: true,
gradient: LinearGradient(
colors: [
ColorTween(begin: gradientColors[0], end: gradientColors[1])
.lerp(0.2)!
.withOpacity(0.1),
ColorTween(begin: gradientColors[0], end: gradientColors[1])
.lerp(0.2)!
.withOpacity(0.1),
],
),
),
);
final LineChartBarData lineChartBarData2 = LineChartBarData(
spots: const [
FlSpot(0, 4.307),
FlSpot(1, 4.307),
FlSpot(2, 4.307),
FlSpot(3, 4.307),
FlSpot(4, 4.307),
FlSpot(5, 4.307),
FlSpot(6, 4.307),
FlSpot(7, 4.307),
FlSpot(8, 4.307),
FlSpot(9, 4.307),
FlSpot(10, 4.307),
FlSpot(11, 4.307),
FlSpot(12, 4.307),
FlSpot(13, 4.307),
FlSpot(14, 4.307),
FlSpot(15, 4.307),
FlSpot(16, 4.307),
FlSpot(17, 4.307),
FlSpot(18, 4.307),
FlSpot(19, 4.307),
FlSpot(20, 4.307),
],
isCurved: true,
gradient: LinearGradient(
colors: [
ColorTween(begin: gradientColors[0], end: gradientColors[1])
.lerp(0.2)!,
ColorTween(begin: gradientColors[0], end: gradientColors[1])
.lerp(0.2)!,
],
),
barWidth: 5,
isStrokeCapRound: true,
dotData: FlDotData(
show: false,
),
belowBarData: BarAreaData(
show: true,
gradient: LinearGradient(
colors: [
ColorTween(begin: gradientColors[0], end: gradientColors[1])
.lerp(0.2)!
.withOpacity(0.1),
ColorTween(begin: gradientColors[0], end: gradientColors[1])
.lerp(0.2)!
.withOpacity(0.1),
],
),
),
);
return [lineChartBarData1, lineChartBarData2];
}
}
draw_grid.dart
import 'package:flutter/material.dart';
Paint paint = Paint()
..color = Colors.grey
..strokeWidth = .5;
final double step = 30; // 小格边长
void drawGrid(Canvas canvas, Size size) {
canvas.save();
Offset p1 = Offset(0, 0);
Offset p2 = Offset(size.width, 0);
for (double i = 0; i < size.height; i += step) {
if (i % 90 == 0) {
paint.color = Colors.black26;
paint.strokeWidth = 1.0;
} else {
paint.color = Colors.black12;
}
canvas.drawLine(p1, p2, paint);
canvas.translate(0, step);
}
canvas.restore();
canvas.save();
p1 = Offset(0, 0);
p2 = Offset(0, size.height);
for (double i = 0; i < size.width; i += step) {
if (i % 90 == 0) {
paint.color = Colors.black26;
paint.strokeWidth = 1.0;
} else {
paint.color = Colors.black12;
}
canvas.drawLine(p1, p2, paint);
canvas.translate(step, 0);
}
canvas.restore();
}
utils_date.dart
leapYear(int year) {
bool leapYear = false;
bool leap = ((year % 100 == 0) && (year % 400 != 0));
if (leap == true) {
leapYear = false;
} else if (year % 4 == 0) {
leapYear = true;
}
return leapYear;
}
daysInMonth(int year, int month) {
List<int> monthLength = [31, 31, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
if (leapYear(year) == true) {
monthLength[1] = 29;
} else {
monthLength[1] = 28;
}
return monthLength[month - 1];
}
yearLength(int year) {
int yearLength = 0;
for (int counter = 1; counter < year; counter++) {
if (counter >= 4) {
if (leapYear(counter) == true)
yearLength += 366;
else
yearLength += 365;
} else
yearLength += 365;
}
return yearLength;
}
lastDayOfMonth(int year, int month) {
return DateTime(year, month + 1, 0);
}
firstDayOfMonth(int year, int month) {
return DateTime(year, month, 1).day;
}
List<String> weekDayTextEn = [
'Mon',
'Tue',
'Wed',
'Thur',
'Fri',
'Sat',
'Sun',
];
List<String> weekDayTextCh = [
'星期一',
'星期二',
'星期三',
'星期四',
'星期五',
'星期六',
'星期日',
];
List<String> monthTextEn = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec',
];
List<String> monthTextCn = [
'一月',
'二月',
'三月',
'四月',
'五月',
'六月',
'七月',
'八月',
'九月',
'十月',
'十一月',
'十二月',
];