Flutter图表展示插件graph_display的使用

发布于 1周前 作者 nodeper 来自 Flutter

Flutter图表展示插件graph_display的使用

这个包提供了可自定义的Flutter图表展示组件。

如果你不使用Flutter或者需要更特定的图表组件,可以考虑使用graph_layout来生成图表布局,并自己编写代码在屏幕上显示这些布局。

示例用法

首先构造你希望可视化的图。

// 创建一个`Graph`对象
// 这个对象是从NetworkX主页上的图创建的 https://networkx.org/
final edgeList = '''
0 1
0 2
0 3
1 2
1 3
2 3
3 4
4 5
5 6
6 7
7 8
7 9
7 10
8 9
8 10
9 10
''';
final graph = Graph.fromEdgeListString(edgeList);

然后,你可以将这个图作为一个小部件展示出来!该图可以是交互式的,允许应用程序的用户拖动节点以探索图;也可以是非交互式的,一旦生成了图布局就不需要再进行计算。

InteractiveGraph(
  layoutAlgorithm: Eades(graph: graph),
);
StaticGraph(
  layoutAlgorithm: FruchtermanReingold(graph: graph),
)

示例代码

以下是完整的示例代码,展示了如何使用graph_display插件。

import 'package:example/jean_node_data.dart';
import 'package:flutter/material.dart';
import 'package:graph_display/graph_display.dart';
import 'package:graph_layout/graph_layout.dart';
import 'package:vector_math/vector_math.dart' hide Colors;

const _demoNames = [
  '完全图(8个顶点),Eades算法',
  '完全图(8个顶点),Fruchterman-Reingold算法',
  '《悲惨世界》角色共现,Eades算法',
  '《悲惨世界》角色共现,Fruchterman-Reingold算法',
];

/// 返回具有[nodes]个节点的完全图。
Graph _generateCompleteGraph(int nodeNumber) {
  final nodes = List<int>.generate(nodeNumber, (i) => i + 1)
      .map((i) => IntegerNode(i))
      .toList();

  final edges = <Edge>{};
  for (int i = 0; i < nodeNumber; i++) {
    for (int j = 0; j < nodeNumber; j++) {
      if (i != j) {
        edges.add(Edge(left: nodes[i], right: nodes[j]));
      }
    }
  }

  return Graph.fromEdgeList(edges);
}

void main() {
  runApp(const ExampleApp());
}

class ExampleApp extends StatefulWidget {
  const ExampleApp({Key? key}) : super(key: key);

  [@override](/user/override)
  State<ExampleApp> createState() => _ExampleAppState();
}

class _ExampleAppState extends State<ExampleApp> {
  var _selectedDemoIndex = 0;

  Widget _displayDemo() {
    switch (_selectedDemoIndex) {
      case 0:
        return InteractiveGraph(
          layoutAlgorithm: Eades(
            graph: _generateCompleteGraph(8),
          ),
        );
      case 1:
        return StaticGraph(
          layoutAlgorithm: FruchtermanReingold(
            graph: _generateCompleteGraph(8),
          ),
        );

      case 2:
      case 3:
        return FutureBuilder<String>(
          future: DefaultAssetBundle.of(context).loadString('assets/jean.dat'),
          builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
            if (snapshot.connectionState == ConnectionState.done &&
                snapshot.hasData) {
              final edges = <Edge>{};

              // 每个匹配对应一章,每章包含一个或多个场景。
              final matches = RegExp(r'(?<=[0-9]:).*$', multiLine: true)
                  .allMatches(snapshot.data!)
                  .map((match) => match[0]!.split(';'))
                  .expand((e) => e);

              // 遍历每个场景。如果两个角色出现在同一个场景中,则认为它们共现。
              for (final m in matches) {
                // 这个场景中的角色。
                final characters = m
                    .split(',')
                    .map((characterString) =>
                        IntegerNode(characterString.hashCode))
                    .toList();

                // 找出这组中的每一对并将其添加为边。无需担心时间复杂度,因为[characters]很小。
                for (final char1 in characters) {
                  for (final char2 in characters) {
                    if (char1 != char2) {
                      edges.add(Edge(left: char1, right: char2));
                    }
                  }
                }
              }
              final theme = GraphThemePreferences(
                backgroundColour: Colors.blueGrey.shade50,
                edgeColour: Colors.blueGrey,
                edgeThickness: 0.2,
                nodeRadius: 15,
                drawNode: (Canvas canvas, Node node, Vector2 position) {
                  // 请参阅jean_node_data.dart以获取此颜色映射的源代码。
                  final nodePaint = Paint()..color = nodeToColour[node]!;
                  final nodeOffset = Offset(position.x, position.y);
                  canvas.drawCircle(nodeOffset, 15, nodePaint);

                  // 绘制角色标签。请参阅jean_node_data.dart以获取此数据的源代码。
                  final textSpan = TextSpan(
                    text: nodeToName[node]!,
                    style: const TextStyle(color: Colors.black),
                  );
                  final textPainter = TextPainter(
                    text: textSpan,
                    textDirection: TextDirection.ltr,
                  );
                  textPainter.layout();
                  textPainter.paint(
                    canvas,
                    nodeOffset - const Offset(7.5, 10),
                  );
                  // TODO: 最终我们需要在这里调用`textPainter.dispose()`。
                  // 请参见https://github.com/flutter/flutter/blob/0b451b6dfd6de73ff89d89081c33d0f971db1872/packages/flutter/lib/src/painting/text_painter.dart#L171。
                },
              );
              return _selectedDemoIndex == 2
                  ? InteractiveGraph(
                      layoutAlgorithm: Eades(
                        graph: Graph.fromEdgeList(edges),
                      ),
                      themePreferences: theme,
                    )
                  : StaticGraph(
                      layoutAlgorithm: FruchtermanReingold(
                        graph: Graph.fromEdgeList(edges),
                      ),
                      themePreferences: theme,
                    );
            } else if (snapshot.hasError) {
              return const Center(child: Text('错误:无法获取图表数据'));
            } else {
              return const Center(child: CircularProgressIndicator());
            }
          },
        );

      default:
        throw ArgumentError.value(_selectedDemoIndex, '_selectedDemoIndex');
    }
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            // 选择要显示的演示的下拉菜单。
            DropdownButton(
              value: _selectedDemoIndex,
              onChanged: (value) {
                setState(() {
                  _selectedDemoIndex = value!;
                });
              },
              items: List.generate(
                _demoNames.length,
                (i) => DropdownMenuItem(
                  value: i,
                  child: Text(_demoNames[i]),
                ),
              ),
            ),
            // 图表演示本身。
            _displayDemo(),
          ],
        ),
      ),
    );
  }
}

更多关于Flutter图表展示插件graph_display的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter图表展示插件graph_display的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是一个关于如何在Flutter中使用graph_display(假设这是一个用于图表展示的插件,虽然实际上Flutter社区中更常用的图表插件可能是fl_chartcharts_flutter,但这里我们按照要求以graph_display为例)的示例代码。请注意,由于graph_display可能不是一个真实存在的广泛使用的插件,这里的代码示例是基于假设的API设计。

首先,确保你已经在pubspec.yaml文件中添加了graph_display依赖:

dependencies:
  flutter:
    sdk: flutter
  graph_display: ^1.0.0  # 假设的版本号

然后,运行flutter pub get来安装依赖。

接下来,是一个简单的Flutter应用示例,展示如何使用graph_display插件来展示一个图表:

import 'package:flutter/material.dart';
import 'package:graph_display/graph_display.dart'; // 假设的导入路径

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Graph Display Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: GraphDisplayScreen(),
    );
  }
}

class GraphDisplayScreen extends StatefulWidget {
  @override
  _GraphDisplayScreenState createState() => _GraphDisplayScreenState();
}

class _GraphDisplayScreenState extends State<GraphDisplayScreen> {
  // 假设的数据
  List<double> dataPoints = [10, 20, 15, 30, 25, 40, 35];
  List<String> labels = ['A', 'B', 'C', 'D', 'E', 'F', 'G'];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Graph Display Demo'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: GraphDisplay(
          dataPoints: dataPoints,
          labels: labels,
          title: 'Sample Graph',
          xAxisLabel: 'Categories',
          yAxisLabel: 'Values',
          // 假设的图表配置选项
          barColor: Colors.blue,
          backgroundColor: Colors.white,
          gridColor: Colors.grey.shade300,
        ),
      ),
    );
  }
}

// 假设的 GraphDisplay 组件实现(实际使用时应该是插件提供的)
class GraphDisplay extends StatelessWidget {
  final List<double> dataPoints;
  final List<String> labels;
  final String title;
  final String xAxisLabel;
  final String yAxisLabel;
  final Color barColor;
  final Color backgroundColor;
  final Color gridColor;

  GraphDisplay({
    required this.dataPoints,
    required this.labels,
    required this.title,
    required this.xAxisLabel,
    required this.yAxisLabel,
    this.barColor = Colors.blue,
    this.backgroundColor = Colors.white,
    this.gridColor = Colors.grey.shade300,
  });

  @override
  Widget build(BuildContext context) {
    // 注意:这里的实现是为了展示目的而编写的,实际插件可能有不同的API
    return CustomPaint(
      size: Size.fromWidthAndHeight(double.infinity, 300), // 假设的图表大小
      painter: GraphPainter(
        dataPoints: dataPoints,
        labels: labels,
        title: title,
        xAxisLabel: xAxisLabel,
        yAxisLabel: yAxisLabel,
        barColor: barColor,
        backgroundColor: backgroundColor,
        gridColor: gridColor,
      ),
    );
  }
}

// 假设的 GraphPainter 类(实际使用时应该是插件提供的绘制逻辑)
class GraphPainter extends CustomPainter {
  final List<double> dataPoints;
  final List<String> labels;
  final String title;
  final String xAxisLabel;
  final String yAxisLabel;
  final Color barColor;
  final Color backgroundColor;
  final Color gridColor;

  GraphPainter({
    required this.dataPoints,
    required this.labels,
    required this.title,
    required this.xAxisLabel,
    required this.yAxisLabel,
    required this.barColor,
    required this.backgroundColor,
    required this.gridColor,
  });

  @override
  void paint(Canvas canvas, Size size) {
    final Paint backgroundPaint = Paint()
      ..color = backgroundColor
      ..style = PaintingStyle.fill;
    canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), backgroundPaint);

    // 绘制标题、坐标轴标签等(省略具体实现细节)
    // ...

    // 绘制柱状图
    final double barWidth = size.width / dataPoints.length * 0.8 / labels.length;
    for (int i = 0; i < dataPoints.length; i++) {
      final double x = i * (size.width / dataPoints.length * 1.2) + barWidth / 2;
      final double y = size.height - dataPoints[i] * (size.height / dataPoints.reduce((a, b) => math.max(a, b)));
      final Rect barRect = Rect.fromLTWH(x - barWidth / 2, y, barWidth, size.height - y);
      final Paint barPaint = Paint()
        ..color = barColor
        ..style = PaintingStyle.fill;
      canvas.drawRect(barRect, barPaint);
    }

    // 绘制网格线(省略具体实现细节)
    // ...
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return oldDelegate != this;
  }
}

请注意,上述代码中的GraphDisplayGraphPainter类是为了演示目的而编写的,并不代表graph_display插件的实际API。在实际使用中,你应该参考插件的官方文档来了解如何正确配置和使用该插件。如果graph_display不是一个真实存在的插件,你可能需要考虑使用fl_chartcharts_flutter等广泛使用的Flutter图表插件。

回到顶部