Flutter---折线图(使用外部库)

一.折线图入门

效果图

代码逻辑

1.坐标范围

2.网格线

3.坐标文字

4.边框

5.折线数据

6.交互行为(点击折线区域)

代码实例

Dart 复制代码
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

import 'package:fl_chart/fl_chart.dart'; //导入包



class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  State<StatefulWidget> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {


  //构建UI
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("折线图入门示例")),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child:
        LineChart( //f1_chart提供的主控件,用于显示折线图
          mainData(),
        ),
      ),
    );
  }

  // 折线图配置
  LineChartData mainData() {
    return LineChartData(

      //1.坐标范围
      minX: 0,
      maxX: 6,
      minY: 0,
      maxY: 6,

      //2.网格线
      gridData: FlGridData(
        //默认情况下内部线是虚线,外部线是实线
        show: true,//启动网格
        horizontalInterval: 1, //每隔一个单元绘制一条水平线
        verticalInterval: 1, //每隔一个单元绘制一条垂直线

        // getDrawingHorizontalLine: (value) {  //内部横实线
        //   return FlLine(
        //     color: Colors.grey,
        //     strokeWidth: 1,
        //     dashArray: null, // 设为0或null代表实线
        //   );
        // },
        //
        // getDrawingVerticalLine: (value) { //内部竖实线
        //   return FlLine(
        //     color: Colors.grey.withOpacity(0.5),
        //     strokeWidth: 1,
        //     dashArray: null, // ← 实线
        //   );
        // },
      ),


      //3.坐标文字
      titlesData: FlTitlesData(
        bottomTitles: //横轴
        AxisTitles(
          sideTitles: SideTitles(
            showTitles: true, //打开X轴标签显示
            interval: 1,//每隔1单位画一个刻度并显示数字
            getTitlesWidget: (value, meta) { //显示每个X值对应显示什么文字(会覆盖默认的显示数字)
              const style = TextStyle(fontSize: 12);
              switch (value.toInt()) {
                case 0: return const Text("一", style: style);
                case 1: return const Text("二", style: style);
                case 2: return const Text("三", style: style);
                case 3: return const Text("四", style: style);
                case 4: return const Text("五", style: style);
                case 5: return const Text("六", style: style);
                case 6: return const Text("日", style: style);
              }
              return const Text("");
            },
          ),
        ),

        leftTitles: //纵轴
        AxisTitles(
          sideTitles: SideTitles(
            showTitles: true, //打开Y轴标签显示
            interval: 1,//每隔1单位画一个刻度并显示数字(默认显示Y轴数字)

          ),
        ),
        rightTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)),//右侧轴
        topTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)), //顶部轴
      ),

      //4.边框
      borderData: FlBorderData(
        show: true, //显示边框
        border: Border.all(color: Colors.red),
      ),

      //5.折线数据
      lineBarsData: [
        LineChartBarData(
          isCurved: true, // 是否平滑曲线
          color: Colors.blue, //折线颜色
          barWidth: 3,
          dotData: FlDotData(show: true), // 显示折线点
          belowBarData: BarAreaData( //曲线下方区域
            show: true,
            color: Colors.blue.withOpacity(0.2), // 曲线下方阴影颜色
          ),
          spots: const [ //折线的点的数据
            FlSpot(0, 1),
            FlSpot(1, 3),
            FlSpot(2, 5),
            FlSpot(3, 4.5),
            FlSpot(4, 3.5),
            FlSpot(5, 5),
            FlSpot(6, 4),
          ],
        ),
      ],

      //6.用来控制折线图的触摸和交互行为
      lineTouchData: LineTouchData(
        touchTooltipData: LineTouchTooltipData(
          tooltipBgColor: Colors.blueAccent.withOpacity(0.8),
        ),
        handleBuiltInTouches: true, // 启用点击浮窗
      ),
    );
  }


}

二.交互式折线图

效果图

代码逻辑

1.定义输入框控制器和焦点控制器

2.初始化存储数据点的列表

3.定义取消焦点的方法

4.定义添加数据点的方法

5.定义删除数据点的方法

6.定义清除所有数据点的方法

7.销毁的相关操作

8.具体UI(输入行,添加按钮,清空按钮,折线图区域,当前坐标点列表)

9.折线图的具体配置

代码实例

Dart 复制代码
import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.dart';

class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  State<StatefulWidget> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {

  final TextEditingController _xController = TextEditingController(); //输入框控制器
  final TextEditingController _yController = TextEditingController(); //输入框控制器
  final FocusNode _xFocusNode = FocusNode();
  final FocusNode _yFocusNode = FocusNode();

  List<FlSpot> userSpots = [ //存储用户输入的数据点
    const FlSpot(0, 1), //初始的数据点
    const FlSpot(1, 2),
    const FlSpot(2, 3),
  ];


  //=================================取消焦点方法==================================
  void _unfocusAll() {
    _xFocusNode.unfocus();  // 取消X输入框焦点
    _yFocusNode.unfocus();  // 取消Y输入框焦点
  }

  //============================添加数据点的方法=================================
  void _addPoint() {
    double? x = double.tryParse(_xController.text);
    double? y = double.tryParse(_yController.text);

    if (x != null && y != null && x <= 10 && y <= 10 ) {
      setState(() {
        userSpots.add(FlSpot(x, y));//添加新点
        userSpots.sort((a, b) => a.x.compareTo(b.x));//按X坐标排序
      });

      //恢复数据框
      _xController.clear();
      _yController.clear();
      _unfocusAll();//取消焦点
    } else {
      //输入无效时显示提示
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text("请输入小于或等于10的有效数字")),
      );

      _unfocusAll();  // 错误时也取消焦点
    }
  }

  //============================删除数据点的方法=================================
  void _removePoint(int index) {
    setState(() {
      userSpots.removeAt(index); //删除指定索引的点
    });
  }

  //=============================清除所有数据点的方法=============================
  void _clearAll() {
    setState(() {
      userSpots.clear();//清空所有点
      _unfocusAll();  // 清空时也取消焦点
    });
  }

  @override
  void dispose() {
    _xController.dispose();
    _yController.dispose();
    _xFocusNode.dispose();
    _yFocusNode.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("交互式折线图")),
      body: Padding(
        padding: const EdgeInsets.all(10),
        child: Column(
          children: [
            // 1.输入行
            Row(
              children: [
                //X坐标输入框
                Expanded(
                  child: TextField(
                    controller: _xController,
                    focusNode: _xFocusNode,  // 绑定FocusNode
                    keyboardType: TextInputType.number, //数字键盘
                    decoration: const InputDecoration(
                      labelText: "X 坐标",
                      border: OutlineInputBorder(),
                    ),
                  ),
                ),
                const SizedBox(width: 10),

                //Y坐标输入框
                Expanded(
                  child: TextField(
                    controller: _yController,
                    focusNode: _yFocusNode,  // 绑定FocusNode
                    keyboardType: TextInputType.number,
                    decoration: const InputDecoration(
                      labelText: "Y 坐标",
                      border: OutlineInputBorder(),
                    ),
                  ),
                ),
                const SizedBox(width: 10),

                //添加按钮
                ElevatedButton(
                  onPressed: _addPoint, //添加的点击事件
                  child: const Text("添加"),
                ),
              ],
            ),

            const SizedBox(height: 15),

            //2.清空按钮
            Row(
              mainAxisAlignment: MainAxisAlignment.end,
              children: [
                TextButton.icon(
                  onPressed: userSpots.isEmpty ? null : _clearAll, //点击事件
                  icon: const Icon(Icons.delete_forever, color: Colors.red),
                  label: const Text(
                    "清空所有",
                    style: TextStyle(color: Colors.red),
                  ),
                ),
              ],
            ),

            const SizedBox(height: 10),

            // 3.折线图区域
            Expanded(
              flex: 2,
              child: LineChart(mainData()),
            ),

            const SizedBox(height: 20),

            //4.当前坐标点列表
            Expanded(
              flex: 1,
              child: userSpots.isEmpty
                  ? const Center(child: Text("暂无数据点"))
                  : ListView.builder(
                itemCount: userSpots.length,
                itemBuilder: (context, index) {
                  final spot = userSpots[index];
                  return Card(
                    child: ListTile(
                      leading: const Icon(Icons.circle, color: Colors.blue),
                      title: Text("X: ${spot.x}, Y: ${spot.y}"),
                      trailing: IconButton(
                        icon: const Icon(Icons.delete, color: Colors.red),
                        onPressed: () => _removePoint(index),
                      ),
                    ),
                  );
                },
              ),
            ),
          ],
        ),
      ),
    );
  }

  //===================================折线图配置==================================
  LineChartData mainData() {
    return LineChartData(

      //1,范围
      minX: 0, maxX: 10, //x轴范围
      minY: 0, maxY: 10, //Y轴范围

      //2.网格线
      gridData: FlGridData(show: true),

      //3.坐标轴标签
      titlesData: FlTitlesData(
        bottomTitles: AxisTitles(
          sideTitles: SideTitles(
              showTitles: true,
              interval: 1,
              reservedSize: 30, //为标签保留空间
          ),
        ),
        leftTitles: AxisTitles(
          sideTitles: SideTitles(
              showTitles: true,
              interval: 1, //标签间隔
            reservedSize: 30, //为标签保留空间
          ),
        ),
        rightTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)),
        topTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)),
      ),

      //4.边框
      borderData: FlBorderData(
        show: true,
        border: Border.all(color: Colors.black26),
      ),

      //5.折线数据
      lineBarsData: [
        LineChartBarData(
          isCurved: true, //曲线连接
          color: Colors.blue, //线条颜色
          barWidth: 3, //线条宽度
          dotData: FlDotData(show: true),//显示数据点
          belowBarData: BarAreaData(show: true, color: Colors.blue.withOpacity(0.2)), //区域填充
          spots: userSpots.isNotEmpty
              ? userSpots
              : [const FlSpot(0, 0)], // 防止空图报错
        ),
      ],

      //6.交互行为
      lineTouchData: LineTouchData(
        touchTooltipData: LineTouchTooltipData(
          tooltipBgColor: Colors.blueAccent.withOpacity(0.8),
        ),
        handleBuiltInTouches: true,
      ),
    );
  }
}
相关推荐
@菜菜_达2 分钟前
jquery.inputmask插件介绍
前端·javascript·jquery
QuZhengRong3 分钟前
【Luck-Report】缓存
java·前端·后端·vue·excel
jiayong237 分钟前
前端面试题库 - 浏览器与网络篇
前端·网络·面试
Csvn11 分钟前
小程序开发:微信小程序与 uni-app 实战指南
前端
摸鱼小李上线了18 分钟前
vue项目页面添加水印实现方法
前端·javascript·vue.js
砍材农夫23 分钟前
物联网 基于netty构建mqtt协议规范(主题通配符订阅)
java·前端·javascript·物联网·netty
彩票管理中心秘书长27 分钟前
智能体状态指示:何时思考、何时调用工具、何时出错
前端·后端·程序员
彩票管理中心秘书长28 分钟前
React + TypeScript拆解一整套“AI 变现代码流程”
前端·后端·程序员
飞龙147756574675028 分钟前
Flutter 安全存储插件全面解析:从入门到进阶
flutter
广州华水科技31 分钟前
单北斗GNSS变形监测在基础设施安全中的应用与维护
前端