You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter中charts_flutter散点图点击显示数据实现需求

实现charts_flutter散点图点击显示对应数据的方案

需求:为charts_flutter的散点图添加点击交互,点击圆点时显示对应的数据。此前使用SfCircularChart可实现类似效果,但Sf散点图为收费功能,需基于现有代码改造。

改造步骤及完整代码

要实现点击显示数据,需将原StatelessWidget改为StatefulWidget,添加选择监听行为并维护选中数据状态,最后通过弹窗或文本展示数据:

import 'package:flutter/material.dart';
import 'package:charts_flutter/flutter.dart' as charts;

class SimpleScatterPlotChart extends StatefulWidget {
  final List<charts.Series<_HeartRateData, num>> seriesList;
  final bool animate;

  const SimpleScatterPlotChart({
    required this.seriesList,
    this.animate = false,
    Key? key,
  }) : super(key: key);

  factory SimpleScatterPlotChart.withSampleData() {
    return SimpleScatterPlotChart(
      seriesList: _createSampleData(),
      animate: false,
    );
  }

  @override
  State<SimpleScatterPlotChart> createState() => _SimpleScatterPlotChartState();
}

class _SimpleScatterPlotChartState extends State<SimpleScatterPlotChart> {
  _HeartRateData? _selectedData;

  @override
  Widget build(BuildContext context) {
    return Stack(
      alignment: Alignment.center,
      children: [
        Container(
          height: 250,
          width: 350,
          child: charts.ScatterPlotChart(
            widget.seriesList,
            animate: widget.animate,
            primaryMeasureAxis: const charts.NumericAxisSpec(
              renderSpec: charts.GridlineRendererSpec(
                labelStyle: charts.TextStyleSpec(
                  color: charts.Color.fromHex(code: '#7A7A7A'),
                ),
              ),
            ),
            domainAxis: const charts.NumericAxisSpec(
              showAxisLine: false,
              renderSpec: charts.NoneRenderSpec(),
            ),
            // 添加点击选择监听
            behaviors: [
              charts.SelectionModelConfig(
                type: charts.SelectionModelType.info,
                changedListener: (charts.SelectionModel model) {
                  if (model.hasDatumSelection) {
                    setState(() {
                      _selectedData = model.selectedDatum[0].datum as _HeartRateData;
                    });
                    // 可选:弹出自动消失的Tooltip
                    _showDataTooltip(context, _selectedData!);
                  } else {
                    setState(() {
                      _selectedData = null;
                    });
                  }
                },
              ),
            ],
          ),
        ),
        // 图表中心悬浮显示选中数据
        if (_selectedData != null)
          Container(
            padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
            decoration: BoxDecoration(
              color: Colors.black.withOpacity(0.7),
              borderRadius: BorderRadius.circular(8),
            ),
            child: Text(
              "日期: ${_selectedData!.date}\n心率: ${_selectedData!.heartRate}",
              style: const TextStyle(color: Colors.white, fontSize: 12),
              textAlign: TextAlign.center,
            ),
          ),
      ],
    );
  }

  // 弹出自动关闭的Tooltip
  void _showDataTooltip(BuildContext context, _HeartRateData data) {
    final overlay = Overlay.of(context);
    final overlayEntry = OverlayEntry(
      builder: (context) => Positioned(
        top: MediaQuery.of(context).size.height / 2 - 100,
        left: MediaQuery.of(context).size.width / 2 - 80,
        child: Material(
          color: Colors.transparent,
          child: Container(
            padding: const EdgeInsets.all(10),
            decoration: BoxDecoration(
              color: Colors.blueGrey,
              borderRadius: BorderRadius.circular(6),
            ),
            child: Text(
              "心率: ${data.heartRate}\n时间点: ${data.date}",
              style: const TextStyle(color: Colors.white),
            ),
          ),
        ),
      ),
    );
    overlay.insert(overlayEntry);
    Future.delayed(const Duration(seconds: 3)).then((_) => overlayEntry.remove());
  }

  /// 创建示例数据(替换为你的apiData)
  static List<charts.Series<_HeartRateData, num>> _createSampleData() {
    final apiData = [
      _HeartRateData(date: 1, heartRate: 55, radius: 8),
      _HeartRateData(date: 2, heartRate: 78, radius: 8),
      _HeartRateData(date: 3, heartRate: 105, radius: 8),
    ];

    return [
      charts.Series<_HeartRateData, num>(
        id: '心率',
        colorFn: (_HeartRateData sales, _) {
          if (sales.heartRate < 61) {
            return charts.Color.fromHex(code: '#F8D277');
          } else if (sales.heartRate < 100) {
            return charts.Color.fromHex(code: '#61D2A4');
          } else {
            return charts.Color.fromHex(code: '#DD5571');
          }
        },
        domainFn: (_HeartRateData sales, _) => sales.date,
        measureFn: (_HeartRateData sales, _) => sales.heartRate,
        radiusPxFn: (_HeartRateData sales, _) => sales.radius,
        data: apiData,
      ),
    ];
  }
}

// 心率数据模型
class _HeartRateData {
  final int date;
  final int heartRate;
  final int radius;

  _HeartRateData({
    required this.date,
    required this.heartRate,
    required this.radius,
  });
}

关键说明

  1. 转为StatefulWidget:通过_selectedData变量维护选中的数据状态
  2. SelectionModelConfig监听:配置图表的选择行为,点击散点时触发回调获取选中的数据源
  3. 数据展示方式:提供两种可选方案:
    • 利用Stack在图表中心悬浮显示选中数据
    • 通过Overlay弹出自动关闭的Tooltip
  4. 数据适配:确保_HeartRateData包含所有需要展示的字段,替换示例数据为你的apiData即可

内容的提问来源于stack exchange,提问作者Nutdanai

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 05:50:11