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

Flutter中实现Tree Map(树状图)图表的最优方案是什么?

在Flutter中实现Tree Map(树状图)的可行方案

由于tree_map库已长期未维护,推荐以下两种可靠方案:

一、使用活跃维护的第三方库

1. Syncfusion Flutter Charts

Syncfusion的Flutter图表库内置TreeMap组件,功能完整且持续更新,支持层级结构、自定义样式、交互事件等。

  • 添加依赖到pubspec.yaml:
dependencies:
  syncfusion_flutter_charts: ^25.1.35 # 请使用最新版本
  • 基础使用示例:
import 'package:syncfusion_flutter_charts/charts.dart';

class TreeMapDemo extends StatelessWidget {
  final List<RegionGDP> data = [
    RegionGDP('北美', 24.2),
    RegionGDP('南美', 4.2),
    RegionGDP('欧洲', 19.8),
    RegionGDP('非洲', 3.4),
    RegionGDP('亚洲', 45.1),
    RegionGDP('澳洲', 2.3),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SfTreeMap(
        dataCount: data.length,
        weightValueMapper: (int index) => data[index].gdp,
        colorMapper: (int index) {
          switch (data[index].region) {
            case '北美': return Colors.blueAccent;
            case '南美': return Colors.greenAccent;
            case '欧洲': return Colors.redAccent;
            case '非洲': return Colors.orangeAccent;
            case '亚洲': return Colors.purpleAccent;
            case '澳洲': return Colors.yellowAccent;
            default: return Colors.grey;
          }
        },
        tooltipBuilder: (context, index) => Container(
          padding: EdgeInsets.all(8),
          child: Text('${data[index].region}: ${data[index].gdp}万亿USD'),
        ),
        onSelectionChanged: (index) => index != null 
            ? print('选中区域:${data[index].region}') 
            : null,
      ),
    );
  }
}

class RegionGDP {
  final String region;
  final double gdp;

  RegionGDP(this.region, this.gdp);
}

2. 基于flutter_echarts嵌入ECharts TreeMap

如果熟悉ECharts,可通过flutter_echarts集成其功能强大的TreeMap组件,支持高度自定义配置。

  • 添加依赖:
dependencies:
  flutter_echarts: ^2.6.0 # 请使用最新版本
  • 基础使用示例:
import 'package:flutter_echarts/flutter_echarts.dart';

class EChartsTreeMapDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Echarts(
        option: '''
        {
          tooltip: {
            trigger: 'item',
            formatter: '{b}: {c}万亿USD'
          },
          series: [
            {
              type: 'treemap',
              data: [
                {name: '北美', value: 24.2},
                {name: '南美', value: 4.2},
                {name: '欧洲', value: 19.8},
                {name: '非洲', value: 3.4},
                {name: '亚洲', value: 45.1},
                {name: '澳洲', value: 2.3}
              ]
            }
          ]
        }
        ''',
      ),
    );
  }
}

二、自定义TreeMap实现(轻量场景)

若不想依赖第三方库,可基于CustomPaint手动实现简单TreeMap,核心是通过切片算法(如squarified)分配区块位置与大小。

简化实现示例

import 'package:flutter/material.dart';

class CustomTreeMap extends StatefulWidget {
  final List<TreeMapItem> items;

  CustomTreeMap({required this.items});

  @override
  _CustomTreeMapState createState() => _CustomTreeMapState();
}

class _CustomTreeMapState extends State<CustomTreeMap> {
  List<Rect> _itemRects = [];
  final _colorPool = [Colors.blue, Colors.green, Colors.red, Colors.orange, Colors.purple, Colors.yellow];

  @override
  void initState() {
    super.initState();
    _calculateBlockRects();
  }

  void _calculateBlockRects() {
    // 简化线性切片逻辑,复杂场景可实现squarified算法
    final totalWeight = widget.items.fold(0.0, (sum, item) => sum + item.weight);
    final screenSize = MediaQuery.of(context).size;
    double currentY = 0;
    for (var i = 0; i < widget.items.length; i++) {
      final item = widget.items[i];
      final blockHeight = (item.weight / totalWeight) * screenSize.height;
      _itemRects.add(Rect.fromLTWH(0, currentY, screenSize.width, blockHeight));
      currentY += blockHeight;
    }
  }

  @override
  Widget build(BuildContext context) {
    return CustomPaint(
      size: MediaQuery.of(context).size,
      painter: _TreeMapPainter(items: widget.items, rects: _itemRects, colors: _colorPool),
      child: GestureDetector(
        onTapDown: (details) {
          final tapPos = details.localPosition;
          for (var i = 0; i < _itemRects.length; i++) {
            if (_itemRects[i].contains(tapPos)) {
              ScaffoldMessenger.of(context).showSnackBar(
                SnackBar(content: Text('选中:${widget.items[i].name}')),
              );
              break;
            }
          }
        },
      ),
    );
  }
}

class _TreeMapPainter extends CustomPainter {
  final List<TreeMapItem> items;
  final List<Rect> rects;
  final List<Color> colors;

  _TreeMapPainter({required this.items, required this.rects, required this.colors});

  @override
  void paint(Canvas canvas, Size size) {
    for (var i = 0; i < items.length; i++) {
      // 绘制区块背景
      final bgPaint = Paint()..color = colors[i % colors.length];
      canvas.drawRect(rects[i], bgPaint);

      // 绘制区块文本
      final textPainter = TextPainter(
        text: TextSpan(
          text: '${items[i].name}\n${items[i].weight}',
          style: TextStyle(color: Colors.white, fontSize: 16),
        ),
        textDirection: TextDirection.ltr,
      );
      textPainter.layout();
      textPainter.paint(canvas, Offset(rects[i].left + 10, rects[i].top + 10));
    }
  }

  @override
  bool shouldRepaint(covariant _TreeMapPainter oldDelegate) {
    return items != oldDelegate.items || rects != oldDelegate.rects;
  }
}

class TreeMapItem {
  final String name;
  final double weight;

  TreeMapItem({required this.name, required this.weight});
}

// 使用方式
// CustomTreeMap(
//   items: [
//     TreeMapItem(name: '北美', weight: 24.2),
//     TreeMapItem(name: '南美', weight: 4.2),
//     TreeMapItem(name: '欧洲', weight: 19.8),
//     TreeMapItem(name: '非洲', weight: 3.4),
//     TreeMapItem(name: '亚洲', weight: 45.1),
//     TreeMapItem(name: '澳洲', weight: 2.3),
//   ],
// )

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 11:05:16