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
相关产品推荐
相关产品推荐

